【问题标题】:Retain cycle warning on __block variable that is an ivar保留作为 ivar 的 __block 变量的循环警告
【发布时间】:2015-11-09 01:13:52
【问题描述】:

我是 AVQueuePlayer 的子类,在我的构造函数中,我传递了它需要播放的 AVPlayerItem,我想在第一个要播放的项目上添加一个观察者。

所以我使用的是 AVPlayer 方法addBoundaryTimeObserverForTimes:queue:usingBlock:。正确的实现需要我在 addBoundary 方法返回的“不透明”对象上调用 removeTimeObserver:

为了将对象保留多长时间,我将其声明为 __block ivar:

@property (nonatomic, copy) __block id obs;

然后在我自己的 init 方法中我有:

__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
        }];     

这里发生了很多事情......虽然当我尝试删除时间观察器时会出现特定警告,但我也发布了一个通知,我也可能会更改它以在对象中传递一个变量:part.我也将self设置为观察者...

我已经阅读了很多关于潜在解决方案的其他答案 (example) 但我还没有真正找到有关使用块变量的任何信息。

我的代码不安全还是我没事?

编辑:我最初将@property 的名称错误地输入为__block id observer,而我确实希望它是__block id obs。因此,接受的答案回答了这两种情况! (太棒了!)

【问题讨论】:

    标签: ios objective-c automatic-ref-counting objective-c-blocks retain-cycle


    【解决方案1】:

    (所有直接输入答案的代码,将其视为伪代码并期望至少有轻微的拼写错误!)

    不幸的是,您误解了__block 的目的和行为 - 这是一个适用于局部变量并修改其生命周期的属性,以便可以安全地更新它们一个街区。所以:

    为了将对象保留多长时间,我将其声明为 __block ivar:

    @property (nonatomic, copy) __block id observer;
    

    是一个无效的属性声明,因为属性是 instance 方法,通常由 instance - 而不是 local - 变量支持。不幸的是,当前的 Apple 编译器只是忽略了无意义的 __block,而不是报告错误 - 以前曾导致 SO inquirers 混淆的错误(您应该向 Apple 提交错误报告以鼓励他们修复它)。

    接下来你写:

    __block AVPlayer* blockPlayer = self;
    

    因此您可以在您的块中使用blockPlayer 而不是self 以避免保留循环。这确实工作,实际上它只是简单地将另一个(匿名)对象添加到循环中......你需要的是一个 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 是一个局部变量,您的代码将无法工作。创建块时,块使用的任何局部变量的 都会复制 到块本身中,因此这里将复制 _obs 的值。然而_obs此时不会有一个有效值,这只会发生在对addBoundaryTimeObserverForTimes:queue:usingBlock:的调用返回并分配其返回值之后,即块已经创建并且传递给同一个调用...

    这个问题和defining a local recursive block类似,解决方法一样。如果使用__block 属性声明局部变量,则修改其生命周期以匹配使用它的任何块的生命周期,从而这些块可以修改其值,则局部变量中的值不被复制 进入块 - 相反,块获得对变量的引用,用于根据需要读取/写入变量。

    所以要使代码正常工作,请将其更改为:

    __block id obs = [self addBoundaryTimeObserverForTimes:times
                                                     queue:NULL
                                                usingBlock:^{ 
                        ...
                        [blockPlayer removeTimeObserver:obs];
                      }];
    

    在这个版本中:

    1. 局部变量obs 的创建方式将使其生命周期至少与您的块一样长(我们将跳过编译器如何安排的细节 - 它们很有趣但不是至关重要);
    2. 创建块时使用了对变量jobs引用
    3. 方法addBoundaryTimeObserverForTimes:queue:usingBlock: 被称为传递块;
    4. 方法返回并将其结果赋值给obs;和
    5. (稍后)调用该块,读取obs,并在方法调用后获取存储在那里的值。

    属性参考

    如果您打错了,并且您打算将 _obs 用作属性 observer,则分配的 LHS 应为 self.observer,而 RHS 应为 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: 块的调用将返回并分配给属性。

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

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

    HTH

    【讨论】:

    • 太棒了!它实际上是一个属性,因为我假设当我到达方法结束时它会被释放,但如果我实际上可以在本地声明它并且它会一直存在直到调用块,这是一个理想的解决方案。感谢您对这两种可能情况的详细回答。
    • 在您的“局部变量”版本中,如果返回的观察者持有对块的强引用,那么这将导致保留循环。
    • @user102008 - 是的,在这种情况下它可能会导致循环,但并非所有循环都有问题。如果这是一个问题,您只需在调用removeTimeObserver 之后添加obs = nil,循环就被打破了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-14
    • 1970-01-01
    相关资源
    最近更新 更多