5

我想在单个块的多次调用中重用对象引用,我很好奇:以下两种方法之间的实际区别是什么?

使用__block变量:

__block Widget *widget = [self buildNewWidget];

for(Gadget *gadget in self.gadgets) {
    [self useGadget:gadget withCallback:^{
        if([widget isBroken]) {
            widget = [self buildNewWidget];
        }

        gadget.widget = widget;
    }];
}

使用static变量:

for(Gadget *gadget in self.gadgets) {
    [self useGadget:gadget withCallback:^{
        static Widget *widget;

        if(!widget || [widget isBroken]) {
            widget = [self buildNewWidget];
        }

        gadget.widget = widget;
    }];
}

显然,这两个代码块从语义角度来看是不同的,但是(实际上)我相信它们做的是相同的基本工作。我的猜测是,从内存管理角度、性能角度或其他角度来看是有区别的。任何说明这些差异(或解释为什么它们没有不同)的见解都会有所帮助。

4

4 回答 4

2

一个例子值一千字:

(是的,这是一个非常简单的例子,但它基本上相当于你在做什么......)

for (int i = 0; i < 3; i++)
{
    // Your example encompasses this scope,
    // not taking into account that we may execute this code multiple times:

    // Call the block
    (^{
        // Every instance/execution of this block will have the same object.
        static Obj *o;

        // Initialize static object
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            o = [Obj new];
        });

        NSLog(@"Object is: %@", o);
    })();
}
// Output:
//   Object is: <Obj: 0x100109fd0>
//   Object is: <Obj: 0x100109fd0>
//   Object is: <Obj: 0x100109fd0>

for (int i = 0; i < 3; i++)
{
    __block Obj *o = [Obj new];

    // Call the block
    (^{
        // This block uses the object from its enclosing scope, which may be different.
        NSLog(@"Object is: %@", o);
    })();
}
// Output:
//   Object is: <Obj: 0x105100420>
//   Object is: <Obj: 0x1003004f0>
//   Object is: <Obj: 0x105300000>
于 2013-01-27T01:46:01.770 回答
1

正如所写,这两个代码片段的工作方式不同,并且它们具有不同的最终结果。

第二组代码是等待发生的故障。如果由于使用了静态变量,此代码同时在两个不同的线程上运行,它将失败。此代码也将失败,因为您从未初始化静态变量。第一次到达该if语句时,应用程序可能会崩溃。

由于循环的每次迭代似乎都取决于 的当前值widget,因此您需要有一个在循环之前初始化的局部变量。由于需要在块内修改此变量,因此您需要将变量设为__block变量。这意味着您的第一组代码是正确的代码。

于 2013-01-26T23:15:32.423 回答
1

出于此答案的目的,假设两个示例都包含在-(void)useGadgetsOnWidgets { ... }.

假设 ARC,您的应用程序是单线程的并且代码是不可重入的(即useGadgetsOnWidgets不调用自身),并且在方法返回后不使用该块,有一个主要区别:

有一个static变量,widget永远存在。这意味着小部件可以在调用之间重用-useGadgetsOnWidgets(这可能是好的也可能是坏的),但也意味着小部件会被永久保留。您可以通过将小部件从循环/块中拉出来更改它(我也在开始时将其初始化为更类似于__block版本:

-(void)useGadgetsOnWidgets {
  static Widget *widget;
  widget = [self buildNewWidget];
  for(Gadget *gadget in self.gadgets) {
    [self useGadget:gadget withCallback:^{
      if([widget isBroken]) {
        widget = [self buildNewWidget];
      }
      gadget.widget = widget;
    }];
  }
  widget = nil;
}

还有第三种变体,它在某种程度上是线程安全的,并假定在方法返回后不使用该块:

-(void)useGadgetsOnWidgets {
  Widget *widget = [self buildNewWidget];
  Widget ** pWidget = &widget;
  for(Gadget *gadget in self.gadgets) {
    [self useGadget:gadget withCallback:^{
      if([*pWidget  isBroken]) {
        *pWidget = [self buildNewWidget];
      }
      gadget.widget = *pWidget ;
    }];
  }
}

这似乎比使用static变量(实际上只是一个全局变量)要好一些,但它仍然很棘手。我也不想教给新手程序员的技术(但话又说回来,任何类型的多线程也不是)。

编辑:对于您描述的问题,比其中任何一个更好的解决方案是将小部件缓存在 ivar/property 上self

-(Widget*)workingWidget {
  // Assuming _cachedWidget is an ivar
  if ([_cachedWidget isBroken]) {
    _cachedWidget = [self buildWidget];
  }
  return _cachedWidget;
}

-(void)useGadgetsOnWidgets {
  for(Gadget *gadget in self.gadgets) {
    [self useGadget:gadget withCallback:^{
      gadget.widget = [self workingWidget];
    }];
  }
}
于 2013-01-27T02:21:59.867 回答
-1

__block 使变量在块内可用,就像它是全局的一样。如果你在块内使用它,它不会被复制,而是被引用,因为它就像全局一样,它仍然是活动的。但是下次你调用该代码块时,另一个变量将被压入堆栈。

static 使变量仅在范围内可见,并且它在程序的整个执行过程中仍然存在。但是,如果您再次调用该代码块,变量将是相同的。

于 2013-01-26T23:20:26.310 回答