代码之家  ›  专栏  ›  技术社区  ›  kennyc

Objective-C中的块是否始终保证捕获变量?

  •  2
  • kennyc  · 技术社区  · 6 年前

    例如,假设您有一个 NSArray 包含大量项目,可能需要很长时间才能解除分配。你需要访问 不可变数组 在主线程上,但一旦处理完它,就可以在后台队列中取消分配它。后台块只需要捕获阵列,然后立即解除分配。它实际上与它没有任何关系。编译器能否检测到这一点,并“错误地”跳过块捕获?

    例子:

    // On the main thread...
    NSArray *outgoingRecords = self.records;
    self.records = incomingRecords;
    
    dispatch_async(background_queue, ^{
      (void)outgoingRecords;
    
      // After this do-nothing block exits, then outgoingRecords
      // should be deallocated on this background_queue.  
    });
    

    outgoingRecords 将始终在该块中捕获,并始终在 background_queue ?

    我将添加更多的上下文来更好地说明我的问题:

    后台读取完成后,我跳转到主线程交换Objective-C对象并重新填充表。

    在这一点上,我根本不关心旧向量或其父Objective-C类的内容。没有什么奇特的析构函数或对象图可以拆卸,但释放数百兆字节甚至千兆字节的内存并不是瞬间的。所以我愿意把它放到一个后台队列中,并在那里进行内存释放。在我的测试中,这似乎工作得很好,在16毫秒过去之前,我在主线程上有更多的时间做其他事情。

    count )因此编译器无法以某种方式对其进行优化。

    这是另一个不使用调度队列但仍然使用块的场景,这是我真正感兴趣的部分。

    id<MTLCommandBuffer> commandBuffer = ...
    
    // A custom class that manages an MTLTexture that is backed by an IOSurface.
    __block MyTextureWrapper *wrapper = ... 
    
    // Issue some Metal calls that use the texture inside the wrapper.
    
    // Wait for the buffer to complete, then release the wrapper.
    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
      wrapper = nil;
    }];
    

    MTLTexture 进入正在循环的像素池。IOSurface正在进程之间共享,而且,据我所知, MTLTexture 不会增加曲面上的使用计数。我的包装器类可以。当我的包装器类被释放时,useCount被递减,然后缓冲池可以自由地回收IOSurface。

    这一切都如预期的那样工作,但我最终得到了上面这样愚蠢的代码,只是因为不确定是否需要在块中“使用”包装器实例以确保它被捕获。如果包装器在完成处理程序运行之前被解除分配,那么IOSurface将被回收,纹理将被覆盖。

    1 回复  |  直到 6 年前
        1
  •  4
  •   Ken Thomases    6 年前

    编辑以解决问题编辑:

    从叮当声中 Language Specification for Blocks :

    块的语句作为常量导入并由块捕获 副本。捕获(绑定)在块执行时执行

    实际上不会计算对变量的引用。 程序员可以通过在 :

    (void) foo;
    

    当捕获变量可能产生副作用时,这一点很重要 .

    (重点加上。)


    不能保证提交到后台队列的块将是最后一个保存对数组的强引用的代码(甚至忽略块是否捕获变量的问题)。

    首先,该块实际上可能在提交它的上下文返回并释放其强引用之前运行。即调用 dispatch_async() 可以从CPU上交换,块可以首先运行。