这是一个棘手的问题,我无法打击。
我理解Obj-C块本身不是闭包,它们的实现与Javascript闭包有些不同但我仍然会使用Javascript示例来展示我想要完成的事情(熟悉Javascript的人会得到它。)
在Javascript上你可以像下面那样创建一个'功能工厂':
//EXAMPLE A
var _arr = [], i = 0;
for(;i<8;++i) {
_arr[i] = function() {
console.log('Result:' + i);
};
}
//BY THE END OF THIS LOOP i == 7
_arr[0]();
_arr[1]();
_arr[2]();
...
_arr[7]();
使用相应的函数填充名为_arr的数组,然后计算所有这些数组。请注意,上面代码的结果将输出...
Result: 7
Result: 7
Result: 7
...
Result: 7
...在所有函数中都是'7',这是正确的,因为在评估函数的时候,i的值等于8,即使i的值是0 ... 7而它们被创建在这里我们得出结论,我是通过引用而不是通过值传递的。
如果我们想要“修复”这个并让每个函数在创建时使用i的值,我们会写这样的东西:
//EXAMPLE B
var _arr = [], i = 0;
for(;i<8;++i) {
_arr[i] = (function(new_i){
return function() {
console.log(new_i);
};
})(i); //<--- HERE WE EVALUATE THE FUNCTION EACH TIME THE LOOP ITERATES, SO THAT EVERYTHING INSIDE OF THIS 'RETAINS' THE VALUES 'AT THAT MOMENT'
}
//BY THE END OF THIS LOOP i == 7, BUT IT DOESN'T MATTER ANYMORE
_arr[0]();
_arr[1]();
_arr[2]();
...
_arr[7]();
而不是直接创建最终函数,而是使用一个中间闭包,它返回最终函数,其中包含正确的值'fixed';因此将返回:
Result: 0
Result: 1
Result: 2
...
Result: 7
现在...
我正在尝试使用Objective-C块做同样的事情。
这是我的示例A的代码(在Obj-C中):
NSMutableArray *_arr = [NSMutableArray arrayWithCapacity:0];
int i = 0;
for(;i<8;++i) {
[_arr addObject:^{
NSLog(@"Result: %i", i);
}];
}
//BY THE END OF THIS LOOP i == 7
[_arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
((void (^)())obj)();
}];
这将输出......
Result: 7
Result: 7
...
Result: 7
...这也是正确的,因为该函数实际上持有对i的引用。
问题是,我应该如何重写上面的循环以模拟示例B中显示的行为?(我保留了函数创建时的值)
我试过写这样的循环:
for(;i<8;++i) {
[_arr addObject:^(int new_i){
return ^{
NSLog(@"Result: %i", new_i);
};
}(i)];
}
但是在编译时它会出现以下错误:返回生成在本地堆栈上的块
谢谢,最好; D!
答案 0 :(得分:11)
您说Objective-C阻止通过引用捕获是不正确的。它们实际上是按价值捕获的(__block
变量除外,我们不会在这里进行讨论。)您可以在此验证:
int x = 42;
void (^foo)() = ^ { NSLog(@"%d", x); };
x = 17;
foo(); // logs "42"
您遇到的问题是块从堆栈开始,堆栈块仅对块表达式的本地范围有效。在这种情况下,块表达式位于for循环中。这意味着在for循环的迭代结束后,块对象不再有效。但是你将一个指向这个块对象的指针放入数组中。
与for循环内部的局部变量一样,堆栈帧中的内存位置然后在下一次迭代时重复使用(在这种情况下,它适用于编译器)堆栈块。环。因此,如果检查存储在数组中的值,您会发现所有对象指针都是相同的。因此,不是有8个指向8个块对象的指针,而是指向同一个块对象的8个指针。这就是为什么你认为它是“通过引用”捕获它。但真正发生的是堆栈上的块在每次迭代时都会被覆盖,因此您的数组包含指向此位置的指针的多个副本,因此您会反复看到同一个块(在最后一次迭代中创建的块)试。
答案是你需要在将块放入数组之前复制它。复制的块位于堆上并具有动态生存期(内存管理与其他对象一样)。
NSMutableArray *_arr = [NSMutableArray arrayWithCapacity:0];
int i = 0;
for(;i<8;++i) {
[_arr addObject:[[^{
NSLog(@"Result: %i", i);
} copy] autorelease]];
}
[_arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
((void (^)())obj)();
}];
你不需要像在JavaScript中那样用Objective-C包装第二个立即执行的闭包。
答案 1 :(得分:2)
如果要返回块,则需要先发送copy
消息或使用Block_copy
功能将其复制。为防止内存泄漏,您必须稍后释放复制的块,例如使用autorelease
for(;i<8;++i) {
[_arr addObject:^(int new_i){
return [[^{
NSLog(@"Result: %i", new_i);
} copy] autorelease];
}(i)];
}