Hooking end of ARC dealloc

你离开我真会死。 提交于 2019-12-24 03:08:01

问题


Given the following simple implementation:

@implementation RTUDeallocLogger
-(void)dealloc
{
    NSLog(@"deallocated");
}
@end

we run the following code under ARC:

@implementation RTURunner
{
    NSArray* arr;
}
-(void)run{
    arr = [NSArray
           arrayWithObjects:[[RTUDeallocLogger alloc]init],
                            [[RTUDeallocLogger alloc]init],
                            [[RTUDeallocLogger alloc]init],
                            nil];
    NSLog(@"nulling arr");
    arr = NULL;
    NSLog(@"finished nulling");
}
@end

we get the following log output:

nulling arr
finished nulling
deallocated
deallocated
deallocated

I'd like to perform an action after all the deallocations have finished. Is this possible?

The aim of this question is really to understand a little more about the mechanics of ARC, in particular, at what point ARC triggers these deallocations, and whether or not this can ever happen synchronously when I drop references.


回答1:


-dealloc is always synchronous, and occurs when the last strong reference is removed. In the case of your code though, +arrayWithObjects: is likely (if compiled at -O0 at least) putting the array in the autorelease pool, so the last strong reference is removed when the pool drains, not when you set the variable to NULL (you should use nil for ObjC objects, btw).

You can likely avoid having the object in the autorelease pool by using alloc/init to create, and you may (implementation detail, bla bla) be able to avoid it by compiling with optimizations turned on. You can also use @autoreleasepool { } to introduce an inner pool and bound the lifetime that way.




回答2:


If I were an engineer from Apple I'd probably argue that your problem is probably your design. There are almost no reasons you'd want effectively to act by watching dealloc rather than having dealloc itself act.

[a huge edit follows: weak properties don't go through the normal property mechanisms, so they aren't KVO compliant, including for internal implicit KVO as originally proposed]

That said, what you can do is bind the lifetime of two objects together via object associations and use the dealloc of the latter as a call-out on the dealloc of the former.

So, e.g.

#import <objc/runtime.h>

@interface DeallocNotifier;
- (id)initWithObject:(id)object target:(id)target action:(SEL)action;
@end

@implementation DeallocNotifier
- (id)initWithObject:(id)object target:(id)target action:(SEL)action
{
    ... blah ...

    // we'll use a static int even though we'll never access by this key again
    // to definitely ensure no potential collisions from lazy patterns
    static int anyOldKeyWellNeverUseAgain;

    objc_setAssociatedObject(object, &anyOldKeyWellNeverUseAgain, self, OBJC_ASSOCIATION_RETAIN);

    ... blah ...
}
- (void)dealloc
{
    [_target performSelector:_action];
}
@end

-(void)run{
    arr = ...

    [[DeallocNotifier alloc]
           initWithObject:arr target:self action:@selector(arrayDidDealloc)];

    /* you may not even need *arr in this case; I'm unclear as
    to why you have an instance variable for something you don't
    want to keep, so I guess it'll depend on your code */
} // end of run


- (void)arrayDidDealloc
{
    NSLog(@"array was deallocated");
}

I've assumed you're able to tie the lifecycle of all the objects you're interested in to that of a single container; otherwise you could associate the notifier to all relevant objects.

The array has definitely gone by the time you get arrayDidDealloc.




回答3:


at what point ARC triggers these deallocations

ARC inserts allocations/deallocations into your code based on static analysis. You can see where it does this by looking at the assembly of your source -- go to Product -> Generate Output in Xcode.

whether or not this can ever happen synchronously when I drop references

Retain/release/autorelease is always synchronous.




回答4:


Your code

arr = [NSArray arrayWithObjects:[[RTUDeallocLogger alloc] init],
                                [[RTUDeallocLogger alloc] init],
                                [[RTUDeallocLogger alloc] init],
                                nil];

will be implicitly placing the objects into an autorelease pool. After the object is allocated, you don't want it retained (because the NSArray will do the retain once it receives the object), but you can't release it immediately, otherwise it will never make it to the NSArray alive. This is the purpose of autorelease - to cover the case where the object would otherwise be in limbo between two owners.

The retain count at alloc time is 1, then it's retained by the autoreleasepool and released by you, so the retain count remains 1. Then, it's retained by the NSArray, so the retain count becomes 2.

Later, the NSArray is released and so the retain count returns to 1, and the objects are finally cleaned up when the autorelease pool gets its chance to run.

You can make the autorelease act faster by nesting another pool - by wrapping your NSArray creation with an @autorelease{} clause.



来源:https://stackoverflow.com/questions/14616119/hooking-end-of-arc-dealloc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!