Runtime的簡單應用

自己最近在研究Runtime,研究好久才知道了一些大概和簡單的應用。在這里做一個筆記。RunTime被稱為iOS開發的黑魔法,功能之強大,簡直就是裝逼神器啊。自己也是摸索著前人的步伐,一步一步探索Runtime機制在開發中的使用。

1.什么是Runtime

Runtime是一套底層的C語言API,包含很多強大實用的C語言數據類型和C語言函數。平時我們編寫的OC代碼,都是基于Runtime實現的。OC是運行時語言,也只有在運行的時候才可以確定對象的類型,并調用類的對象的相應的方法,其中最主要的是消息機制。所以利用Runtime機制可以在程序運行的時候動態修改類的方法、類的對象的屬性、方法、創建類別。這些應該是Runtime的基本用法吧,也是我們在平時的開發中用到的。

例如下邊的這個方法在運行時會被轉化:

/* OC方法調用 */

[obj makeTest];

/* 編譯時Runtime會將上面的代碼轉為下面的消息發送 */

objc_msgSend(obj, @selector(makeText));

iOS的頂層基類NSObject含有一個指向objc_class結構體的isa指針:

@interface NSObject{

Class isa;

}

typedef struct objc_class *Class;

struct objc_class {

Class isa; // 指向metaclass,也就是靜態的Class

Class super_class ; // 指向其父類

const char *name ; // 類名

long version ; // 類的版本信息,初始化默認為0

/* 一些標識信息,如CLS_CLASS(0x1L)表示該類為普通class;

CLS_META(0x2L)表示該類為metaclass */

long info;

long instance_size ; // 該類的實例變量大小(包括從父類繼承下來的實例變量);

struct objc_ivar_list *ivars; // 用于存儲每個成員變量的地址

/* 與info的一些標志位有關,如是普通class則存儲對象方法,如是metaclass則存儲類方法; */

struct objc_method_list **methodLists ;

struct objc_cache *cache; // 指向最近使用的方法的指針,用于提升效率;

struct objc_protocol_list *protocols; // 存儲該類遵守的協議

}

在objc_msgSend函數的調用過程:

1.首先通過obj的isa指針找到obj對應的Class。

2.在Class中先去cache中通過SEL查找對應函數method

3.若cache中未找到,再去methodLists中查找

4.若methodLists中未找到,則進入superClass按前面的步驟進行遞歸查找

5.若找到method,則將method加入到cache中,以方便下次查找,并通過method中的函數指針跳轉到對應的函數中去執行。

6.如果一直查找到NSObject還沒查找到,則會進入消息動態處理流程。

消息動態處理流程:

/* 1. 時機處理之一,在這個方法中我們可以利用runtime的特性動態添加方法來處理 */

+ (BOOL)resolveInstanceMethod:(SEL)sel;

/* 2. 時機處理之二,在這個方法中看代理能不能處理,如果代理對象能處理,則轉接給代理對象 */

- (id)forwardingTargetForSelector:(SEL)aSelector;

/* 3. 消息轉發之一,該方法返回方法簽名,如果返回nil,則轉發流程終止,拋出異常 */

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

/* 4. 消息轉發之二,在該方法中我們可以對調用方法進行重定向 */

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

2.Runtime的應用場景

1>.程序運行過程中動態創建類,如:KVO的實現

2>.程序運行過程中動態修改對象的屬性、方法

3>.遍歷一個類的所有成員變量、方法

4>.交換方法

5>.運行時創建類

3.場景舉例:

(1).動態創建類---實現自己的KVO監聽

KVO監聽相信大家在平時開發中都有用到過,但是它是怎么監聽到屬性變化的呢?懶加載大家都有用到吧,自己重寫屬性的set方法。對,KVO就是監聽屬性的set方法。讓我們先看第一張圖:

截圖1

圖中實例對象p的isa指針指向的是Person類,讓我們單步往下走

截圖2

現在p的isa指針指向的是NSKVONotifying_Person這個類。在程序運行的時候,蘋果利用runtime機制動態的創建了一個繼承Person類的NSKVONotifying_Person這個類,并將isa指針動態指向子類方法。蘋果在動態創建的NSKVONotifying_Person這個類中重寫父類屬性的set方法,方法實現中再調用父類的set方法。

重寫父類屬性的set方法

現在我們自己利用Runtime實現自己的KVO監聽,關鍵代碼如下:

-(void)LR_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{

//獲取類名

NSString* oldClass = NSStringFromClass([self class]);

NSString* newClass = [@"LR" stringByAppendingString:oldClass];

const char* name = [newClass UTF8String];

//1.動態生成一個類

Class myClass = objc_allocateClassPair([self class], name, 0);

//添加一個方法

class_addMethod(myClass, @selector(setName:), (IMP)setName, "");

//2.注冊這個類

objc_registerClassPair(myClass);

//3.修改isa指針

object_setClass(self, myClass);

//4.實現setName方法

void setName(id self, SEL _cmd,NSString* newName){

NSLog(@"我來餓了");

}

(2).程序運行過程中動態修改對象的屬性、方法

我們在demo中調用Person類一個沒有實現的方法,然后command+R會怎樣?Crash?。。?/p>

Person* p = [[Person alloc] init];

[p performSelector:@selector(run)];

現在我們可以利用runtime機制實現方法的懶加載。

關鍵代碼如下:

+(BOOL)resolveInstanceMethod:(SEL)sel{

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

//1.cls 類類型

//2.name 方法編號

//3.imp 方法實現。函數指針指向一個實現

//4.types 返回值類型

class_addMethod([Person class], sel, (IMP)haha, "v");

}

/*

第四個參數的含義:

v表示void,@表示id類型,:表示SEL類型

"v@:@":表示返回值為void,接受一個id類型、一個SEL類型、一個id類型的方法

"@@:":表示返回值為id類型,接受一個id類型和一個SEL類型參數的方法

*/

return [super resolveInstanceMethod:sel];

}

實現動態添加的方法的實現

void haha(id self ,SEL _cmd){

NSLog(@"%@===%@",self,NSStringFromSelector(_cmd));

NSLog(@"你說啥");

}

當一個類調用了沒有實現的方法,就會來到runtime的這個方法resolveInstanceMethod,進行方法的尋找,如果子類中沒有方法的實現,就會在父類中尋找,如果父類也沒有,就往父類的父類取尋找。所以在這個方法中我們使用class_addMethod方法動態的為Person類添加方法。

注:在所有的方法中都會隱式接收2個參數----self,_cmd。self調用的類,_cmd方法的編碼名。這兩個參數只有你傳入之后才可以在方法實現中拿到。

(3).遍歷一個類的所有成員變量、方法

主要用到的方法如下:

class_copyIvarList--->獲取類的成員變量列表-->多用于字典轉模型,歸解檔的操作。(有興趣的可以研究一下MJExtension的內部實現,受益匪淺)

class_copyPropertyList--->獲取類的屬性列表

代碼如圖:

截圖3

(4).交換方法

通過runtime的method_exchangeImplementations方法來實現方法的互換(實際是利用runtime改變了兩個方法的isa指針指向)。一般用自己寫的方法(常用在自己封裝的類或寫的框架中,添加某些防錯措施)來替換系統的方法,如:

在數組中的越界訪問或數組使用addObject方法添加元素為nil時導致的程序崩潰??梢孕陆ㄒ粋€分類實現方法的交換來防止程序的崩潰,如圖:

新建分類添加方法交換

(5).動態添加一個類

動態添加一個類

4.Runtime的簡單應用

當我們的項目越做越大越復雜的時候,建立的控制器也會越來越多,相應的跳轉也會增加。特別是你接收一個大項目的時候,對整體的業務邏輯不熟悉,整體的架構體系也是一頭霧水,然而你又要修復某個頁面的BUG,估計要找到對應的頁面都要找好久。有沒有一種方式可以快速找到某個頁面對應的ciewController呢?

方案一、在整個項目建立初期構建一個基類viewController,此后創建的VC均繼承于基類。我們只需要重寫基類的viewWillAppear方法。

- (void)viewWillAppear:(BOOL)animated {

[super?viewWillAppear:animated];

NSString?*className?=?NSStringFromClass([self?class]);

NSLog(@"%@?will?appear",?className);

}

方案二、給viewController創建一個分類,在分類里邊進行方法的替換。這里所說的方法替換并不是使用method_exchangeImplementations改變兩個方法的isa指針指向。而是先拿到系統方法的IMP實現,然后構建一個新的符合我們需求的IMP實現來替代系統方法的IMP實現。如圖:

改變方法的IMP實現

具體代碼如下:

修改IMP實現

5.Runtime使用心得

Runtime很強大,這只是我的一個初步的了解,對于很多東西不是很了解。這應該算是Runtime的一個基礎用法吧。不過黑魔法就是用著酸爽。要理解透徹,并且在開發中熟練應用感覺任重道遠啊。

RuntimeDemo傳送門

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,789評論 0 9
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,164評論 0 9
  • 前言 runtime其實在我們日常開發過程中很少使用到,尤其是像我現在比較初級的程序猿就更用不到了。但是去面試很多...
    WolfTin閱讀 673評論 0 2
  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,232評論 0 7
  • “你是不是喜歡我?”余知霖擦擦眼淚,擤了擤鼻子。坐在公園的長椅上,側著腦袋問宋民宇。 “我以為我做的夠明顯的了?!?..
    阿九小叔閱讀 433評論 0 0