2011-05-18 43 views
2

我一直在玩塊,遇到奇怪的行爲。 這是接口/實現,它只是擁有一個塊與執行它的能力:Objective-C帶閉包的塊引用宿主對象

@interface TestClass : NSObject { 
#if NS_BLOCKS_AVAILABLE 
    void (^blk)(void); 
#endif 
} 
- (id)initWithBlock:(void (^)(void))block; 
- (void)exec; 
@end 

@implementation TestClass 
#if NS_BLOCKS_AVAILABLE 
- (id)initWithBlock:(void (^)(void))block { 
    if ((self = [super init])) { 
     blk = Block_copy(block); 
    } 
    return self; 
} 
- (void)exec { 
    if (blk) blk(); 
} 
- (void)dealloc { 
    Block_release(blk); 
    [super dealloc]; 
} 
#endif 
@end 

而普通實例並通過定期塊的工作:

TestClass *test = [[TestClass alloc] initWithBlock:^{ 
    NSLog(@"TestClass"); 
}]; 
[test exec]; 
[test release]; 

使用參考塊到正被創建的對象並不:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{ 
    NSLog(@"TestClass %@", test1); 
}]; 
[test1 exec]; 
[test1 release]; 

錯誤是EXC_BAD_ACCESS,上Block_copy(塊)堆棧跟蹤; 調試程序:0x000023b2 < 0050>添加爲0x18 $,%ESP

我不停地打轉轉,和移動初始化上述分配代碼,它的工作:

TestClass *test2 = [TestClass alloc]; 
test2 = [test2 initWithBlock:^{ 
    NSLog(@"TestClass %@", test2); 
}]; 
[test2 exec]; 
[test2 release]; 

並結合這兩個片段也工作:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{ 
    NSLog(@"TestClass %@", test1); 
}]; 
[test1 exec]; 
[test1 release]; 

TestClass *test2 = [TestClass alloc]; 
test2 = [test2 initWithBlock:^{ 
    NSLog(@"TestClass %@", test2); 
}]; 
[test2 exec]; 
[test2 release]; 

這是怎麼回事?

回答

5

在賦值表達式中,右值在賦值給左值之前被求值。

這意味着,在:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{ 
    NSLog(@"TestClass %@", test1); 
}]; 

被執行的操作的以下序列。 編輯:如由Jonathan Grynspan指出的那樣,有一個爲步驟1和2,以便它可能是步驟2中的情況下,沒有定義的順序步驟1.

  1. 之前執行發送+allocTestClass
  2. 創建指代test1的塊,尚未初始化。 test1包含一個任意的內存地址。
  3. 發送-initWithBlock:到步驟1中創建的對象。
  4. 將右值指定爲test1

注意test1指向一個有效的對象只有一步後4

在:

TestClass *test2 = [TestClass alloc]; 
test2 = [test2 initWithBlock:^{ 
    NSLog(@"TestClass %@", test2); 
}]; 
[test2 exec]; 
[test2 release]; 

的順序是:

  1. 發送+allocTestClass
  2. 分配其中的右值爲test2現在指向TestClass對象。
  3. 創建一個塊,它指的是test2,它指向步驟2中的TestClass對象。
  4. 發送-initWithBlock:test2,將其正確地在步驟2
  5. 右值分配給test2分配。
+2

實際上,'[TestClass alloc]'和'^ {...}'之間的評估順序是未定義的。在C級別,兩者都被評估爲'-initWithBlock:'的參數,因此在*'[TestClass alloc]被調用之前實際可以創建塊。 :) – 2011-05-18 23:20:07

+0

@Jonathan確實!我已經編輯了相應的答案。 – 2011-05-18 23:23:05

+0

謝謝,這是有道理的,這就是爲什麼我試圖分裂陳述。但是,如果將非工作代碼段和工作代碼段合併在一起,它是如何工作的? – elado 2011-05-18 23:26:33

1

問題是當創建一個塊時,它將複製它所捕獲的任何非__block變量(複製一份)。由於test1在創建塊時未初始化,所以在運行塊時將使用未初始化的指針test1

適當的解決方案是聲明test1__block。這樣一來,狀態是塊和包圍範圍之間共享,並且test1在封閉範圍分配之後,該塊可以在改變值:

__block TestClass *test1; 
test1 = [[TestClass alloc] initWithBlock:^{ 
    NSLog(@"TestClass %@", test1); 
}]; 
[test1 exec]; 
[test1 release]; 

P.S.第三個例子(在做alloc之前,然後分配init的結果)是不可靠的,因爲通常對象的init方法不保證返回它被調用的對象(init被允許釋放自身並在失敗時返回nil,例如)。


更新:上面的代碼只對MRC,如__block變量不是由塊保留。

但是在ARC中,上面的代碼會導致一個保留週期,因爲__block對象指針變量默認由塊保留。在ARC中,正確的代碼是:

TestClass *test1; 
__block __weak TestClass *weakTest1; 
weakTest1 = test1 = [[TestClass alloc] initWithBlock:^{ 
    NSLog(@"TestClass %@", weakTest1); 
}]; 
[test1 exec];