[英]Objective-C Blocks, How to preserve context values?
這是一個棘手的問題,我只是無法打擊。
我知道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!
你說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中的方式包裝立即執行的第二個閉包。
如果要返回塊,則需要先通過發送copy
消息或使用Block_copy
函數進行copy
。 為了避免泄漏內存,您必須稍后釋放復制的塊,例如使用autorelease
for(;i<8;++i) {
[_arr addObject:^(int new_i){
return [[^{
NSLog(@"Result: %i", new_i);
} copy] autorelease];
}(i)];
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.