2

我正在解决一些有关块/ARC 的保留周期问题,并且我正在努力解决这些细微差别。任何指导表示赞赏。

Apple 关于“块和变量”的文档 (http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html) 说明如下:

如果在方法的实现中使用块,对象实例变量的内存管理规则会更加微妙:

如果通过引用访问实例变量,则保留 self;如果您按值访问实例变量,则会保留该变量。以下示例说明了两种不同的情况:

dispatch_async(queue, ^{
    // instanceVariable is used by reference, self is retained
    doSomethingWithObject(instanceVariable);
});


id localVariable = instanceVariable;
dispatch_async(queue, ^{
    // localVariable is used by value, localVariable is retained (not self)
    doSomethingWithObject(localVariable);
});

我觉得这个解释令人困惑。

  1. 这是对“按价值”/“按参考”术语的适当使用吗?假设这些变量属于同一类型(id),它们之间的区别特征似乎是它们的范围。
  2. 我看不到“通过引用”示例中如何引用 self ?如果正在使用访问器方法(例如 - 下面),我可以看到 self 被保留。

    doSomethingWithObject(self.instanceVariable);

  3. 您对何时可能想要以一种或另一种方式做事有任何指导吗?

  4. 如果传统智慧是利用“按值”变量,那么这似乎会导致大量额外的代码用于额外的变量声明?
  5. 在嵌套块开始发挥作用的情况下,避免在彼此内部声明块似乎更易于维护,因为最终可能会出现一堆无意保留的对象?
4

2 回答 2

4

将 usinginstanceVariable视为等效于 writing的情况self->instanceVariable。根据定义,实例变量“附加”到self对象上,并且在自身对象存在时存在。

使用instanceVariable(or self->instanceVariable) 意味着您从 self 的地址开始,并要求一个实例变量(即从self对象原始地址的一些字节的偏移量)。

usinglocalVariable本身是一个变量,它不依赖于自身,也不是相对于另一个对象的地址。

由于块在创建时捕获变量,因此当您的意思是“执行块时,我想在执行时获取实例变量的值”时,您通常更喜欢使用实例变量,因为您会询问self对象对于当时的实例变量的值(与您调用的方式完全相同[self someIVarAccessorMethod])。但是请注意不要创建一些保留周期。

另一方面,如果使用,则在创建块时将捕获localVariable局部变量(而不是),因此即使在创建块后局部变量发生变化,块内也会使用旧值。self

// Imagine instanceVariable being an ivar of type NSString
// And property being a @property of type NSString too
instanceVariable = @"ivar-before";
self.property = @"prop-before";
NSString* localVariable = @"locvar-before";

// When creating the block, self will be retained both because the block uses instanceVariable and self.property
// And localVariable will be retained too as it is used directly
dispatch_block_t block = ^{
  NSLog(@"instance variable = %@", instanceVariable);
  NSLog(@"property = %@", self.property);
  NSLog(@"local variable = %@", localVariable);
};

// Modify some values after the block creation but before execution
instanceVariable = @"ivar-after";
self.property = @"prop-after";
localVariable = @"locvar-after";

// Execute the block
block();

在该示例中,输出将显示instanceVariableandself.property是通过self对象访问的,因此 self 被保留,但在块的代码中查询instanceVariableandself.property的值,它们将在执行时分别返回它们的值"ivar-after""prop-after"。另一方面,localVariable在创建块时被保留,并且它的值在那时被 const-copy,所以最后一个NSLog会显示"locvar-before".

self当您在块的代码中使用实例变量或属性或调用 self 本身的方法时,将保留。当您直接在块的代码中使用它们时,会保留局部变量。

注意:我建议您观看讨论该主题的 WWDC'11 和 WWDC'12 视频,它们非常有启发性。

于 2012-09-05T19:45:18.303 回答
3
  1. 这是对“按价值”/“按参考”术语的适当使用吗?它至少类似于典型的使用。将值复制到局部变量就像将值复制到堆栈中一样;使用 ivar 就像将指针传递给存储在其他地方的值。

  2. 我看不到“通过引用”示例中如何引用 self ?当你在方法中使用实例变量时,引用self是隐含的。有些人实际上编写self->foo而不是foo访问 ivar 只是为了提醒自己这foo是一个 ivar。我不建议这样做,但重点是这fooself->foo意思是一样的。如果您访问块内的 ivar,self将保留该 ivar,以确保在该块的持续时间内保留该 ivar。

  3. 您对何时可能想要以一种或另一种方式做事有任何指导吗?在这里考虑按引用传递/按值传递的区别是有用的。正如 AliSoftware 所解释的,在创建块时会保留局部变量,就像在调用函数时复制按值传递的参数一样。通过 self 访问 ivar 就像通过指针访问通过引用传递的参数一样,因此在您实际使用它之前不会确定它的值。

  4. 似乎这会导致大量额外的代码用于额外的变量声明?块已经成为该语言的一个特性已经有一段时间了,我还没有注意到这是一个问题。更常见的是,您想要相反的行为:本地声明的变量,您可以在一个块(或几个块)内修改。__block存储类型使这成为可能。

  5. 似乎避免在彼此内部声明块可能更易于维护,因为最终可能会以无意中保留的对象的混合而告终?让一个或多个块在需要时保留一个对象并没有错——只要使用它的块终止,该对象就会被释放。这与通常的 Objective-c 手动内存管理理念完美契合,每个对象只关心平衡自己的保留。避免多层嵌套块的一个更好的理由是,这种代码可能比它需要的更难理解。

于 2012-09-06T05:33:41.673 回答