How to get notified when a zeroing weak reference

2020-02-24 12:56发布

Is there a mechanism which would allow an object to know that a zeroing weak reference turned nil?

For example I have a property

@property (nonatomic, weak) MyClass *theObject;

when theObject deallocates and the property turns nil I want to get notified. But how? Does the zeroing weak reference system use the setter to set the property to nil when the object goes away?

7条回答
我只想做你的唯一
2楼-- · 2020-02-24 13:15

The runtime just sets the weak ivar _theObect to nil, a custom setter is not called.

What you could do (if you really need the notification):

  • define a local "watcher" class and implement dealloc in that class,
  • create a watcher object and set it as "associated object" of _theObject.

When _theObject is deallocated, the associated object is released and deallocated (if there are no other strong refereces to it). Therefore its dealloc method is called. This is your "notification".

(I'm writing this on the phone and can fill in the details later if necessary.)

查看更多
Bombasti
3楼-- · 2020-02-24 13:16

I implemented this using a so-called weak reference registry, see the class BMWeakReferenceRegistry, part of my open source BMCommons framework for iOS.

This class associates context objects with the object of interest. When this object is released, so is the context object and the cleanup block is called.

See the 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
查看更多
Rolldiameter
4楼-- · 2020-02-24 13:21

There is no notification about object deallocation.

The system will not use setter method (this means no KVO notifications will be raised). The ivar is the real weak reference which gets zeroed. The weak keyword on a property is merely an instruction for synthesizing the ivar, and a public declaration that the object is not retained.

Though you can always invent your own notifications and send them from dealloc method of your classes, note that normally you should not ever be interested in such notifications and there is at least one good reason that they don't exist.

Whenever there is any kind of automatic memory management is in use, you can not (by definition) expect objects to die exactly when you need them to, that applies to Objective-C reference counting. Because any component may unexpectedly prolong lifetime of any object for unknown period of time, relying program behavior on assumption that dealloc will be called exactly when you need it to is bad design and a recipe for trouble. dealloc should be used for cleaning up only.

Try this rule of thumb: will the program still work correctly if dealloc does not get called at all? If not, you should rethink program's logic rather than sending out dealloc notifications.

查看更多
5楼-- · 2020-02-24 13:25

The following is an example that I used to implement multicast of delegates. It might be useful to illustrate how to monitor the 'dealloc' of weak referenced objects (the delegates).

There will be a master DelegateRef object. Its array keeps record of all delegateRefs which wrap the real delegates. The main purpose here is to remove the strong reference to delegateRefs kept by the array when the real delegates dealloc. Therefore, a local watch object is created and associated to delegate when adding the delegate. When the local watch dealloc, the delegateRef gets removed from the master DelegateRef's array.

#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
查看更多
Anthone
6楼-- · 2020-02-24 13:26

Based on Martin R's answer, I came up with the following snippet. Just make sure you don't create any retain cycles with your onDeinit closure!

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()
    }
}

Call it like this when initializing your weak var:

self.weakVar = obj
WeakWatcher.watch(obj, onDeinit: { /* do something */ })
查看更多
在下西门庆
7楼-- · 2020-02-24 13:29

there is no notification system for weak vars.

查看更多
登录 后发表回答