runtime詳解
公司項目用到一個三方開源庫赐纱,里面有個bug,不能改動源碼,我想來想去效五,只能通過runtime這個萬能的手段來解決。但是runtime 并不怎么會用脉执,怎么辦,馬上學習唄玻熙。說到runtime,它是Objective-c里面最核心的技術,被人們傳呼的神乎其神,但是感覺有一層神秘的面紗籠罩其上蜻直,畢竟使用場景不多盯质,相信大多數(shù)開發(fā)者都不會熟練的運用。而網(wǎng)絡上也有無數(shù)的文章來講解runtime概而,但是真的非常的亂呼巷,非常的碎片化,很少有講解的比較全面的赎瑰。
最初是在onevcat的博客上看到runtime的runtime的博客王悍,說句實話集惋,看完后我還是蒙的,這里面主要講了一下runtime 比較核心的功能-Method Swizzling霞溪,不過看完后還是有些不知如何下手的感覺。下面是我自己對runtime的整理,從零開始,由淺入深冰寻,并且?guī)Я藥讉€runtime實際的應用場景琴庵。看完之后陵霉,你可以再回過頭來看喵神的這篇文章忍疾,應該就能看的懂了洗出。
Runtime基本是用C和匯編寫的蚌本,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力咬崔。你可以在這里下到蘋果維護的開源代碼曙旭。蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致蛮位。Objective-C 從三種不同的層級上與 Runtime 系統(tǒng)進行交互拂封,分別是通過 Objective-C 源代碼磨德,通過 Foundation 框架的NSObject類定義的方法般堆,通過對 runtime 函數(shù)的直接調用在孝。大部分情況下你就只管寫你的Objc代碼就行,runtime 系統(tǒng)自動在幕后辛勤勞作著淮摔。
RunTime簡稱運行時,就是系統(tǒng)在運行的時候的一些機制私沮,其中最主要的是消息機制。
對于C語言和橙,函數(shù)的調用在編譯的時候會決定調用哪個函數(shù)顾彰,編譯完成之后直接順序執(zhí)行,無任何二義性胃碾。
OC的函數(shù)調用成為消息發(fā)送涨享。屬于動態(tài)調用過程。在編譯的時候并不能決定真正調用哪個函數(shù)(事實證明仆百,在編 譯階段厕隧,OC可以調用任何函數(shù),即使這個函數(shù)并未實現(xiàn)俄周,只要申明過就不會報錯吁讨。而C語言在編譯階段就會報錯)。
只有在真正運行的時候才會根據(jù)函數(shù)的名稱找 到對應的函數(shù)來調用峦朗。
我們寫的oc代碼建丧,它在運行的時候也是轉換成了runtime方式運行的,更好的理解runtime波势,也能幫我們更深的掌握oc語言翎朱。
每一個oc的方法橄维,底層必然有一個與之對應的runtime方法。
當我們用OC寫下這樣一段代碼
[tableView cellForRowAtIndexPath:indexPath];
在編譯時RunTime會將上述代碼轉化成[發(fā)送消息]
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);
unsigned int count;
獲取屬性列表
1
2
3
4
5
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
? ? for (unsigned int i=0; i
? ? ? ? const char *propertyName = property_getName(propertyList[i]);
? ? ? ? NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
? ? }
獲取方法列表
1
2
3
4
5
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i
? ? Method method = methodList[i];
? ? NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
獲取成員變量列表
1
2
3
4
5
6
Ivar *ivarList = class_copyIvarList([self class], &count);
? for (unsigned int i; i
? ? ? Ivar myIvar = ivarList[i];
? ? ? const char *ivarName = ivar_getName(myIvar);
? ? ? NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
? }
獲取協(xié)議列表
1
2
3
4
5
6
7
8
9
10
11
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
? ? for (unsigned int i; i
? ? ? ? Protocol *myProtocal = protocolList[i];
? ? ? ? const char *protocolName = protocol_getName(myProtocal);
? ? ? ? NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
? ? }
? ```
>現(xiàn)在有一個Person類拴曲,和person創(chuàng)建的xiaoming對象,有test1和test2兩個方法
- 獲得類方法
Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
1
2
- 獲得實例方法
Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selector(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
1- 添加方法
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
1
2
- 替換原方法實現(xiàn)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
1- 交換兩個方法
method_exchangeImplementations(oriMethod, cusMethod);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
### 四:常見作用
- 動態(tài)的添加對象的成員變量和方法
- 動態(tài)交換兩個方法的實現(xiàn)
- 攔截并替換方法
- 在方法上增加額外功能
- 實現(xiàn)NSCoding的自動歸檔和解檔
- 實現(xiàn)字典轉模型的自動轉換
### 五:代碼實現(xiàn)
要使用runtime争舞,要先引入頭文件`#import `
這些代碼的實例有淺入深逐步講解,最后附上一個我在公司項目中遇到的一個實際問題澈灼。
#### 1. 動態(tài)變量控制
? 在程序中竞川,xiaoming的age是10,后來被runtime變成了20叁熔,來看看runtime是怎么做到的委乌。
##### 1.動態(tài)獲取XiaoMing類中的所有屬性[當然包括私有]?
`Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);`?
##### 2.遍歷屬性找到對應name字段?
`const char *varName = ivar_getName(var);`
##### 3.修改對應的字段值成20
`object_setIvar(self.xiaoMing, var, @"20");`?
##### 4.代碼參考
-(void)answer{
unsigned int count = 0;
Ivar?ivar = class_copyIvarList([self.xiaoMing class], &count);
for (int i = 0; i
Ivar var = ivar[i];
const char?
varName = ivar_getName(var);
NSString *name = [NSString stringWithUTF8String:varName];
if ([name isEqualToString:@”_age”]) {
object_setIvar(self.xiaoMing, var, @”20”);
break;
}
}
NSLog(@”XiaoMing’s age is %@”,self.xiaoMing.age);
}
1
2
3
4
5
6
#### 2.動態(tài)添加方法
在程序當中,假設XiaoMing的中沒有`guess`這個方法荣回,后來被Runtime添加一個名字叫guess的方法福澡,最終再調用guess方法做出相應。那么驹马,Runtime是如何做到的呢??
##### 1.動態(tài)給XiaoMing類中添加guess方法:
class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
這里參數(shù)地方說明一下:
>(IMP)guessAnswer 意思是guessAnswer的地址指針;
>"v@:" 意思是除秀,v代表無返回值void糯累,如果是i則代表int;@代表 id sel; : 代表 SEL _cmd;
>“v@:@@” 意思是册踩,兩個參數(shù)的沒有返回值泳姐。?
##### 2.調用guess方法響應事件:?
[self.xiaoMing performSelector:@selector(guess)];
##### 3.編寫guessAnswer的實現(xiàn):?
void guessAnswer(id self,SEL _cmd){
? ? NSLog(@"i am from beijing");?
}
這個有兩個地方留意一下:
* void的前面沒有+、-號暂吉,因為只是C的代碼胖秒。
* 必須有兩個指定參數(shù)(id self,SEL _cmd)?
##### 4.代碼參考
```?
-(void)answer{
? ? class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
? ? if ([self.xiaoMing respondsToSelector:@selector(guess)]) {
? ? ? ? [self.xiaoMing performSelector:@selector(guess)];
? ? } else{
? ? ? ? NSLog(@"Sorry,I don't know");
? ? }
}
void guessAnswer(id self,SEL _cmd){
? ? NSLog(@"i am from beijing");
}
在程序當中,假設XiaoMing的中有test1?和?test2這兩個方法慕的,后來被Runtime交換方法后阎肝,每次調動test1?的時候就會去執(zhí)行test2,調動test2?的時候就會去執(zhí)行test1肮街, 风题。那么,Runtime是如何做到的呢嫉父?
1
2
3
Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(test1));
? ? Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(test2));
? ? method_exchangeImplementations(m1, m2);
交換方法之后沛硅,以后每次調用這兩個方法都會交換方法的實現(xiàn)
在程序當中,假設XiaoMing的中有test1這個方法绕辖,但是由于某種原因摇肌,我們要改變這個方法的實現(xiàn),但是又不能去動它的源代碼(正如一些開源庫出現(xiàn)問題的時候)仪际,這個時候runtime就派上用場了围小。
我們先增加一個tool類昵骤,然后寫一個我們自己實現(xiàn)的方法-change,
通過runtime把test1替換成change吩抓。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class PersionClass = object_getClass([Person class]);
Class toolClass = object_getClass([tool class]);
? ? ////源方法的SEL和Method
? ? SEL oriSEL = @selector(test1);
? ? Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);
? ? ////交換方法的SEL和Method
? ? SEL cusSEL = @selector(change);
? ? Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);
? ? ////先嘗試給源方法添加實現(xiàn)涉茧,這里是為了避免源方法沒有實現(xiàn)的情況
? ? BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
? ? if (addSucc) {
? ? ? ? ? // 添加成功:將源方法的實現(xiàn)替換到交換方法的實現(xiàn)? ?
? ? ? ? class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
? ? }else {
? ? //添加失敗:說明源方法已經有實現(xiàn)疹娶,直接將兩個方法的實現(xiàn)交換即
method_exchangeImplementations(oriMethod, cusMethod);?
? }
有這樣一個場景伴栓,出于某些需求,我們需要跟蹤記錄APP中按鈕的點擊次數(shù)和頻率等數(shù)據(jù)雨饺,怎么解決钳垮?當然通過繼承按鈕類或者通過類別實現(xiàn)是一個辦法,但是帶來其他問題比如別人不一定會去實例化你寫的子類额港,或者其他類別也實現(xiàn)了點擊方法導致不確定會調用哪一個饺窿,runtime可以這樣解決:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@implementation UIButton (Hook)
+ (void)load {
? ? static dispatch_once_t onceToken;
? ? dispatch_once(&onceToken, ^{
? ? ? ? Class selfClass = [self class];
? ? ? ? SEL oriSEL = @selector(sendAction:to:forEvent:);
? ? ? ? Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
? ? ? ? SEL cusSEL = @selector(mySendAction:to:forEvent:);
? ? ? ? Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
? ? ? ? BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
? ? ? ? if (addSucc) {
? ? ? ? ? ? class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
? ? ? ? }else {
? ? ? ? ? ? method_exchangeImplementations(oriMethod, cusMethod);
? ? ? ? }
? ? });
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
? ? [CountTool addClickCount];
? ? [self mySendAction:action to:target forEvent:event];
}
@end
load方法會在類第一次加載的時候被調用,調用的時間比較靠前,適合在這個方法里做方法交換,方法交換應該被保證移斩,在程序中只會執(zhí)行一次肚医。
如果你實現(xiàn)過自定義模型數(shù)據(jù)持久化的過程,那么你也肯定明白向瓷,如果一個模型有許多個屬性肠套,那么我們需要對每個屬性都實現(xiàn)一遍encodeObject?和?decodeObjectForKey方法,如果這樣的模型又有很多個猖任,這還真的是一個十分麻煩的事情你稚。下面來看看簡單的實現(xiàn)方式。
假設現(xiàn)在有一個Movie類朱躺,有3個屬性刁赖,它的h文件這這樣的
1
2
3
4
5
6
7
8
9
10
#import
//1. 如果想要當前類可以實現(xiàn)歸檔與反歸檔,需要遵守一個協(xié)議NSCoding
@interface Movie : NSObject
@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;
@end
如果是正常寫法长搀,?m文件應該是這樣的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "Movie.h"
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)aCoder
{
? ? [aCoder encodeObject:_movieId forKey:@"id"];
? ? [aCoder encodeObject:_movieName forKey:@"name"];
? ? [aCoder encodeObject:_pic_url forKey:@"url"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
? ? if (self = [super init]) {
? ? ? ? self.movieId = [aDecoder decodeObjectForKey:@"id"];
? ? ? ? self.movieName = [aDecoder decodeObjectForKey:@"name"];
? ? ? ? self.pic_url = [aDecoder decodeObjectForKey:@"url"];
? ? }
? ? return self;
}
@end
如果這里有100個屬性宇弛,那么我們也只能把100個屬性都給寫一遍。
不過你會使用runtime后源请,這里就有更簡便的方法涯肩。
下面看看runtime的實現(xiàn)方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#import "Movie.h"
#import
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)encoder
{
? ? unsigned int count = 0;
? ? Ivar *ivars = class_copyIvarList([Movie class], &count);
? ? for (int i = 0; i
? ? ? ? // 取出i位置對應的成員變量
? ? ? ? Ivar ivar = ivars[i];
? ? ? ? // 查看成員變量
? ? ? ? const char *name = ivar_getName(ivar);
? ? ? ? // 歸檔
? ? ? ? NSString *key = [NSString stringWithUTF8String:name];
? ? ? ? id value = [self valueForKey:key];
? ? ? ? [encoder encodeObject:value forKey:key];
? ? }
? ? free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder
{
? ? if (self = [super init]) {
? ? ? ? unsigned int count = 0;
? ? ? ? Ivar *ivars = class_copyIvarList([Movie class], &count);
? ? ? ? for (int i = 0; i
? ? ? ? // 取出i位置對應的成員變量
? ? ? ? Ivar ivar = ivars[i];
? ? ? ? // 查看成員變量
? ? ? ? const char *name = ivar_getName(ivar);
? ? ? // 歸檔
? ? ? NSString *key = [NSString stringWithUTF8String:name];
? ? ? id value = [decoder decodeObjectForKey:key];
? ? ? // 設置到成員變量身上
? ? ? ? [self setValue:value forKey:key];
? ? ? ? }
? ? ? ? free(ivars);
? ? }
? ? return self;
}
@end
這樣的方式實現(xiàn),不管有多少個屬性巢钓,寫這幾行代碼就搞定了病苗。怎么,還嫌麻煩症汹,下面看看更加簡便的方法:兩句代碼搞定硫朦。
我們把encodeWithCoder?和?initWithCoder這兩個方法抽成宏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#import "Movie.h"
#import
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\
#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)encoder
{
? ? encodeRuntime(Movie)
}
- (id)initWithCoder:(NSCoder *)decoder
{
? ? initCoderRuntime(Movie)
}
@end
我們可以把這兩個宏單獨放到一個文件里面,這里以后需要進行數(shù)據(jù)持久化的模型都可以直接使用這兩個宏背镇。
字典轉模型的應用可以說是每個app必然會使用的場景,雖然實現(xiàn)的方式略有不同咬展,但是原理都是一致的:遍歷模型中所有屬性泽裳,根據(jù)模型的屬性名,去字典中查找key破婆,取出對應的值涮总,給模型的屬性賦值。
像幾個出名的開源庫:JSONModel,MJExtension等都是通過這種方式實現(xiàn)的祷舀。
先實現(xiàn)最外層的屬性轉換
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 創(chuàng)建對應模型對象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.獲取成員屬性數(shù)組
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍歷所有的成員屬性名,一個一個去字典中取出對應的value給模型屬性賦值
for (int i = 0; i < count; i++) {
? ? // 2.1 獲取成員屬性
? ? Ivar ivar = ivarList[i];
? ? // 2.2 獲取成員屬性名 C -> OC 字符串
? ? NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
? ? // 2.3 _成員屬性名 => 字典key
? ? NSString *key = [ivarName substringFromIndex:1];
? ? // 2.4 去字典中取出對應value給模型屬性賦值
? ? id value = dict[key];
? ? // 獲取成員屬性類型
? ? NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
? ? }
如果模型比較簡單瀑梗,只有NSString,NSNumber等裳扯,這樣就可以搞定了抛丽。但是如果模型含有NSArray,或者NSDictionary等饰豺,那么我們還需要進行第二步轉換亿鲜。
內層數(shù)組,字典的轉換
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
? ? ? ? ? ? //? 是字典對象,并且屬性名對應類型是自定義類型
? ? ? ? ? ? // 處理類型字符串 @\"User\" -> User
? ? ? ? ? ? ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
? ? ? ? ? ? ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
? ? ? ? ? ? // 自定義對象,并且值是字典
? ? ? ? ? ? // value:user字典 -> User模型
? ? ? ? ? ? // 獲取模型(user)類對象
? ? ? ? ? ? Class modalClass = NSClassFromString(ivarType);
? ? ? ? ? ? // 字典轉模型
? ? ? ? ? ? if (modalClass) {
? ? ? ? ? ? ? ? // 字典轉模型 user
? ? ? ? ? ? ? ? value = [modalClass objectWithDict:value];
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if ([value isKindOfClass:[NSArray class]]) {
? ? ? ? ? ? // 判斷對應類有沒有實現(xiàn)字典數(shù)組轉模型數(shù)組的協(xié)議
? ? ? ? ? ? if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
? ? ? ? ? ? ? ? // 轉換成id類型冤吨,就能調用任何對象的方法
? ? ? ? ? ? ? ? id idSelf = self;
? ? ? ? ? ? ? ? // 獲取數(shù)組中字典對應的模型
? ? ? ? ? ? ? ? NSString *type =? [idSelf arrayContainModelClass][key];
? ? ? ? ? ? ? ? // 生成模型
? ? ? ? ? ? ? ? Class classModel = NSClassFromString(type);
? ? ? ? ? ? ? ? NSMutableArray *arrM = [NSMutableArray array];
? ? ? ? ? ? ? ? // 遍歷字典數(shù)組蒿柳,生成模型數(shù)組
? ? ? ? ? ? ? ? for (NSDictionary *dict in value) {
? ? ? ? ? ? ? ? ? ? // 字典轉模型
? ? ? ? ? ? ? ? ? ? id model =? [classModel objectWithDict:dict];
? ? ? ? ? ? ? ? ? ? [arrM addObject:model];
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? // 把模型數(shù)組賦值給value
? ? ? ? ? ? ? ? value = arrM;
? ? ? ? ? ? }
? ? ? ? }
我自己覺得系統(tǒng)自帶的KVC模式字典轉模型就挺好的,假設movie是一個模型對象漩蟆,dict 是一個需要轉化的?[movie setValuesForKeysWithDictionary:dict];?這個是系統(tǒng)自帶的字典轉模型方法垒探,個人感覺也還是挺好用的,不過使用這個方法的時候需要在模型里面再實現(xiàn)一個方法才行爆安,
- (void)setValue:(id)value forUndefinedKey:(NSString *)key?重寫這個方法為了實現(xiàn)兩個目的:1. 模型中的屬性和字典中的key不一致的情況,比如字典中有個id,我們需要把它賦值給uid屬性仔引;2. 字典中屬性比模型的屬性還多的情況扔仓。
如果出現(xiàn)以上兩種情況而沒有實現(xiàn)這個方法的話,程序就會崩潰咖耘。
這個方法的實現(xiàn):
1
2
3
4
5
6
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
? ? if ([key isEqualToString:@"id"]) {
? ? ? ? self.uid = value;
? ? }
}
以上的幾種方法應該算是runtime在實際場景中所應用的大部分的情況了翘簇,平常的編碼中差不多足夠用了。
如果從頭仔細看到尾儿倒,相信你基本的用法應該會了版保,雖然會用是主要的目的,有幾個基本的參數(shù)概念還是要了解一下的夫否。
1
2
3
4
5
6
7
8
9
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
這是官方的聲明彻犁,從這個函數(shù)的注釋可以看出來了,這是個最基本的用于發(fā)送消息的函數(shù)凰慈。另外汞幢,這個函數(shù)并不能發(fā)送所有類型的消息,只能發(fā)送基本的消息微谓。比如森篷,在一些處理器上输钩,我們必須使用objc_msgSend_stret來發(fā)送返回值類型為結構體的消息,使用objc_msgSend_fpret來發(fā)送返回值類型為浮點類型的消息仲智,而又在一些處理器上买乃,還得使用objc_msgSend_fp2ret來發(fā)送返回值類型為浮點類型的消息。
最關鍵的一點:無論何時钓辆,要調用objc_msgSend函數(shù)剪验,必須要將函數(shù)強制轉換成合適的函數(shù)指針類型才能調用。
從objc_msgSend函數(shù)的聲明來看岩馍,它應該是不帶返回值的碉咆,但是我們在使用中卻可以強制轉換類型搓萧,以便接收返回值藤树。另外,它的參數(shù)列表是可以任意多個的运翼,前提也是要強制函數(shù)指針類型双谆。
其實編譯器會根據(jù)情況在objc_msgSend,?objc_msgSend_stret,?objc_msgSendSuper, 或?objc_msgSendSuper_stret四個方法中選擇一個來調用壳咕。如果消息是傳遞給超類,那么會調用名字帶有”Super”的函數(shù)顽馋;如果消息返回值是數(shù)據(jù)結構而不是簡單值時谓厘,那么會調用名字帶有”stret”的函數(shù)。
objc_msgSend函數(shù)第二個參數(shù)類型為SEL寸谜,它是selector在Objc中的表示類型(Swift中是Selector類)竟稳。selector是方法選擇器,可以理解為區(qū)分方法的 ID熊痴,而這個 ID 的數(shù)據(jù)結構是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串他爸,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個SEL類型的方法選擇器。
不同類中相同名字的方法所對應的方法選擇器是相同的果善,即使方法名字相同而變量類型不同也會導致它們具有相同的方法選擇器诊笤,于是 Objc 中方法命名有時會帶上參數(shù)類型(NSNumber一堆抽象工廠方法),Cocoa 中有好多長長的方法哦巾陕。
objc_msgSend第一個參數(shù)類型為id讨跟,大家對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結構體包含一個isa指針鄙煤,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類晾匠。
PS:isa指針不總是指向實例對象所屬的類,不能依靠它來確定類型梯刚,而是應該用class方法來確定實例對象的類混聊。因為KVO的實現(xiàn)機理就是將被觀察對象的isa指針指向一個中間類而不是真實的類,這是一種叫做 isa-swizzling 的技術,詳見官方文檔.
之所以說isa是指針是因為Class其實是一個指向objc_class結構體的指針:
typedef struct objc_class *Class;
objc_class里面的東西多著呢:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
可以看到運行時一個類還關聯(lián)了它的超類指針句喜,類名预愤,成員變量,方法咳胃,緩存植康,還有附屬的協(xié)議。
在objc_class結構體中:ivars是objc_ivar_list指針展懈;methodLists是指向objc_method_list指針的指針销睁。也就是說可以動態(tài)修改?*methodLists?的值來添加成員方法,這也是Category實現(xiàn)的原理.
上面講到的所有東西都在Demo里存崖,如果你覺得不錯冻记,還請為我的Demo star一個。