美文网首页
Runtime总结

Runtime总结

作者: CowboyBebop | 来源:发表于2019-01-07 10:05 被阅读5次

    参考: Objc Runtime 总结
    runtime

    一, runtime 关联属性

    • 1,设置关联值
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    

    object:与谁关联,通常是传self
    key:唯一键,在获取值时通过该键获取,通常是使用static const void *来声明
    value:关联所设置的值
    policy:内存管理策略,比如使用copy

    • 2,获取关联值
    id objc_getAssociatedObject(id object, const void *key)
    

    object:与谁关联,通常是传self,在设置关联时所指定的与哪个对象关联的那个对象
    key:唯一键,在设置关联时所指定的键

    • 3, 移除对象上所有关联
    objc_removeAssociatedObjects
    

    二, runtime在模型与字典互转当中的应用

    • 1,字典转模型

    for 循环字典,根据key 生成set 方法,类似 setName: , 然后调用 ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value) 方法,调用set方式,就可以给模型的属性赋值了。

    //开头大写,其余小写
    NSString *propertySetter = key.capitalizedString;
    //拼接set方法
    propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];
    // 生成setter方法
    SEL setter = NSSelectorFromString(propertySetter);
    if ([self respondsToSelector:setter]) {
        if (setter != nil) {
         //利用运行时调用set 方法
          ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
         }
    }
    

    还有种方式,通过KVC 方式也可以实现,具体步骤 :根据 class_copyIvarList方法获取该类的成员变量列表, 然后for 循环 根据 Ivar 作为key 去字典中取出value ,最后根据setValue:forKey 去设置。

    unsigned int count;
    // 获取类中的所有成员属性
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
          // 根据角标,从数组取出对应的成员属性
          Ivar ivar = ivarList[i];
          // 获取成员属性名
          NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
          // 处理成员属性名->字典中的key
          // 从第一个角标开始截取,去掉下划线
          NSString *key = [name substringFromIndex:1];
          // 根据成员属性名去字典中查找对应的value
          id value = dict[key];
          if (value) { // 有值,才需要给模型的属性赋值
              // 利用KVC给模型中的属性赋值
              [objc setValue:value forKey:key];
          }
    }
    
    • 2, 模型转字典

    第一种思路:先获取属性列表,然后根据单个属性生成getter 方法,然后运用 objc_msgSend q去调用getter 方法,获取到属性的值,添加到字典中去。

        unsigned int outCount = 0;
        //获取属性列表
        objc_property_t* properties = class_copyPropertyList([self class], &outCount);
        if (outCount != 0) {
            NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:outCount];
            for (unsigned int i = 0; i < outCount; ++i) {
                //获取单个属性
                objc_property_t property = properties[i];
                //获取属性名称
                const void * propertyName = property_getName(property);
                //获取getter 方法
                SEL getter = sel_registerName(propertyName);
                if ([self respondsToSelector:getter]) {
                    //调用getter 方法,获取value
                    id value = ((id (*) (id,SEL)) objc_msgSend)(self,getter);
                    if (value) {
                        NSString * key = [NSString stringWithUTF8String:propertyName];
                        [dict setObject:value forKey:key];
                    }
                }
            }
            return dict;
        }
    

    第二种思路:生成getter 方法的方式差不多,调用getter 方法不再是通过objc_msgSend,而是通过 NSInvocation ,NSInvocation 可以指定某个 类去掉用某个方法,具体代码如下。

        unsigned int outCount = 0;
        //获取属性列表
        objc_property_t* properties = class_copyPropertyList([self class], &outCount);
        if (outCount != 0) {
            NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:outCount];
            for (unsigned int i = 0; i < outCount; ++i) {
                //获取单个属性
                objc_property_t property = properties[i];
                //获取属性名称
                const void * propertyName = property_getName(property);
                NSString * key = [NSString stringWithUTF8String:propertyName];
                //获取getter 方法
                SEL getter = NSSelectorFromString(key);
                if ([self respondsToSelector:getter]) {
                    //获取方法签名
                    NSMethodSignature * signature= [NSMethodSignature methodSignatureForSelector:getter];
                    // 根据方法签名获取NSInvocation对象
                    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature];
                    // 设置target
                    [invocation setTarget:self];
                    // 设置selector
                    [invocation setSelector:getter];
                    //调用
                    [invocation invoke];
                    __unsafe_unretained NSObject * propertyValue = nil;
                    //返回值
                    [invocation getReturnValue:&propertyValue];
                    if (propertyValue != nil) {
                        [dict setObject:propertyValue forKey:key];
                    }
                }
            }
            return dict;
        }
    

    三,消息转发机制

    #import <Foundation/Foundation.h>
    int main(){
        @autoreleasepool {
            NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
        }
        return 0;
    }
    

    上面的main.m 的文件,我们可以用Clang 编译的知识,通过命令 clang -rewrite-objc main.m ,就能得到main.cpp 文件,打开后就会发现转换后的C 代码,其中一个重要的函数就是 objc_msgSend。

    int main(){
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            NSString* site = ((NSString * _Nullable (*)(id, SEL, const char * _Nonnull))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), (const char *)"starming");
    
        }
        return 0;
    }
    

    我们先来了解下objc_msgSend 的调用的检测过程:

    • 1, 检测这个selector 是否要忽略。
    • 2, 检测这个target 是不是nil 对象,nil 对象执行任何一个方法不会crash 是因为会被忽略掉。
    • 3, 查找这个类的IMP,也就是方法实现。先从方法缓存列表cache中查找,若找到则跳到对应的函数去执行;若找不到,则查找方法分发表。如果分发表找不到就到父类的分发表去找,直到找到或者查找到NSObject根类为止。
    • 4, 前三步都找不到,则开始进入动态方法解析了

    其流程是这样的:
    第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel实现方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过class_addMethod 函数动态地添加方法,消息得到处理,此流程完毕。
    第二步:在第一步返回的是NO时,就会进入
    - (id)forwardingTargetForSelector:(SEL)aSelector 方法,这是运行时给我们的第二次机会,用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应者,则会进入第三步。若返回某个对象,则会调用该对象的方法。
    第三步:若第二步返回的是nil,则我们首先要通过- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法签名,若返回nil,则表示不处理。若返回方法签名,则会进入下一步。
    第四步:当第三步返回方法方法签名后,就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等
    第五步:若没有实现- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么会进入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方法,那么就会crash,然后提示找不到响应的方法。到此,动态解析的流程就结束了。

    • 1, 第一种思路:在+ (BOOL)resolveInstanceMethod:(SEL)sel 拦截然后动态添加方法。
    //第一步:在调用某个对象的某个方法找不到的时候,会先调用这个方法,允许我动态添加方法
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
            class_addMethod(self.class, sel, (IMP)(eat), "v:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    void eat(id self, SEL cmd){
        NSLog(@"%@ is eat",self);
    }
    
    • 2,第二种思路,重定向接受者
    @interface ZDPig ()
    {
        ZDDog * _dog;
    }
    
    @end
    
    @implementation ZDPig
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _dog = [ZDDog new];
        }
        return self;
    }
    
    //第一步,我们不动态添加方法,返回NO
    +(BOOL)resolveInstanceMethod:(SEL)sel
    {
        return NO;
    }
    //第二步,重定向接受者
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(eat)) {
            //要确定 _dog  要有这个方法,不然也会crash 的
            return _dog;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    • 3, 第三种思路:在消息转发的的最后一个阶段,将消息转给其他对象
    // 第一步,我们不动态添加方法,返回NO
    +(BOOL)resolveInstanceMethod:(SEL)sel
    {
        return NO;
    }
    // 第二步,备选提供响应aSelector的对象,我们不备选,因此设置为nil,就会进入第三步
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        return nil;
    }
    //必须重写这个方法,消息转发使用这个方法获得的信息创建NSInvocation对象。
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if ([ZDDog instancesRespondToSelector:aSelector]) {
            return [ZDDog instanceMethodSignatureForSelector:aSelector];
        }
        return nil;
    }
    //这一步是最后机会将消息转发给其它对象,对象会将未处理的消息相关的selector,target和参数都封装在anInvocation中。forwardInvocation:像未知消息分发中心,将未知消息转发给其它对象。注意的是forwardInvocation:方法只有在消息接收对象无法正常响应消息时才被调用。
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        [anInvocation invokeWithTarget:[ZDDog new]];
    }
    

    四,Method Swizzling

    Method 的相关函数

    // 调用指定方法的实现,返回的是方法实现时的返回,参数receiver不能为空,这个比method_getImplementation和method_getName快
    id method_invoke ( id receiver, Method m, ... );
    // 调用返回一个数据结构的方法的实现
    void method_invoke_stret ( id receiver, Method m, ... );
    // 获取方法名,希望获得方法明的C字符串,使用sel_getName(method_getName(method))
    SEL method_getName ( Method m );
    // 返回方法的实现
    IMP method_getImplementation ( Method m );
    // 获取描述方法参数和返回值类型的字符串
    const char * method_getTypeEncoding ( Method m );
    // 获取方法的返回值类型的字符串
    char * method_copyReturnType ( Method m );
    // 获取方法的指定位置参数的类型字符串
    char * method_copyArgumentType ( Method m, unsigned int index );
    // 通过引用返回方法的返回值类型字符串
    void method_getReturnType ( Method m, char *dst, size_t dst_len );
    // 返回方法的参数的个数
    unsigned int method_getNumberOfArguments ( Method m );
    // 通过引用返回方法指定位置参数的类型字符串
    void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
    // 返回指定方法的方法描述结构体
    struct objc_method_description * method_getDescription ( Method m );
    // 设置方法的实现
    IMP method_setImplementation ( Method m, IMP imp );
    // 交换两个方法的实现
    void method_exchangeImplementations ( Method m1, Method m2 );
    

    关于 Method Swizzling 已经有很多文章了,这里就不多说了,推荐一个第三库 RSSwizzle介绍RSSwizzle 的资料

    相关文章

      网友评论

          本文标题:Runtime总结

          本文链接:https://www.haomeiwen.com/subject/tupazftx.html