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

Posted

技术标签:

【中文标题】如何在 ARC 下的 Objective-C 上零弱引用为零时得到通知?【英文标题】:How to get notified when a zeroing weak reference becomes nil on Objective-C under ARC? 【发布时间】:2013-10-21 01:34:37 【问题描述】:

是否有一种机制可以让对象知道归零弱引用变为 nil?

例如我有一个财产

@property (nonatomic, weak) MyClass *theObject;

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

【问题讨论】:

【参考方案1】:

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

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

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

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

(我在手机上写这个,以后有需要可以填写。)

【讨论】:

【参考方案2】:

如果您关心对象何时消失,则不应使用弱引用。你想做什么?

【讨论】:

【参考方案3】:

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

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

尽管您总是可以发明自己的通知并从您的类的dealloc 方法发送它们,但请注意,通常您不应该对此类通知感兴趣,并且至少有一个充分的理由表明它们不存在。

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

试试这个经验法则:如果dealloc 根本没有被调用,程序还能正常工作吗?如果没有,您应该重新考虑程序的逻辑,而不是发送 dealloc 通知。

【讨论】:

【参考方案4】:

我使用所谓的弱引用注册表实现了这一点,请参阅 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

【讨论】:

这个拐杖完全不可靠。希望我永远不必用那种糟糕的解决方法来支持代码。 重新实现以消除计时器,我同意原始解决方案不是最佳的【参考方案5】:

基于Martin R的answer,我想出了以下sn-p。只要确保你的 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 */ )

【讨论】:

【参考方案6】:

弱变量没有通知系统。

【讨论】:

【参考方案7】:

以下是我用来实现委托多播的示例。说明如何监视弱引用对象(委托)的“dealloc”可能很有用。

会有一个主 DelegateRef 对象。它的数组记录了包装真实代表的所有 delegateRefs。这里的主要目的是在真正的委托dealloc时删除数组保存的对delegateRefs的强引用。因此,在添加委托时会创建一个本地监视对象并将其关联到委托。当本地 watch dealloc 时,delegateRef 会从 master 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

【讨论】:

【参考方案8】:

Apple 通过使用 UIPageViewController 的私有 _UIWeakHelper 类在 UIPageViewController 的弱 dataSource 属性上实现了这一点,但您可以轻松实现相同的目标。在setDataSource setter 中,他们创建了[_UIWeakHelper.alloc initWithDeallocationBlock:block] 的实例,并且在弱/强舞蹈之后块调用self.dataSource = nil 以避免保留循环。然后他们在 dataSource 对象上调用objc_setAssociatedObject 设置弱辅助对象。最后在_UIWeakHelperdealloc 中,他们调用了释放块。它之所以有效,是因为当 dataSource 被释放时,关联的 _UIWeakHelper 也会被释放。

如果您想知道他们为什么需要这个,那是因为当 dataSource 解除分配时,他们想要禁用页面滚动,因为没有页面可以滚动到。

只是不要创建相同的mistake Apple (从 iOS 13.4.1 Xcode 11.4.1 开始)他们将帮助对象关联到 dataSourcedelegate 的相同键,所以只有一个释放块被触发,doh!

【讨论】:

以上是关于如何在 ARC 下的 Objective-C 上零弱引用为零时得到通知?的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C关于非ARC模式下的对象引用计数

当没有其他引用保留对象时,如何将 id 对象安全地存储在 ARC 下的 C++ void* 成员中?

如何实现与 ARC 兼容的 Objective-C 单例?

ARC Objective-C 中的输出参数

在 Objective-C 中使用 ARC 时 AutoRelease 是多余的吗?

Objective-c开发教程--MRC和ARC混编