在作为 ivar 的 __block 变量上保留循环警告
Retain cycle warning on __block variable that is an ivar
我正在子类化 AVQueuePlayer,在我的构造函数中,我传递了它需要播放的 AVPlayerItem,我想在要播放的第一个项目上添加一个观察者。
所以我正在使用 AVPlayer 方法 addBoundaryTimeObserverForTimes:queue:usingBlock:
。正确的实现需要我在 addBoundary 方法 returns.
的 "opaque" 对象上调用 removeTimeObserver:
为了保留该对象需要多长时间,我将其声明为 __block ivar:
@property (nonatomic, copy) __block id obs;
然后在我自己的初始化方法中我有:
__block AVPlayer* blockPlayer = self;
_obs = [self addBoundaryTimeObserverForTimes: times
queue:NULL
usingBlock:^{
// Post a notification that I can then act on
[[NSNotificationCenter defaultCenter]
postNotificationName:@"PlaybackStartedNotification"
object:nil];
// Remove the boundary time observer
[blockPlayer removeTimeObserver:_obs]; // Warning here
}];
这里发生了很多事情...虽然当我尝试删除时间观察器时会出现特定警告,但我也发布了一个通知,我也可能会更改它以在对象中传递一个变量:部分。我也将自己设置为观察者...
我已经阅读了很多关于潜在解决方案的其他答案 (example)
但我还没有真正发现任何关于使用块变量的信息。
我的代码不安全还是我还好?
编辑:我最初将 @属性 的名称错误输入为 __block id observer
,而我确实本意是 __block id obs
。因此,接受的答案回答了这两种情况! (太棒了!)
(所有直接输入答案的代码,将其视为伪代码,并希望至少有轻微的错别字!)
不幸的是,您误解了 __block
的目的和行为 - 这是一个 仅 应用于局部变量并修改其 lifetime 这样它们就可以安全地被一个块更新。所以:
In order to retain the object for however long is necessary, I declared it as a __block ivar:
@property (nonatomic, copy) __block id observer;
是无效的 属性 声明,因为属性是 instance 方法,通常由 instance 支持 - 而不是 local - 变量。不幸的是,当前的 Apple 编译器只是忽略了无意义的 __block
而不是报告错误 - 这个错误之前曾引起 SO inquirers 的混淆(您应该向 Apple 提交错误报告以鼓励他们修复它)。
接下来你写:
__block AVPlayer* blockPlayer = self;
所以你可以在你的块中使用 blockPlayer
而不是 self
来避免保留循环。这确实 not 工作,实际上它简单地将另一个(匿名)对象添加到循环中......你在这里需要的是 weak 参考:
__weak AVPlayer *blockPlayer = self;
弱引用打破了一个循环,但是在块中你必须首先从它们创建一个强引用并检查它不是 NULL
- 如果它弱引用的对象已经被销毁,它就会是。在您的块中执行此操作的代码类似于:
// Remove the boundary time observer if blockPlayer still exists
AVPlayer *strongBlockPlayer = blockPlayer; // obtain strong reference from weak one
if (strongBlockPlayer)
[strongBlockPlayer ...];
即使在进行了这些更改之后,您仍面临更大的问题。在你的代码中你有:
_obs = [self addBoundaryTimeObserverForTimes:times
queue:NULL
usingBlock:^{
...
[blockPlayer removeTimeObserver:_obs];
}];
这里你试图在你的块中使用 _obs
的值。
这时候你的问题就不清楚了,_obs
是局部变量还是属性observer
?我们将考虑这两种情况:
局部变量
如果 _obs
是局部变量,您的代码将无法运行。创建块时,块使用的任何局部变量的 values 都被 copied 到块本身,所以这里 [=22 的值=] 将被复制。但是 _obs
此时不会有有效值,这只会在调用 addBoundaryTimeObserverForTimes:queue:usingBlock:
returns 并分配其 return 值后发生,即 在 块创建并传递给同一个调用后...
此题与defining a local recursive block类似,解法相同。如果使用 __block
属性声明局部变量,以便修改其生命周期以匹配使用它的任何块的生命周期,因此这些块可以修改其值,则局部变量中的值为 未复制 到块中 - 相反,块获取对变量的引用,它根据需要用于 read/write 变量。
因此,为了使代码正常工作,您将其更改为:
__block id obs = [self addBoundaryTimeObserverForTimes:times
queue:NULL
usingBlock:^{
...
[blockPlayer removeTimeObserver:obs];
}];
在这个版本中:
- 局部变量
obs
将以其生命周期至少与您的块 一样长的方式创建(我们将跳过编译器如何安排它的细节 -它们很有趣但并不重要);
- 块是用 引用 变量
jobs
; 创建的
- 调用方法
addBoundaryTimeObserverForTimes:queue:usingBlock:
将块传递给它;
- 方法returns及其结果赋值给
obs
;和
- (稍后)调用块,读取
obs
,并在方法调用后获取存储在那里的值。
属性参考
如果您打错了字并且您打算 _obs
成为 属性 observer
那么作业的左轴应该是 self.observer
并且右轴应该是 blockPlayer.observer
,考虑到需要弱引用的情况是:
__weak AVPlayer *blockPlayer = self;
self.observer = [self addBoundaryTimeObserverForTimes:times
queue:NULL
usingBlock:^{
...
// Remove the boundary time observer if blockPlayer still exists
AVPlayer *strongBlockPlayer = blockPlayer; // obtain strong reference from weak one
if (strongBlockPlayer)
[strongBlockPlayer removeTimeObserver:strongBlockPlayer.observer];
}];
这将在块被调用和读取时工作 strongBlockPlayer.observer
对 addBoundaryTimeObserverForTimes:queue:usingBlock:
块的调用将具有 returned 并且分配给 属性制作。
局部变量与属性 for obs
/observer
?
以上两个版本哪个更好?局部变量版本可能是 (a) 你似乎不需要 属性 其他地方和 (b) 它将变量的需要本地化为语句,方法调用,它需要它,这反过来有助于可读性和调试。 但是这是一个意见,有些人可能不同意 - 请自行选择!
HTH
我正在子类化 AVQueuePlayer,在我的构造函数中,我传递了它需要播放的 AVPlayerItem,我想在要播放的第一个项目上添加一个观察者。
所以我正在使用 AVPlayer 方法 addBoundaryTimeObserverForTimes:queue:usingBlock:
。正确的实现需要我在 addBoundary 方法 returns.
removeTimeObserver:
为了保留该对象需要多长时间,我将其声明为 __block ivar:
@property (nonatomic, copy) __block id obs;
然后在我自己的初始化方法中我有:
__block AVPlayer* blockPlayer = self;
_obs = [self addBoundaryTimeObserverForTimes: times
queue:NULL
usingBlock:^{
// Post a notification that I can then act on
[[NSNotificationCenter defaultCenter]
postNotificationName:@"PlaybackStartedNotification"
object:nil];
// Remove the boundary time observer
[blockPlayer removeTimeObserver:_obs]; // Warning here
}];
这里发生了很多事情...虽然当我尝试删除时间观察器时会出现特定警告,但我也发布了一个通知,我也可能会更改它以在对象中传递一个变量:部分。我也将自己设置为观察者...
我已经阅读了很多关于潜在解决方案的其他答案 (example) 但我还没有真正发现任何关于使用块变量的信息。
我的代码不安全还是我还好?
编辑:我最初将 @属性 的名称错误输入为 __block id observer
,而我确实本意是 __block id obs
。因此,接受的答案回答了这两种情况! (太棒了!)
(所有直接输入答案的代码,将其视为伪代码,并希望至少有轻微的错别字!)
不幸的是,您误解了 __block
的目的和行为 - 这是一个 仅 应用于局部变量并修改其 lifetime 这样它们就可以安全地被一个块更新。所以:
In order to retain the object for however long is necessary, I declared it as a __block ivar:
@property (nonatomic, copy) __block id observer;
是无效的 属性 声明,因为属性是 instance 方法,通常由 instance 支持 - 而不是 local - 变量。不幸的是,当前的 Apple 编译器只是忽略了无意义的 __block
而不是报告错误 - 这个错误之前曾引起 SO inquirers 的混淆(您应该向 Apple 提交错误报告以鼓励他们修复它)。
接下来你写:
__block AVPlayer* blockPlayer = self;
所以你可以在你的块中使用 blockPlayer
而不是 self
来避免保留循环。这确实 not 工作,实际上它简单地将另一个(匿名)对象添加到循环中......你在这里需要的是 weak 参考:
__weak AVPlayer *blockPlayer = self;
弱引用打破了一个循环,但是在块中你必须首先从它们创建一个强引用并检查它不是 NULL
- 如果它弱引用的对象已经被销毁,它就会是。在您的块中执行此操作的代码类似于:
// Remove the boundary time observer if blockPlayer still exists
AVPlayer *strongBlockPlayer = blockPlayer; // obtain strong reference from weak one
if (strongBlockPlayer)
[strongBlockPlayer ...];
即使在进行了这些更改之后,您仍面临更大的问题。在你的代码中你有:
_obs = [self addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{ ... [blockPlayer removeTimeObserver:_obs]; }];
这里你试图在你的块中使用 _obs
的值。
这时候你的问题就不清楚了,_obs
是局部变量还是属性observer
?我们将考虑这两种情况:
局部变量
如果 _obs
是局部变量,您的代码将无法运行。创建块时,块使用的任何局部变量的 values 都被 copied 到块本身,所以这里 [=22 的值=] 将被复制。但是 _obs
此时不会有有效值,这只会在调用 addBoundaryTimeObserverForTimes:queue:usingBlock:
returns 并分配其 return 值后发生,即 在 块创建并传递给同一个调用后...
此题与defining a local recursive block类似,解法相同。如果使用 __block
属性声明局部变量,以便修改其生命周期以匹配使用它的任何块的生命周期,因此这些块可以修改其值,则局部变量中的值为 未复制 到块中 - 相反,块获取对变量的引用,它根据需要用于 read/write 变量。
因此,为了使代码正常工作,您将其更改为:
__block id obs = [self addBoundaryTimeObserverForTimes:times
queue:NULL
usingBlock:^{
...
[blockPlayer removeTimeObserver:obs];
}];
在这个版本中:
- 局部变量
obs
将以其生命周期至少与您的块 一样长的方式创建(我们将跳过编译器如何安排它的细节 -它们很有趣但并不重要); - 块是用 引用 变量
jobs
; 创建的
- 调用方法
addBoundaryTimeObserverForTimes:queue:usingBlock:
将块传递给它; - 方法returns及其结果赋值给
obs
;和 - (稍后)调用块,读取
obs
,并在方法调用后获取存储在那里的值。
属性参考
如果您打错了字并且您打算 _obs
成为 属性 observer
那么作业的左轴应该是 self.observer
并且右轴应该是 blockPlayer.observer
,考虑到需要弱引用的情况是:
__weak AVPlayer *blockPlayer = self;
self.observer = [self addBoundaryTimeObserverForTimes:times
queue:NULL
usingBlock:^{
...
// Remove the boundary time observer if blockPlayer still exists
AVPlayer *strongBlockPlayer = blockPlayer; // obtain strong reference from weak one
if (strongBlockPlayer)
[strongBlockPlayer removeTimeObserver:strongBlockPlayer.observer];
}];
这将在块被调用和读取时工作 strongBlockPlayer.observer
对 addBoundaryTimeObserverForTimes:queue:usingBlock:
块的调用将具有 returned 并且分配给 属性制作。
局部变量与属性 for obs
/observer
?
以上两个版本哪个更好?局部变量版本可能是 (a) 你似乎不需要 属性 其他地方和 (b) 它将变量的需要本地化为语句,方法调用,它需要它,这反过来有助于可读性和调试。 但是这是一个意见,有些人可能不同意 - 请自行选择!
HTH