【问题标题】:How to store references to objects and blocks如何存储对对象和块的引用
【发布时间】:2012-11-21 22:12:02
【问题描述】:

我想实现类似于NSNotificationCenter-addObserverForName:object:queue:usingBlock: 的行为。使用类似的方法

- (void)addRefetchObserver:(id)observer
                   handler:(FJRefetchHandler)handler;

应该存储一个块以供以后调用(FJRefetchHandler 定义如下:typedef void(^FJRefetchHandler)(void)

因为以后要删除块,所以我也存储observer,并声明如下方法:

- (void)removeRefetchObserver:(id)observer;

用法如下所示:

// some place in code
[controller addRefetchObserver:self handler:^{
    // refetch some stuff, i.e.
    self.data = [self updateData];
}];    
// some other place in code:
[controller removeRefetchObserver:self];

我的问题是:我应该如何实现-addRefetchObserver:handler:,这样我就不会创建任何保留周期?我应该如何存储观察者和处理者?

显然,NSNotificationCenter 以某种方式存储观察者而不保留它 - 否则我将无法在 -dealloc 中调用 [center removeObserver:self],因为永远不会调用 -dealloc

另外,在块中引用self 时,有没有办法绕过使用__unsafe_unretained?即像这样:

__unsafe_unretained MyObject *blockSelf = self;
[controller addRefetchObserver:self handler:^{
    blockSelf.data = [blockSelf updateData];
}];

【问题讨论】:

    标签: iphone objective-c memory-management objective-c-blocks


    【解决方案1】:

    显然,NSNotificationCenter 以某种方式存储了观察者而不 保留它 - 否则我将无法致电 [center removeObserver:self] in -dealloc 因为 -dealloc 永远不会得到 调用。

    是的,他们保持对它的弱引用。你也可以很容易地在你的类中保留对观察者的弱引用:如果你需要一个弱引用的集合,你可以制作一个非保留版本的 NSArray 或 NSSet 或 NSDictionary,使用 Core Foundation 函数来创建 CFArray / CFSet / CFDictionary(它们是免费桥接的,即与 NS 等效项相同),允许您明确指定保留/释放行为;因此,如果没有强引用,您只需让它在保留和释放时什么都不做。

    您存储观察者有点奇怪。使用 NSNotificationCenter,它们存储观察者,因为它们需要观察者和选择器来进行调用。有了你的,block已经足够调用了,block封装了所有使用observer的逻辑,所以单独存储一个“observer”似乎很奇怪。似乎您拥有它的唯一原因是有办法将其删除。就此而言,它可以是任何对象,只要您传入相同的对象以添加和删除即可。

    而 NSNotificationCenter 只有一个对“观察者”的引用,而您的系统有两个引用——一个作为传入的“观察者”,但您也有一个对块的引用,它很可能引用了“观察者”也。如果你想让它和 NSNotificationCenter 一样工作,你需要确保它们都是弱引用。我想你已经弄清楚了——使用我在第一段中描述的内容,你保持弱的直接“观察者”引用;该块对“观察者”的引用也必须是弱的。

    另外,有没有办法在什么情况下使用 __unsafe_unretained 在块中引用自我?即像这样:

    您所拥有的是从块中弱引用某些内容的正确方法。更具体地说,如果您使用 ARC 并且仅针对 iOS 5+,则应使用 __weak。如果您使用 ARC,则应该使用 __unsafe_unretained(就像您一样)。如果您不使用 ARC,则应使用 __block

    【讨论】:

    • 感谢您的回答。我将使用 CF 来存储我的对象。你是对的,我只是存储“观察者”以便以后有办法删除它。我只是觉得这很方便,因为用户不必担心创建一个唯一的对象来作为观察者传递,并且可以传递self。存储块需要用户保持对它的尊重,我认为这很不方便 - 我宁愿将块声明到位。但我会再考虑一下。
    【解决方案2】:

    在块内调用self的更好方法是将self的引用复制到相同的弱变量中,并在块内使用它;

    MyController __weak *__weakController = controller;
    [__weakController addRefetchObserver:self handler:^{
        // refetch some stuff, i.e.
        __weakController.data = [__weakController updateData];
    }];    
    // some other place in code:
    [controller removeRefetchObserver:self];
    

    但是,有时您可能正在处理长时间持续的操作,同时控制器可能会在块仍在进行中时被释放。由于块被放置在堆栈上并继续执行,因此更好的方法是在调用控制器之前检查控制器是否仍然存在,例如;

     MyController __weak *__weakController = controller;
        [__weakController addRefetchObserver:self handler:^{
             // some long on going tasks ...
            MyController __strong *strongController = __weakController;
            if(strongController)
              strongController.data = [strongController updateData];
        }];    
        // some other place in code:
        [controller removeRefetchObserver:self];
    

    【讨论】:

    • 我从没想过检查控制器是否仍然存在,谢谢。
    • 还有一点,块内的弱对象可以随时被垃圾回收。为此,该变量在块内被强烈引用,以便它一直保留到块的生命周期。由于控制器现在在块内的范围有限,因此避免了保留周期。
    • 如果您确保在保留/释放控制器的同一线程上执行块,则可以避免“控制器可能在块仍在进行中时被释放”的问题。
    • 是的,确实如此。如果您正在使用 NSOperationQueue 或 gcd 并在另一个线程中执行某些操作,您的控制器可能会同时被释放。但是,即使我不在后台队列中也有关系。测试对象是否存在并向其发送消息是一件好事不是吗。
    猜你喜欢
    • 1970-01-01
    • 2012-10-28
    • 1970-01-01
    • 2012-06-11
    • 1970-01-01
    • 1970-01-01
    • 2014-10-27
    • 2021-09-16
    相关资源
    最近更新 更多