如何在ARC下的Objective-C上将归零弱引用变为零时得到通知?

时间:2013-10-13 10:22:23

标签: ios objective-c memory-management automatic-ref-counting

是否有一种机制允许对象知道归零弱引用变为零?

例如我有一个属性

@property (nonatomic, weak) MyClass *theObject;

当对象解除分配并且属性变为零时,我希望得到通知。但是怎么样?当对象消失时,归零弱引用系统是否使用setter将属性设置为nil?

8 个答案:

答案 0 :(得分:23)

运行时只是将弱ivar _theObect设置为nil,不调用自定义setter。

你能做什么(如果真的需要通知):

  • 定义一个本地“watcher”类并在该类中实现dealloc,
  • 创建一个观察者对象并将其设置为_theObject的“关联对象”。

当_theObject被释放时,关联的对象被释放并释放(如果没有其他强烈的引用)。因此,它的dealloc方法被称为。这是你的“通知”。

(我在手机上写这个,如有必要,可以在以后填写详细信息。)

答案 1 :(得分:3)

如果您关心物体何时消失,则不应使用弱参考。你想做什么?

答案 2 :(得分:3)

没有关于对象释放的通知。

系统不会使用setter方法(这意味着不会引发KVO通知)。 ivar是真正的弱参考,它被归零。属性上的weak关键字仅仅是用于合成ivar的指令,以及不保留该对象的公开声明。

虽然您可以随时发明自己的通知并从课程的dealloc方法发送它们,但请注意,通常您不应该对此类通知感兴趣,并且至少有一个很好的理由是它们不会存在。

每当使用任何类型的自动内存管理时,您都不能(根据定义)期望对象在您需要时完全死亡,这适用于Objective-C引用计数。因为任何组件可能会在未知的时间段内意外地延长任何对象的生命周期,所以在假设dealloc将在您需要时准确调用时依赖于程序行为是糟糕的设计和麻烦的处方。 dealloc应仅用于清理。

尝试这样的经验法则:如果dealloc根本没有被调用,程序是否仍能正常工作?如果没有,你应该重新考虑程序的逻辑,而不是发出dealloc通知。

答案 3 :(得分:2)

我使用所谓的弱引用注册表实现了这一点,请参阅类BMWeakReferenceRegistry,这是我的iOS开源BMCommons框架的一部分。

此类将上下文对象与感兴趣的对象相关联。释放此对象时,上下文对象也将被调用。

参见API:

/**
 * Registry for monitoring the deallocation of objects of interest to perform cleanup logic once they are released.
 */
@interface BMWeakReferenceRegistry : BMCoreObject

BM_DECLARE_DEFAULT_SINGLETON

/**
 * Cleanup block definition
 */
typedef void(^BMWeakReferenceCleanupBlock)(void);

/**
 * Registers a reference for monitoring with the supplied cleanup block.
 * The cleanup block gets called once the reference object gets deallocated.
 *
 * It is possible to register the same reference multiple times with different cleanup blocks (even if owner is the same).
 * If this is not intended behavior, check hasRegisteredReference:forOwner: before calling this method.
 *
 * @param reference The object to monitor
 * @param owner An optional owner (may be specified to selectively deregister references)
 * @param cleanup The cleanup block
 */
- (void)registerReference:(id)reference forOwner:(id)owner withCleanupBlock:(BMWeakReferenceCleanupBlock)cleanup;

/**
 * Deregisters the specified reference for monitoring. If owner is not nil, only the monitor(s) for the specified owner is/are removed.
 *
 * @param reference The monitored reference
 * @param owner The optional owner of the reference
 */
- (void)deregisterReference:(id)reference forOwner:(id)owner;

/**
 * Checks whether a monitor already exists for the specified reference/owner. If the owner parameter is nil all owners are checked.
 *
 * @param reference The monitored reference
 * @param owner The optional owner
 * @return True if registered, false otherwise.
 */
- (BOOL)hasRegisteredReference:(id)reference forOwner:(id)owner;

@end

答案 4 :(得分:0)

没有针对弱变量的通知系统。

答案 5 :(得分:0)

以下是我用于实现委托多播的示例。说明如何监控' dealloc'弱引用对象(委托)。

将有一个主DelegateRef对象。它的数组记录了包装真实委托的所有delegateRefs。这里的主要目的是在真正的委托dealloc时删除对数组保留的delegateRefs的强引用。因此,在添加委托时,会创建本地监视对象并将其与委托关联。当本地监视dealloc时,delegateRef将从主DelegateRef的数组中删除。

#import <objc/runtime.h>
@interface WeakWatcher : NSObject
@property (nonatomic, weak) NSMutableArray *masterarray;
@property (nonatomic, weak) DelegateRef *delegateRef;
@end
@implementation WeakWatcher
-(void)dealloc
{ // when the object dealloc, this will be called
    if(_delegateRef != nil)
    {
        if([self.masterarray containsObject:_delegateRef])
        {
            [_masterarray removeObject:_delegateRef];
        }
    }
}
@end

@interface DelegateRef()
@end

@implementation DelegateRef
static char assoKey[] = "assoKey";

- (NSMutableArray *)array {
    if (_array == nil) {
        _array = [NSMutableArray array];
    }
    return _array;
}

-(void)addWeakRef:(id)ref
{
    if (ref == nil) {
        return;
    }
    DelegateRef *delRef = [DelegateRef new];
    WeakWatcher* watcher = [WeakWatcher new];  // create local variable
    watcher.delegateRef = delRef;
    watcher.masterarray = self.array;

    [delRef setDelegateWeakReference:ref];
    objc_setAssociatedObject(ref, assoKey, watcher, OBJC_ASSOCIATION_RETAIN);

    [self.array addObject:delRef];
}
@end

答案 6 :(得分:0)

基于Martin Ranswer,我提出了以下代码段。只要确保您没有使用onDeinit闭包创建任何保留周期即可!

private var key: UInt8 = 0

class WeakWatcher {
    private var onDeinit: () -> ()

    init(onDeinit: @escaping () -> ()) {
        self.onDeinit = onDeinit
    }

    static func watch(_ obj: Any, onDeinit: @escaping () -> ()) {
        watch(obj, key: &key, onDeinit: onDeinit)
    }

    static func watch(_ obj: Any, key: UnsafeRawPointer, onDeinit: @escaping () -> ()) {
        objc_setAssociatedObject(obj, key, WeakWatcher(onDeinit: onDeinit), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }

    deinit {
        self.onDeinit()
    }
}

在初始化弱变量时像这样调用它:

self.weakVar = obj
WeakWatcher.watch(obj, onDeinit: { /* do something */ })

答案 7 :(得分:0)

Apple通过使用其私有的UIPageViewController类在dataSource的弱_UIWeakHelper属性上实现了这一目标,但您可以轻松实现相同的目标。在setDataSource设置器中,他们创建了[_UIWeakHelper.alloc initWithDeallocationBlock:block]的实例,该块在弱跳/强跳之后调用self.dataSource = nil以避免保留周期。然后,他们在dataSource对象上调用objc_setAssociatedObject,以设置弱辅助对象。最后,它们在_UIWeakHelper dealloc中调用解除分配块。之所以起作用,是因为当dataSource被解除分配时,关联的_UIWeakHelper也将如此。

如果您想知道为什么他们需要这样做,那是因为当dataSource取消分配时,由于没有要滚动的页面,他们想禁用页面的滚动。

只是使用与苹果公司(自iOS 13.4.1 Xcode 11.4.1起)创建的mistake不同,它们将dataSource和{ {1}},所以只有一个解除分配块被触发了!