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中的兩個隱藏引數:
- self : 接收訊息的物件
- _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動態繫結過程
- 根據SEL找到相應的IMP,由於不同的類可能具有相同的selector,所以在IMP的過程中要通過receiver的class來找到精確的IMP
- 呼叫IMP,把receiver,及引數傳入IMP
- 返回其返回值
上圖顯示了訊息的分發流程,當向一個物件傳送訊息時,首先根據物件的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框架並實現SERVER與CLIENT端的簡單訊息傳遞!
Hibernate系列學習階段到此結束了,那麼緊接著進入Apache Mina的開發學習,很多童鞋在微薄和QQ中疑問Himi為什麼突然脫離遊戲開發了,嘿嘿,其實可能更多的童鞋已經看出來了,Himi在偏向伺服器Server端開發了,Hibernate、MySQL等都是為了Server端Mina開發而做的
Java與C#通過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培訓、期待與您交流! ---------- 類方法與物件方法相比較,最根本的好處在於節省記憶體空間。 物件方法在執行時是先從記憶體棧區訪問記憶體堆區的地址,再從記憶體堆區尋找方法位於記憶體程式碼區的地址,最終返回值。
利用介面實現service與Activity訊息傳遞
背景 最近在仿Android版QQ,在實現訊息傳遞的時候遇到一個問題:當service接收到一條聊天訊息後,需要將該聊天訊息傳遞給聊天介面,但怎麼能是訊息無延時的在service和Activity中傳遞呢? 一般的做法是使用廣播,如在音樂播放器中播放進度的顯