【问题标题】:ARC __block behavior equivalent in MRC?MRC中的ARC __block行为等效?
【发布时间】:2012-10-03 19:20:31
【问题描述】:

如您所知,在 ARC 中,块中使用的对象指针类型的 __block 变量由块保留。所以举个简化的例子:

__block id foo = getObject();
void (^aBlock)() = ^ {
    NSLog(@"%@", foo);
    foo = getObject();
}
runBlockAsynchronouslyMultipleTimes(aBlock);

foo指向的对象被block保留,这样当block运行(异步)时,对象仍然有效,可以打印。当我们在块内进行赋值时,ARC 像任何其他强引用一样管理它(旧值被释放,新值被保留)。 (分配迫使我们首先使用__block。)当不再需要该块时,ARC 会以某种方式释放其由foo 指向的保留对象(它没有泄漏)。

好的,现在假设我想在 MRC 下做同样的事情(为什么不重要;这是关于语言的问题)。如您所知,块中使用的对象指针类型的 __block 变量不会被 MRC 中的块保留。这很好;我们将自己管理它(毕竟这是 MRC)。所以尝试看起来像这样:

__block id foo = [getObject() retain];
void (^aBlock)() = ^ {
    NSLog(@"%@", foo);
    [foo release];
    foo = [getObject() retain];
}
runBlockAsynchronouslyMultipleTimes(aBlock);
// where to release foo?

大部分都是直截了当的——对象最初是由我们手动保留的;在块内部,当我们重新分配指针时,我们会酌情释放并保留新值。

但是问题来了:当块不再需要时,我们如何释放对象?由于我们手动管理内存,理想情况下我们应该在块被释放时手动释放对象。但似乎没有一个简单的方法来做到这一点。

我可能会想到一种方法:使用关联引用将对象绑定到块。但是为了重新分配块内的关联引用,块需要对自身的引用,因此块变量也需要为__block,并且需要在设置变量之前复制块。这一切都非常丑陋。或者,我们将对象放在一个可变的容器对象中,然后由块保留;但这也很丑。

【问题讨论】:

  • __block 与 ARC 无关阅读文档。
  • 当您的runBlockAsynchronouslyMultipleTimes() 完成多次分派您的aBlock 时,您只需对[foo release] 进行一次额外的最终分派(如果您的队列是并发的,则使用分派屏障,它显然不是在这种情况下)。比关联的引用或类似的东西更简单、更合乎逻辑。
  • @Rob:这取决于知道runBlockAsynchronouslyMultipleTimes() 在内部做什么。当然,它可能适用于这种特殊情况;但不是一般情况——如果除了被多次分派之外,该块还可能存储在其他地方并可能在以后以不可预测的方式调用呢?
  • 同意。但是通常当使用__block 进行变量声明时,您已经在某种程度上参与了该块在内部所做的事情,隐含地声明调用方法是所有者,该块将更新它,但通常调用者仍然假设清理的责任(因此,如果你的块是异步运行的,调用者也将异步调度清理)。就个人而言,我发现异步重复更新方法的 local 变量的整个概念(超出方法的生命周期)有点好奇。
  • 我认为,当您开始考虑正在考虑使用此模式的实际情况时,这些理论问题就会明显缓解。你有这样的场景吗?然后你可以问问自己如何补救代码示例,或者问问自己是否可以重构,采用更简单的模式。

标签: objective-c automatic-ref-counting objective-c-blocks


【解决方案1】:

可变容器尽可能干净。您可以使用单个 object 属性创建一个简单的包装器来稍微清理它,然后您可以从属性访问器获得内存管理。

一种看起来更干净,但实际上有点混乱的方法是有一个不可变的包装器,它接受一个指针,并在释放该指针时释放它。

@interface ObjectReleaser : NSObject {
    id *objectPointer;
}
- (id)setObjectPointer:(id *)pointer;
- (void)captureMe;
@end

@implementation ObjectReleaser
- (void)setObjectPointer:(id *)pointer {
    if(!objectPointer && pointer) {
        objectPointer = pointer;
        [*objectPointer retain];
    }
}
- (void)dealloc {
    if(objectPointer) [*objectPointer release];
    [super dealloc];
}
- (void)captureMe {} // Blocks can call this to capture the object
@end

块会捕获并保留这个对象,因为它不是__block。您可以像往常一样修改您的__block 对象,使用所有正确的retains 和releases。然后,当块被释放时,它会释放释放器,释放器将被释放并释放指针当前指向的任何东西。

__block id foo = getObject();
ObjectReleaser *releaser = [[ObjectReleaser alloc] init];
void (^aBlock)() = ^ {
    [releaser captureMe];
    NSLog(@"%@", foo);
    [foo release];
    foo = [getObject() retain];
}
aBlock = [aBlock copy];
[releaser setObjectPointer:&foo];

请注意,您不需要仅为该块保留foo,因为发布者会为您执行此操作。你确实必须在复制块之后设置释放者的指针,因为复制会改变foo的指针。这也是为什么在函数返回后保存堆栈变量的指针是安全的:变量实际上不在堆栈上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-12
    • 1970-01-01
    相关资源
    最近更新 更多