1. 程式人生 > >OC Runtime 方法與訊息傳遞

OC Runtime 方法與訊息傳遞

OC的方法呼叫我們已經很熟悉了,一段簡單的程式碼,一個名為MyObject的類

#import <Foundation/Foundation.h>

@interface MyObject : NSObject

-(void)printSomeThing:(NSString *)age;
-(void)printSomeThing;

@end
#import "MyObject.h"

@implementation MyObject

-(void)printSomeThing:(NSString *)age {

    NSLog(@"name = %@, age = %@"
,NSStringFromClass([self class]), age); } -(void)printSomeThing{ NSLog(@"come here"); } @end

在一個ViewController中這樣呼叫


#import "ViewController.h"
#import "MyObject.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
MyObject *object = [[MyObject alloc] init]; [object printSomeThing:@"kobe"]; [object printSomeThing]; }

通過 xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m 轉成c++程式碼如下:

生成的ViewController.cpp,其中我們想要的核心程式碼

static void _I_ViewController_viewDidLoad(ViewController * self
, SEL _cmd) { ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad")); MyObject *object = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)object, sel_registerName("printSomeThing:"), (NSString *)&__NSConstantStringImpl__var_folders_db_k2cngcsd0g91h3dbfbbgrjdr0000gn_T_ViewController_c46f7e_mi_0); ((void (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("printSomeThing")); }

objc_msgSend

從上面的程式碼中我們發現,方法呼叫是通過 objc_msgSend 來實現的,編譯器會把我們的程式碼轉換成objc_msgSend

id objc_msgSend(id self, SEL op, ...);

self: 要接受訊息的物件

op: 處理訊息的方法的selector

…: 可變引數

返回值: 方法的返回值

當objc_msgSend 遇到方法呼叫時,編譯器會選擇呼叫下面方法中的一個

objc_msgSend 普通物件的訊息傳送

objc_msgSend_stret 當返回值是資料結構體時的息傳送

objc_msgSendSuper 父類的訊息傳送

objc_msgSendSuper_stret 當返回值是資料結構體時父類的訊息傳送

在Apple的開放原始碼中有這樣一句話相當重要:

These functions must be cast to an appropriate function pointer type
before being called。
這個方法呼叫前必須強制轉成合適的函式指標型別。

在C語言中如果把函式指標強制轉化成另一種型別,會出現行為未定義的錯誤,所以在上面的方法中先把objc_msgSend轉成void *
然後再轉成所需要的型別(void ()(id, SEL, NSString )),因為呼叫前必須強制轉成合適的函式指標型別。另一個就轉成了(void (**)(id, SEL))。

SEL

定義了一個不透明的型別來表示一個方法選擇器


typedef struct objc_selector *SEL;

方法選擇器用來表示runtime中的方法名字,方法選擇器就是被註冊在OC Runtime中一個C字串,當一個類被載入時,由編譯器生成的selector會自動註冊到runtime中。

使用sel_registerName可以新增一個新的selector到runtime中,或者從runtime中獲取一個已經存在的runtime。上面的程式碼中多次使用。

在使用selector時必須使用sel_registerName的返回值或者@selector()或者NSSelectorFromString(),不能直接把C字串轉成一個SEL。

在OC中如果方法相同,那麼他們就會具有相同的selector,在runtime中他們就是同一個C字串。在同一個類中不可能存在相同的方法,所以不用考慮如何區分。對於不同類中的相同的selector,用上一篇提到的isa來區分就ok了。

IMP

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

selector只是一個方法對應的名稱字串,而真正的實現是由IMP來實現的。IMP 就是為了表示函式的地址,本質就是一個函式指標,它指向了真正的函式實現過程。

那麼SEL跟IMP是如何關聯到一起的?

METHOD

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

Method把SEL跟IMP聯絡到了一起,這樣通過SEL就可以找到相應的method然後對應到其相應的實現IMP.


id method_invoke(id receiver, Method m, ...); //呼叫method的實現
SEL method_getName(Method m);  獲取method的名字 ,返回一個SEL
unsigned int method_getNumberOfArguments(Method m);  返回方法引數個數

IMP中的兩個隱藏引數:

  1. self : 接收訊息的物件
  2. _cmd : 此IMP對應的SEL

獲取方法地址: methodForSelector

void (*setter)(id, SEL, BOOL);  // 函式指標
int i;

setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)]; //給setter函式指標賦值
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES); //函式呼叫

objc_msgSend動態繫結過程

  1. 根據SEL找到相應的IMP,由於不同的類可能具有相同的selector,所以在IMP的過程中要通過receiver的class來找到精確的IMP
  2. 呼叫IMP,把receiver,及引數傳入IMP
  3. 返回其返回值

訊息傳遞過程

上圖顯示了訊息的分發流程,當向一個物件傳送訊息時,首先根據物件的isa找到其對應的class,然後到class的cache中查詢所要執行的selector,如果不存在繼續到其方法列表中查詢,如果沒有,繼續向上到其父類中查詢,直到根類,如果在根類都沒有找到相應的方法,那麼就會報錯,如果找到了就執行selector對應的IMP。

動態方法繫結

當我們向一個物件傳送訊息時,如果訊息對應的selector不存在時就會出錯。對於這樣的問題丟擲錯誤是一個相當明智的解決方法,這樣就會杜絕很多bug。物件在接收到未知的訊息時,首先會呼叫所屬類的類方法+resolveInstanceMethod:(例項方法)或者+resolveClassMethod:(類方法)。在這個方法中我們可以動態的給selector繫結一個IMP, 這樣selector就有了一個實現。


void dynamicMethodIMP(id self, SEL _cmd) {

    NSLog(@"This is a  dynimaic ");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    if (sel == @selector(dynimaicSel)) {

        class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "[email protected]:");
        return YES;
    }

    return [super resolveInstanceMethod:sel];
}

dynimaicSel 沒有實現, 通過class——addMethod為其添加了一個實現,相當於動態添加了一個Method。

當向一個物件傳送一個未知的訊息時,在系統報錯前我們有三個時機來解決這問題,第一時機就是我們上一篇提到的使用resolveInstanceMethod:來動態載入一個實現,第二時機就是使用- (id)forwardingTargetForSelector:(SEL)aSelector把訊息轉發給另一個物件,第三個時機就是Message Forwarding。

-(id)forwardingTargetForSelector:(SEL)aSelector

用於把未知訊息轉發給另一個物件
當在類中實現此方法或者從父類繼承來,方法的返回值non-nil就會作為訊息的新的接收者。如果是nil,沒有新的接收者時只需要return [super forwardingTargetForSelector:aSelector];

Forwarding

當物件收到一個未知的訊息時,如果上面的步驟沒有對訊息進行都處理,在報錯之前,runtime會向物件傳送forwardInvocation:訊息並且會攜帶一個NSInvocation物件作為唯一的引數。NSInvocation攜帶了原始的訊息和引數。

在呼叫forwardInvocation方法之前,必須為呼叫的方法提供一個簽名,我們需要重寫methodSignatureForSelector方法,返回方法的簽名。 如查返回nil,則不會繼續往下執行,不會呼叫forwardInvocation方法。得到的錯誤仍然會是unrecognized selector

定義了兩個類,一個Person, 一個Plane, 在Plane中定義了一個 -(void)fly;
在Person中重寫了下面的函式來處理向person傳送fly訊息。

// 返回要呼叫方法的簽名:instanceMethodSignatureForSelector
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        if ([Plane instanceMethodSignatureForSelector:aSelector]) {

            sig = [Plane instanceMethodSignatureForSelector:aSelector];
        }
    }

    return sig;
}

-(void)forwardInvocation:(NSInvocation *)anInvocation{

    if ([Plane instanceMethodSignatureForSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:[Plane new]]; // 呼叫方法
    } else {
        [super forwardInvocation:anInvocation];
    }
}

相關推薦

OC Runtime 方法訊息傳遞

OC的方法呼叫我們已經很熟悉了,一段簡單的程式碼,一個名為MyObject的類 #import <Foundation/Foundation.h> @interface MyObject : NSObject -(void)printSom

Objective-C Runtime 執行時之三:方法訊息

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL

[ObjectC]Runtime執行時之三:方法訊息

這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。 基礎資料型別 SEL SEL又叫選擇器,是表示一個方法的selector的指標,其定義如下:typedef struct objc_selector *SEL;o

iOS學習筆記56(Runtime)-Objective-C Runtime 執行時之三:方法訊息

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL

OC方法函式的區別

方法:方法是Objective-C獨有的一種結構,只能在Objective-C中宣告、定義和使用,C語言不能宣告、定義和使用。1、類方法以+號開頭,物件方法以-號開頭+ (void) init;        // 類方法- (void) show;            

OC學習Runtime訊息傳遞,訊息轉發機制

堅持 成長 每日一篇 相關類和函式 介紹訊息傳送機制之前介紹一下會用到的幾個相關類和函式 NSMethodSignature(方法簽名) 方法簽名:用語記錄一個方法的引數和返回值型別的類。類似於objc_method_description結構體。方

Struts2配置使用參數接收,轉發重定向,多方法,ognl使用傳遞,struts標簽使用

isp -name users .org 填充 導航 建模 尋址 XML 本文檔包括了 Struts2配置使用參數接收,轉發與重定向,多方法,ognl使用與值傳遞,struts標簽使用 (1)首先加入jar包(最小jar組合) (1) 在web.xml中註冊

iOS總結-Runtime篇之類的訊息傳遞

訊息傳遞的核心機制就是objc_msgSend id objc_msgSend(receiver self, selector _cmd, arg1,arg2,...) self和_cmd是隱藏引數,編譯器插入,self指向訊息的接受者  _cmd是SEL型別 當向一般物件傳送

《深入淺出MFC》第九章 訊息對映命令傳遞

Windows程式的本質是藉著訊息來維持脈動。每個訊息都有一個程式碼,並以WM_開頭的常量表示。來自選單和工具欄者,都以WM_COMMAND表示,引數wParam記錄訊息的發出者。 MFC的訊息分為三大類,命令訊息(WM_COMMAND),凡派生自CCmdTarget的類都有資格接收命令訊息。除WM_COM

OC-RunTime 傳送訊息過程(一)

廢話少說,直接上乾貨。 1、首先用X-code建立一個工程選擇->macOS->Command Line Tool->填寫工程名稱oc -info。 2、建立完成後會有一個main.m檔案,在檔案內看到如下程式碼: #import <Founda

Content指令碼擴充套件的其他頁面指令碼的訊息傳遞

                我正在實現一個Chrome瀏覽器擴充套件來解析某社交網站的資料(也就是扒網頁)。點選擴充套件的圖示,它將會pop up出一個氣泡視窗,上面顯示當前解析的結果。解析的工作由content page的一個javascript指令碼負責。更具體的資料流程是:點選圖標出現氣泡視窗(po

MFC六大關鍵技術之——訊息對映命令傳遞

1 BOOL CWnd::OnWndMsg(UINT message,WPARAM wParam,LPARAM lParam,LRESULT* pResult)2 {3 if(message==WM_COMMAND)4 {5 OnCommand(wParam,lParam);6 ……7 }8 9 if(mes

HTTP請求方法HTTP訊息結構

HTTP是基於客戶端/服務端(C/S)的架構模型,通過一個可靠的連結來交換資訊,是一個無狀態的請求/響應協議。 一個HTTP"客戶端"是一個應用程式(Web瀏覽器或其他任何客戶端),通過連線到伺服器達到向伺服器傳送一個或多個HTTP的請求的目的。 一個HTTP"伺服器"同樣也是一個應用程式(通常是一個Web

Java語言中的方法引數——值傳遞引用傳遞

Java語言中的方法引數有兩種: 1、基本資料型別(數字、布林值) 2、物件引用 基於方法引數的傳遞有兩種方式:值傳遞和引用傳遞。 值傳遞表示方法接收的是呼叫者提供的值。 引用傳遞表示方法接收的是呼叫者提供的變數地址。 Java程式設計語言總是採用按值傳遞的方式,也就是說

OD條件訊息斷點的設定方法

一、條件斷點: 使用方法(如): 在當前行按[Shift+F2]鍵->條件斷點(這個不太好用,因為程式BUG偶爾失效)。 在當前行按[Shift+F4]鍵->條件記錄斷點(只要設定上條件語句和按什麼條件生效就可以了)。 條件語句(如): EAX == 0

【APACHE MINA2.0開發之一】搭建APACHE MINA框架並實現SERVERCLIENT端的簡單訊息傳遞

Hibernate系列學習階段到此結束了,那麼緊接著進入Apache Mina的開發學習,很多童鞋在微薄和QQ中疑問Himi為什麼突然脫離遊戲開發了,嘿嘿,其實可能更多的童鞋已經看出來了,Himi在偏向伺服器Server端開發了,Hibernate、MySQL等都是為了Server端Mina開發而做的

JavaC#通過Byte[]位元組陣列實現訊息傳遞,跨語言資料序列化

支援型別: byte、short、int、long、float、double、boolean、char、String、byte[] Java程式碼 package com.itshidu.io; import java.nio.charset.Charset;

QGraphicsItem中子Item父Item的訊息傳遞問題

現在要做一個需要在父item上新增控制點(子item)來調整父item形狀的功能。 關鍵是子item移動之後,要把自己的位置資訊交給父item,讓父item更新自己的形狀。 查看了很久Qt的document,需要在父ITEM中用setFiltersChildEvents

黑馬程式設計師 oc隨記 類方法物件方法

------- android培訓、java培訓、ios培訓、期待與您交流! ---------- 類方法與物件方法相比較,最根本的好處在於節省記憶體空間。 物件方法在執行時是先從記憶體棧區訪問記憶體堆區的地址,再從記憶體堆區尋找方法位於記憶體程式碼區的地址,最終返回值。

利用介面實現serviceActivity訊息傳遞

背景 最近在仿Android版QQ,在實現訊息傳遞的時候遇到一個問題:當service接收到一條聊天訊息後,需要將該聊天訊息傳遞給聊天介面,但怎麼能是訊息無延時的在service和Activity中傳遞呢? 一般的做法是使用廣播,如在音樂播放器中播放進度的顯