iOS中Runtime的观察
Runtime
OC是一门动态性比较强的语言,这个动态性是依靠Runtime API来支撑的。
前面我们介绍isa与对象的时候说过方法存储在 method_t
这种结构中,里面存储的是 函数名、函数编码、函数所处的地址指针等。
我们也说到 Class 内部结构中有个方法缓存 cache_t
,里面存储的是曾经调用过的方法列表,内部结构是 bucket_t
这种散列表(哈希表),可以提高方法的查找速度。
这里说了这么多都是讲方法的查找,其实要说的就是运行时方法查找的过程。
消息机制
OC的方法调用编译之后其实都是转换成 objc_msgSend
和 objc_msgSendSuper
函数的调用,而这个过程可以分为三大阶段:
消息发送
这一步其实就是之前说的方法查找的过程
我们以实例对象为例,如图所示,先判断消息接收者是否是nil,如果是nil就退出;如果不是的话,先通过isa指针查找到类对象,先从 cache_t
缓存中查找;如果查找不到就去类对象的 class_rw_t
中的方法列表中查找,如果查找到就调用方法并将方法缓存在类对象的 cache
中;如果查找不到就通过 superClass
指针去父类查找,在父类中查找也是先从 cache
开始的,查找不到再去父类的 class_rw_t
中查找,找到之后缓存到 cache
;如果查找到没有父类的情况下都找不到,就要进入第二步动态解析了。
在 class_rw_t
中查找的时候,如果方法是已经排序过的,则使用二分查找,如果没有排序,则使用的是线性排序。
动态解析 如果消息发送不成功就会进入动态解析;这里有个标识,表示是否动态解析过,如果动态解析过就不会再解析了,直接走消息转发;动态解析的时候添加完方法又会走一遍消息发送。
代码示例
-(void)other {
NSLog(@"----%s----", __func__);
}
/// 动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(personTest)) {
NSLog(@"动态解析实例方法----- %s",__func__);
Method other = class_getInstanceMethod(self, @selector(other));
class_addMethod(self, sel, method_getImplementation(other), method_getTypeEncoding(other));
return YES;
}
return [super resolveInstanceMethod:sel];
}
void c_other(id self, SEL _cmd) {
NSLog(@"c_other-----%@-----%@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if(sel == @selector(personTest)) {
// 类方法添加到元类对象中
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
}
return [super resolveClassMethod:sel];
}
消息转发
如果没有动态解析,则会走消息转发,先调用 forwardingTargetForSelector:
,返回值不为 nil 的话直接走 objc_msgSend
,返回 nil 则走签名;在 methodSignatureForSelector:
中创建一个方法签名,然后会走 forwardInvocation:
,返回值为 nil 的话调用 doesNotRecognizeSelector:
方法。开发者可以在forwardInvocation:
⽅法中⾃定义任何逻辑。
代码示例
/// 消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(forwardingTarget)) {
NSLog(@"------- forwardingTarget");
// objc_msgSend([[DcoinTest alloc] init], aSelector);
// return [[NSObject alloc] init];
return nil; //返回是nil的话走签名
}
return [super forwardingTargetForSelector:aSelector];
}
//forwardingTargetForSelector返回是nil的话走签名,返回值类型,参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if(aSelector == @selector(forwardingTarget)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation 封装了一个方法调用,包括:方法调用者、方法、方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if(anInvocation.selector == @selector(forwardingTarget)) {
// anInvocation.target = [[DcoinTest alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[DcoinTest alloc] init]];
}
return [super forwardInvocation:anInvocation];
}
Runtime 的应用
方法替换 这个我们比较常用,但是替换的时候要注意,有的类底层类名不同,比如类簇
+ (void)load {
//类簇 NSString NSArray NSDictionary
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(dcoin_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
}
- (void)dcoin_insertObject:(id)anObject atIndex:(NSUInteger)index {
if(anObject != nil) {
[self dcoin_insertObject:anObject atIndex:index];
} else {
NSLog(@"参数不能为空");
}
}
@end
解析Json 利用 runtime 遍历属性列表,并使用 kvc 设置值
@implementation NSObject (Json)
// 要做成框架还要考虑继承的成员变量,字典是否存在这个变量,复杂类型的变量(模型嵌套),变量名是ID,变量是驼峰命名等等
+ (instancetype)dcoin_objectWithJson:(NSDictionary *)json {
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
[obj setValue:json[name] forKey:name];
}
free(ivars);
return obj;
}
@end
利用Runtime添加类 有时候需要运行时去添加一些类
- (void)api {
Class LaoZhang = objc_allocateClassPair([NSObject class], "LaoZhang", 0);
NSString *name =@"name";
//添加属性
class_addIvar(LaoZhang, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(LaoZhang, "_age", sizeof(int), log2(sizeof(int)), @encode(int));
//添加方法
class_addMethod(LaoZhang, @selector(run), (IMP)run, "v16@0:8");
//使用的话要先注册
objc_registerClassPair(LaoZhang);
id dog = [[LaoZhang alloc] init];
[dog setValue:@"老张" forKey:@"name"];
[dog setValue:@10 forKey:@"_age"];
[dog run];
NSLog(@" %@----->%@ ", [dog valueForKey:@"name"], [dog valueForKey:@"_age"]);
//使用完了注销掉
objc_disposeClassPair(LaoZhang);
}
void run(id self, SEL _cmd) {
NSLog(@"%@ 开始跑了", [self valueForKey:@"name"]);
}
防止崩溃
如果方法找不到可以在 forwardInvocation:
先处理防止崩溃,并上报错误
几个问题
isMemberOfClass
与 isKindOfClass
的区别:
- (void) memberAndKind {
id person = [[Dcoin alloc] init];
// isMemberOfClass 本质 [self class] == cls
NSLog(@"---------------> %d", [person isMemberOfClass:[Dcoin class]]); //1
NSLog(@"---------------> %d", [person isMemberOfClass:[NSObject class]]); //0
// isKindOfClass 本质 遍历父类并判断
NSLog(@"---------------> %d", [person isKindOfClass:[Dcoin class]]); //1
NSLog(@"---------------> %d", [person isKindOfClass:[NSObject class]]); //1
// 这里都是比较元类的
NSLog(@"---------------> %d", [Dcoin isKindOfClass:[Dcoin class]]); //0
NSLog(@"---------------> %d", [Dcoin isMemberOfClass:[Dcoin class]]); //0
NSLog(@"---------------> %d", [Dcoin isKindOfClass: object_getClass([Dcoin class])]); //1
NSLog(@"---------------> %d", [Dcoin isMemberOfClass:object_getClass([Dcoin class])]); //1
// 元类对象查不到最后会走类对象
NSLog(@"---------------> %d", [Dcoin isMemberOfClass: [NSObject class]]); //1
}
创建一个类 Student
,继承于 NSObject
,打印下面
// 打印结果: Student NSObject Student NSObject
NSLog(@"%@ %@ %@ %@",[self class], [self superclass], [super class], [super superclass]);}
主要是 [super class]
值
class
:获取当前方法调用者的类
superclass
:获取当前方法调用者的父类
super
:仅仅是一个编译指示器,就是给编译器看的,不是一个指针
本质:只要编译器看到super
这个标志,就会让当前对象去调用父类方法,但是本质还是当前对象在调用