1 對象模型圖

- NSObject 的類中定義了實例方法,例如 -(id)init 方法 和 - (void)dealloc 方法。
- NSObject 的元類中定義了類方法,例如 +(id)alloc 方法 和 + (void)load 、+ (void)initialize 方法。
- NSObject 的元類繼承自 NSObject 類,所以 NSObject 類是所有類的根,因此 NSObject 中定義的實例方法可以被所有對象調(diào)用,例如 - (id)init 方法 和 - (void)dealloc 方法。
- NSObject 的元類的 isa 指向自己。
2 實例對象在內(nèi)存中的結(jié)構(gòu)

實例的內(nèi)存結(jié)構(gòu)是由其類決定的,已經(jīng)存在的類,是無法動態(tài)加成員變量的。因為如果類加了成員變量,該類的所有實例,其內(nèi)存結(jié)構(gòu)必須做相應(yīng)的增加,試想一下如果是增加了NSObject類的成員變量,那內(nèi)存中所有的實例都得修改,成本太高。
3 消息發(fā)送和轉(zhuǎn)發(fā)
- 檢測這個 selector 是不是要忽略的。比如 Mac OS X 開發(fā),有了垃圾回收就不理會 retain, release 這些函數(shù)了。
- 檢測這個 target 是不是 nil 對象。ObjC 的特性是允許對一個 nil 對象執(zhí)行任何一個方法不會 Crash,因為會被忽略掉。
- 如果上面兩個都過了,那就開始查找這個類的 IMP,先從 cache 里面找,完了找得到就跳到對應(yīng)的函數(shù)去執(zhí)行。
- 如果 cache 找不到就找一下方法分發(fā)表。
- 如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找,直到找到NSObject類為止。
-
如果還找不到就要開始進(jìn)入動態(tài)方法解析和消息轉(zhuǎn)發(fā)
動態(tài)方法解析和消息轉(zhuǎn)發(fā).png
動態(tài)方法解析
- 重載resolveInstanceMethod:和resolveClassMethod:方法分別添加實例方法實現(xiàn)和類方法實現(xiàn)
- class_addMethod函數(shù)完成向特定類添加特定方法實現(xiàn)
- 如果返回YES,就會重新啟動一次消息發(fā)送過程
#import <Foundation/Foundation.h>
@interface Student : NSObject
+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;
@end
#import "Student.h"
#import <objc/runtime.h>
@implementation Student
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(learnClass:)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(goToSchool:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
+ (void)myClassMethod:(NSString *)string {
NSLog(@"myClassMethod = %@", string);
}
- (void)myInstanceMethod:(NSString *)string {
NSLog(@"myInstanceMethod = %@", string);
}
@end
重定向
- 通過重載- (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對象
- 替換類方法的接受者,需要覆寫 + (id)forwardingTargetForSelector:(SEL)aSelector 方法,并返回類對象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
轉(zhuǎn)發(fā)
- 重寫methodSignatureForSelector:方法,否則會拋異常
- 重寫forwardInvocation:,forwardInvocation:方法就像一個不能識別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對象。或者它也可以象一個運輸站將所有的消息都發(fā)送給同一個接收對象。它可以將一個消息翻譯成另外一個消息,或者簡單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯誤。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
整體流程圖

相關(guān)問題
編譯器如何編譯成objc_msgSend
objc_msgSend是匯編語言,其實在 [objc-msg-x86_64.s] 中包含了多個版本的 objc_msgSend方法,它們是根據(jù)返回值的類型和調(diào)用者的類型分別處理的:
- objc_msgSendSuper:向父類發(fā)消息,返回值類型為 id
- objc_msgSend_fpret:返回值類型為 floating-point,其中包含 objc_msgSend_fp2ret入口處理返回值類型為 long double的情況
- objc_msgSend_stret:返回值為結(jié)構(gòu)體
- objc_msgSendSuper_stret:向父類發(fā)消息,返回值類型為結(jié)構(gòu)體
這也是為什么 objc_msgSend要用匯編語言而不是 OC、C 或 C++ 語言來實現(xiàn),因為單獨一個方法定義滿足不了多種類型返回值,有的方法返回 id,有的返回 int
。考慮到不同類型參數(shù)返回值排列組合映射不同方法簽名(method signature)的問題,那 switch 語句得老長了。。。這些原因可以總結(jié)為 [Calling Convention],也就是說函數(shù)調(diào)用者與被調(diào)用者必須約定好參數(shù)與返回值在不同架構(gòu)處理器上的存取規(guī)則,比如參數(shù)是以何種順序存儲在棧上,或是存儲在哪些寄存器上。除此之外還有其他原因,比如其可變參數(shù)用匯編處理起來最方便,因為找到 IMP 地址后參數(shù)都在棧上。要是用 C++ 傳遞可變參數(shù)那就悲劇了,prologue 機(jī)制會弄亂地址(比如 i386 上為了存儲 ebp
向后移位 4byte),最后還要用 epilogue 打掃戰(zhàn)場。而且匯編程序執(zhí)行效率高,在 Objective-C Runtime 中調(diào)用頻率較高的函數(shù)好多都用匯編寫的。
消息緩存
struct objc_cache {
uintptr_t mask; /* total = mask + 1 */
uintptr_t occupied;
cache_entry *buckets[1];
};
嗯,objc_cache的定義看起來很簡單,它包含了下面三個變量:
1)、mask:可以認(rèn)為是當(dāng)前能達(dá)到的最大index(從0開始的),所以緩存的size(total)是mask+1
2)、occupied:被占用的槽位,因為緩存是以散列表的形式存在的,所以會有空槽,而occupied表示當(dāng)前被占用的數(shù)目
3)、buckets:用數(shù)組表示的hash表,cache_entry類型,每一個cache_entry代表一個方法緩存
(buckets定義在objc_cache的最后,說明這是一個可變長度的數(shù)組)
消息是如何緩存起來的
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
buckets = (cache_entry **)cache->buckets;
for (index = CACHE_HASH(sel, cache->mask);
buckets[index] != NULL;
index = (index+1) & cache->mask)
{
// empty
}
buckets[index] = entry;
//hash的方式
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
方法緩存存在什么地方?
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
我們看到在類的定義里就有cache字段,沒錯,類的所有緩存都存在metaclass上,所以每個類都只有一份方法緩存,而不是每一個類的object都保存一份。
類的方法緩存大小有沒有限制?
/* When _class_slow_grow is non-zero, any given cache is actually grown
* only on the odd-numbered times it becomes full; on the even-numbered
* times, it is simply emptied and re-used. When this flag is zero,
* caches are grown every time. */
static const int _class_slow_grow = 1;
其實不用再看進(jìn)一步的代碼片段,僅從注釋我們就可以看到問題的答案。注釋中說明,當(dāng)_class_slow_grow是非0值的時候,只有當(dāng)方法緩存第奇數(shù)次滿(使用的槽位超過3/4)的時候,方法緩存的大小才會增長(會清空緩存,否則hash值就不對了);當(dāng)?shù)谂紨?shù)次滿的時候,方法緩存會被清空并重新利用。 如果_class_slow_grow值為0,那么每一次方法緩存滿的時候,其大小都會增長。
所以單就問題而言,答案是沒有限制,雖然這個值被設(shè)置為1,方法緩存的大小增速會慢一點,但是確實是沒有上限的。
其他問題:編譯器如何編譯成objc_msgSend,消息Cache機(jī)制,消息轉(zhuǎn)發(fā)機(jī)制,objc_msgSend的各個版本,objc_msgSend的實現(xiàn),跳板機(jī)制
Method Swizzling
- class_replaceMethod 替換類方法的定義,當(dāng)類中沒有想替換的原方法時,該方法會調(diào)用class_addMethod來為該類增加一個新方法,也因為如此,class_replaceMethod在調(diào)用時需要傳入types參數(shù),而method_exchangeImplementations和method_setImplementation卻不需要
- method_exchangeImplementations 交換 2 個方法的實現(xiàn),其內(nèi)部實現(xiàn)相當(dāng)于調(diào)用了 2 次method_setImplementation方法
- method_setImplementation 設(shè)置 1 個方法的實現(xiàn)
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
參考文章
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
http://blog.devtang.com/2013/10/15/objective-c-object-model/
http://tech.meituan.com/DiveIntoMethodCache.html
