zoukankan      html  css  js  c++  java
  • Objective-C runtime的常见应用

      用Objective-C等面向对象语言编程时,"对象"(object)就是"基本构造单元"(building block)。开发者可以通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程就叫做"消息传递"(Messaging)。当程序运行起来以后,为其提供相关支持的代码叫做"Objective-C运行期环境"(Objective-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。即我们写的oc代码,它在运行的时候是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的runtime方法。

      那什么是Objective-C runtime?
      简单来说,Objective-C runtime是一个实现Objective-C语言的C库。对象可以用C语言中的结构体表示,而方法(methods)可以用C函数实现。事实上,他们差不多也是这么干了,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类,对象和它们的方法。
      runtime的常见方法
       //返回一个指向类的成员变量数组的指针
        class_copyIvarList()
        //返回一个指向类的属性数组的指针
        class_copyPropertyList()
        注意:根据Apple官方runtime.h文档所示,上面两个方法返回的指针,在使用完毕之后必须free()。
        ---------------------------------------------------
        //获取成员变量名-->C类型的字符串
        ivar_getName()
        //获取属性名-->C类型的字符串
        property_getName()
        ---------------------------------------------------
        typedef struct objc_method *Method;
        class_getInstanceMethod()
        //以上两个函数传入返回Method类型
        class_getClassMethod()
        ---------------------------------------------------
        //交换两个方法的实现
        method_exchangeImplementations()

      

      runtime在开发中的用途

    1.动态的遍历一个类的所有成员变量,属性,方法,协议等,可用于字典转模型,归档解档操作

        unsigned int count;
    //获取成员变量的结构体 Ivar *ivars = class_copyIvarList([Person class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; //根据ivar获得其成员变量的名称 const char *name = ivar_getName(ivar); //C的字符串转OC的字符串 NSString *key = [NSString stringWithUTF8String:name]; NSLog(@"%d == %@",i,key); } //记得释放 free(ivars);   //获得指向该类所有属性的指针 objc_property_t *properties = class_copyPropertyList([Person class], &count); for (int i = 0; i < count; i++) { //获得该类的一个属性的指针 objc_property_t property = properties[i]; //获取属性的名称 const char *name = property_getName(property); //将C的字符串转为OC的 NSString *key = [NSString stringWithUTF8String:name]; NSLog(@"%d == %@",i,key); } //记得释放 free(properties);   //获取指向该类所有方法的指针 Method *methods = class_copyMethodList([Person class], &count); for (int i = 0; i < count; i++) { //获取该类的一个方法的指针 Method method = methods[i]; //获取方法 SEL methodSEL = method_getName(method); //将方法转换为C字符串 const char *name = sel_getName(methodSEL); //将C字符串转为OC字符串 NSString *methodName = [NSString stringWithUTF8String:name]; //获取方法参数个数 int arguments = method_getNumberOfArguments(method); NSLog(@"%d == %@ %d",i,methodName,arguments); } //记得释放 free(methods);   //获取指向该类遵循的所有协议的指针 __unsafe_unretained Protocol **protocols = class_copyProtocolList([self class], &count); for (int i = 0; i < count; i++) { //获取该类遵循的一个协议指针 Protocol *protocol = protocols[i]; //获取C字符串协议名 const char *name = protocol_getName(protocol); //C字符串转OC字符串 NSString *protocolName = [NSString stringWithUTF8String:name]; NSLog(@"%d == %@",i,protocolName); } //记得释放 free(protocols);

    应用场景:

    • 可以利用遍历类的属性,来快速的进行归档操作。
    • 将从网络上下载的json数据进行字典转模型。

    //注意:归档解档需要遵守<NSCoding>协议,实现以下两个方法
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        unsigned int count;
        //获得指向当前类的所有属性的指针
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        
        for (int i = 0; i < count; i++) {
            //获取指向当前类的一个属性的指针
            objc_property_t property = properties[i];
            //获取C字符串属性名
            const char *name = property_getName(property);
            //C字符串转OC字符串
            NSString *propertyName = [NSString stringWithUTF8String:name];
            //通过关键词取值
            NSString *propertyValue = [self valueForKey:propertyName];
            //编码属性
            [aCoder encodeObject:propertyValue forKey:propertyName];
        }
        //记得释放
        free(properties);
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        unsigned int count;
        //获得指向当前类的所有属性的指针
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        
        for (int i = 0; i < count; i++) {
            //获取指向当前类的一个属性的指针
            objc_property_t property = properties[i];
            //获取C字符串属性名
            const char *name = property_getName(property);
            //C字符串转OC字符串
            NSString *propertyName = [NSString stringWithUTF8String:name];
            //解码属性值
            NSString *propertyValue = [aDecoder decodeObjectForKey:propertyName];
            [self setValue:propertyValue forKey:propertyName];
        }
        //记得释放
        free(properties);
        return self;
    }
    //重写description方法,打印出自定义的属性
    - (NSString *)description{
        NSString *string = [NSString stringWithFormat:@"name=%@ age=%d apples=%@",_name,_age,_apples];
        return string;
    }

       归档解档方法

            //自定义类
         Person *p = [[Person alloc] init]; p.name = @"张三"; p.age = 18; p.apples = @[@"iphone",@"ipad"]; //归档 NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"person.archiver"]; BOOL success = [NSKeyedArchiver archiveRootObject:p toFile:filePath]; if(success){ NSLog(@"归档成功"); }
    //解归档 Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; NSLog(@"%@",person);

    字典转模型

    #import "NSObject+Item.h"
    #import <objc/message.h>
    
    @implementation NSObject (Item)
    
    // 字典转模型
    + (instancetype)objectWithDict:(NSDictionary *)dict
    {
        // 创建对应模型对象
        id objc = [[self alloc] init];
    unsigned
    int count = 0; // 1.获取成员属性数组 Ivar *ivarList = class_copyIvarList(self, &count); // 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值 for (int i = 0; i < count; i++) { // 2.1 获取成员属性 Ivar ivar = ivarList[i]; // 2.2 获取成员属性名 C -> OC 字符串 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 2.3 _成员属性名 => 字典key NSString *key = [ivarName substringFromIndex:1]; // 2.4 去字典中取出对应value给模型属性赋值 id value = dict[key]; // 获取成员属性类型 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // 二级转换,字典中还有字典,也需要把对应字典转换成模型 // // 判断下value,是不是字典 if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
           // 是字典对象,并且属性名对应类型是自定义类型 // user User // 处理类型字符串 @"User" -> User ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@""" withString:@""];      //自定义对象,并且值是字典
    // value :user 字典 -> User模型
        // 获取模型(user)类对象
        Class modalClass = NSClassFromString(ivarType);

        //字典转模型
        if(modalClass){

            value = [modalClass objectWithDict:value];

        }

            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            // 判断值是否是数组
    if ([value isKindOfClass:[NSArray class]]) { // 判断对应类有没有实现字典数组转模型数组的协议 if ([self respondsToSelector:@selector(arrayContainModelClass)]) { // 转换成id类型,就能调用任何对象的方法 id idSelf = self; // 获取数组中字典对应的模型 NSString *type = [idSelf arrayContainModelClass][key]; // 生成模型 Class classModel = NSClassFromString(type); NSMutableArray *arrM = [NSMutableArray array]; // 遍历字典数组,生成模型数组 for (NSDictionary *dict in value) { // 字典转模型 id model = [classModel objectWithDict:dict]; [arrM addObject:model]; } // 把模型数组赋值给value value = arrM; } } // 2.5 KVC字典转模型 if (value) { [objc setValue:value forKey:key]; } } // 返回对象 return objc; } @end

     2.交换方法,可以是不同类的方法,也可以是同类的,用在修改全局属性很便利.

       Method one =  class_getInstanceMethod([Person0 class], @selector(oneMethod));
    
        Method two =  class_getInstanceMethod([Person1 class], @selector(twoMethod));
    
        method_exchangeImplementations(one, two);

    3.添加方法

    - (void)sayFrom
    {
    
        class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");
        if ([self.person respondsToSelector:@selector(guess)]) {
            //Method method = class_getInstanceMethod([self.xiaoMing class], @selector(guess));
            [self.person performSelector:@selector(guess)];
            
        } else{
            NSLog(@"Sorry,I don't know");
        }
        self.textview.text = @"beijing";
    }
    
    void guessAnswer(id self,SEL _cmd){
        
        NSLog(@"i am from beijing");
        
    }

    4.拦截调用动态添加

    /*
    *  
    + (BOOL)resolveClassMethod:(SEL)sel;//调用不存在的类方法时返回No,可以加上自己的处理,返回Yes
    *  + (BOOL)resolveInstanceMethod:(SEL)sel;//跟上面的类似,不过处理的是实例方法
    */

    //
    首先从外部隐式调用一个不存在的方法: [target performSelector:@selector(resolveAdd:) withObject:@"test"];
    //然后,在target对象内部重写拦截调用的方法,动态添加方法。
    void runAddMethod(id self, SEL _cmd, NSString *string){ NSLog(@"add C IMP ", string); }
    + (BOOL)resolveInstanceMethod:(SEL)sel{ //给本类动态添加一个方法 if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) { class_addMethod(self, sel, (IMP)runAddMethod, "v@:*"); } return YES; }

    5.方法上加功能,可用于页面埋点加log

    @implementation UIButton(count)
    //load方法会在类第一次被加载的时候调用

    + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class selfClass = [self class]; SEL oriSEL = @selector(sendAction:to:forEvent:); Method oriMethod = class_getInstanceMethod(selfClass, oriSEL); SEL cusSEL = @selector(mySendAction:to:forEvent:); Method cusMethod = class_getInstanceMethod(selfClass, cusSEL); BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod)); if (addSucc) { class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else { method_exchangeImplementations(oriMethod, cusMethod); } }); } - (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { [[Tool sharedManager] addCount]; [self mySendAction:action to:target forEvent:event]; }

    6.动态变量控制

      unsigned int count = 0;
        Ivar *ivar = class_copyIvarList([self.person class], &count);
        for (int i = 0; i<count; i++) {
            Ivar var = ivar[i];
            const char *varName = ivar_getName(var);
            NSString *proname = [NSString stringWithUTF8String:varName];
            
            if ([proname isEqualToString:@"_name"]) {   //这里别忘了给属性加下划线
                object_setIvar(self.person, var, @"daming");//使用runtime将.name属性的值修改
                break;
            }
        }
        NSLog(@"XiaoMing change name  is %@",self.person.name);

    7.拓展属性

    /*
    *在开发中经常需要给已有的类添加方法和属性,但是Objective-C是不允许给已有类通过分类添加属性的,因为类分类是不会自动生成成员变量的。但是,我们可以通过运行时机制就可以做到了。
    */
    @interface NSObject(height)
    //头文件中声明一个属性 @property (nonatomic, assign) double height;
    @end @implementation NSObject(height)
    static double heightKey;//用来参考 -(void)setHeight:(double)height {

    /*设置关联值(Setter)
    *  void objc_setAssociatedObject(id object, const void *key,id value,objc_AssociationPolicy)

    *  object:与谁关联,通常都是传self

    *  key:唯一键,在获取值时通过该键获取,通常是使用static const void *来声明
    *  value: 关联所设置的值

    *  policy:内存管理策略,比如使用copy

    */
        objc_setAssociatedObject(self, &heightKey, @(height), OBJC_ASSOCIATION_ASSIGN);
    }
    
    -(double)height
    {

    /*获取关联值(Getter)
    *  id objc_getAssociatedObject(id object, const void *key)
    *  object:与谁关联,通常都是传self
    *  key:唯一键,在设置关联时所使用的键
    */

    return [objc_getAssociatedObject(self, &heightKey) doubleValue];
    }

    @end

     以上是关于runtime一些常见的用法,关于详细的原理介绍推荐阅读以下博客:

    Runtime快速入门

    Runtime运行时特性详解

    Objective-C Runtime 运行时


  • 相关阅读:
    开源框架/软件汇总
    如何查看Maven项目的jar包依赖
    我的前端技术栈(2018版)
    解决在Mac上用pyenv安装python3失败的问题
    学习jenv
    学习sbtenv
    解决MAC下修改系统文件没权限的问题
    学习Spring Boot
    学习音标
    C# 对List中的Object进行排序
  • 原文地址:https://www.cnblogs.com/dongliu/p/5435641.html
Copyright © 2011-2022 走看看