user519179
user519179

Reputation:

Callback when a block is deallocated

I am calling into Cocoa from C, all through the Obj-C runtime.

I am able to create block objects with the info from here[1] and pass them as arguments to Cocoa methods which retain them as needed, and release them when they are no longer needed. The problem is that I need to release other resources associated with the block when the block reaches refcount 0 and is deallocated, so I need a way to set a callback for when that happens.

With normal objects, I would just subclass and override dealloc(). I hear blocks are objects too - is there a Block class that can be subclassed? Or is there any other way to hook up a function on release and/or dealloc of blocks?

Thanks.

[1] http://clang.llvm.org/docs/Block-ABI-Apple.html

Upvotes: 0

Views: 848

Answers (3)

CRD
CRD

Reputation: 53000

Expanding on my comment:

I'll assume you are using the Clang compiler to create your blocks in C, if you are creating the block description structs yourself the idea is the same but you can create the structs directly with the correct values.

If you wish to call a cleanup function when a block is disposed of then (in outline):

if (bObject->flags & BLOCK_HAS_COPY_DISPOSE)
{
   // block already has a dispose helper
   // save current dispose helper in a lookup table with key the bObject
   bObject->descriptor->dispose_helper = function which:
                                         a) uses the lookup table to call the original helper
                                         b) removes the entry from the lookup table
                                         c) calls your cleanup function
}
else
{
   // block does not have a dispose helper
   bObject->flags |= BLOCK_HAS_COPY_DISPOSE; // set is has helpers
   bObject->descriptor->copy_helper = dummy copy function
   bObject->descriptor->dispose_helper = dispose function which just calls your cleanup
}

You need a lookup table to store a map from block addresses to helper addresses, e.g. NSMapTable.

HTH

Addendum

As requested in comments my quick'n'dirty test code, it just follows the pseudo-code above. Run this and you should see the second and third blocks get disposed, the first is not as its a static literal and doesn't need disposing.

void DummyBlockCopy(void *src, void *dst)
{
}

void BlockDispose(void *src)
{
    printf("BlockDispose %p\n", src);
}

typedef void (*HelperFunction)(void *);

NSMapTable *disposeHelpers;

void BlockDisposeCallExisting(void *src)
{
    HelperFunction helper = (__bridge void *)[disposeHelpers objectForKey:(__bridge id)(src)];
    if (helper)
    {
        helper(src);
        [disposeHelpers removeObjectForKey:(__bridge id)(src)];
    }
    printf("BlockDisposeCallExisting %p\n", src);
}

void block_trap_dispose(void *aBlock)
{
    BlockObject *bObject = aBlock;
    if (bObject->flags & BLOCK_HAS_COPY_DISPOSE)
    {
        [disposeHelpers setObject:(__bridge id)(void *)bObject->descriptor->dispose_helper forKey:(__bridge id)(aBlock)];
        bObject->descriptor->dispose_helper = BlockDisposeCallExisting;
    }
    else
    {
        bObject->flags |= BLOCK_HAS_COPY_DISPOSE;
        bObject->descriptor->copy_helper = DummyBlockCopy;
        bObject->descriptor->dispose_helper = BlockDispose;
    }
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    disposeHelpers = [NSMapTable.alloc initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
                                             valueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
                                                 capacity:2];

    void (^b1)(void) = ^{ printf("hello world\n"); };
    printf("b1: %p\n", b1);
    b1();
    block_trap_dispose((__bridge void *)(b1));

    int x = 10;
    void (^b2)(void) = ^{ printf("x is %d\n", x); };
    printf("b2: %p\n", b2);
    b2();
    block_trap_dispose((__bridge void *)(b2));
    
    NSObject *anObject = NSObject.new;
    void (^b3)(void) = ^{ printf("anObject: %p\n", anObject); };
    printf("b3: %p\n", b3);
    b3();
    block_trap_dispose((__bridge void *)(b3));
}

Upvotes: 1

user519179
user519179

Reputation:

Ok, this is how I solved it.

First I created a block_literal (defined to have a block_descriptor attached).

struct block_descriptor {
    unsigned long int reserved;         // NULL
    unsigned long int size;             // sizeof(struct block_literal)
    copy_helper_t     copy_helper;      // IFF (1<<25)
    dispose_helper_t  dispose_helper;   // IFF (1<<25)
};

struct block_literal {
    struct block_literal *isa;
    int flags;
    int reserved;
    void *invoke;
    struct block_descriptor *descriptor;
    struct block_descriptor d; // because they come in pairs
};

This is how you should set the fields:

block.isa        = _NSConcreteStackBlock //stack block because global blocks are not copied/disposed
block.flags      = 1<<25 //has copy & dispose helpers
block.reserved   = 0
block.invoke     = my_callback_function
block.descriptor = &block.d
block.d.reserved = 0
block.d.size     = sizeof(block_literal)
block.d.copy_helper    = my_copy_callback
block.d.dispose_helper = my_dispose_callback

I keep a refcount per each created block which starts at 1, and which is incremented in my_copy_callback and decremented in my_dispose_callback. When the refcount reaches 0 the resources associated with the block gets released.

Note: copy/dispose helpers will not be called on synchronous methods like NSString's enumerateLinesUsingBlock because these methods don't retain/release the block while using it because they assume that the block remains available for the duration of the call. OTOH, an async method like dispatch_async() does invoke the helpers. Calling dispatch_async() multiple times on the same block should show the refcount incremented twice and then decremented.

Upvotes: 0

Darren
Darren

Reputation: 25619

You can use the Obj-C Associated Objects API to associate an object instance with a block instance. The associated object will (if it is not accessed anywhere else) be deallocated when the block is deallocated.

Use the -dealloc method of the associated object to execute any desired resource cleanup, etc.

Upvotes: 1

Related Questions