簡體   English   中英

如何在 ARC 下的 Objective-C 上清零弱引用變為 nil 時得到通知?

[英]How to get notified when a zeroing weak reference becomes nil on Objective-C under ARC?

是否有一種機制可以讓對象知道歸零弱引用變為 nil?

例如我有一個財產

@property (nonatomic, weak) MyClass *theObject;

當對象解除分配並且屬性變為 nil 時,我想得到通知。 但是如何? 當對象消失時,歸零弱參考系統是否使用 setter 將屬性設置為 nil?

運行時只是將弱 ivar _theObect 設置為 nil,不會調用自定義設置器。

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

  • 定義一個本地“觀察者”類並在該類中實現 dealloc,
  • 創建一個觀察者對象並將其設置為_theObject 的“關聯對象”。

當 _theObject 被釋放時,關聯的對象被釋放和釋放(如果沒有其他強引用)。 因此它的 dealloc 方法被調用。 這是您的“通知”。

(我是在電話上寫的,如有必要,稍后可以填寫詳細信息。)

如果您關心對象何時消失,則不應使用弱引用。 你想做什么?

沒有關於對象釋放的通知。

系統不會使用 setter 方法(這意味着不會引發 KVO 通知)。 ivar 是真正的弱引用,它被歸零。 屬性上的weak關鍵字只是用於合成 ivar 的指令,以及不保留對象的公共聲明。

盡管您總是可以發明自己的通知並從您的類的dealloc方法發送它們,但請注意,通常您不應該對此類通知感興趣,並且至少有一個很好的理由說明它們不存在。

無論何時使用任何類型的自動內存管理,您都不能(根據定義)期望對象在您需要它們時確切地消亡,這適用於 Objective-C 引用計數。 因為任何組件都可能在未知的時間段內意外地延長任何對象的生命周期,依賴程序行為假設dealloc將在您需要時被准確調用是糟糕的設計和麻煩的秘訣。 dealloc應該只用於清理。

試試這個經驗法則:如果根本沒有調用dealloc程序,程序還能正常工作嗎? 如果沒有,您應該重新考慮程序的邏輯而不是發送 dealloc 通知。

我使用所謂的弱引用注冊表實現了這一點,請參閱類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

根據Martin R回答,我想出了以下代碼段。 只要確保您沒有使用 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 */ })

沒有弱變量的通知系統。

以下是我用來實現委托多播的示例。 說明如何監視弱引用對象(委托)的“dealloc”可能很有用。

將有一個主 DelegateRef 對象。 它的數組記錄了包裝真實委托的所有委托引用。 這里的主要目的是在真正的委托解除分配時刪除數組保存的對 delegateRefs 的強引用。 因此,在添加委托時會創建一個本地監視對象並將其關聯到委托。 當本地手表解除分配時,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

Apple 通過使用他們的私有_UIWeakHelper類在UIPageViewController的弱dataSource屬性上實現了這一點,但您可以輕松實現相同的功能。 setDataSource setter 中,他們創建了一個[_UIWeakHelper.alloc initWithDeallocationBlock:block]實例,並且該塊在弱/強舞蹈之后調用self.dataSource = nil以避免保留循環。 然后他們在 dataSource 對象上調用objc_setAssociatedObject設置弱助手對象。 最后在_UIWeakHelper dealloc他們調用了 deallocation 塊。 它起作用是因為當dataSource被釋放時,關聯的_UIWeakHelper也會被_UIWeakHelper

如果您想知道他們為什么需要這個,那是因為當dataSource解除分配時,他們想要禁用頁面滾動,因為沒有要滾動到的頁面。

只是不要Apple犯過的同樣的錯誤(從 iOS 13.4.1 Xcode 11.4.1 開始)他們將 helper 對象關聯到dataSourcedelegate的相同鍵,因此只有一個釋放塊被觸發,哦!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM