Objective-C Runtime
Describes the macOS Objective-C runtime library support functions and data structures.
Overview(概述)
以下是官方文檔中對Runtime給出的定義
The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps.
正式runtime這一個庫給予了Objective-C language動態(tài)的屬性, 所有的OC App都可以直接使用它
You typically don't need to use the Objective-C runtime library directly when programming in Objective-C. This API is useful primarily for developing bridge layers between Objective-C and other languages, or for low-level debugging.
一般programming時不會直接使用到runtime庫, runtime這一功能或者屬性一般用在跨域語言編程, 或者在較底層的debug中
Note
All char * in the runtime API should be considered to have UTF-8 encoding.
在runtime API中所有char類型都是以UTF-8編碼的
以上是文檔中對runtime做的一些簡單介紹
經過之前看過的其他人對runtime的經驗總結和自己的實踐, 目前對Runtime的概念:
消息動態(tài)解析
消息重定向
消息轉發(fā)
動態(tài)解析 在運行時(程序運行中)動態(tài)地:
給類中的已經定義但尚未實現(xiàn)的方法, 動態(tài)地綁定實現(xiàn)方法
給類增加或綁定既未定義也未實現(xiàn)方法, 說簡單就是給類增加方法
文檔中接下來是runtime方法的介紹, 我們在暫停在這里 先對上面幾個概念做一個簡單的說明
在之前必要我們先來看下[receiver message];這句話的實現(xiàn)過程, 也就是消息機制是如何在運作的
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;
每一個NSObject對象都有成員變量列表, 方法列表, 緩存, 接口列表
方法列表中存儲方法的指針(IMP)
緩存中存儲的是曾經被調用的方法
[receiver message];會被轉換成消息發(fā)送的模式:
id objc_msgSend(id self, SEL _cmd, …);
當對象接收到消息時會按照以下順序依次檢查, 在任何一個環(huán)節(jié)如果被響應則結束 否則報錯
-> 對象接收到消息
-> 查看緩存中是否有匹配的方法, 如果有則響應 否則繼續(xù)
-> 查看方法列表中是否有匹配的方法, 如果有則響應 否則繼續(xù)
-> 查看父類中是否有匹配的方法, 如果有則響應 否則繼續(xù)
->進入動態(tài)解析 + (BOOL)resolveInstanceMethod:(SEL)sel, 如果有指定動態(tài)解析方法則響應 否則繼續(xù)
->進入消息重定向 - (id)forwardingTargetForSelector:(SEL)aSelector, 如果有指定消息接收對象則將消息轉由接收對象響應 否則繼續(xù)
->開始消息轉發(fā) - (void)forwardInvocation:(NSInvocation *)anInvocation, 如果有指定轉發(fā)對象則轉發(fā)給該對象響應, 否則拋出異常
再消息轉發(fā)前我們有兩次機會來修改或者設定對象方法的實現(xiàn)
下面再逐一說說
動態(tài)解析
假如我們有一個ClassA, 在它的頭文件中我們定義了一個- (void)printName;方法, 但我們并沒有在.m文件中讓它實現(xiàn)
如果我們直接在Viewcontroller中使用[[ClassA new] printName];程序不會出錯 但也不會做任何事情
我們可以重寫resolveInstanceMethod:或者resolveClassMethod:方法, 在這里我們給printName方法添加實現(xiàn)
/**
要動態(tài)綁定的方法
@param self 要綁定方法的對象
@param _cmd 方法信息
*/
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"SEL: %s method is added", sel_getName(_cmd));
NSLog(@"Name: Jackey");
}
/**
動態(tài)綁定和解析方法
@param sel 方法信息
@return 是否已經處理該方法
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"SEL: %s method does not exist", sel_getName(sel));
if (sel == @selector(printName)) {
class_addMethod ([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
這樣我們再運行[[ClassA new] printName];就會輸出Name: Jackey
重定向:
如果經過動態(tài)解析后, 消息還沒有被響應就會進入到重定向階段
我們可以重寫- (id)forwardingTargetForSelector:(SEL)aSelector將消息重定向給可以響應的對象
/**
方法重定向
@param aSelector 方法信息
@return 返回重定向后要相應的對象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"Current class can't response to SEL: %s", sel_getName(aSelector));
if (aSelector == @selector(printRightName)) {
NSLog(@"Forward to target: %@", [ClassB class]);
return [ClassB new];
}
return [super forwardingTargetForSelector:aSelector];
}
最后如果前面都沒有處理就會進入到消息轉發(fā), 我們可以通過重寫
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
/**
轉發(fā)前, 獲取方法簽名
@param selector 方法信息
@return NSInvocation消息對象
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:@"set"].location == 0){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
else{
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
}
/**
轉發(fā)
@param anInvocation 消息對象
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"No class can't response to SEL: %s", sel_getName([anInvocation selector]));
ClassC *c = [ClassC new];
if ([c respondsToSelector:[anInvocation selector]]) {
NSLog(@"method apply deliver to %@", [ClassC class]);
[anInvocation invokeWithTarget:c];
}
else {
[super forwardInvocation:anInvocation];
}
}
消息的轉發(fā)彌補了OC不能多繼承的問題
最后我們來看下Method Swizzling
我們可以直接修改方法的指針, 讓一個方法名指向其他的方法實現(xiàn)
Method ori_method = class_getInstanceMethod([ClassB class], @selector(printRightName));
Method my_method = class_getInstanceMethod([ClassC class], @selector(printFamilyName));
method_exchangeImplementations(ori_method, my_method);
[[ClassB new] printRightName];
使用method_exchangeImplementation交換了兩個對象方法的指針
printRightName執(zhí)行的實際是printFamilyName