3

這裏的問題更多的是教育問題。我在一個小時前開始考慮這個問題,而 在一個樂高積木上翻轉(我知道是愚蠢的)。塊和消息

根據我的理解,塊是一個在堆棧上創建的對象。

咱們說A是一個對象,這意味着我們可以這樣做:

[A message]; 

此基礎上,如果一個塊是一個對象,我們也可以這樣做:

[block message]; 

我是正確?

而當運行時看到的是,它會召喚:

objc_msgSend(block, @selector(message), nil); 

所以我的問題是,如何才能發送塊的消息?

如果這是可能的,我會想象它也可以發送一個消息塊的參數也是可能的嗎?

而且,如果我們可以做調用塊:

block(); 

這是否意味着我們甚至可以使一個塊作爲消息(SEL)爲好,因爲塊具有類似於簽名void (^)(void)一個方法?

因爲如果有可能,那麼下面會真的讓我感到吃驚:

objc_msgSend(block, @selector(block), block); 

或:

objc_msgSend(block1, @selector(block2), block3); 

我希望我的想象不運行有點野性,我的理解是不要在這裏(如果是的話,請糾正我)。

+0

塊文字具有'invoke()'函數指針。它們不需要用於調用的objective-c接口。這就需要爲不同佈局的參數爲每個塊文本創建一個NSBlock的唯一子類(塊不僅僅是void(^)(void)),只需要一個良好類型的invoke方法。 – CodaFi

+0

通過這種方式,編譯器可以完全控制必須生成的任何函數,並且只需在堆棧上創建該塊,並在使用文字時指定它的函數指針。 – CodaFi

+0

@CodaFi對不起,但你能告訴我更多,並告訴我怎樣才能完成一些更詳細的問題(即樣本,推理等),因爲我仍然有點迷路。謝謝。 – Unheilig

回答

10

塊是僅用於存儲和引用目的的對象。通過使它們成爲對象,塊可以被保留/釋放,因此可以被推入數組或其他集合類中。他們也迴應copy

就是這樣。即使一個塊在堆棧上啓動,在很大程度上也是一個編譯器實現細節。

當一個塊的代碼被調用時,這不是通過objc_msgSend()完成的。如果你讀了塊運行和LLVM編譯器源,那麼你會發現:

  • 塊確實是一個C結構,其中包含已捕獲的數據描述(所以它可以被清理)和一個指向的功能 - 的代碼塊 - 即塊

  • 塊函數是一個標準的C函數的可執行部分,其中第一個參數必須始終作爲該塊的參考。參數列表的其餘部分是任意的,並且像任何舊的C函數或Objective-C方法一樣工作。

所以,你手動調用objc_msgSend()治療塊像任何其他隨機ObjC對象,因此,不會調用塊的代碼,也沒有,如果是這樣的(且有SPI可以做這從一個方法...但是,不要使用它)可以通過一個完全可控的參數列表。


一邊,儘管相關性。

imp_implementationWithBlock()獲取塊引用並返回一個可插入到Objective-C類中的IMP(請參閱class_addMethod()),以便在調用該方法時調用該塊。

imp_implementationWithBlock()的實現利用了objective-c方法與塊的調用站點的佈局。 A嵌段是始終:

blockFunc(blockRef, ...) 

而一個ObjC方法始終是:

methodFunc(selfRef, SEL, ...) 

因爲我們希望imp_implementationWithBlock()塊,以始終以目標對象的方法作爲第一個塊參數(即自),imp_implementationWithBlock()返回一個蹦牀函數調用時(通過objc_msgSend()):

- slides the self reference into the slot for the selector (i.e. arg 0 -> arg 1) 
- finds the implementing block puts the pointer to that block into arg 0 
- JMPs to the block's implementation pointer 

發現實施b鎖位有點兒有趣,但與這個問題無關(地獄,imp_implementationWithBlock()也有點不相關,但也許是有趣的)。


感謝響應。這絕對是一個大開眼界。關於 塊關於調用的部分不是通過objc_msgSend()告訴我它是 ,因爲塊不是正常對象的一部分(但尾聲的 提到NSBlock似乎駁斥了我目前瞭解的內容,因爲NSBlock會使其成爲對象層次的一部分)。如果我的理解到目前爲止還沒有解決,請隨時撥打 來刺我一下。我對 非常感興趣,更多地瞭解以下1:SPI和 的方式(如何)調用該方法。 2:底層機制: 將自引用滑入槽中。 3:找到執行 塊,並將指向該塊的指針置於arg 0中。如果您有時間 來分享並詳細地寫一些詳細信息,那麼我就是耳朵;我 發現這一切非常迷人。首先十分感謝。

這些塊本身就是一個標準的Objective-C對象。塊實例包含指向某些可執行代碼的指針,任何捕獲的狀態以及用於將堆棧中的狀態複製到堆(如果請求)的一些幫助程序,並清除塊被銷燬時的狀態。

該塊的可執行代碼不像方法那樣被調用。一個塊有其他方法 - retain,release,copy等 - - 可以像任何其他方法一樣直接調用,但可執行代碼不是公開的那些方法之一。

SPI沒有做任何特別的事情;它只適用於沒有參數的塊,它只不過是在做block()

如果你想知道整個參數幻燈片的工作原理(以及它如何使尾部調用到塊),我建議讀thisthis。此外,塊運行時的源代碼,objc運行時和llvm都可用。

這包括IMP抓取塊並將其推入arg0的有趣位。

+0

感謝您的回覆。這絕對是一個大開眼界。關於塊調用的部分不是通過'objc_msgSend()'完成的,它告訴我這是因爲塊不是普通對象的一部分(但是尾巴提到的'NSBlock'似乎駁斥了我目前爲止理解的內容,因爲'NSBlock '會使它成爲對象層次的一部分)。如果我的理解到目前爲止依然存在,請隨時採取刺探措施。我非常想知道更多關於以下內容的信息: – Unheilig

+0

(續)** 1:** SPI和調用該方法的方式(如何)。 ** 2:**基本機制:將自我參考滑入插槽。 ** 3:**找到執行塊,並將指向該塊的指針置於arg 0中。如果您有時間詳細分享和詳細說明這些內容,那麼我就會全神貫注;我覺得這非常迷人。首先十分感謝。 – Unheilig

+0

剛剛意識到你是Playstation的無痛自動消防系統的發明者... – Unheilig

3

是的,塊是對象。是的,這意味着你可以發送消息給他們。

但是你認爲一個塊響應了什麼信息?除了內存管理消息retainreleasecopy以外,我們不會告知任何消息塊支持。所以如果你發送一個任意的消息給一個塊,很可能會拋出一個「不能識別選擇器」的異常(如果你發送一個任意的消息給你不知道該接口的任何對象時會發生這種情況) 。

調用塊通過不同於消息發送的機制發生,並且由編譯器實現,並且不會向程序員公開。

塊和選擇器是非常不同的。 (選擇器只是一個實際的字符串,即方法名稱的字符串。)Blocks和IMPs(實現方法的函數)有點類似。然而,它們仍然不同,因爲方法接收接收器(self)和選擇器稱爲特殊參數,而塊沒有它們(實現塊的功能只接收塊本身作爲隱藏參數,程序員無法訪問)。

+0

關於_的事情「無法識別選擇器」_告訴我發送一條消息是「合法的」 - 它只是不識別SEL。有沒有辦法讓這成爲可能?你能告訴我更多關於部分「函數實現方法」和「實現塊的函數只接收塊本身作爲隱藏參數」嗎?但正如你所說,程序員不能訪問,但至少在概念上?所以,當一個塊被調用時,運行時不會使用'objc_msgSend',因爲我錯誤地認爲它會.. ..?謝謝。 – Unheilig

+0

@Unheilig:「運行時不會使用objc_msgSend,因爲我錯誤地認爲它會...?」那麼,程序員不知道它在底層使用什麼。但我們知道的一件事是,傳遞給'nil'的消息不會崩潰,但調用'nil'塊會崩潰。 「你能告訴我更多關於部件的」函數實現方法「嗎?」你可以使用'methodForSelector:'獲得一個指向實現方法的函數的指針(一個IMP)。它是一個C函數,在開始時帶有兩個隱式參數,'self'和'_cmd',後面是「常規」參數。 – newacct

+0

@Unheilig:實現塊的功能不可訪問,但可以使用運行時函數imp_implementationWithBlock()將具有特定簽名的塊轉換爲IMP。 – newacct