古詩詞大全網 - 個性簽名 - 使用 NSInvocation 向對象發送消息

使用 NSInvocation 向對象發送消息

?最底層的轉發函數為 objc_msgSend ,它的定義如下

從以上的定義我們可以得出壹個消息轉發包含了幾大要素:target、selector、arguments、return value, objc_msgSend 是 C 函數,蘋果不提倡我們直接使用該函數來向對象消息。

想必大家都知道使用 performSelector 給對象發送消息,但是其有幾個短板

NSInvocation 是蘋果工程師們提供的壹個高層的消息轉發系統。它是壹個命令對象,可以給任何 Objective-C 對象類型發送消息,接下來將介紹 NSInvocation 的?用法。

必須使用工廠方法 invocationWithMethodSignature: 來創建壹個 NSInvocation 實例。工廠方法的參數是壹個 NSMethodSignature 對象。壹般使用 NSObject 的實例方法 methodSignatureForSelector: 或者類方法 instanceMethodSignatureForSelector: 來創建對應 selector 的 NSMethodSignature 對象。

例:創建類方法的簽名與實例方法簽名

需要註意的是 NSMethodSignature 對象僅僅表示了方法的簽名:方法的請求、返回數據的編碼。所以在使用 NSMethodSignature 來創建 NSInvocation 對象之後 仍需指定消息的接收對象和選擇子

原則是接收對象的對應選擇子需要跟 NSMethodSignature 相匹配。但是根據實踐來說,只要不造成 NSInvocation setArgument:atIndex 越界的異常,都是可以成功轉發消息的,並且轉發成功之後,未賦值的參數都將被賦值為 nil。

例如:

執行結果:

以上為 NSInvocation 類中定義針對參數的操作。 argumentLocation 參數為 void * 類型,表示需要傳遞指針地址給它。idx 參數是從 2 開始的, 0 和 1 分別代表 target 和 selector,雖然可以?直接使用 getArgument:atIndex 來獲取 target 和 selector,但是不如 NSInvocation 的 target 以及 selector 屬性來的方便 。需要註意的是當 idx 超過對應 NSMethodSignature 的參數個數的時候獲取參數和設置參數的方法都會拋出 NSInvalidArgumentException 異常。

例如:給 greetingWithName: 方法傳參

需要特別註意 setArgument:atIndex: 默認不會強引用它的 argument,如果 argument 在 NSInvocation 執行的時候之前被釋放就會造成野指針異常(EXC_BAD_ACCESS)。

如上圖所示, invocation 未?強引用它的 target,在控制器彈出之後,target ?被釋放,然後再 invoke 這個 invocation 會造成野指針異常。 調用 retainArguments 方法來強引用參數(包括 target 以及 selector)

NSInvocation 類中的返回數據的方法如下

可以看到返回數據仍然是通過傳入指針來進傳值的。例:

輸出結果為:

需要註意的是:考慮到 getReturnValue 方法僅僅是將返回數據拷貝到提供的緩存區(retLoc)內,並不會考慮到此處的 內存管理 ,所以如果返回數據是對象類型的,實際上獲取到的返回數據是 __unsafe_unretained 類型的,上層函數再?把它作為返回數據返回的時候就會造成野指針異常。通常的解決方法有2種:

第壹種:新建壹個相同類型的對象並指向它,這樣做 result 就會強引用 tempResult,當做返回數據返回之後會自動添加 autorelease 關鍵字,也就不會造成野指針異常。

第二種:?使用 __bridge 將緩存區轉換為 Objective-C 類型,這種做法其實跟第壹種相似,但是我們更建議使用這種方式來解決以上問題,因為 getReturnValue ?本來就是給緩存區寫入數據,緩存區聲明為 void* 類型更為合理,然後通過 __bridge 方式轉換為 Objective-C 類型並?且將該內存區的內存管理交給 ARC。