簡體   English   中英

Objective-C Blocks,如何保留上下文值?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM