Runtime

作者: cdd48b9d36e0 | 来源:发表于2017-02-15 00:06 被阅读19次

    概念

    Runtime就是一个C语言写的API库,通过Runtime这个库,我们可以在程序运行时对类、实例对象、变量、属性、方法进行各种操作。

    1. isa:Runtime中每个对象都有一个isa指针。实例对象的isa指向类,类也是一个对象,类的isa指向该类的元类,所有的元类的isa指向基类(NSObject)的元类,基类的元类的isa指针指向自己。需要注意一点,基类(NSObject)的父类为空,而基类的元类的父类就是基类。
    2. SEL:选择器。我的理解是这就是一个指向方法名字的指针,它的存在就是为了找到函数指针IMP。两个类都有一个相同名字的方法,那么他们的SEL就相同,所以一个SEL可能指向不同的IMP。
    3. IMP:就是函数指针,也就是方法实现的首地址,找到了他就能执行方法。
    4. Method:SEL到IMP的映射。

    消息机制

    1. 先判断receiver为不为nil,是的话直接忽略这次消息发送
    2. 根据isa指针找到实例所属的类
    3. 在类中根据sel查找IMP,先从方法缓存Cache里找,再从方法分发列表里面找
    4. 还是没找到的话就根据指向父类的指针superclass找到父类,再从父类里面去找,直到找到基类NSObject里面为止
    5. 最后还是没找到,那么触发消息转发机制

    消息转发机制(轻松学习消息转发

    void run(id self,SEL _cmd){
    //    NSLog(@"%@正在%@",self,NSStringFromSelector(_cmd));
        NSLog(@"%@正在%s",self,sel_getName(_cmd));
    }
    
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"20");
    //    if (sel == @selector(run)) {
    //        class_addMethod(self, sel, (IMP)run, "v@:");
    //    }
        return [super resolveInstanceMethod:sel];
    }
    
    -(id)forwardingTargetForSelector:(SEL)aSelector{
        NSLog(@"28");
    //    return [[Car alloc]init];
        return self;
    }
    
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSLog(@"34");
        NSString *sel = NSStringFromSelector(aSelector);
        //判断你要转发SEL
        if ([sel isEqualToString:@"run"]) {
            //为你的转发方法手动生成签名
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    -(void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"43");
        SEL aSelector = [anInvocation selector];
        //新建需要转发的消息对象
        Car *aCar = [Car new];
        if ([aCar respondsToSelector:aSelector]) {
            //唤醒这个方法
            [anInvocation invokeWithTarget:aCar];
        }
    }
    

    应用场景(OC最实用的runtime总结,面试、工作你看我就足够了

    应用场景1:解归档,特别是当模型的属性比较多的时候

    核心API:class_copyIvarList这个方法可以得到一个类h和m文件里所有属性的个数及其名字

     - (void)encode:(NSCoder *)aCoder {
        // 一层层父类往上查找,对父类的属性执行归解档方法
        Class c = self.class;
        while (c &&c != [NSObject class]) {
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList([self class], &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
                // 如果有实现该方法再去调用
                if ([self respondsToSelector:@selector(ignoredNames)]) {
                    if ([[self ignoredNames] containsObject:key]) continue;
                }
    
                id value = [self valueForKeyPath:key];
                // 归档
                [aCoder encodeObject:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }
    }
    
     - (void)decode:(NSCoder *)aDecoder {
        // 一层层父类往上查找,对父类的属性执行归解档方法
        Class c = self.class;
        while (c &&c != [NSObject class]) {
    
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList(c, &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
                // 如果有实现该方法再去调用
                if ([self respondsToSelector:@selector(ignoredNames)]) {
                    if ([[self ignoredNames] containsObject:key]) continue;
                }
    
                id value = [aDecoder decodeObjectForKey:key];
                //解档
                [self setValue:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }
    }
    
    应用场景2:方法替换

    楼主经常的做法是用自定义方法替换系统方法防止崩溃。比如往数组、字典里写入空值会导致cash,比如数组读取越界元素,比如URL的网址带有中文时该url会被系统判定为空,此时发起request请求会cash等。这时候就可以用runtime进行方法替换,让他们什么都不变却执行我们新写的加了判断的方法。
    核心API: class_getInstanceMethod/class_getClassMethod/method_exchangeImplementations这三个API可以取得两个方法的名字并对他们进行交换

    //加载这个类的load方法
     +(void)load{
        //class_getClassMethod : 获取类方法
        //class_getInstanceMethod : 获取对象方法
        
        //1.拿到两个方法 苹果原来的URLWithString  和HK_URLWithString
        //2.1 类类型
        //2.2 方法编号
        Method mURLWithStr = class_getClassMethod([NSURL class], @selector(URLWithString:));
        Method mHK_URLWithStr = class_getClassMethod([NSURL class], @selector(HK_URLWithString:));
        //2.交换这两个方法!(你调用A 个么会执行 B )
        method_exchangeImplementations(mURLWithStr, mHK_URLWithStr);    
    }
    +(instancetype)HK_URLWithString:(NSString *)URLString
    {
        NSURL * url = [NSURL HK_URLWithString:URLString];//会找 URLWithString这个方法
        if (url == nil) {
            NSLog(@"哥么是一个空的URL");
        }
        return url;
    }
    
    应用场景3:方法的懒加载(这个实际上消息转发机制里面的方案一)

    核心API:resolveInstanceMethod/resolveClassMethod当一个类被调用了没有实现实例方法或者类方法就会触发,然后class_addMethod动态添加方法,具体的格式全是C语言的,如下

    //C语言的
    //所有的C语言的函数里面!都有这两个隐式参数!只要调用,系统都会传递进来!
    void eat(id self, SEL _cmd){
        NSLog(@"调用了%@对象的%@方法",self,NSStringFromSelector(_cmd));
    }
    void eat1(id self, SEL _cmd,id obj){
         NSLog(@"今晚吃五个%@",obj);
    }
    //当这个类被调用了没有实现的方法!就会来到这里
    +(BOOL)resolveInstanceMethod:(SEL)sel{
    //    NSLog(@"你没有实现这个方法%@",NSStringFromSelector(sel));
        if (sel == @selector(eat)) {
            /*
             1.cls: 类类型
             2.name: 方法编号
             3.imp: 方法实现,函数指针!
             4.types:函数类型 C字符串(代码) void === "v"
             */
           //v代表返回值,后面的都代表参数,具体哪个符号什么意思参看文档,这里@代表Object,:代表方法
            class_addMethod([Pseron class],sel, (IMP)eat, "v@:");
        }else if(sel == @selector(eat:)){
            class_addMethod([Pseron class], sel, (IMP)eat1, "v@:@");
        }
        return [super resolveInstanceMethod:sel];
    }
    
    应用场景4:避免动态绑定

    避免动态绑定的唯一办法就是取得方法的地址,并且直接像函数调用一样调用它。当一个方法会被连续调用很多次,而且您希望节省每次调用方法都要发送消息的开销时,使用方法地址来直接调用方法就显得很有效。

    应用场景5:给Category添加成员变量
    //步骤一:创建一个分类,比如给任何一个对象都添加一个name属性,就是NSObject添加分类(NSObject+Category)
    //步骤二:先在.h 中@property 声明出get 和 set 方法,方便点语法调用
    @property(nonatomic,copy)NSString *name;
    
    //步骤三:在.m 中重写set 和 get 方法,内部利用runtime 给属性赋值和取值
    char nameKey;
     - (void)setName:(NSString *)name {
        // 将某个值跟某个对象关联起来,将某个值存储到某个对象中
        objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
     - (NSString *)name {
        return objc_getAssociatedObject(self, &nameKey);
    
    应用场景6: 字典转模型(这其实就是YYModel或者MJExtension的大致原理)

    以往我们都是利用KVC进行字典转模型,但是它还是有一定的局限性,例如:模型属性和键值对对应不上会crash(虽然可以重写setValue:forUndefinedKey:方法防止报错),模型属性是一个对象或者数组时不好处理等问题,所以无论是效率还是功能上,利用runtime进行字典转模型都是比较好的选择。

    三种特殊情况:
    1.当字典的key和模型的属性匹配不上
    2.模型中嵌套模型(模型属性是另外一个模型对象)
    3.数组中装着模型(模型的属性是一个数组,数组中是一个个模型对象)


    • 当字典的key和模型的属性匹配不上

    不对应有两种,一种是字典的键值大于模型属性数量,这时候我们不需要任何处理,因为runtime是先遍历模型所有属性,再去字典中根据属性名找对应值进行赋值,多余的键值对也当然不会去看了;另外一种是模型属性数量大于字典的键值对,这时候由于属性没有对应值会被赋值为nil,就会导致crash,我们只需加一个判断即可

     - (void)setDict:(NSDictionary *)dict {
    
        Class c = self.class;
        while (c &&c != [NSObject class]) {
    
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList(c, &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
                // 成员变量名转为属性名(去掉下划线 _ )
                key = [key substringFromIndex:1];
                // 取出字典的值
                id value = dict[key];
    
                // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
                if (value == nil) continue;
    
                // 将字典中的值设置到模型上
                [self setValue:value forKeyPath:key];
            }
            free(ivars);
            c = [c superclass];
        }
    }
    
    • 模型的属性是另外一个模型对象

    这时候我们就需要利用runtime的ivar_getTypeEncoding 方法获取模型对象类型,对该模型对象类型再进行字典转模型,也就是进行递归,需要注意的是我们要排除系统的对象类型,例如NSString,下面的方法中我添加了一个类方法方便递归。

     - (void)setDict:(NSDictionary *)dict {  
        Class c = self.class;
        while (c &&c != [NSObject class]) {
    
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList(c, &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
                // 成员变量名转为属性名(去掉下划线 _ )
                key = [key substringFromIndex:1];
                // 取出字典的值
                id value = dict[key];
    
                // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
                if (value == nil) continue;
    
                // 获得成员变量的类型
                NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
    
                // 如果属性是对象类型
                NSRange range = [type rangeOfString:@"@"];
                if (range.location != NSNotFound) {
                    // 那么截取对象的名字(比如@"Dog",截取为Dog)
                    type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
                    // 排除系统的对象类型
                    if (![type hasPrefix:@"NS"]) {
                        // 将对象名转换为对象的类型,将新的对象字典转模型(递归)
                        Class class = NSClassFromString(type);
                        value = [class objectWithDict:value];
                    }
                }
    
                // 将字典中的值设置到模型上
                [self setValue:value forKeyPath:key];
            }
            free(ivars);
            c = [c superclass];
        }
    }
     + (instancetype )objectWithDict:(NSDictionary *)dict {
        NSObject *obj = [[self alloc]init];
        [obj setDict:dict];
        return obj;
    }
    
    • 模型的属性是一个数组,数组中是一个个模型对象

    我们既然能获取到属性类型,那就可以拦截到模型的那个数组属性,进而对数组中每个模型遍历并字典转模型,但是我们不知道数组中的模型都是什么类型,我们可以声明一个方法,该方法目的不是让其调用,而是让其实现并返回模型的类型。
    这块语言可能解释不太清楚,可以参考我的demo,直接运行即可。

     - (void)setDict:(NSDictionary *)dict {  
        Class c = self.class;
        while (c &&c != [NSObject class]) {
    
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList(c, &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
                // 成员变量名转为属性名(去掉下划线 _ )
                key = [key substringFromIndex:1];
                // 取出字典的值
                id value = dict[key];
    
                // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
                if (value == nil) continue;
    
                // 获得成员变量的类型
                NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
    
                // 如果属性是对象类型
                NSRange range = [type rangeOfString:@"@"];
                if (range.location != NSNotFound) {
                    // 那么截取对象的名字(比如@"Dog",截取为Dog)
                    type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
                    // 排除系统的对象类型
                    if (![type hasPrefix:@"NS"]) {
                        // 将对象名转换为对象的类型,将新的对象字典转模型(递归)
                        Class class = NSClassFromString(type);
                        value = [class objectWithDict:value];
    
                    }else if ([type isEqualToString:@"NSArray"]) {
    
                        // 如果是数组类型,将数组中的每个模型进行字典转模型,先创建一个临时数组存放模型
                        NSArray *array = (NSArray *)value;
                        NSMutableArray *mArray = [NSMutableArray array];
    
                        // 获取到每个模型的类型
                        id class ;
                        if ([self respondsToSelector:@selector(arrayObjectClass)]) {
    
                            NSString *classStr = [self arrayObjectClass];
                            class = NSClassFromString(classStr);
                        }
                        // 将数组中的所有模型进行字典转模型
                        for (int i = 0; i < array.count; i++) {
                            [mArray addObject:[class objectWithDict:value[i]]];
                        }
    
                        value = mArray;
                    }
                }
    
                // 将字典中的值设置到模型上
                [self setValue:value forKeyPath:key];
            }
            free(ivars);
            c = [c superclass];
        }
    }
     + (instancetype )objectWithDict:(NSDictionary *)dict {
        NSObject *obj = [[self alloc]init];
        [obj setDict:dict];
        return obj;
    }
    

    相关文章

      网友评论

          本文标题:Runtime

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