目錄html
1.RunTime 概述ios
咱們在面試的時候,常常都會被問到這麼個問題:爲何說OC是一門動態的語言???其實也就是想知道你對runtime的瞭解程度。c++
2.RunTime消息機制git
1.消息機制是運行時裏面最重要的機制,OC中任何方法的調用,本質都是發送消息。eg:github
當咱們實例化這個對象時:MyClass *object = [[MyClass alloc] init]; 就會調這個實例化方法:[object showUserName];面試
咱們大概來看一下它的底層實現:json
證實過程:數組
在終端用命令打開此類文件所在的文件夾,繼續寫入命令:clang -rewrite-objc MyClass.m(把oc代碼轉寫成xcode
c/c++代碼,咱們經常使用它來窺探OC的底層實現),不一會在原來的同一目錄下會多出一個 MyClass.cpp 文件app
雙擊打開,能夠看到 init 方法已經被編譯器轉化爲下面這樣:
objc_msgSend 函數被定義在 objc/message.h 目錄下,其函數原型是:
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
該函數有兩個參數,一個 id 類型(消息接收對象),一個 SEL 類型(方法的selector)。
@selector (SEL):是一個SEL方法選擇器。SEL其主要做用是快速的經過方法名字查找到對應方法的函數指針,而後調用其函數。SEL其自己是一個Int類型的地址,地址中存放着方法的名字。
對於一個類中,每個方法對應着一個SEL。因此一個類中不能存在2個名稱相同的方法(有歧義。。。),即便參數類型不一樣,由於SEL是根據方法名字生成的,相同的方法名稱只能對應一個SEL。
歧義解釋:- (void)go {} + (void)go {} 這兩個方法能夠共存(咱們知道,這兩個方法的名字都是go)。
我我的的理解是:當咱們向一個對象或一個類發送消息時,runtime都會根據方法名去這個對象所屬的這個類的方法列表中查找方法,而方法列表的外層應該是一個字典,根據所傳的接收消息對象不一樣,查找的方法列表也不一樣。
objc_msgSend([MyClass class], @selector(go));
objc_msgSend([[MyClass alloc] init], @selector(go));
Id :是一個結構體指針類型,它能夠指向 Objective-C 中的任何對象。
Class vclass = NSClassFromString(@"ViewController");
id vc = [[vclass alloc] init];
objc_object 結構體定義以下:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};
這就是咱們一般所說的對象,這個結構體只有一個成員變量 isa,對象能夠經過 isa 指針找到其所屬的類。isa 是一個 Class 類型的成員變量,那麼 Class 又是什麼呢?以下:
通過以上的講述,咱們大概能夠了解到,當調用一個方法時,其運行過程大體以下:
3.RunTime交換方法
應用場景:當系統自帶的方法功能不夠,須要給系統自帶的方法擴展一些功能時。
eg:實現image添加圖片的時候,自動判斷image是否爲空,若是爲空則提醒圖片不存在。
有如下三種比較好的解決方法:
1.自定義類, 重寫系統自帶的imageName:方法,這種方法雖然能夠實現,可是它的弊端就是必需要使用本身的類,依賴性強。
2.給UIImage添加一個分類, 改變系統類的實現,給系統的類添加方法的時候調用(每次使用都須要導入頭文件,而且若是項目比較大,以前使用的方法所有須要更改)。
3.使用runtime的交互方法,給系統的方法添加功能. 具體實現 : 添加一個分類 --> 在分類中提供一個自定義判空增長新功能的方法 --> 將這個方法的實現和系統自帶的方法的實現交互.
交換方法的本質實際上是交換兩個方法的實現,即調換le_imageNamed:和imageName:方法,達到調用le_imageNamed:其實就是調用imageNamed:方法的目的。
那麼首先須要明白方法在哪裏交換,由於之後都是使用本身定義的方法取代系統的方法,因此,當程序一啓動,就要求能使用本身定義的功能方法。咱們通常在 + (void)load 方法裏實現交換方法 (當程序啓動的時候就會調用該方法,換句話說,只要程序一啓動就會調用load方法,整個程序運行中只會調用一次)
4.RunTime消息轉發
在方法調用的時候,若是沒有找到方法就會轉向消息轉發(攔截調用)。
攔截調用是指,在找不到調用的方法程序崩潰以前,你有機會經過重寫NSObject
的五個方法來處理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
- (id)forwardingTargetForSelector:(SEL)aSelector;
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
NSInvocation
傳給你。作完你本身的處理後,調用invokeWithTarget:
方法讓某個target觸發這個方法。eg: Monkey *monkey = [[Monkey alloc] init];
((void (*) (id, SEL)) objc_msgSend) (monkey, sel_registerName("fly")); (猴子是不可能有飛的天賦的,除非它是孫猴子。。。)
"v@:"的含義請看官方文檔:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
消息轉發的做用主要是處理異常,下面來看一個實例:
NSMutableArray *arrM = [NSMutableArray array];
NSString *str = @"」;
[arrM addObject:str]; 這行代碼會致使程序崩潰,由於數組添加的對象不能爲空。解決方法:
總結:能夠利用category + runtime + 異常的捕獲寫一個防止崩潰的框架,已經有大神在作了,具體請看:https://github.com/chenfanfang/AvoidCrash
5.RunTime關聯對象
應用場景:當你準備用一個系統的類或者是你寫的類,可是這個類並不能知足你的需求,你須要額外添加一個屬性。
通常解決辦法要麼是extends(繼承),要麼使用category(類別)。
但基本不推薦使用extends,主要是耦合性太強,主要使用category。
咱們都知道,分類中是沒法設置屬性的,若是在分類的聲明中寫@property 只能爲其生成get 和 set 方法的聲明, 這時候,runtime的關聯屬性就能發揮它的做用了。
注意:使用property的時候,同時重寫set get方法會報錯。複寫了get和set方法以後@property默認生成的@synthesize就不會起做用了,這也就意味着你的類不會自動生成出來實例變量了,你就必需要本身聲明實例變量。
設置關聯對象使用
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
獲取關聯對象使用
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
要刪除某一個被關聯的對象,使用 objc_setAssociatedObject 方法將對應的 key 設置成 nil 便可。
移除源對象中全部的關聯對象
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
eg:爲view類添加一個點擊手勢(主要實現它的子類(UILabel 、UIImageView能夠像button同樣,有本身的點擊方法)
6.RunTime實現字典與模型互轉
其中,獲取屬性的方法可讓咱們拿到各個類的私有屬性,讓後利用kvc賦值,加以應用。
歸檔解檔:
常規的歸檔解檔方法,但是當model的屬性不少時,這樣寫就有點尷尬了。。。
利用runtime實現批量歸檔解檔:
咱們在使用一些json轉model的第三方框架(JSONModel,MJExtension等)時,它們的底層都是利用runtime去實現的:
字典轉模型的時候:
1.根據字典的 key 生成 setter 方法
2.使用 objc_msgSend 調用 setter 方法爲 Model 的屬性賦值(或者 KVC)
模型轉字典的時候:
1.調用 class_copyPropertyList 方法獲取當前 Model 的全部屬性
2.調用 property_getName 獲取屬性名稱
3.根據屬性名稱生成 getter 方法
4.使用 objc_msgSend 調用 getter 方法獲取屬性值(或者 KVC)
感謝這幾個篇文章對個人幫助:
http://www.cocoachina.com/ios/20160523/16386.html
http://www.jianshu.com/p/5d625f86bd02
因爲本身以前查閱資料時,沒有及時寫總結,如今有點閒時了纔開始寫,因此有些參考文章都不記得了,請相關做者見諒。。。