iOS中Runtime的观察

标签: 编程学习 iOS学习
Runtime

OC是一门动态性比较强的语言,这个动态性是依靠Runtime API来支撑的。

前面我们介绍isa与对象的时候说过方法存储在 method_t 这种结构中,里面存储的是 函数名、函数编码、函数所处的地址指针等。

我们也说到 Class 内部结构中有个方法缓存 cache_t,里面存储的是曾经调用过的方法列表,内部结构是 bucket_t 这种散列表(哈希表),可以提高方法的查找速度。

这里说了这么多都是讲方法的查找,其实要说的就是运行时方法查找的过程。

消息机制

OC的方法调用编译之后其实都是转换成 objc_msgSendobjc_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: 先处理防止崩溃,并上报错误

几个问题

isMemberOfClassisKindOfClass 的区别:

- (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这个标志,就会让当前对象去调用父类方法,但是本质还是当前对象在调用