美文网首页
iOS基础之runtime

iOS基础之runtime

作者: ValienZh | 来源:发表于2016-05-16 10:14 被阅读68次

    运行时-RunTime

    1. 什么是运行时

    1. 一套纯C语言的API
    2. 可以做很多底层操作,例如:
      • 动态添加对象的成员变量 和 方法.
      • 动态交换两个方法的 实现.(替换系统的方法/监听一些事).
      • 获得某个类 所有成员方法,所有成员变量.

    因为 编译器 最终都会将 OC代码 转化 为运行时代码;

    例:调用方法即是 向其发送了消息  等于底层:
     objc_msgSend(receiver,@selector(方法)); //向receiver 发送方法消息
    验证: 可以通过终端 输入命令 : clang -rewrite-objc 用C重写,可以看到上面代码;
    

    2. 运行时使用

    常用场景

    1. 将某些OC代码转为运行时代码;探究底层,比如block的实现原理;
    2. 拦截系统自带的方法调用,交换方法;比如alloc,imageNamed等; (Swizzle黑魔法)
    3. 获取所有成员属性列表,实现字典和模型的自动转换;(KVC有缺陷:名字要一样, 键值对数有要求.)
    4. 用分类增加成员属性(正常情况下:分类中声明的属性没有合成存取器的实现),写框架;

    常用函数:

    • 获取类方法 : class_getClassMethod(class cls,@select());
    • 获取对象方法: class_getInstanceMethod( class cls,@select());
    • 交换方法: method_exchangeImplementations(m1,m2);
    • 调用发送消息: objc_msgSend(receiver,@selector(方法));
    • 将value跟对象关联起来: objc_setAssociatedObject
    • 获得某个类的所有成员变量: class_copyIvarList

    2.1 交换方法/拦截消息.

    引用头文件<objc/runtime.h>

    //交换Person 的 两个方法;
    Method m1 = class_getClassMethod ([Person class],@Selector(run));
    Method m2 = class_getClassMethod ([Person class],@Selector(eat));
    method_exchangeImplementations(m1,m2);
    

    一般是用来交换系统的方法;当你觉得系统方法不够好,功能不够,或是有特殊需求要整体更换某一效果的;(PS.分类重写系统方法虽然可以覆盖系统的方法,但是这样系统的此方法就会完全的失效,想要保留的功能也会失效.)

    //例:1. 增加系统方法的 一个适配iOS7 功能:不同系统显示不同风格图片 
    + (void)load  //当某个类或者分类加载进内场是,就会调用一次. 在此方法里设置交换;
    {把 UIImage 的 imageNamed:方法和我们给它分类添加的 bq_imageNamed: 方法交换};
    + (UIImage *)bq_imageNamed:(NSString *)name {
        double version = [[UIDevice currentDevice].systemVersion doubleValue];
        if (version >= 7.0){ //系统大于7.0 进行图片名拼接;  
            name = [name stringByAppendingString:@"_ios7"];
        } 
        return [UIImage bq_imageNamed:name];  // 注意: 方法已经交换了; 
    }
    //2. 交换系统方法后就可以拦截系统调用的消息,进行系统方法的监听,原理代码如上;
    

    2.2 分类增加成员属性

    引用头文件<objc/runtime.h>

    例1 : 如果要给所有OC对象添加一个属性:--本质:将某个值 跟某个对象 关联起来
    相当于:让对象内存中间挤出一个空间用来存值(每个对象 存的值都是独立的);

    @interface NSObject (Extension)
    @property (nonatomic, copy) NSString *name; 
    // 分类中使用@property只会生成set/get的声明没有实现; 
    @end
    
    //.m 中
    @implementation NSObject (Extension)
    char NameKey;     
    
    //用运行时实现set方法
    -(void)setName: (NSString * )name {
        /*
          设置关联对象 /Users/Valien/百度云同步盘/同步盘/百度云同步盘/同步盘/运行时runtime.mkd
          参数1: 关联哪个对象 
          参数2: 取值的key (每调用一次objc方法,就会关联一个值,为了知道要取的是哪一个值,给value联系一个key)  
          参数3: 值
          参数4: 属性的存储关键字(如copy,retain等)
        */
        objc_setAssociatedObject(self, &NameKey, name, OBJC_ASSOCIATION_COPY_ANATOMIC);  
    }
    
    -(NSString*) name {
        return objc_getAssociatedObject(self, &NameKey);
    }
    

    可以使用分类来增加计算型属性, 来设置读取,本来已有的属性 ; 只需声明后,正常实现setget方法即可.

    2.3 字典模型自动转换--获取类的所有成员变量

    Unsigned int outCount ;  //用于存储成员变量数量
    
    //返回的是指针, 把指针和count结合起来, 就如同获取 成员变量数组
    Ivar *ivars = class_copyIvarList([Person class], &outCount); 
    
    //遍历: 
    for (int i=0; i<outCount;i++) {
        Ivar ivar = ivars[i] ;
        
        //获得每个成员变量 和 类型;
        const char *name = ivar_getName(ivar);
        const char *type = ivar_getTypeEncoding(ivar);
    }
    free(ivars);
    
    

    1. 归解档

    //1. 归档解档时,通过协议方法需要告诉对象 存取哪些成员变量  如何存取;
        如果:  对象的成员很多 , 怎么办, 写垃圾代码么 ?
    //归档
    -(void)encodeWithCoder:(NSCoder *) encoder {
        Unsigned int outCount ; 
        Ivar *ivars = class_copyIvarList([Person class], &outCount); 
    
        for (int i=0; i<outCount; i++){
            Ivar ivar = ivars[i] ;
    
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            id value = [self valueForKeyPath:key]; 
            [encoder encodeObject:value forKey:key]; //编码
        }
        free(ivars);
    }
    //解档
    - (void)initWithCoder:(NSCoder*)decoder {
        Unsigned int outCount ; 
        Ivar *ivars = class_copyIvarList([Person class], &outCount); 
        for (int i=0; i<outCount; i++) {
            Ivar ivar = ivars[i] ;
    
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            id value = [decoder decodeobjectForKey:key];
            [self setValue:value forKeyPath:key]; //解码
        }
        free(ivars);
    }
    

    注意:由于C语言中包含creat/copy/retain/new等关键字,那么最后需要单独的释放free()/xxxrelease(如有CG前缀的); // initWithCoder不要写在分类中,否则父类方法会被覆盖;

    ps. 如果某个类继承,那么从父类继承的属性按上面方法不能完全同步解归档;需要补充

    Class c = self.class;
    while(c&& c!= [NSObject class]) { //没有父类就为nil
        Unsigned 
        xxxxx  
        free(ivars)
        
        c = [c superclass];
    }
    

    2. 实现字典模型自转换

    //当属性 name与字典 key一一对应时;(其实不对应也成功,应为是遍历的是属性组,有这个属性才赋值,没有就不会执行) 
    Class c = self.class;
    while(c&& c!= [NSObject class]) { //没有父类就为nil
        Unsigned int outCount ; 
        Ivar *ivars = class_copyIvarList([Person class], &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];
            // 将字典中的值设置到模型上
            [self setValue:value forKeyPath:key];
            //也可使用运行时方法: objc_setIvar(c,name,value)
        }
        free(ivars);
        c = [c superclass];
    }
    
    //当模型嵌套模型时: 
        //当遍历到这个嵌套属性时. 传入的value 就是一个就是字典; 
        //只需调用一次这个模型的字典模型转换方法,传入字典; 具体细节在于判断 
        //for循环中添加如下代码:
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        NSRange range = [type rangeOfString:@"@"];
        if(range.location != NSNotFound) { // 为了通用,能找到@,表示是@{}字典  
            type = [type substringWithRange:NSMakeRange(2,type.length-3)];//根据类型名字位置与长度截取 .type就是类型名了
            if(![type hasPrefix:@"NS"]) { // 排除 NS开头的类
                value = [NSClassFormString(type) objectWithDict:value]; //递归方法
            }
        }
    
    //最终: 可以解决一般字典转模型自动转换 :
    // 唯一还有一点是 属性是模型数组的话 ; 需要继续解决;
    
    //如果模型有某个属性, 字典里面没有, 访问字典就会报错. KVC的value不能设空值.
    加一句: if (nil == value)  continue;
    

    具体细节就不一一列出了,本文准主要讨论的runtime.对字典模型自转换有兴趣的可以去github上面搜索,这类框架有很多.

    相关文章

      网友评论

          本文标题:iOS基础之runtime

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