ios - 保留有关ivar的__block变量的循环警告

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

我将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
        }];     

这里发生了很多事情……尽管当我尝试删除时间观察者时出现了特定的警告,但我也在发布通知,我也可以更改该通知以在object:part中传递变量。我也将自己设置为观察者...

我已经阅读了许多其他有关潜在解决方案的答案(example)
但是我还没有发现有关使用块变量的任何信息。

我的代码不安全还是可以吗?

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

最佳答案

(所有直接输入答案的代码,都将其视为伪代码,并期望至少有少量错字!)
不幸的是,您误解了__block的目的和行为-这是一个仅适用于局部变量并修改其生存期的属性,因此可以通过块安全地对其进行更新。所以:

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

@property (nonatomic, copy) __block id observer;

是无效的属性声明,因为属性是通常由实例变量而非本地变量支持的实例方法。不幸的是,当前的Apple编译器只是忽略了无意义的__block而不是报告错误-以前引起SO inquirers混乱的错误(您应该向Apple提交错误报告,以鼓励他们对其进行修复)。
接下来,您编写:
__block AVPlayer* blockPlayer = self;

因此,您可以在块中使用blockPlayer而不是self,以尝试避免保留周期。这样做不是而不是工作,的确确实简单地在循环中添加了另一个(匿名)对象。
__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];
                  }];
在此版本中:
  • 将以这样的方式创建局部变量obs,使其寿命至少与您的块一样长(我们将跳过有关编译器如何安排它的详细信息-它们很有趣,但并不是至关重要的);
  • 该块是通过引用变量jobs创建的;
  • 调用addBoundaryTimeObserverForTimes:queue:usingBlock:方法将其传递给块;
  • 该方法返回并将其结果分配给obs;和
  • (稍后),将调用该块,读取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局部变量与属性?
    以上两个版本中的哪一个(哪个都应该工作)更好?可能是局部变量版本为(a)您似乎在其他地方不需要该属性,并且(b)将变量的需求本地化为仅语句,方法调用(需要它),这反过来有助于提高可读性和调试。但是,这是一种意见,有些人可能会不同意-自行选择!
    高温超导

    关于ios - 保留有关ivar的__block变量的循环警告,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32032789/

    相关文章:

    ios - 提示一般 iOS 定位服务

    ios - 如何在 iOS 8.0+ 中以编程方式打开设备设置应用程序而不是应用程序设置

    ios - 如何快速取消OperationQueue中的特定操作

    ios - 如何确定 map 上的某个点是否在用户当前位置在 iOS 上的半径范围内?

    ios - 1024x1024 应用程序图标在哪里使用?

    objective-c - 不兼容的 block 指针类型将 'void (^)(bool)' 发送到类型 'void(^)()' 的参数

    objective-c - Xcode 9.3 现在设置 CLANG_ENABLE_OBJC_WEAK = YES 非 ARC 分支

    ios - ARC 的多线程自动释放问题?

    objective-c - Objective-C - ARC - 何时使用@autoreleasepool

    ios - watchOS上带有CloudKit的CoreData不同步