取消分配块时的回调

时间:2014-05-27 00:36:37

标签: objective-c c macos cocoa

我通过Obj-C运行时从C调用Cocoa。

我能够使用来自here [1]的信息创建块对象,并将它们作为参数传递给Cocoa方法,这些方法根据需要保留它们,并在不再需要它们时释放它们。问题是当块到达refcount 0并且被释放时我需要释放与块相关的其他资源,所以我需要一种方法来设置回调时间。

对于普通对象,我只是子类化并覆盖dealloc()。我听说块也是对象 - 是否有可以被子类化的Block类?或者还有其他方法可以在块的释放和/或释放上挂钩函数吗?

感谢。

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

3 个答案:

答案 0 :(得分:1)

您可以使用Obj-C Associated Objects API将对象实例与块实例相关联。当块被释放时,关联的对象(如果在其他任何地方不被访问)将被释放。

使用关联对象的-dealloc方法执行任何所需的资源清理等。

答案 1 :(得分:0)

扩展我的评论:

我假设您正在使用Clang编译器在C中创建块,如果您自己创建块描述结构,这个想法是相同的,但您可以使用正确的值直接创建结构。

如果您希望在处理某个块时调用清理功能(概述):

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
}

您需要一个查找表来存储从块地址到辅助地址的映射,例如: NSMapTable

HTH

<强>附录

根据评论中的要求,我的quick'n'dirty测试代码,它只是遵循上面的伪代码。运行它,您应该看到第二个和第三个块被释放,第一个块不是静态文字,不需要处理。

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

答案 2 :(得分:0)

好的,这就是我解决它的方法。

首先我创建了一个block_literal(定义为附加block_descriptor。)

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
};

这是你应该如何设置字段:

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

我为每个创建的块保留一个引用计数,它从1开始,并在my_copy_callback中递增,在my_dispose_callback中递减。当refcount达到0时,与块关联的资源被释放。

注意:不会在NSString的enumerateLinesUsingBlock之类的同步方法上调用copy / dispose helper,因为这些方法在使用时不保留/释放块,因为它们假设块在持续时间内保持可用状态。呼叫。 OTOH,像dispatch_async()这样的异步方法会调用助手。在同一个块上多次调用dispatch_async()应该显示refcount递增两次然后递减。