在作为 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];
                  }];

在这个版本中:

  1. 局部变量 obs 将以其生命周期至少与您的块 一样长的方式创建(我们将跳过编译器如何安排它的细节 -它们很有趣但并不重要);
  2. 块是用 引用 变量 jobs;
  3. 创建的
  4. 调用方法 addBoundaryTimeObserverForTimes:queue:usingBlock: 将块传递给它;
  5. 方法returns及其结果赋值给obs;和
  6. (稍后)调用块,读取 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.observeraddBoundaryTimeObserverForTimes:queue:usingBlock: 块的调用将具有 returned 并且分配给 属性制作。

局部变量与属性 for obs/observer?

以上两个版本哪个更好?局部变量版本可能是 (a) 你似乎不需要 属性 其他地方和 (b) 它将变量的需要本地化为语句,方法调用,它需要它,这反过来有助于可读性和调试。 但是这是一个意见,有些人可能不同意 - 请自行选择!

HTH