? 前言:OC是一門動(dòng)態(tài)性比較強(qiáng)的語言盈滴,它的動(dòng)態(tài)性就是由Runtime支撐和實(shí)現(xiàn)的嘉蕾。本文先介紹了Runtime的概念画切,然后詳細(xì)地介紹了OC的消息轉(zhuǎn)發(fā)機(jī)制荚恶,最后介紹了幾種Runtime常見的使用場(chǎng)景姜贡,并配上具體的代碼說明试吁。
一、Runtime概念:
1楼咳、Runtime概念:
? Runtime是一套C語言的API熄捍,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)。OC是一門動(dòng)態(tài)性比較強(qiáng)的語言母怜,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行余耽,OC的動(dòng)態(tài)性就是由Runtime支撐和實(shí)現(xiàn)的,平時(shí)編寫的OC代碼糙申,底層都是轉(zhuǎn)換成Runtime API進(jìn)行調(diào)用宾添。
2船惨、OC的消息機(jī)制:
? OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)化成了objc_msgSend函數(shù)調(diào)用,給receiver(方法調(diào)用者)發(fā)送一條消息(selector方法名)缕陕,objc_msgSend有三個(gè)階段:消息發(fā)送階段(當(dāng)前類粱锐、父類中查找)、動(dòng)態(tài)方法解析階段扛邑、消息轉(zhuǎn)發(fā)階段怜浅。
二、探尋消息轉(zhuǎn)發(fā)機(jī)制:
1蔬崩、OC中方法調(diào)用過程:
? 如果需要了解OC對(duì)象的內(nèi)部結(jié)構(gòu)恶座,請(qǐng)點(diǎn)擊OC對(duì)象的本質(zhì)。
? 一個(gè)普通的對(duì)象方法[objc method]沥阳,編譯時(shí)轉(zhuǎn)成消息發(fā)送objc_msgSend(objc, method)跨琳,Runtime執(zhí)行時(shí)流程如下:
? 1)通過objc的ISA指針找到它的class;
? 2)在它的class里methods找到method方法桐罕;
? 3)如果它的class沒有該method脉让,那么就去父類中查找;
? 4)一旦找到該method功炮,那么就執(zhí)行它的實(shí)現(xiàn)IMP溅潜;
? 5)如果在父類中還找不到,那么進(jìn)入動(dòng)態(tài)方法解析+(BOOL)resolveInstanceMethod:(SEL)sel(經(jīng)測(cè)試薪伏,如果直接返回YES或者NO都會(huì)執(zhí)行后續(xù)的forward方法)滚澜;
? 6)如果動(dòng)態(tài)解析沒有添加方法,那么進(jìn)入到消息轉(zhuǎn)發(fā)階段-(id)forwardingTargetForSelector:(SEL)aSelector嫁怀;
? 7)如果消息轉(zhuǎn)發(fā)的對(duì)象還找不到方法设捐,就拋出經(jīng)典異常unrecognized selector,找不到該方法眶掌;
? 在第一步objc通過ISA指針找到它的class之后挡育,嚴(yán)格來說先從它的class中的緩存中查詢方法method,如果沒有則繼續(xù)在methods方法列表進(jìn)行查詢朴爬,后續(xù)操作一致。
代碼驗(yàn)證如下:創(chuàng)建一個(gè)Dog類
@interface Dog : NSObject
- (void)wangWang; //正常調(diào)用
- (void)wangWangResolve; //方法解析
- (void)wangWangForward_YES; //消息轉(zhuǎn)發(fā)YES
- (void)wangWangForward_NO; //消息轉(zhuǎn)發(fā)NO
- (void)wangWangForward_All; //完整的消息轉(zhuǎn)發(fā)
@end
// .m文件
#import "Dog.h"
#import "Cat.h"
#import <objc/runtime.h>
@implementation Dog
// 直接實(shí)現(xiàn)方法
- (void)wangWang {
NSLog(@"dog wangWang");
}
// 動(dòng)態(tài)解析實(shí)例方法
//+ (BOOL)resolveClassMethod:(SEL)sel //解析類方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(wangWangResolve))
{
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// C語言函數(shù)
void dynamicMethodIMP(id self, SEL _cmd)
{
NSLog(@"dog wangWangResolve");
}
// 轉(zhuǎn)發(fā)備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(wangWangForward_YES)) {
return [Cat new];
}
return [super forwardingTargetForSelector:aSelector];
}
//簽名橡淆,進(jìn)入forwardInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(wangWangForward_All)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Cat *cat = [Cat new];
if([cat respondsToSelector:sel]) {
[anInvocation invokeWithTarget:cat];
} else {
[self doesNotRecognizeSelector:sel];
}
}
@end
創(chuàng)建一個(gè)Cat類:
@interface Cat : NSObject
- (void)wangWangForward_YES; //消息轉(zhuǎn)發(fā)YES
- (void)wangWangForward_All; //完整的消息轉(zhuǎn)發(fā)
@end
// .m文件
@implementation Cat
- (void)wangWangForward_YES {
NSLog(@"cat wangWangForward_YES");
}
- (void)wangWangForward_All {
NSLog(@"cat wangWangForward_All");
}
@end
分別運(yùn)行注釋代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Dog *dog = [Dog new];
[dog wangWang];
// [dog wangWangResolve];
// [dog wangWangForward_YES];
// [dog wangWangForward_NO];
// [dog wangWangForward_All];
}
打印結(jié)果:
// [dog wangWang] 正常在.m文件實(shí)現(xiàn)方法
dog wangWang
// [dog wangWangResolve] 動(dòng)態(tài)解析添加方法
dog wangWangResolve
// [dog wangWangForward_YES] 成功消息轉(zhuǎn)發(fā)召噩,對(duì)象是cat
cat wangWangForward_YES
// [dog wangWangForward_NO] 前面階段都找不到方法,拋出異常
-[Dog wangWangForward_NO]: unrecognized selector sent to instance 0x6000031ec350
// [dog wangWangForward_All] 完整的消息轉(zhuǎn)發(fā)
cat wangWangForward_All
2逸爵、完整的消息轉(zhuǎn)發(fā):
? 如果forwardingTargetForSelector這一步還不能處理消息具滴,那唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。首先發(fā)送一個(gè)methodSignatureForSelector消息师倔,如果有簽名返回构韵,則進(jìn)入到forwardInvocation,在該方法里進(jìn)行最后的消息轉(zhuǎn)發(fā),否則直接發(fā)送doesNotRecognizeSelector消息疲恢,程序拋出異常凶朗。
至于“v@:”具體的意思,可以查看官方文檔Type Encodings显拳。
三棚愤、Runtime應(yīng)用:
? Runtime應(yīng)用場(chǎng)景非常多,這里介紹一些項(xiàng)目中常用的場(chǎng)景:
? 1)關(guān)聯(lián)對(duì)象(AssociateObject)給分類添加的屬性實(shí)現(xiàn)setter和getter方法杂数,運(yùn)行中方法替換(method_exchangeIMP)宛畦,傳送門-Category分類;
? 2)KVO的底層實(shí)現(xiàn),通過runtime動(dòng)態(tài)創(chuàng)建一個(gè)派生類對(duì)象揍移,傳送門-KVC & KVO;
? 3)實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)次和,通過runtime遍歷Model的所有屬性,把服務(wù)器返回的JSON格式轉(zhuǎn)成字典那伐,然后通過KVC給Model賦值踏施;
? 4)實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔,通過runtime遍歷Model的所有屬性喧锦,并對(duì)屬性進(jìn)行encode和decode操作读规;
? 5)其它runtime的API調(diào)用;
1燃少、字典和模型的自動(dòng)轉(zhuǎn)換:
? 對(duì)NSObject寫一個(gè)分類束亏,添加一個(gè)initWithDict:(NSDictionary)dict分類,如下:
@interface NSObject (DictToModel)
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
// .m
@implementation NSObject (DictToModel)
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
// 獲取類的屬性及屬性對(duì)應(yīng)的類型
NSMutableArray *keys = [NSMutableArray array];
NSMutableArray *attributes = [NSMutableArray array];
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通過property_getName函數(shù)獲得屬性的名字
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
NSString *propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
// 立即釋放properties指向的內(nèi)存
free(properties);
// 根據(jù)類型給屬性賦值
for (NSString *key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}
@end
創(chuàng)建一個(gè)Person類:
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end
運(yùn)行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *dict = @{@"name": @"路飛",
@"age": @(15)
};
Person *p = [[Person alloc] initWithDict:dict];
NSLog(@"name = %@, age = %ld", p.name, p.age);
}
打印結(jié)果:
name = 路飛, age = 15
2阵具、NSCoding的自動(dòng)歸檔和解檔:
? 在Model的基類中重寫方法:
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end
// .m文件
@implementation Person
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i]; //拿到Ivar
const char *name = ivar_getName(ivar); //獲取到屬性的C字符串名稱
NSString *key = [NSString stringWithUTF8String:name];
//解檔
id value = [coder decodeObjectForKey:key];
// 利用KVC賦值
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
//告訴系統(tǒng)歸檔的屬性是哪些
unsigned int count = 0; //表示對(duì)象的屬性個(gè)數(shù)
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
//歸檔 -- 利用KVC
[coder encodeObject:[self valueForKey:key] forKey:key];
}
free(ivars);//在OC中使用了Copy碍遍、Creat、New類型的函數(shù)阳液,需要釋放指針E戮础!(注:ARC管不了C函數(shù))
}
@end
運(yùn)行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *dict = @{@"name": @"路飛",
@"age": @(15)
};
Person *p = [[Person alloc] initWithDict:dict];
NSString *temp = NSTemporaryDirectory();
NSString *filePath = [temp stringByAppendingPathComponent:@"archive.text"]; //注:保存文件的擴(kuò)展名可以任意取帘皿,不影響东跪。
//NSLog(@"%@", filePath);
// 歸檔
[NSKeyedArchiver archiveRootObject:p toFile:filePath];
// 解檔
Person *p1 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"name = %@, age = %ld", p1.name, p1.age);
}
打印結(jié)果:
name = 路飛, age = 15
3、其它runtime的API調(diào)用:
? 1)[super class]的底層調(diào)用鹰溜,都是通過object_getClass(self)進(jìn)行調(diào)用虽填,代碼如下:
? PS:正常情況下的super與self比較,super是從父類開始查找方法曹动,self是從本類開始斋日;
// 創(chuàng)建一個(gè)Person類
@interface Person : NSObject
@end
@implementation Person
@end
// 創(chuàng)建一個(gè)Student類繼承自Person
@interface Student : Person
@end
@implementation Student
- (id)init {
if (self = [super init]) {
Class cls1 = [self class]; //調(diào)用object_getClass(self)
Class cls2 = [self superclass];
Class cls3 = [super class]; //給當(dāng)前接受者發(fā)送消息,當(dāng)前接受者就是self
Class cls4 = [super superclass];
NSLog(@"cls1 = %@, cls2 = %@, cls3 = %@, cls4 = %@", cls1, cls2, cls3, cls4);
}
return self;
}
@end
運(yùn)行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [Student new];
}
打印結(jié)果:
cls1 = Student, cls2 = Person, cls3 = Student, cls4 = Person
在objc4源碼中的NSObject.mm文件中墓陈,class的實(shí)現(xiàn):
- (Class)class {
return object_getClass(self);
}
- (Class)superclass {
return [self class]->superclass;
}
? 2)isMemberOfClass與isKindOfClass的區(qū)別:
? -(BOOL)isMemberOfClass:(Class)cls:調(diào)用者是否是cls本類的實(shí)例恶守;
? -(BOOL)isKindOfClass:(Class)cls:調(diào)用者是否是cls本類的實(shí)例或者子類的實(shí)例第献;
? 查看objc4源碼,很容易理解:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls; //是否是本類
}
- (BOOL)isKindOfClass:(Class)cls {
// self是否是本類或者子類
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
代碼驗(yàn)證兔港,運(yùn)行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [Student new];
BOOL isMember = [stu isMemberOfClass:[Person class]];
BOOL isKind = [stu isKindOfClass:[Person class]];
BOOL instance_isMember = [stu isMemberOfClass:[Student class]];
BOOL class_isMember = [Student isMemberOfClass:[Student class]];
NSLog(@"isMember = %d, isKind = %d", isMember, isKind);
NSLog(@"instance_isMember = %d, class_isMember = %d", instance_isMember, class_isMember);
}
打印結(jié)果:
isMember = 0, isKind = 1
// [Student isMemberOfClass:[Student class]] 表示Student的元類與Student類比較庸毫,結(jié)果是false
instance_isMember = 1, class_isMember = 0
覺得寫的不錯(cuò),有些啟發(fā)或幫助押框,點(diǎn)個(gè)贊哦岔绸!