方法的調(diào)用[p eat],會(huì)被編譯器轉(zhuǎn)成runtime庫中的objc_msgSend調(diào)用的方式來執(zhí)行,即:
[p eat] 轉(zhuǎn)
objc_msgSend(p, sel_registerName("eat"))。
第一步:對(duì)象通過
isa指針找到它所繼承的類class;
第二步:在class的method_list中查找對(duì)應(yīng)的方法;
第三步:如果未查找到當(dāng)前方法,會(huì)向superclass類中查找,直到找到當(dāng)前調(diào)用的方法。
如果每次調(diào)用方法都需要遍歷,系統(tǒng)消耗比較大,因此需要對(duì)常用的方法做緩存操作,每次查找先找緩存,就可以避免大量的無效操作。
每一個(gè)對(duì)象都存在一個(gè)isa指針,指向?qū)ο蟮念悾愐彩且粋€(gè)對(duì)象也存在一個(gè)isa指針指向元類,元類指向根元類,根元類指向自己。類中保存所有的實(shí)列方法,元類保存了所有類方法。方法查找過程:

我們都知道Objective-C是一門動(dòng)態(tài)語言, 動(dòng)態(tài)之處體現(xiàn)在它將許多靜態(tài)語言編譯鏈接時(shí)要做的事通通放到運(yùn)行時(shí)去做, 這大大增加了我們編程的靈活性.
毫不過分地說, Runtime就是OC的靈魂.
接下來我就要撥開OC最外層的外衣, 帶大家看看OC的真面目(C/C++).
目錄
1.類和對(duì)象
2.消息發(fā)送和轉(zhuǎn)發(fā)
3.KVO原理
深入代碼理解instance、class object、metaclass
面向?qū)ο缶幊讨校钪匾母拍罹褪穷?,下面我們就從代碼入手,看看OC是如何實(shí)現(xiàn)類的。
instance對(duì)象實(shí)例
我們經(jīng)常使用id來聲明一個(gè)對(duì)象,那id的本質(zhì)又是什么呢?打開#import<objc/objc.h>文件,可以發(fā)現(xiàn)以下幾行代碼
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
通過注釋和代碼不難發(fā)現(xiàn),我們創(chuàng)建的一個(gè)對(duì)象或?qū)嵗鋵?shí)就是一個(gè)struct objc_object結(jié)構(gòu)體,而我們常用的id也就是這個(gè)結(jié)構(gòu)體的指針。
這個(gè)結(jié)構(gòu)體只有一個(gè)成員變量,這是一個(gè)Class類型的變量isa,也是一個(gè)結(jié)構(gòu)體指針,那這個(gè)指針又指向什么呢?
面向?qū)ο笾忻恳粋€(gè)對(duì)象都必須依賴一個(gè)類來創(chuàng)建,因此對(duì)象的isa指針就指向?qū)ο笏鶎俚念惛鶕?jù)這個(gè)類模板能夠創(chuàng)建出實(shí)例變量、實(shí)例方法等。
比如有如下代碼
NSString *str = @"Hello World";
通過上文我們知道這個(gè)str對(duì)象本質(zhì)就是一個(gè)objc_object結(jié)構(gòu)體,而這個(gè)結(jié)構(gòu)體的成員變量isa指針則表明了str is a NSString,因此這個(gè)isa就指向了NSString類,這個(gè)NSString類其實(shí)是類對(duì)象,不明白就繼續(xù)往下看。
class object(類對(duì)象)/metaclass(元類)
繼續(xù)查看結(jié)構(gòu)體objc_class的定義
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;
/* Use `Class` instead of `struct objc_class *` */
struct objc_classs結(jié)構(gòu)體里存放的數(shù)據(jù)稱為元數(shù)據(jù)(metadata),通過成員變量的名稱我們可以猜測(cè)里面存放有指向父類的指針、類的名字、版本、實(shí)例大小、實(shí)例變量列表、方法列表、緩存、遵守的協(xié)議列表等,這些信息就足夠創(chuàng)建一個(gè)實(shí)例了,該結(jié)構(gòu)體的第一個(gè)成員變量也是isa指針,這就說明了Class本身其實(shí)也是一個(gè)對(duì)象,我們稱之為類對(duì)象,類對(duì)象在編譯期產(chǎn)生用于創(chuàng)建實(shí)例對(duì)象,是單例,因此前文中的栗子其實(shí)應(yīng)該表達(dá)為str的isa指針指向了NSString類對(duì)象那么這個(gè)結(jié)構(gòu)體的isa指針又指向什么呢?
類對(duì)象中的元數(shù)據(jù)存儲(chǔ)的都是如何創(chuàng)建一個(gè)實(shí)例的相關(guān)信息,那么類對(duì)象和類方法應(yīng)該從哪里創(chuàng)建呢?就是從isa指針指向的結(jié)構(gòu)體創(chuàng)建,類對(duì)象的isa指針指向的我們稱之為元類(metaclass),元類中保存了創(chuàng)建類對(duì)象以及類方法所需的所有信息,因此整個(gè)結(jié)構(gòu)應(yīng)該如下圖所示:

通過上圖我們可以清晰的看出來一個(gè)實(shí)例對(duì)象也就是struct objc_object結(jié)構(gòu)體它的isa指針指向類對(duì)象,類對(duì)象的isa指針指向了元類,super_class指針指向了父類的類對(duì)象,而元類的super_class指針指向了父類的元類,那元類的isa指針又指向了什么?為了更清晰的表達(dá)直接使用一個(gè)大神畫的圖。

通過上圖我們可以看出整個(gè)體系構(gòu)成了一個(gè)自閉環(huán),如果是從NSObject中繼承而來的上圖中的Root class就是NSObject。至此,整個(gè)實(shí)例、類對(duì)象、元類的概念也就講清了,接下來我們?cè)诖a中看看這些概念該怎么應(yīng)用。
如圖所示
1.每一個(gè)實(shí)例包含一個(gè)isa對(duì)象
2.isa指向類,類是一個(gè)objc_class結(jié)構(gòu)體,包含實(shí)例的方法列表,參數(shù)列表,category等,除此之外,objc_class中還有一個(gè)super_class,指向其類的父類,isa指針,這里的isa指針指向元類,即metaClass,元類存儲(chǔ)類方法等信息
3.元類里也包含isa指針,元類里的isa指針指向 根元類,根元類的isa指針指向自己
4.obj_msgSend發(fā)送實(shí)例消息的時(shí)候,先找到實(shí)例,然后通過實(shí)例的isa指針找到類的方法列表及參數(shù)列表等,如果找到,返回,如果沒有找到,則通過super_class在其父類中重復(fù)此過程
5.obj_msgSend發(fā)送類消息的時(shí)候,通過類的isa,找到元類,然后流程與步驟4相同
類對(duì)象
有如下代碼
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Class c1 = [p class];
Class c2 = [Person class];
//輸出 1
NSLog(@"%d", c1 == c2);
}
return 0;
}
c1是通過一個(gè)實(shí)例對(duì)象獲取的Class,實(shí)例對(duì)象可以獲取到其類對(duì)象,類名作為消息的接受者時(shí)代表的是類對(duì)象,因此類對(duì)象獲取Class得到的是其本身,同時(shí)也印證了類對(duì)象是一個(gè)單例的想法。
那么如果我們想獲取isa指針的指向?qū)ο竽兀?/p>
介紹兩個(gè)函數(shù)
OBJC_EXPORT BOOL class_isMetaClass(Class cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
OBJC_EXPORT Class object_getClass(id obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
class_isMetaClass用于判斷Class對(duì)象是否為元類,object_getClass用于獲取對(duì)象的isa指針指向的對(duì)象。
再看如下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//輸出1
NSLog(@"%d", [p class] == object_getClass(p));
//輸出0
NSLog(@"%d", class_isMetaClass(object_getClass(p)));
//輸出1
NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
//輸出0
NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
}
return 0;
}
通過代碼可以看出,一個(gè)實(shí)例對(duì)象通過class方法獲取的Class就是它的isa指針指向的類對(duì)象,而類對(duì)象不是元類,類對(duì)象的isa指針指向的對(duì)象是元類。
類和對(duì)象
@interface Person : NSObject {
NSString *_name;
int _age;
}
- (void)study;
+ (void)study;
@end
@implementation Person
- (void)study
{
NSLog(@"instance - study");
}
+ (void)study
{
NSLog(@"class - study");
}
@end
為了更好地說明類在底層的表現(xiàn)形式是怎樣, 我們將上面代碼利用clang -rewrite-objc Person.m指令將其用C/C++重寫, 一窺究竟.
把不必要的刪除, 整理后為下面
struct _class_t {
struct _class_t *isa; // isa指針
struct _class_t *superclass; // 父類
void *cache;
void *vtable;
struct _class_ro_t *ro; // class的其他信息
};
// class包含的信息
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name; // 類名
const struct _method_list_t *baseMethods; // 方法列表
const struct _objc_protocol_list *baseProtocols; // 協(xié)議列表
const struct _ivar_list_t *ivars; // ivar列表
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties; // 屬性列表
};
// Person(class)
struct _class_t OBJC_CLASS_$_Person = {
.isa = &OBJC_METACLASS_$_Person, // 指向Person-metaclass
.superclass = &OBJC_CLASS_$_NSObject, // 指向NSObject-class
.cache = &_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_Person, // 包含了實(shí)例方法, ivar信息等
};
// Person(metaclass)
struct _class_t OBJC_METACLASS_$_Person = {
.isa = &OBJC_METACLASS_$_NSObject, // 指向NSObject-metaclass
.superclass = &OBJC_METACLASS_$_NSObject, // 指向NSObject-metaclass
.cache = &_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_Person, // 包含了類方法
};
原來(顯然), 我們的類其實(shí)就是一個(gè)結(jié)構(gòu)體!!! 類跟我們的對(duì)象一樣, 都有一個(gè)isa指針, 所以類其實(shí)也是對(duì)象的一種.
isa指針
isa指針非常重要, 對(duì)象需要通過isa指針找到它的類, 類需要通過isa找到它的元類. 這在調(diào)用實(shí)例方法和類方法的時(shí)候起到重要的作用.

實(shí)例對(duì)象在調(diào)用方法時(shí), 首先通過isa指針找到它所屬的類, 然后在類的緩存(cache)里找該方法的IMP, 如果沒有, 則去類的方法列表中查找, 然后找到則調(diào)用該方法, 找不到則報(bào)錯(cuò).
類對(duì)象調(diào)用方法則如出一轍, 通過isa指針找到元類, 然后就跟上述一致了. 這里涉及的發(fā)送消息機(jī)制下面會(huì)詳細(xì)講..
下面展示一些運(yùn)行時(shí)動(dòng)態(tài)獲取對(duì)象和類的屬性的C語言方法
類和類名 :
// 返回對(duì)象的類
Class object_getClass ( id obj );
// 設(shè)置對(duì)象的類
Class object_setClass ( id obj, Class cls );
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 創(chuàng)建一個(gè)新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 在應(yīng)用中注冊(cè)由objc_allocateClassPair創(chuàng)建的類
void objc_registerClassPair ( Class cls );
// 銷毀一個(gè)類及其相關(guān)聯(lián)的類
void objc_disposeClassPair ( Class cls );
// 獲取類的類名
const char * class_getName ( Class cls );
// 返回給定對(duì)象的類名
const char * object_getClassName ( id obj );
ivar和屬性 :
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 返回類的某一ivar
Ivar class_getInstanceVariable(__unsafe_unretained Class cls, const char *name)
// 返回對(duì)象中實(shí)例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設(shè)置對(duì)象中實(shí)例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );
// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
方法 :
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實(shí)現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 交換兩個(gè)方法的實(shí)現(xiàn)(Method Swizzling)
void method_exchangeImplementations(Method m1, Method m2);
這里說個(gè)注意點(diǎn) : addIvar并不能為一個(gè)已經(jīng)存在的類添加成員變量, 只能為那些運(yùn)行時(shí)動(dòng)態(tài)添加的類, 并且只能在objc_allocateClassPair與objc_registerClassPair這兩個(gè)方法之間才能添加Ivar.
消息發(fā)送和轉(zhuǎn)發(fā)機(jī)制
在OC中, 如果向某對(duì)象發(fā)送消息, 那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法. OC的方法在底層都是普通的C語言函數(shù), 所以對(duì)象收到消息后究竟要調(diào)用什么函數(shù)完全由運(yùn)行時(shí)決定, 甚至可以在運(yùn)行時(shí)改變執(zhí)行的方法.
[person read:book];
會(huì)被編譯成
objc_msgSend(person, @selector(read:), book);
objc_msgSend的具體流程如下
1. 通過isa指針找到所屬類
2. 查找類的cache列表, 如果沒有則下一步
3. 查找類的"方法列表"
4. 如果能找到與選擇子名稱相符的方法, 就跳至其實(shí)現(xiàn)代碼
5. 找不到, 就沿著繼承體系繼續(xù)向上查找
6. 如果能找到與選擇子名稱相符的方法, 就跳至其實(shí)現(xiàn)代碼
7. 找不到, 執(zhí)行"消息轉(zhuǎn)發(fā)".

消息轉(zhuǎn)發(fā)
上面我們提到, 如果到最后都找不到, 就會(huì)來到消息轉(zhuǎn)發(fā)
動(dòng)態(tài)方法解析 : 先問接收者所屬的類, 你看能不能動(dòng)態(tài)添加個(gè)方法來處理這個(gè)"未知的選擇子"? 如果能, 則消息轉(zhuǎn)發(fā)結(jié)束.
備胎(后備接收者) : 請(qǐng)接收者看看有沒有其他對(duì)象能處理這條消息? 如果有, 則把消息轉(zhuǎn)給那個(gè)對(duì)象, 消息轉(zhuǎn)發(fā)結(jié)束.
消息簽名 : 這里會(huì)要求你返回一個(gè)消息簽名, 如果返回nil, 則消息轉(zhuǎn)發(fā)結(jié)束.
完整的消息轉(zhuǎn)發(fā) : 備胎都搞不定了, 那就只能把該消息相關(guān)的所有細(xì)節(jié)都封裝到一個(gè)NSInvocation對(duì)象, 再問接收者一次, 快想辦法把這個(gè)搞定了. 到了這個(gè)地步如果還無法處理, 消息轉(zhuǎn)發(fā)機(jī)制也無能為力了.
動(dòng)態(tài)方法解析 :
對(duì)象在收到無法解讀的消息后, 首先調(diào)用其所屬類的這個(gè)類方法 :
+ (BOOL)resolveInstanceMethod:(SEL)selector
// selector : 那個(gè)未知的選擇子
// 返回YES則結(jié)束消息轉(zhuǎn)發(fā)
// 返回NO則進(jìn)入備胎
假如尚未實(shí)現(xiàn)的方法不是實(shí)例方法而是類方法, 則會(huì)調(diào)用另一個(gè)方法resolveClassMethod:
備胎 :
動(dòng)態(tài)方法解析失敗, 則調(diào)用這個(gè)方法
- (id)forwardingTargetForSelector:(SEL)selector
// selector : 那個(gè)未知的選擇子
// 返回一個(gè)能響應(yīng)該未知選擇子的備胎對(duì)象
通過備胎這個(gè)方法, 可以用"組合"來模擬出"多重繼承".
消息簽名 :
備胎搞不定, 這個(gè)方法就準(zhǔn)備要被包裝成一個(gè)NSInvocation對(duì)象, 在這里要先返回一個(gè)方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
// NSMethodSignature : 該selector對(duì)應(yīng)的方法簽名
完整的消息轉(zhuǎn)發(fā) :
給接收者最后一次機(jī)會(huì)把這個(gè)方法處理了, 搞不定就直接程序崩潰!
- (void)forwardInvocation:(NSInvocation *)invocation
// invocation : 封裝了與那條尚未處理的消息相關(guān)的所有細(xì)節(jié)的對(duì)象
在這里能做的比較現(xiàn)實(shí)的事就是 : 在觸發(fā)消息前, 先以某種方式改變消息內(nèi)容, 比如追加另外一個(gè)參數(shù), 或是改變選擇子等等. 實(shí)現(xiàn)此方法時(shí), 如果發(fā)現(xiàn)某調(diào)用操作不應(yīng)該由本類處理, 可以調(diào)用超類的同名方法. 則繼承體系中的每個(gè)類都有機(jī)會(huì)處理該請(qǐng)求, 直到NSObject. 如果NSObject搞不定, 則還會(huì)調(diào)用doesNotRecognizeSelector:來拋出異常, 此時(shí)你就會(huì)在控制臺(tái)看到那熟悉的unrecognized selector sent to instance..

上面這4個(gè)方法均是模板方法,開發(fā)者可以override,由runtime來調(diào)用。最常見的實(shí)現(xiàn)消息轉(zhuǎn)發(fā),就是重寫方法3和4,忽略這個(gè)消息或者代理給其他對(duì)象.
Method Swizzling
被稱為黑魔法的一個(gè)方法, 可以把兩個(gè)方法的實(shí)現(xiàn)互換.
如上文所述, 類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)上, 使得"動(dòng)態(tài)消息派發(fā)系統(tǒng)"能夠據(jù)此找到應(yīng)該調(diào)用的方法. 這些方法均以函數(shù)指針的形式來表示, 這種指針叫做IMP,
<pre> id (*IMP)(id, SEL, ...)</pre>

OC運(yùn)行時(shí)系統(tǒng)提供了幾個(gè)方法能夠用來操作這張表, 動(dòng)態(tài)增加, 刪除, 改變選擇子對(duì)應(yīng)的方法實(shí)現(xiàn), 甚至交換兩個(gè)選擇子所映射到的指針. 如,

如何交換兩個(gè)已經(jīng)寫好的方法實(shí)現(xiàn)?
// 取得方法
Method class_getInstanceMethod(Class aClass, SEL aSelector)
// 交換實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2)
通過Method Swizzling可以為一些完全不知道其具體實(shí)現(xiàn)的黑盒方法增加日志記錄功能, 利于我們調(diào)試程序. 并且我們可以將某些系統(tǒng)類的具體實(shí)現(xiàn)換成我們自己寫的方法, 以達(dá)到某些目的. (例如, 修改主題, 修改字體等等)
KVO原理
KVO的實(shí)現(xiàn)也依賴Runtime. Apple文檔曾簡單提到過KVO的實(shí)現(xiàn)原理 :
Apple的文檔提得不多, 但是大神Mike Ash在很早很早以前就已經(jīng)做過研究, 摘下了KVO神秘的面紗了, 有興趣的可以去查下, 這里不多深究, 只是簡單闡述下原理.
原來當(dāng)你對(duì)一個(gè)對(duì)象進(jìn)行觀察時(shí), 系統(tǒng)會(huì)自動(dòng)新建一個(gè)類繼承自原類, 然后重寫被觀察屬性的setter方法. 然后重寫的setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法前后通知觀察者. 然后把原對(duì)象的isa指針指向這個(gè)新類, 我們知道, 對(duì)象是通過isa指針去查找自己是屬于哪個(gè)類, 并去所在類的方法列表中查找方法的, 所以這個(gè)時(shí)候這個(gè)對(duì)象就自然地變成了新類的實(shí)例對(duì)象.
不僅如此, Apple還重寫了原類的- class方法, 視圖欺騙我們, 這個(gè)類沒有變, 還是原來的那個(gè)類. 只要我們懂得Runtime的原理, 這一切都只是掩耳盜鈴罷了.