2013-11-22 124 views
12

我將在序言中提出這個問題,指出我要問的只是教育和可能的調試目的。如何創建NSBlock對象?

如何在Objective C運行時內部創建塊對象?

我看到所有表示各種塊類型的類的層次結構,並且層次結構中最低的超類,NSObject,是NSBlock。對班級數據的轉儲顯示它實施了+ alloc,+ allocWithZone:,+ copy+ copyWithZone:方法。其他塊子類都沒有實現這些類方法,這導致我相信,可能錯誤地認爲NSBlock負責塊處理。

但是這些方法似乎在塊的使用期限內的任何時候都不會被調用。我用自己的方式交換了實現,並在每個實現中都放置了一個斷點,但他們從來沒有被調用。使用NSObject的實現方式進行類似的練習使我確切地知道我想要什麼。

所以我假設塊以不同的方式實現?任何人都可以闡明這個實現是如何工作的?即使我不能掛鉤塊的分配和複製,我想了解內部實現。

+0

我不認爲這是可能使用'[ NSBlock alloc]'創建一個塊,這就是爲什麼他們不被調用。 –

回答

11

tl; dr

編譯器直接將塊文字轉換爲結構和函數。這就是爲什麼你看不到alloc電話。


討論

雖然塊是全面的Objective-C對象,這其實是很少暴露在他們的使用,使他們頗爲滑稽的野獸。

第一個怪癖是塊通常是在棧上創建的(除非它們是全局塊,即塊沒有引用周圍的上下文),然後只在需要時纔在堆上移動。到目前爲止,它們是唯一可以在堆棧上分配的Objective-C對象。

可能由於分配中的這種怪異,語言設計者決定只允許通過塊文字(即使用^運算符)創建塊。 以這種方式,編譯器完全控制塊分配。

clang specification解釋,編譯器會自動生成兩個結構和對於每個塊的至少一個功能字面它遇到:

  • 塊字面結構
  • 塊描述符結構
  • 一個塊調用功能

例如對於字面

^ { printf("hello world\n"); } 
32位系統中的編譯器將產生以下

struct __block_literal_1 { 
    void *isa; 
    int flags; 
    int reserved; 
    void (*invoke)(struct __block_literal_1 *); 
    struct __block_descriptor_1 *descriptor; 
}; 

void __block_invoke_1(struct __block_literal_1 *_block) { 
    printf("hello world\n"); 
} 

static struct __block_descriptor_1 { 
    unsigned long int reserved; 
    unsigned long int Block_size; 
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 }; 

(順便說一下,該塊有資格作爲全局塊,所以它會在內存中的一個固定的位置被創建)

所以塊是Objective-C對象,但是處於低級別的方式:它們只是指針爲isa的結構。儘管從形式上來看它們是NSBlock的具體子類的實例,Objective-C API從不用於分配,所以這就是爲什麼你看不到調用:文本被編譯器直接轉換爲結構。

+0

我不相信全局塊是在堆上創建的;您鏈接的文檔清楚地顯示(緊接在您拔出的代碼片段之後)該塊將成爲堆棧塊。 (這是有道理的;如果沒有運行時數據,你爲什麼要把它放在堆上呢?)另外,我不相信它們是唯一的被棧分配的objective-c對象;在64位ABI下,整個對象可能嵌入到指針中;所以他們甚至可以只是註冊分配。 :) –

+1

@JesseRusak全局塊非常像'NSString'文字。他們生活在一個固定的位置,因爲他們是不變的對象。在堆棧中創建它們需要在每次構建堆棧框架時分配它們,但是由於它們沒有引用周圍的上下文,因此將其優化。這裏很好地解釋了這個問題:http://www.cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html –

+0

@JesseRusak關於64位關注的問題,雖然它可以完成,沒有任何參考資料表明它實際上已經完成。據我所知,所有Objective-C對象都只能在堆上生存,而塊的顯着例外。當然,如果你有額外的參考,你可以提供:)我會喜歡被證明是錯誤的:) –

2

塊基本上是編譯器魔術。與普通對象不同,它們實際上是直接分配在堆棧上的 - 它們只在複製時才放在堆上。

您可以閱讀Clang的block implementation specification以獲得一個好主意在幕後發生的事情。就我的理解而言,簡短版本是定義了一個結構類型(表示塊及其捕獲狀態)和一個函數(用於調用塊),並且對該塊的任何引用都被替換爲具有結構類型的值其調用指針設置爲已生成的函數,並將其字段填入適當的狀態。

6

正如其他答案中所述,塊對象是直接在全局存儲中(通過編譯器)或堆棧中(通過編譯代碼)創建的。它們最初不是在堆上創建的。

塊對象與橋接CoreFoundation對象類似:Objective-C接口是底層C接口的封面。塊對象的-copyWithZone:方法調用_Block_copy()函數,但有些代碼直接調用_Block_copy()。這意味着-copyWithZone:上的斷點不會捕獲所有副本。

(是的,你可以使用塊對象在普通的C代碼。有一個qsort_b()功能和atexit_b()功能,呃,這可能是它。)

+0

嗨,所以全球存儲不等於堆棧? – Unheilig