Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放在運(yùn)行時來處理,這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時更具有靈活性,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象或隨意交換一個方法的實(shí)現(xiàn)。
這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運(yùn)行時系統(tǒng)來執(zhí)行編譯的代碼。對于Objective-C來說。這個運(yùn)行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行,這個運(yùn)行時系統(tǒng)即Objc Runtime。Objc Runtime其實(shí)是一個Runtime庫,它基本上是用C和匯編寫的,這個庫使得C語言有了面向?qū)ο蟮哪芰Α?/p>
Runtime庫主要做下面幾件事:
1.封裝:在這個庫中,對象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實(shí)現(xiàn),另外加一些額外的特性。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們可以在程序運(yùn)行時創(chuàng)建,檢查,修改類對象和它們的方法了。
2.找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object dosomething]時,會向消息接受者(object)發(fā)送一條信息(doSomething),RunTime會根據(jù)消息接受者是否能響應(yīng)消息做出不同的反應(yīng)。
Class
Objective-C類是由Class類表示的,它實(shí)際是一個指向objc_class結(jié)構(gòu)體的指針,它的定義如下:
typedef struct objc_class *Class;
在objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
1.isa:在Object-C中,所有的類的自身也是一個對象,類和類的實(shí)例沒有任何本質(zhì)上的區(qū)別,任何對象都有isa的指針。isa是一個Class類型的指針,每個實(shí)例對象都有一個isa的指針,他指向?qū)ο蟮念?而類(Class)里也有個isa的指針,指向meteClass(元類)。
2.super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject)則super_class為NULL。
3.char *name: 類名。
4.version:我們可以使用這個字段來提供類的版本信息。
5.info運(yùn)行期使用的一些位標(biāo)識。
6.instance_size: 該類的實(shí)例變量大小。
7.ivars:objc_ivar_list結(jié)構(gòu)體存儲著objc_ivar成員變量數(shù)組列表,而'obj_ivar'結(jié)構(gòu)體存儲了類的單個成員變量的信息。
在objec_class中,所有得到成員變量,屬性是放在鏈表ivars中的。ivars是一個數(shù)組,數(shù)組中每個元素都指向Ivar(變量信息)的指針。
objc_ivar_list *ivars
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
ifdef LP64int space OBJC2_UNAVAILABLE;
endif/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
Ivar是表示實(shí)例變量的類型,其實(shí)際是一個指向objc_ivar結(jié)構(gòu)體的指針,其定義如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 變量名
char *ivar_type OBJC2_UNAVAILABLE; // 變量類型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
從上面可以看出類的實(shí)例變量和屬性經(jīng)過runtime經(jīng)過struct的儲存形式存在,并且單個實(shí)例變量保存其名字、類型、偏移量和儲存空間。類中所有實(shí)例變量是以list類型進(jìn)行儲存。
8.objc_method_list是Obj_method方法列表。
9.cache:用于緩存最近使用的方法,一個接收者對象收到一個消息時,會根據(jù)isa指針去查找能夠響應(yīng)這個消息的對象,但是在實(shí)際使用中,這個對象只有一部分方法是常用的,很多方法很少或者根本用不上,這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差,這時cache就有用了,在我們每次調(diào)用 一個方法后,這個方法就會被緩存到cache列表中,下次調(diào)用的時候 runtime就會優(yōu)先去cache中找,如果cache沒有,才會去methodLists中查找方法。
objc_object 與 id
objc_object是表示一個類的實(shí)例的結(jié)構(gòu)體,它的定義如下(objc/objc.h):
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
可以看到,objc_object這個結(jié)構(gòu)體只有一個字體Class isa OBJC_ISA_AVAILABILITY是指向其類的isa指針,這樣當(dāng)我們向一個Objective-C對象發(fā)送消息時,Runtime庫會根據(jù)實(shí)例對象的isa指針找到這個實(shí)例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應(yīng)的selector指向的方法。找到后運(yùn)行這個方法。
當(dāng)創(chuàng)建一個特定類的實(shí)例 對象時,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實(shí)例變量的數(shù)據(jù),NSObject類的alloc和allocWithZone方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)。
id,它實(shí)際上是一個objc_object結(jié)構(gòu)類型的指針。該類型的對象可以轉(zhuǎn)換為任何一種對象。
id類型是動態(tài)類型的,即運(yùn)行時再決定對象的類型。id類型即通用的對象類,任何對象都可以被id指針?biāo)?而在實(shí)際使用中,往往使用introspection來確定該對象的實(shí)際所屬類:
id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
someClass *classSpecifiedInstance = (someClass *)obj;
// Do Something to classSpecifiedInstance which now is an instance of someClass
//...
}
元類(Meal Class)
所有的類的自身也是一個對象,我們可以向這個對象發(fā)送消息(即調(diào)用類方法),如:
NSDictionary *dictionary = [NSDictionary dictionary];
+dictionary消息發(fā)送給了NSDictionary類,而這個NSDictionary也是一個
對象,既然是對象,那么它也是一個objc_object指針,它包含一個指向其類的一個isa指針。為了調(diào)用+dictionary方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結(jié)構(gòu)體。這就引出了meta-class的概念
met-class是一個類對象的類。
當(dāng)我們向一個對象發(fā)送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而像一個類發(fā)送消息時,會在這個類的meta-class的方法列表中查找。
meta-class它存儲著一個類的所有類方法。每個類都會有一個單獨(dú)的meta-class,每個類的類方法基本不可能完全相同。
meta-class也是一個,也可以向它發(fā)送一個消息,那么它的isa又是指向哪里,為了不讓這種結(jié)構(gòu)無限延伸下去,Object-C的設(shè)計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即任何NSObject繼承體系下的meta-class都會使用NSObject的meta-class作為自己所屬類,而基類的isa指針是指向它自己。這樣就形成了一個完美的閉環(huán)

消息處理
SEL
SEL又叫選擇器,是表示一個方法的selector的指針,其定義如下:
typedef struct objc_selector *SEL;
selector用于表示運(yùn)行時方法的名字,Objective-C編譯時,會根據(jù)每一個方法的名字、參數(shù)序列,生成一個唯一整型標(biāo)識(Int類型的地址),這個標(biāo)識就是SEL,如下代碼所示:
SEL sel = @selector(method);
NSLog(@"sel : %p", sel);
上面的輸出為:
2016-11-28 14:04:41.151 RuntimeDemo[89015:1134944] sel : 0x103076a22**
兩個類之間,不管它們是父類與子類的關(guān)系,還是之間沒有這種關(guān)系,只要方法名相同,那么SEL就是一樣的,每一個方法都對應(yīng)著一個SEL。所以在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,即使參數(shù)類型不同也不行。相同的方法只能對應(yīng)一個SEL。這就導(dǎo)致Objecttive-C在處理相同方法名字且參數(shù)個數(shù)相同但是參數(shù)類型不同的方法方面的能力很差。如:
- (void)dealTotalPriceWithProductCount:(int)productCount;
- (void)dealTotalPriceWithProductCount:(NSUInteger)productCount;
這種定義會被編譯器認(rèn)為是一種編譯錯誤。不同類的實(shí)例對象執(zhí)行相同的selector時,會在各自的方法列表中去根據(jù)selector去尋找自己對應(yīng)的IMP。
在一個工程中所有的SEL組成一個Set集合,Set 的特點(diǎn)是唯一,因此SEL是唯一的。因此,如果我們想到這個方法集合 中查找某個方法時,只要去找到這個方法對應(yīng)的SEL就行了,SEL實(shí)際上就是根據(jù)方法名hash化了一個字符串,而對字符串的比較僅僅需要比較它們的地址就可以了,速度上是非??斓?。
本質(zhì)上,SEL只是一個指向方法的指針(準(zhǔn)確說,只是一個方法名hash化了的KEY值,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度。
我們可以在運(yùn)行時添加新的selector,也可以在運(yùn)行時獲取已存在的selector,有三種方法來獲取SEL:
1.sel_registerName函數(shù)
2.Objective-C編譯器提供的@selector()
3.nSSelectorFromString()方法
IMP
IMP實(shí)際上是一個函數(shù)指針,指向方法實(shí)現(xiàn)的首地址。它是一個函數(shù)指針,由編譯器生成,SEL就是為了查找方法的最終實(shí)現(xiàn)IMP。每個方法對應(yīng)唯一的SEL,因此我們可以快速準(zhǔn)確地獲得它對應(yīng)的IMP,取得IMP后,我們就獲得了執(zhí)行這個方法代碼的入口點(diǎn),通過取得IMP,我們可以跳過Runtime的消息傳遞機(jī)制,直接執(zhí)行IMP指向的函數(shù)實(shí)現(xiàn),這樣就省去了Runtime消息傳遞過程中的一系列查找操作,會比直接向?qū)ο蟀l(fā)送消息高效一些。
id (*IMP)(id, SEL, ...)
第一個參數(shù)是指向self的指針(如果是實(shí)例方法,則是類實(shí)例的內(nèi)存地址;如果是類方法,則是指向元類的指針),第二個參數(shù)是方法選擇器(selector),接下來是方法的實(shí)際參數(shù)列表。
Method
Method用于表示類定義中的方法,則定義如下:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; //方法名
char *method_types OBJC2_UNAVAILABLE; //方法類型
IMP method_imp OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}
Method結(jié)構(gòu)體包含一個SEL和 一個IMP,實(shí)際上相當(dāng) 與在SEL和IMP之間作了一個映射。有了SEL,我們可以找到對應(yīng)的IMP,從而調(diào)用方法的實(shí)現(xiàn)代碼。
SEL:代表方法名類型,在不同的類中定義,它們的方法選擇器也不一樣。
method_types : method_types方法類型是一個char指針,存儲著方法的參數(shù)類型和返回類型。
method_imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個函數(shù)指針。
方法調(diào)用流程
在Objective-C,消息直到運(yùn)行時才綁定到方法實(shí)現(xiàn)上,編譯器會將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個消息函數(shù)的調(diào)用,即objc_msgSend。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù),如下:
objc_msgSend(receiver, selector)
如果消息中還有其它參數(shù),則該方法的形式如下所示:
objc_msgSend(receiver, selector, arg1, arg2, ...)
這個函數(shù)完成了動態(tài)綁定的所有事情:
1..首先找到selector`對應(yīng)的方法實(shí)現(xiàn)。因為同一個方法可能在不同的類中有不同的實(shí)現(xiàn),所以我們需要依賴接受者的類來找到確切的實(shí)現(xiàn)。
2.它調(diào)用方法實(shí)現(xiàn),并將接受者對象及方法的所有參數(shù)傳給它。
3.最后,它將實(shí)現(xiàn)返回的值作為自己的返回值。
消息的關(guān)鍵在于前面有解釋的結(jié)構(gòu)體objc_class,這個結(jié)構(gòu)體有連個字段是我們在分發(fā)消息的時候需要關(guān)注的:
1.指向父類的指針
2.一個類的方法分發(fā)表,即methodList。
當(dāng)我們創(chuàng)建一個新對象的時候,先為其分配內(nèi)存,并初始化其成員變量。其中isa指針也會被初始化,讓對象可以訪問類及類得得繼承體系。

當(dāng)消息發(fā)送給一個對象時,objc_msgSend通過對象的isa指針獲取到類的結(jié)構(gòu)體,然后在方法分發(fā)表里查找方法的selector。如果沒有找到selector,則通過objc_msgSend結(jié)構(gòu)體中的指向父類的指針找到其父類,并在父類的分發(fā)表里面查找方法的selector。依次,會一直沿著類的繼承體系到達(dá)NSObject類。一旦定位到selector,函數(shù)就獲取到了實(shí)現(xiàn)得得入口點(diǎn),并傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn)。如果沒有定位到selector,為了加速消息的處理,運(yùn)行時系統(tǒng)緩存使用過的'selector'級對應(yīng)的方法的地址。
下面以實(shí)例對象調(diào)用方法[student speek]為例描述調(diào)用的流程:
1.編譯器會把`[student speak]`轉(zhuǎn)化為`objc_msgSend(student, SEL)`,SEL為@selector(speek)。
2.runtime會在student對象對應(yīng)的Student類的方法緩存列表里查找方法的SEL。
3.如果沒有找到,則在Student類的方法分發(fā)表查找SEL,類對象由對象isa指針指向,分發(fā)列表即methodList。
4.如果沒有找到,則在父類(設(shè)Student的父類是Person類)的方法分發(fā)表里查找方法的SEL(父類由類的superClass指向)。
5.如果沒有找到,則沿著繼承體系繼續(xù)找下去,最終到達(dá)NSObject類停止。
6.如果在2 、3 、4的其中一步找到,會通過SEL找打?qū)?yīng)的IMP,即定位到了方法實(shí)現(xiàn)的入口,執(zhí)行具體實(shí)現(xiàn)。
7.如果最后還是沒有找到,則會進(jìn)行消息轉(zhuǎn)發(fā)。
獲取方法地址
Runtime中方法的動態(tài)綁讓我們寫代碼的時候更具有靈活性,如我們可以消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換一個方法的實(shí)現(xiàn)等動態(tài)綁定不過靈活性的提升也帶來了性能上的一些損耗。畢竟我們需要去查找方法的實(shí)現(xiàn)。而不像函數(shù)調(diào)用得那么直接。當(dāng)然方法得當(dāng)緩存一定程度上解決了這一問題。
如果想要避開這種動態(tài)綁定方式,我們可以獲取方法實(shí)
Method Swizzing
Method swizzling 用于改變一個已經(jīng)存在的selector的實(shí)現(xiàn),這項技術(shù)使得在運(yùn)行時候改變方法的調(diào)用成為可能。例如我們想要在一款iOS app中追蹤每一個界面呈現(xiàn)給力用戶多少次:可以通過在每個視圖控制器的viewDidAppear方法中添加追蹤代碼來實(shí)現(xiàn),但這樣會有大量重復(fù)的代碼,繼承也會有同樣的問題,利用method swizzling可以較完美實(shí)現(xiàn):
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
//源方法的SEL
SEL originalSelector = @selector(viewWillAppear:);
//交換方法的SEL
SEL swizzledSelector = @selector(prefix_viewWillAppear:);
/*
通過class_getInstanceMethod( ) 函數(shù)從當(dāng)前對象中的method list獲取method結(jié)構(gòu)體,
如果類方法那么就使用class_getClassMethod ( ) 函數(shù)獲取。
*/
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
/**
* 我們在這里使用class_addMethod()函數(shù)對Method Swizzling做了一層驗證,如果self沒有實(shí)現(xiàn)被交換的方法,會導(dǎo)致失敗。
* 而且self沒有交換的方法實(shí)現(xiàn),但是父類有這個方法,這樣就會調(diào)用父類的方法,結(jié)果就不是我們想要的結(jié)果了。
* 所以我們在這里通過class_addMethod()的驗證,如果self實(shí)現(xiàn)了這個方法,class_addMethod()函數(shù)將會返回NO,我們就可以對其進(jìn)行交換了。
*/
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)
);
if (didAddMethod) {
//添加成功:將源方法的實(shí)現(xiàn)替換到交換方法的實(shí)現(xiàn)
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod)
);
} else {
//添加失敗: 說明源方法已經(jīng)有實(shí)現(xiàn), 直接將兩個方法的實(shí)現(xiàn)交換即可
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
swizzling應(yīng)該只在+load中完成。在Object-C的運(yùn)行時中,每個類都有兩個方法自動調(diào)用。
+load是在一個類被初始裝載時調(diào)用,+initialize是在應(yīng)用應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗?br>
方法前調(diào)用。兩個方法都是可選的,并且只有在方法被實(shí)現(xiàn)的情況下才會被調(diào)用。
** swizzling 應(yīng)該只在 dispatch_once 中完成**
由于 swizzling 改變了全局的狀態(tài),所以我們需要確保每個預(yù)防措施在運(yùn)行時都是可用的。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預(yù)防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch 的 dispatch_once 滿足了所需要的需求,并且應(yīng)該被當(dāng)做使用 swizzling 的初始化單例方法的標(biāo)準(zhǔn)。