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動態的屬性, 所有的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的概念:
消息動態解析
消息重定向
消息轉發
動態解析 在運行時(程序運行中)動態地:
給類中的已經定義但尚未實現的方法, 動態地綁定實現方法
給類增加或綁定既未定義也未實現方法, 說簡單就是給類增加方法
文檔中接下來是runtime方法的介紹, 我們在暫停在這里 先對上面幾個概念做一個簡單的說明
在之前必要我們先來看下[receiver message];這句話的實現過程, 也就是消息機制是如何在運作的
1 struct objc_class { 2 Class isa OBJC_ISA_AVAILABILITY; 3 #if !__OBJC2__ 4 Class super_class OBJC2_UNAVAILABLE; 5 const char *name OBJC2_UNAVAILABLE; 6 long version OBJC2_UNAVAILABLE; 7 long info OBJC2_UNAVAILABLE; 8 long instance_size OBJC2_UNAVAILABLE; 9 struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; 10 struct objc_method_list **methodLists OBJC2_UNAVAILABLE; 11 struct objc_cache *cache OBJC2_UNAVAILABLE; 12 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; 13 #endif 14 } OBJC2_UNAVAILABLE;
每一個NSObject對象都有成員變量列表, 方法列表, 緩存, 接口列表
方法列表中存儲方法的指針(IMP)
緩存中存儲的是曾經被調用的方法
[receiver message];會被轉換成消息發送的模式:
id objc_msgSend(id self, SEL _cmd, …);
當對象接收到消息時會按照以下順序依次檢查, 在任何一個環節如果被響應則結束 否則報錯
-> 對象接收到消息
-> 查看緩存中是否有匹配的方法, 如果有則響應 否則繼續
-> 查看方法列表中是否有匹配的方法, 如果有則響應 否則繼續
-> 查看父類中是否有匹配的方法, 如果有則響應 否則繼續
->進入動態解析 + (BOOL)resolveInstanceMethod:(SEL)sel, 如果有指定動態解析方法則響應 否則繼續
->進入消息重定向 - (id)forwardingTargetForSelector:(SEL)aSelector, 如果有指定消息接收對象則將消息轉由接收對象響應 否則繼續
->開始消息轉發 - (void)forwardInvocation:(NSInvocation *)anInvocation, 如果有指定轉發對象則轉發給該對象響應, 否則拋出異常
再消息轉發前我們有兩次機會來修改或者設定對象方法的實現
下面再逐一說說
動態解析
假如我們有一個ClassA, 在它的頭文件中我們定義了一個- (void)printName;方法, 但我們並沒有在.m文件中讓它實現
如果我們直接在Viewcontroller中使用[[ClassA new] printName];程序不會出錯 但也不會做任何事情
我們可以重寫resolveInstanceMethod:或者resolveClassMethod:方法, 在這里我們給printName方法添加實現
1 /** 2 要動態綁定的方法 3 4 @param self 要綁定方法的對象 5 @param _cmd 方法信息 6 */ 7 void dynamicMethodIMP(id self, SEL _cmd) { 8 9 NSLog(@"SEL: %s method is added", sel_getName(_cmd)); 10 NSLog(@"Name: Jackey"); 11 } 12 13 14 /** 15 動態綁定和解析方法 16 17 @param sel 方法信息 18 @return 是否已經處理該方法 19 */ 20 + (BOOL)resolveInstanceMethod:(SEL)sel { 21 22 NSLog(@"SEL: %s method does not exist", sel_getName(sel)); 23 24 if (sel == @selector(printName)) { 25 26 class_addMethod ([self class], sel, (IMP) dynamicMethodIMP, "v@:"); 27 return YES; 28 } 29 30 return [super resolveInstanceMethod:sel]; 31 }
這樣我們再運行[[ClassA new] printName];就會輸出Name: Jackey
重定向:
如果經過動態解析后, 消息還沒有被響應就會進入到重定向階段
我們可以重寫- (id)forwardingTargetForSelector:(SEL)aSelector將消息重定向給可以響應的對象
1 /** 2 方法重定向 3 4 @param aSelector 方法信息 5 @return 返回重定向后要相應的對象 6 */ 7 - (id)forwardingTargetForSelector:(SEL)aSelector { 8 9 NSLog(@"Current class can't response to SEL: %s", sel_getName(aSelector)); 10 11 if (aSelector == @selector(printRightName)) { 12 13 NSLog(@"Forward to target: %@", [ClassB class]); 14 return [ClassB new]; 15 } 16 17 return [super forwardingTargetForSelector:aSelector]; 18 19 }
最后如果前面都沒有處理就會進入到消息轉發, 我們可以通過重寫
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
來自定義
1 /** 2 轉發前, 獲取方法簽名 3 4 @param selector 方法信息 5 @return NSInvocation消息對象 6 */ 7 - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { 8 9 NSString *sel = NSStringFromSelector(selector); 10 if ([sel rangeOfString:@"set"].location == 0){ 11 12 return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; 13 } 14 else{ 15 16 return [NSMethodSignature signatureWithObjCTypes:"@@:"]; 17 } 18 } 19 20 /** 21 轉發 22 23 @param anInvocation 消息對象 24 */ 25 - (void)forwardInvocation:(NSInvocation *)anInvocation { 26 27 NSLog(@"No class can't response to SEL: %s", sel_getName([anInvocation selector])); 28 29 ClassC *c = [ClassC new]; 30 if ([c respondsToSelector:[anInvocation selector]]) { 31 32 NSLog(@"method apply deliver to %@", [ClassC class]); 33 [anInvocation invokeWithTarget:c]; 34 } 35 36 else { 37 38 [super forwardInvocation:anInvocation]; 39 } 40 }
消息的轉發彌補了OC不能多繼承的問題
最后我們來看下Method Swizzling
我們可以直接修改方法的指針, 讓一個方法名指向其他的方法實現
1 Method ori_method = class_getInstanceMethod([ClassB class], @selector(printRightName)); 2 Method my_method = class_getInstanceMethod([ClassC class], @selector(printFamilyName)); 3 4 method_exchangeImplementations(ori_method, my_method); 5 6 [[ClassB new] printRightName];
使用method_exchangeImplementation交換了兩個對象方法的指針
printRightName執行的實際是printFamilyName