前言
運作時API的應用:
- 路由的實作(接口控制app跳任意界面)
- 擷取修改對象的成員屬性
- 動态添加/交換方法的實作
- 屬性關聯
- iOS 間接實作多繼承的方式:消息轉發、類别、delegate和protocol(委托協助主體完成操作任務,将需要定制化的操作預留給委托對象來自定義實作 與block類似) 。
I runtime的作用
因為objective-c是一門動态語言,也就是說隻有編譯器是不夠的,還需要一個運作時系統(runtime system)來執行編譯後的代碼。
runtime簡稱運作時,其中最主要的就是消息機制。
對于編譯期語言,會在編譯的時候決定調用哪個函數。對于OC的函數,是動态調用的,在編譯的時候并不能決定真正調用哪個函數,隻有在運作時才會根據函數的名稱找到對應的函數來調用。
1.1 消息發送的步驟
messages aren’t bound to method implementations until Runtime。(消息直到運作時才會與方法實作進行綁定)
objc_msgSend 是當方法的實作被調用後才會傳回資料。
- 當向someObject發送消息時,先在本類中的方法緩存清單中進行查找
- 如果找不到就在本類中的法清單中進行查找
- 如果沒找到,就去父類中進行查找。
- 如果沒找到,runtime system并不會立即報錯使程式崩潰,而是依次執行消息轉發。
檢查是否有動态添加對應的方法->檢查是否有其他對象實作了對應的方法(快速轉發給其他對象處理)->(标準消息轉發)
- 動态方法解析 :
向目前類發送 resolveInstanceMethod: 信号,檢查是否動态向該類添加了方法。
- 快速消息轉發: 擷取轉發對象,
。若該方法傳回值對象非nil或非self,那麼就進行消息的正常轉發。檢查該類是否實作了 forwardingTargetForSelector: 方法,若實作了則調用這個方法
- 标準消息轉發:擷取方法簽名,
。傳回值非空則通過runtime發送methodSignatureForSelector:消息擷取Selector對應的方法簽名
轉發消息,傳回值為空則向目前對象發送forwardInvocation:
消息,程式崩潰退出。doesNotRecognizeSelector:
1.2 Runtime API
- 通過對 Runtime 庫函數的直接調用
runtime源碼:github.com/opensource-…opensource.apple.com/source/objc…其中主要使用的函數定義在message.h和runtime.h這兩個檔案中。
- 通過 Foundation 架構的 NSObject 類定義的方法
Cocoa 程式中絕大部分類都是 NSObject 類的子類,是以都繼承了 NSObject 的行為。(NSProxy 類是個例外,它是個抽象超類)
- class方法傳回對象的類;
- isKindOfClass: 和 -isMemberOfClass: 方法檢查對象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者目前類的成員變量);
- respondsToSelector: 檢查對象能否響應指定的消息;
-
檢查對象是否實作了指定協定類的方法;conformsToProtocol:
-
傳回指定方法實作的位址。methodForSelector:
1.3 了解instance、class object、metaclass的關系
一個執行個體對象
struct objc_object
的isa指針指向它的
struct objc_class
類對象,類對象的isa指針指向它的元類;
super_class
指針指向了父類的
類對象
,而
元類
的
super_class
指針指向了父類的
元類
。
II 消息轉發
2.1 消息處理(動态地添加一個方法實作)
當在相應的類以及父類中找不到類方法實作時會執行
+resolveInstanceMethod:
這。該方法如果在類中不被重寫的話,預設傳回NO。如果傳回NO就表明不做任何處理,走下一步。
如果傳回YES的話,就說明在該方法中對這個找不到實作的方法進行了處理。
在
+resolveInstanceMethod:
方法中,我們可以為找不到實作的SEL動态地添加一個方法實作,添加完畢後,就會執行我們添加的方法實作。這樣,當一個類調用不存在的方法時,就不會崩潰了。具體做法如下所示:
//運作時方法攔截
- (void)dynamicAddMethod: (NSString *) value {
NSLog(@"OC替換的方法:%@", value);
}
/**
沒有找到SEL的IMP實作時會執行下方的方法
@param sel 目前對象調用并且找不到IMP的SEL
@return 找到其他的執行方法,并傳回yes
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO; //當傳回NO時,會接着執行forwordingTargetForSelector:方法,
[KNRuntimeKit addMethod:[self class] method:sel method:@selector(dynamicAddMethod:)];//動态地添加一個方法實作
return YES;
}
2.2 消息快速轉發
如果不對上述消息進行處理的話,也就是+resolveInstanceMethod:傳回NO時,會走下一步消息轉發,即
-forwardingTargetForSelector:
。
該方法會傳回一個類的對象,這個類的對象有SEL對應的實作。當調用這個找不到的方法時,就會被轉發到SecondClass中去進行處理。
/**
将目前對象不存在的SEL傳給其他存在該SEL的對象
@param aSelector 目前類中不存在的SEL
@return 存在該SEL的對象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self;//當該方法傳回self或者nil, 說明不對相應的方法進行轉發,那麼就進行消息的正常轉發。
return [SecondClass new]; //讓SecondClass中相應的SEL去執行該方法
例子: 教師執行手術
//Returns the object to which unrecognized messages should first be directed.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
Doctor *doctor = [[Doctor alloc]init];
if ([doctor respondsToSelector:aSelector]) {
return doctor;
}
return nil;
}
這個方式的好處是隐藏了我要轉發的消息,沒有類目那麼清晰。
2.3 消息正常轉發
如果不将消息轉發給其他類的對象,那麼就隻能自己進行處理了。
如果上述方法傳回nil或者self的話,會執行
-methodSignatureForSelector:
方法來擷取方法的參數以及傳回資料類型(方法簽名)。
傳回值非空則通過forwardInvocation:轉發消息,傳回值為空則向目前對象發送
doesNotRecognizeSelector:
消息,程式崩潰,報出找不到相應的方法實作的崩潰資訊。
//在+resolveInstanceMethod:傳回NO時就會執行下方的方法,下方也是将該方法轉發給SecondClass。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
//查找父類的方法簽名
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if(signature == nil) {
// signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
signature = [someObj methodSignatureForSelector:aSelector]; //傳回指定方法簽名
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SecondClass * forwardClass = [SecondClass new];
SEL sel = invocation.selector;
if ([forwardClass respondsToSelector:sel]) {
[invocation invokeWithTarget:forwardClass];
} else {
[self
2.4 兩種消息轉發方式的比較
- 快速消息轉發:簡單、快速、但僅能轉發給一個對象。
- 标準消息轉發:稍複雜、較慢、但轉發操作實作可控,可以實作多對象轉發
III 編譯器指令 @encode()
蘋果的 Objective-C 運作時庫内部利用類型編碼幫助加快消息分發。
ocrtTypeEncodings
NSValue 的
+valueWithBytes:objCType:
第二個參數需要用
@encode()
指令來建立一種内部表示的字元串 。
@encode(int) → i
+ (NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;
+ (NSValue *)value:(const void *)value withObjCType:(const char
3.1 Objective-C type encodings
Code | Meaning |
c | A char |
i | An int |
s | A short |
l | A long ,l is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
void)testtypeEncodings{
NSLog(@"int : %s", @encode(int));
NSLog(@"float : %s", @encode(float));
NSLog(@"float * : %s", @encode(float*));//指針的标準編碼是加一個前置的 ^
NSLog(@"char : %s", @encode(char));
NSLog(@"char * : %s", @encode(char *));//char * 擁有自己的編碼 *,因為 C 的字元串被認為是一個實體,而不是指針。
NSLog(@"BOOL : %s", @encode(BOOL));//BOOL 是 c,而不是i。原因是 char 比 int 小。BOOL 更确切地說是 signed char 。
NSLog(@"void : %s", @encode(void));
NSLog(@"void * : %s", @encode(void *));
NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));
int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(intArray)));
float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(floatArray)));
typedef struct _struct {
short a;
long long b;
unsigned long long c;
} Struct;
NSLog(@"struct : %s", @encode(typeof(Struct)));
}
結果:
2022-06-14 11:18:06.069425+0800 SDKSample[9300:3648699] int : i
2022-06-14 11:18:06.069517+0800 SDKSample[9300:3648699] float : f
2022-06-14 11:18:06.069559+0800 SDKSample[9300:3648699] float * : ^f
2022-06-14 11:18:06.069597+0800 SDKSample[9300:3648699] char : c
2022-06-14 11:18:06.069634+0800 SDKSample[9300:3648699] char * : *
2022-06-14 11:18:06.069669+0800 SDKSample[9300:3648699] BOOL : B
2022-06-14 11:18:06.069706+0800 SDKSample[9300:3648699] void : v
2022-06-14 11:18:06.070191+0800 SDKSample[9300:3648699] void * : ^v
2022-06-14 11:18:06.070230+0800 SDKSample[9300:3648699] NSObject * : @
2022-06-14 11:18:06.070267+0800 SDKSample[9300:3648699] NSObject : {NSObject=#}
2022-06-14 11:18:06.070303+0800 SDKSample[9300:3648699] [NSObject] : #
2022-06-14 11:18:06.070339+0800 SDKSample[9300:3648699] NSError ** : ^@
2022-06-14 11:18:06.070374+0800 SDKSample[9300:3648699] int[] : [5i]
2022-06-14 11:18:06.070870+0800 SDKSample[9300:3648699] float[] : [3f]
2022-06-14 11:18:06.072298+0800 SDKSample[9300:3648699] struct : {_struct=sqQ}
3.2 method encodings
Code | Meaning |
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
帶inout 的參數表明它在發消息時對象即可傳入又可傳出,将參數特别标注為 in 或 out,程式将避免一些來回的開銷。
增加一個 bycopy 修飾符以保證發送了一份完整的拷貝
IV 運作時API應用
- 路由的實作(接口控制app跳任意界面)
- 擷取修改對象的成員屬性
- 動态添加/交換方法的實作
- 屬性關聯
- iOS 間接實作多繼承的方式:消息轉發、類别、delegate和protocol(委托協助主體完成操作任務,将需要定制化的操作預留給委托對象來自定義實作 與block類似) 。
- NSClassFromString、NSSelectorFromString 使用例子
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#import "KNRuntimeKit.h"
@implementation KNRuntimeKit
/**
擷取類名
@param class 相應類
@return NSString:類名
*/
+ (NSString *)fetchClassName:(Class)class {
const char *className = class_getName(class);
return [NSString stringWithUTF8String:className];
}
/**
擷取成員變量
@param class Class
@return NSArray
*/
+ (NSArray *)fetchIvarList:(Class)class {
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
const char *ivarName = ivar_getName(ivarList[i]);
const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
dic[@"type"] = [NSString stringWithUTF8String: ivarType];
dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];
[mutableList addObject:dic];
}
free(ivarList);
return [NSArray arrayWithArray:mutableList];
}
/**
擷取類的屬性清單, 包括私有和公有屬性,以及定義在延展中的屬性
@param class Class
@return 屬性清單數組
*/
+ (NSArray *)fetchPropertyList:(Class)class {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
const char *propertyName = property_getName(propertyList[i]);
[mutableList addObject:[NSString stringWithUTF8String: propertyName]];
}
free(propertyList);
return [NSArray arrayWithArray:mutableList];
}
/**
擷取類的執行個體方法清單:getter, setter, 對象方法等。但不能擷取類方法
@param class <#class description#>
@return <#return value description#>
*/
+ (NSArray *)fetchMethodList:(Class)class {
unsigned int count = 0;
Method *methodList = class_copyMethodList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
/**
擷取協定清單
@param class <#class description#>
@return <#return value description#>
*/
+ (NSArray *)fetchProtocolList:(Class)class {
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
[mutableList addObject:[NSString stringWithUTF8String: protocolName]];
}
return [NSArray arrayWithArray:mutableList];
return nil;
}
/**
往類上添加新的方法與其實作
@param class 相應的類
@param methodSel 方法的名
@param methodSelImpl 對應方法實作的方法名
*/
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl {
Method method = class_getInstanceMethod(class, methodSelImpl);
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(class, methodSel, methodIMP, types);
}
/**
方法交換
@param class 交換方法所在的類
@param method1 方法1
@param method2 方法2
*/
+ (void)methodSwap:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
Method firstMethod = class_getInstanceMethod(class, method1);
Method secondMethod = class_getInstanceMethod(class, method2);
method_exchangeImplementations(firstMethod, secondMethod);
}
@end