Search code examples
objective-cobjective-c-blocksobjective-c-runtimeobjective-c-category

Objective-C Runtime - Run Code at Deallocation of Any Object


I was reading this article by Jeff Kelley and trying to do the same. However the code was written before ARC was adopted and now fails to compile.

http://blog.slaunchaman.com/2011/04/11/fun-with-the-objective-c-runtime-run-code-at-deallocation-of-any-object/

The main problem is in this part of the printout, some casting errors and then blocked release messages. I found it to be a very interesting example but I can't seem to get it to work.

The problems are:

0. Autosynthesized property 'block' will use synthesized instance variable '_block', not existing instance variable 'block' on the @implementation JKBlockExecutor

1. Cast of block pointer type 'voidBlock' (aka 'void (^)(void)') to C pointer type 'const void *' requires a bridged cast and Cast of C pointer type 'void *' to block pointer type 'typeof (aBlock)' (aka 'void (^__strong)(void)') requires a bridged cast" on the block = Block_copy(aBlock); line

2. Cast of block pointer type 'voidBlock' (aka 'void (^)(void)') to C pointer type 'const void *' requires a bridged cast on Block_release(block);

typedef void (^voidBlock)(void);

@interface JKBlockExecutor : NSObject {
    voidBlock   block;
}

@property (nonatomic, readwrite, copy) voidBlock    block;

- (id)initWithBlock:(voidBlock)block;

@end

@implementation JKBlockExecutor

@synthesize block;

- (id)initWithBlock:(voidBlock)aBlock
{
    self = [super init];

    if (self) {
        block = Block_copy(aBlock);
    }

    return self;
}

- (void)dealloc
{
    if (block != nil) {
        block();
        Block_release(block);
    }

    [super dealloc];
}

@end

This is where he creates a category on NSObject.

const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;

@interface NSObject (JK_RunAtDealloc)

- (void)runAtDealloc:(voidBlock)block;

@end

@implementation NSObject (JK_RunAtDealloc)

- (void)runAtDealloc:(voidBlock)block
{
    if (block) {
        JKBlockExecutor *executor = [[JKBlockExecutor alloc] initWithBlock:block];

        objc_setAssociatedObject(self,
                                 runAtDeallocBlockKey,
                                 executor,
                                 OBJC_ASSOCIATION_RETAIN);

        [executor release];
    }
}

@end

This is how you execute the example.

NSObject *foo = [[NSObject alloc] init];

[foo runAtDealloc:^{
    NSLog(@"Deallocating foo!");
}];

[foo release];

Or another way to get other information.

NSObject *foo = [[NSObject alloc] init];

__block id objectRef = foo;

[foo runAtDealloc:^{
    NSLog(@"Deallocating foo at address %p!", objectRef);
}];

[foo release];

Can this code be fixed somehow? I took out all the release messages to no avail.


Solution

  • Code below builds and works (or at least seems so), and prints "Deallocating foo!" when I expect it to print it. Part 1:

    typedef void (^voidBlock)(void);
    
    @interface JKBlockExecutor : NSObject {
        voidBlock   block;
    }
    
    @property (nonatomic, readwrite, copy) voidBlock    block;
    
    - (id)initWithBlock:(voidBlock)block;
    
    @end
    
    @implementation JKBlockExecutor
    
    @synthesize block = block;
    
    - (id)initWithBlock:(voidBlock)aBlock
    {
        self = [super init];
    
        if (self) {
            block = [aBlock copy];
        }
    
        return self;
    }
    
    - (void)dealloc
    {
        if (block != nil) {
            block();
            block = nil;
        }
    }
    
    @end
    

    Part 2:

    const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;
    
    @interface NSObject (JK_RunAtDealloc)
    
    - (void)runAtDealloc:(voidBlock)block;
    
    @end
    
    @implementation NSObject (JK_RunAtDealloc)
    
    - (void)runAtDealloc:(voidBlock)block
    {
        if (block) {
            JKBlockExecutor *executor = [[JKBlockExecutor alloc] initWithBlock:block];
    
            objc_setAssociatedObject(self,
                                     runAtDeallocBlockKey,
                                     executor,
                                     OBJC_ASSOCIATION_RETAIN);
        }
    }
    
    @end
    

    Testing if it works:

    @autoreleasepool {
            NSObject *foo = [[NSObject alloc] init];
    
            [foo runAtDealloc:^{
                NSLog(@"Deallocating foo!");
            }];
        }
    

    EDIT

    Changed Block_release(block); to block = nil;