什么是Runtime
Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語(yǔ)言 API,其為 iOS 內(nèi)部的核心之一,我們平時(shí)編寫(xiě)的 OC 代碼,底層都是基于它來(lái)實(shí)現(xiàn)的
- 我們寫(xiě)的代碼在程序運(yùn)行過(guò)程中都會(huì)被轉(zhuǎn)化成runtime的C代碼執(zhí)行,例如[target doSomething];會(huì)被轉(zhuǎn)化成objc_msgSend(target, @selector(doSomething));。
- OC中一切都被設(shè)計(jì)成了對(duì)象,我們都知道一個(gè)類(lèi)被初始化成一個(gè)實(shí)例,這個(gè)實(shí)例是一個(gè)對(duì)象。實(shí)際上一個(gè)類(lèi)本質(zhì)上也是一個(gè)對(duì)象,在runtime中用結(jié)構(gòu)體表示。
// 通過(guò)類(lèi)名獲取類(lèi)
Class catClass = objc_getClass("Cat");
//注意Class實(shí)際上也是對(duì)象,所以同樣能夠接受消息,向Class發(fā)送alloc消息
Cat *cat = objc_msgSend(catClass, @selector(alloc));
//發(fā)送init消息給Cat實(shí)例cat
cat = objc_msgSend(cat, @selector(init));
//發(fā)送eat消息給cat,即調(diào)用eat方法
objc_msgSend(cat, @selector(eat));
//匯總消息傳遞過(guò)程
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Cat"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));
/// 描述類(lèi)中的一個(gè)方法
typedef struct objc_method *Method;
/// 實(shí)例變量
typedef struct objc_ivar *Ivar;
/// 類(lèi)別Category
typedef struct objc_category *Category;
/// 類(lèi)中聲明的屬性
typedef struct objc_property *objc_property_t;
//類(lèi)在runtime中的表示
struct objc_class { Class isa;//指針,顧名思義,表示是一個(gè)什么,
//實(shí)例的isa指向類(lèi)對(duì)象,類(lèi)對(duì)象的isa指向元類(lèi)
#if !__OBJC2__
Class super_class;//指向父類(lèi)
const char *name;//類(lèi)名
long version;
long info;
long instance_size struct objc_ivar_list *ivars //成員變量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//緩存
//一種優(yōu)化,調(diào)用過(guò)的方法存入緩存列表,下次調(diào)用先找緩存
struct objc_protocol_list *protocols //協(xié)議列表
#endif
}
OBJC2_UNAVAILABLE;
objc_ivar_lis結(jié)構(gòu)體用來(lái)存儲(chǔ)成員變量的列表,而 objc_ivar則是存儲(chǔ)了單個(gè)成員變量的信息;同理,objc_method_list 結(jié)構(gòu)體存儲(chǔ)著方法數(shù)組的列表,而單個(gè)方法的信息則由 objc_method結(jié)構(gòu)體存儲(chǔ)。
值得注意的時(shí),objc_class中也有一個(gè) isa 指針,這說(shuō)明 Objc 類(lèi)本身也是一個(gè)對(duì)象。為了處理類(lèi)和對(duì)象的關(guān)系,Runtime 庫(kù)創(chuàng)建了一種叫做 Meta Class(元類(lèi)) 的東西,類(lèi)對(duì)象所屬的類(lèi)就叫做元類(lèi)。Meta Class 表述了類(lèi)對(duì)象本身所具備的元數(shù)據(jù)。
我們所熟悉的類(lèi)方法,就源自于 Meta Class。我們可以理解為類(lèi)方法就是類(lèi)對(duì)象的實(shí)例方法。每個(gè)類(lèi)僅有一個(gè)類(lèi)對(duì)象,而每個(gè)類(lèi)對(duì)象僅有一個(gè)與之相關(guān)的元類(lèi)。
當(dāng)你發(fā)出一個(gè)類(lèi)似 NSObject alloc 的消息時(shí),實(shí)際上,這個(gè)消息被發(fā)送給了一個(gè)類(lèi)對(duì)象(Class Object),這個(gè)類(lèi)對(duì)象必須是一個(gè)元類(lèi)的實(shí)例,而這個(gè)元類(lèi)同時(shí)也是一個(gè)根元類(lèi)(Root Meta Class)的實(shí)例。所有元類(lèi)的 isa 指針最終都指向根元類(lèi)。
所以當(dāng) [NSObject alloc]這條消息發(fā)送給類(lèi)對(duì)象的時(shí)候,運(yùn)行時(shí)代碼 objc_msgSend() 會(huì)去它元類(lèi)中查找能夠響應(yīng)消息的方法實(shí)現(xiàn),如果找到了,就會(huì)對(duì)這個(gè)類(lèi)對(duì)象執(zhí)行方法調(diào)用。

上圖實(shí)現(xiàn)是 super_class 指針,虛線(xiàn)時(shí) isa 指針。而根元類(lèi)的父類(lèi)是 NSObject,isa指向了自己。而 NSObject 沒(méi)有父類(lèi)。最后 objc_class中還有一個(gè) objc_cache,緩存,它的作用很重要,后面會(huì)提到。
獲取列表
我們可以通過(guò)runtime的一系列方法獲取類(lèi)的一些信息(包括屬性列表,方法列表,成員變量列表,和遵循的協(xié)議列表)。
unsigned int count;
//獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//獲取成員變量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//獲取協(xié)議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
消息(方法)調(diào)用

1.首先檢測(cè)這個(gè) selector是不是要忽略。比如 Mac OS X 開(kāi)發(fā),有了垃圾回收就不理會(huì) retain,release 這些函數(shù)。
2.檢測(cè)這個(gè) selector的 target 是不是 nil,Objc 允許我們對(duì)一個(gè) nil 對(duì)象執(zhí)行任何方法不會(huì) Crash,因?yàn)檫\(yùn)行時(shí)會(huì)被忽略掉。
3.在相應(yīng)操作的對(duì)象中的緩存方法列表中找調(diào)用的方法,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)并執(zhí)行。
4.如果沒(méi)找到,在相應(yīng)操作的對(duì)象中的方法列表中找調(diào)用的方法,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)執(zhí)行
5.如果沒(méi)找到,去父類(lèi)指針?biāo)赶虻膶?duì)象中執(zhí)行3,4.
6.以此類(lèi)推,如果一直到根類(lèi)還沒(méi)找到,轉(zhuǎn)向攔截調(diào)用。
7.如果沒(méi)有重寫(xiě)攔截調(diào)用的方法,程序報(bào)錯(cuò)。
- 重寫(xiě)父類(lèi)的方法,并沒(méi)有覆蓋掉父類(lèi)的方法,只是在當(dāng)前類(lèi)對(duì)象中找到了這個(gè)方法后就不會(huì)再去父類(lèi)中找了。
- 如果想調(diào)用已經(jīng)重寫(xiě)過(guò)的方法的父類(lèi)的實(shí)現(xiàn),只需使用super這個(gè)編譯器標(biāo)識(shí),它會(huì)在運(yùn)行時(shí)跳過(guò)在當(dāng)前的類(lèi)對(duì)象中尋找方法的過(guò)程。
1)攔截調(diào)用
在方法調(diào)用中說(shuō)到了,如果沒(méi)有找到方法就會(huì)轉(zhuǎn)向攔截調(diào)用。那么什么是攔截調(diào)用呢。攔截調(diào)用就是,在找不到調(diào)用的方法程序崩潰之前,你有機(jī)會(huì)通過(guò)重寫(xiě)NSObject
的四個(gè)方法來(lái)處理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后兩個(gè)方法需要轉(zhuǎn)發(fā)到其他的類(lèi)處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- 第一個(gè)方法是當(dāng)你調(diào)用一個(gè)不存在的類(lèi)方法的時(shí)候,會(huì)調(diào)用這個(gè)方法,默認(rèn)返回NO,你可以加上自己的處理然后返回YES。
- 第二個(gè)方法和第一個(gè)方法相似,只不過(guò)處理的是實(shí)例方法。
- 第三個(gè)方法是將你調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類(lèi),只需要你返回一個(gè)有這個(gè)方法的target。
- 第四個(gè)方法是將你調(diào)用的不存在的方法打包成NSInvocation傳給你。做完你自己的處理后,調(diào)用invokeWithTarget:方法讓某個(gè)target觸發(fā)這個(gè)方法。
2)動(dòng)態(tài)添加方法
當(dāng)調(diào)用一個(gè)未實(shí)現(xiàn)的方法,或者說(shuō)發(fā)送未知的消息給接收者時(shí)候,消息的接受者會(huì)調(diào)用resolveInstanceMethod
//An Objective-C method is simply a C function that take at least two arguments—self and _cmd.
void run(id self, SEL _cmd, NSNumber *number){ NSLog(@"run for %@", number);}
//收到run:消息時(shí)候,為該類(lèi)添加一個(gè)方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == NSSelectorFromString(@"run:")){
class_addMethod(self, @selector(run:), run, "v@:*");
return YES;
}
return [super resolveInstanceMethod:sel];}
//另外針對(duì)類(lèi)方法的為 resolveClassMethod
其中class_addMethod的四個(gè)參數(shù)分別是:
- Class cls 給哪個(gè)類(lèi)添加方法,本例中是self
- SEL name 添加的方法,本例中是重寫(xiě)的攔截調(diào)用傳進(jìn)來(lái)的selector。
- IMP imp 方法的實(shí)現(xiàn),C方法的方法實(shí)現(xiàn)可以直接獲得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;獲得方法的實(shí)現(xiàn)。
- "v@:*"方法的簽名,代表有一個(gè)參數(shù)的方法。
3)消息轉(zhuǎn)發(fā)
// 第一步,消息接收者沒(méi)有找到對(duì)應(yīng)的方法時(shí)候,會(huì)先調(diào)用此方法,可在此方法實(shí)現(xiàn)中動(dòng)態(tài)添加新的方法
// 返回YES表示相應(yīng)selector的實(shí)現(xiàn)已經(jīng)被找到,或者添加新方法到了類(lèi)中,否則返回NO
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;
}
// 第二步, 如果第一步的返回NO或者直接返回了YES而沒(méi)有添加方法,該方法被調(diào)用
// 在這個(gè)方法中,我們可以指定一個(gè)可以返回一個(gè)可以響應(yīng)該方法的對(duì)象, 注意如果返回self就會(huì)死循環(huán)
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;
}
// 第三步, 如果forwardingTargetForSelector:返回了nil,則該方法會(huì)被調(diào)用,
系統(tǒng)會(huì)詢(xún)問(wèn)我們要一個(gè)合法的『類(lèi)型編碼(Type Encoding)』
// 若返回 nil,則不會(huì)進(jìn)入下一步,而是無(wú)法處理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 當(dāng)實(shí)現(xiàn)了此方法后,-doesNotRecognizeSelector: 將不會(huì)被調(diào)用
// 在這里進(jìn)行消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 在這里可以改變方法選擇器
[anInvocation setSelector:@selector(unknown)];
// 改變方法選擇器后,需要指定消息的接收者
[anInvocation invokeWithTarget:self];
}
- (void)unknown {
NSLog(@"unkown method.......");
}
// 如果沒(méi)有實(shí)現(xiàn)消息轉(zhuǎn)發(fā) forwardInvocation 則調(diào)用此方法
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"unresolved method :%@", NSStringFromSelector(aSelector));
}
重定向
消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,Runtime 系統(tǒng)允許我們替換消息的接收者為其他對(duì)象。通過(guò) - (id)forwardingTargetForSelector:(SEL)aSelector
方法。
- (id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];}
如果此方法返回 nil 或者 self,則會(huì)計(jì)入消息轉(zhuǎn)發(fā)機(jī)制(forwardInvocation:),否則將向返回的對(duì)象重新發(fā)送消息。
轉(zhuǎn)發(fā)
當(dāng)動(dòng)態(tài)方法解析不做處理返回 NO時(shí),則會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制。這時(shí) forwardInvocation:
方法會(huì)被執(zhí)行,我們可以重寫(xiě)這個(gè)方法來(lái)自定義我們的轉(zhuǎn)發(fā)邏輯:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([someOtherObject respondsToSelector: [anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else [super forwardInvocation:anInvocation];}
唯一參數(shù)是個(gè) NSInvocation類(lèi)型的對(duì)象,該對(duì)象封裝了原始的消息和消息的參數(shù)。我們可以實(shí)現(xiàn) forwardInvocation:方法來(lái)對(duì)不能處理的消息做一些處理。也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象處理,而不拋出錯(cuò)誤。
轉(zhuǎn)發(fā)與繼承
雖然轉(zhuǎn)發(fā)可以實(shí)現(xiàn)繼承的功能,但是 NSObject 還是必須表面上很?chē)?yán)謹(jǐn),像 respondsToSelector: 和 isKindOfClass:這類(lèi)方法只會(huì)考慮繼承體系,不會(huì)考慮轉(zhuǎn)發(fā)鏈。
如果上圖中的 Warrior對(duì)象被問(wèn)到是否能響應(yīng) negotiate消息:
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) ...
回答當(dāng)然是 NO, 盡管它能接受 negotiate消息而不報(bào)錯(cuò),因?yàn)樗哭D(zhuǎn)發(fā)消息給 Diplomat類(lèi)響應(yīng)消息。如果你就是想要讓別人以為 Warrior 繼承到了 Diplomat 的 negotiate方法,你得重新實(shí)現(xiàn) respondsToSelector:和 isKindOfClass:來(lái)加入你的轉(zhuǎn)發(fā)算法:
- (BOOL)respondsToSelector:(SEL)aSelector{
if ( [super respondsToSelector:aSelector] )
return YES;
else { /* Here, test whether the aSelector message can
* * be forwarded to another object and whether that * *
object can respond to it. Return YES if it can. */ }
return NO;
}
除了 respondsToSelector:和 isKindOfClass:之外,instancesRespondToSelector: 中也應(yīng)該寫(xiě)一份轉(zhuǎn)發(fā)算法。如果使用了協(xié)議,conformsToProtocol:同樣也要加入到這一行列中。
如果一個(gè)對(duì)象想要轉(zhuǎn)發(fā)它接受的任何遠(yuǎn)程消息,它得給出一個(gè)方法標(biāo)簽來(lái)返回準(zhǔn)確的方法描述 methodSignatureForSelector:,這個(gè)方法會(huì)最終響應(yīng)被轉(zhuǎn)發(fā)的消息。從而生成一個(gè)確定的 NSInvocation對(duì)象描述消息和消息參數(shù)。這個(gè)方法最終響應(yīng)被轉(zhuǎn)發(fā)的消息。它需要像下面這樣實(shí)現(xiàn):
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
4)關(guān)聯(lián)對(duì)象
使用關(guān)聯(lián),我們可以不用修改類(lèi)的定義而為其對(duì)象增加存儲(chǔ)空間。這在我們無(wú)法訪(fǎng)問(wèn)到類(lèi)的源碼的時(shí)候或者是考慮到二進(jìn)制兼容性的時(shí)候是非常有用。 關(guān)聯(lián)是基于關(guān)鍵字的,因此,我們可以為任何對(duì)象增加任意多的關(guān)聯(lián),每個(gè)都使用不同的關(guān)鍵字即可。關(guān)聯(lián)是可以保證被關(guān)聯(lián)的對(duì)象在關(guān)聯(lián)對(duì)象的整個(gè)生命周期都是可用的(在垃圾自動(dòng)回收環(huán)境下也不會(huì)導(dǎo)致資源不可回收)。
//首先定義一個(gè)全局變量,用它的地址作為關(guān)聯(lián)對(duì)象的key
static char associatedObjectKey;
//設(shè)置關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串屬性", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//獲取關(guān)聯(lián)對(duì)象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
objc_setAssociatedObject的四個(gè)參數(shù):
- id object給誰(shuí)設(shè)置關(guān)聯(lián)對(duì)象。
- const void *key關(guān)聯(lián)對(duì)象唯一的key,獲取時(shí)會(huì)用到。
- id value關(guān)聯(lián)對(duì)象。
- objc_AssociationPolicy關(guān)聯(lián)策略,有以下幾種策略:
enum { OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403
};
- 關(guān)鍵字是一個(gè)void類(lèi)型的指針。每一個(gè)關(guān)聯(lián)的關(guān)鍵字必須是唯一的。通常都是會(huì)采用靜態(tài)變量來(lái)作為關(guān)鍵字。
- 關(guān)聯(lián)策略表明了相關(guān)的對(duì)象是通過(guò)賦值,保留引用還是復(fù)制的方式進(jìn)行關(guān)聯(lián)的;還有這種關(guān)聯(lián)是原子的還是非原子的。這里的關(guān)聯(lián)策略和聲明屬性時(shí)的很類(lèi)似。這種關(guān)聯(lián)策略是通過(guò)使用預(yù)先定義好的常量來(lái)表示的。
5)方法交換
方法交換,顧名思義,就是將兩個(gè)方法的實(shí)現(xiàn)交換。例如,將A方法和B方法交換,調(diào)用A方法的時(shí)候,就會(huì)執(zhí)行B方法中的代碼,反之亦然。
- (void)viewDidLoad {
[super viewDidLoad];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL simpleness_Sel = @selector(simpleness_jsonToModle);
SEL complex_Sel = @selector(complex_dicToObject);
//兩個(gè)方法的Method
Method simpleMethod = class_getInstanceMethod([self class], simpleness_Sel);
Method complexMethod = class_getInstanceMethod([self class], complex_Sel);
//首先動(dòng)態(tài)添加方法,實(shí)現(xiàn)是被交換的方法,返回值表示添加成功還是失敗
BOOL isAdd = class_addMethod([self class], simpleness_Sel, method_getImplementation(complexMethod), method_getTypeEncoding(complexMethod));
if (isAdd) {
//如果成功,說(shuō)明類(lèi)中不存在這個(gè)方法的實(shí)現(xiàn)
//將被交換方法的實(shí)現(xiàn)替換到這個(gè)并不存在的實(shí)現(xiàn)
class_replaceMethod([self class], simpleness_Sel, method_getImplementation(simpleMethod), method_getTypeEncoding(simpleMethod));
}else{
//否則,交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(simpleMethod, complexMethod);
}
});
//方法交換后 這里實(shí)際調(diào)的是complex_dicToObject 的實(shí)現(xiàn)
[self simpleness_jsonToModle];
// [self complex_dicToObject];
}
參考文檔
如果你都看到這里了,請(qǐng)給我點(diǎn)個(gè)贊吧,你的點(diǎn)贊是我裝逼(~ 啊不,堅(jiān)持原創(chuàng))的不竭動(dòng)力。
另外.....
我的愿望是.......
世界和平.........