美文网首页
Runtime 的概念和使用场景 (二)

Runtime 的概念和使用场景 (二)

作者: 洱舟 | 来源:发表于2020-05-12 15:47 被阅读0次

    runtime 的概念和使用场景

    一、runtime 是什么
    1. 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时
    2. Runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
    3. 平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者
    4. Object-C需要Runtime来创建类和对象,进行消息发送和转发
    二、runtime 的使用场景
    • 给系统分类添加属性、方法
    • 方法交换
    • 获取对象的属性、私有属性
    • 字典转换模型
    • KVC、KVO
    • 归档(编码、解码)
    • NSClassFromString class<->字符串
    • block
    • 类的自我检测
    1.0、给对象的增加属性
    1.1为什么不能再分类中直接增加成员变量?这里我们可以查看分类的实现。
    //Category表示一个结构体指针的类型
    
    struct _category_t {
        const char *name;
        struct _class_t *cls;
        const struct _method_list_t *instance_methods;
        const struct _method_list_t *class_methods;
        const struct _protocol_list_t *protocols;
        const struct _prop_list_t *properties;
    };
    

    结果:可以看到,实现的结构体指针中有 类名、类、实例方法列表、类方法列表、协议列表、属性列表。但是却没有

    const struct _ivar_list_t *ivars;
    

    在分类中添加属性,不能生成成员变量,以及生成Geter/Seter方法,需要自己手动去实现GET/SET方法。然后利用关联对象去设置属性的值,但是却还是没有生成对应成员变量。

    #import "Person.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person (Name)
    
    @property (nonatomic, copy) NSString *name;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "Person+Name.h"
    #import <objc/runtime.h>
    
    static NSString *nameKey = @"name";
    
    @implementation Person (Name)
    
    - (void)setName:(NSString *)name{
        
        /**
         * @param object 关联的源对象
         * @param key 关联的Key
         * @param value 关联的值
         * @param policy 关联策略
         * objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
         id _Nullable value, objc_AssociationPolicy policy);
         */
        objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name{
        
        /**
         * @param object 关联的源对象
         * @param key 关联的Key
         * objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
         */
        
        return objc_getAssociatedObject(self, &nameKey);
    }
    
    @****end****
    
    1.2 给对象增加方法
    //viewController 中p对象调用未实现方法eat
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        Person *p = [[Person alloc]init];
        //调用未实现的方法
        [p performSelector:@selector(eat)];
    }
    
    #import "Person.h"
    #import <objc/message.h>
    
    void eat(id self ,SEL _cmd)
    {
          // 实现内容
          NSLog(@"%@的%@方法动态实现了",self,NSStringFromSelector(_cmd));
    }
    
    @implementation Person
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        
        /*
          NSString *sel = [NSStringFromSelector(sel); //将SEL 数据转换成字符串
        
          第一个参数: cls:给哪个类添加方法
          第二个参数: SEL name:添加方法的编号
          第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)
          第四个参数: types :方法类型(函数的返回值类型,参数类型),需要用特定符号,参考API
          v -> void 表示无返回值
          @ -> object 表示id参数
          : -> method selector 表示SEL
    
         */
        if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
            class_addMethod(self, sel, (IMP)eat, "v@:");
            return YES;
        }
        
        return [super resolveInstanceMethod:sel];
    }
    
    @end
    
    
    2.0 方法交换

    [图片上传失败...(image-eb8338-1589277849589)]

    /*
        实例方法交换
    */
    
    
    + (void)load
    {
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
    
      Method origleMe = class_getInstanceMethod([self class], @selector(viewWillAppear:)); // 如果子类没有实现  这个获取到的是superClass的方法
      Method newMe = class_getInstanceMethod([self class], @selector(my_viewWillAppear:));
    
    // 这个方法  往类里面添加 方法
    //1.如果类没有实现这个方法,class_addMethod给类添加一个方法,方法的选择器还是它本身,方法的实现和参数类型都是要替换的新的方法,这种情况返回的bool是YES,在调用class_replaceMethod替换新增的方法的实现为继承的superclass的方法实现,
    
    // 疑问1 class_addMethod 如果添加成功,添加的方法的实现其实是新增的方法的实现,class_replaceMethod 替换的时候获取方法IMP时候应该用最开始获取的method,如果不这样有可能用,再次获取Method那么class_replaceMethod替换的还是新增的方法,相当于系统的和新的方法的实现都是新增的实现 验证 正确 ,如果这样写 会循环调用
     //2.如果类里面以及有这个方法,class_addMethod添加失败,直接交换两个方法的实现即可
       BOOL isSuccess = class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(newMe), method_getTypeEncoding(newMe));
    
    // 测试 疑问1
    //    Method aOrigleMe = class_getInstanceMethod([self class], @selector(viewWillAppear:));
       if (isSuccess) {
            class_replaceMethod([self class], method_getName(newMe), method_getImplementation(origleMe), method_getTypeEncoding(origleMe));
    // 测试 疑问1
    //      class_replaceMethod([self class], method_getName(newMe), method_getImplementation(aOrigleMe), method_getTypeEncoding(aOrigleMe));
        } else {
            method_exchangeImplementations(origleMe, newMe);
        }
      });
    }
    - (void)my_viewWillAppear:(BOOL)animated
    {
         [self my_viewWillAppear:animated];
    //  [self viewWillAppear:animated];
         NSLog(@"%s",__func__);
    }
    
    
    /*
        类方法交换
    */
    + (void)load
    {
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
      // 这个应为要获取类方法,所以需要获取到元类对象
      // 因为实例方法存放在类对象里面,类方法存放在元类对象里面
      Class aClass = object_getClass(self);
    
    
      SEL orgiSel = @selector(testOrgiClassMethod);
      SEL newSel = @selector(testNewClassMethod);
    
      Method oriClassMe = class_getClassMethod(aClass, orgiSel);
      Method newClassMe = class_getClassMethod(aClass, newSel);    
        BOOL isSuccess = class_addMethod(aClass, orgiSel, method_getImplementation(newClassMe), method_getTypeEncoding(newClassMe));
        if (isSuccess) {
          class_replaceMethod(aClass, newSel, method_getImplementation(oriClassMe), method_getTypeEncoding(oriClassMe));
        } else {
        method_exchangeImplementations(oriClassMe, newClassMe);
        }
      });
    }
    
    + (void)testOrgiClassMethod
    {
      NSLog(@"%s",__func__);
    }
    
    + (void)testNewClassMethod
    {
      NSLog(@"%s",__func__);
    }
    

    这里需要注意实例方法和类方法交换的不同点,获取类,和获取方法实现(IMP)

    3.0 获取对象的属性、私有属性

    //获取UIPageControl 属性
    
    unsigned int count;
      objc_property_t *propertyList = class_copyPropertyList([UIPageControl class], &count);
      for (unsigned int i=0; i<count; i++) {
          
        objc_property_t v = propertyList[i];
        const char *name = property_getName(v);
        NSLog(@"name = %s ",name);
    }
    
    //打印属性
    
    2020-05-12 13:37:49.477157+0800 Business[8658:654027] name = hash
    2020-05-12 13:37:49.477234+0800 Business[8658:654027] name = superclass
    2020-05-12 13:37:49.477294+0800 Business[8658:654027] name = description
    2020-05-12 13:37:49.477346+0800 Business[8658:654027] name = debugDescription
    2020-05-12 13:37:49.477398+0800 Business[8658:654027] name = legibilityStyle
    2020-05-12 13:37:49.477458+0800 Business[8658:654027] name = legibilitySettings
    2020-05-12 13:37:49.477514+0800 Business[8658:654027] name = numberOfPages
    2020-05-12 13:37:49.477564+0800 Business[8658:654027] name = currentPage
    2020-05-12 13:37:49.477616+0800 Business[8658:654027] name = hidesForSinglePage
    2020-05-12 13:37:49.477666+0800 Business[8658:654027] name = defersCurrentPageDisplay
    2020-05-12 13:37:49.477715+0800 Business[8658:654027] name = pageIndicatorTintColor
    2020-05-12 13:37:49.477758+0800 Business[8658:654027] name = currentPageIndicatorTintColor
    

    如果要获取私有私有属性的话,方法类似,只不过改成获取成员变量列表了

    // 获取成员变量数组
    unsigned int count;
      Ivar *ivars = class_copyIvarList(cla, &outCount);
      for (unsigned int i=0; i<count; i++) {
          
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(v);
        NSLog(@"name = %s ",name);
    }
    

    4.0 字典转模型

    4.1 简单的Dictionary
    NSDictionary *dictionary = @{
         @"name" : @"Xiaoming",
         @"age" : @18,
         @"sex" : @"男"
        };
    
    #import "NSObject+Model.h"
    #import <objc/message.h>
    
    @implementation NSObject (Model)
    
    + (instancetype)modelWithDictionary:(NSDictionary *)dictionary{
        NSObject *object = [[self alloc] init];
        [object configModelWithDictionary:dictionary];
        return object;
    }
    
    - (void)configModelWithDictionary:(NSDictionary *)dictionary{
        
        Class cls = [self class];
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(cls, &count);
        for (unsigned int i = 0; i < count; i ++) {
            
            Ivar ivar = ivars[i];
            //获取成员变量的名字
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //去除下划线
            key = [key substringFromIndex:1];
            // 取出字典的值
            id value = dictionary[key];
            if (value == nil) continue;
            // 利用KVC将字典中的值设置到模型上
            [self setValue:value forKeyPath:key];
        }
        //释放指针
        free(ivars);
    }
    
    @end
    
    NSDictionary *dictionary = @{
         @"name" : @"Xiaoming",
         @"age" : @18,
         @"sex" : @"男"
        };
    
        Person *p = [Person modelWithDictionary:dictionary];
        NSLog(@"p.name = %@",p.name);
        NSLog(@"p.nageame = %@",p.age);
        NSLog(@"p.sex = %@",p.sex);
        
    
    2020-05-12 14:09:00.503762+0800 Business[9629:761184] p.name = Xiaoming
    2020-05-12 14:09:00.503856+0800 Business[9629:761184] p.nageame = 18
    2020-05-12 14:09:00.503930+0800 Business[9629:761184] p.sex = 男
    
    4.2 字典中嵌套字典
    NSDictionary *dictionary = @{
         @"name" : @"Xiaoming",
         @"age" : @18,
         @"sex" : @"男",
         @"school" : @{
                         @"name" : @"海淀一中",
                         @"address" : @"海淀区",
                         @"grade" : @{
                                      @"name" : @"九年级",
                                      @"teacher" : @"Mr Li"
                                      }
                        }
        };
    
    //创建Model  Person.h
    
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    @class School;
    @class Grade;
    
    @interface Person : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *sex;
    @property (nonatomic, strong) NSNumber *age;
    @property (nonatomic, strong) School *school;
    @end
    
    @interface School : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *address;
    @property (nonatomic, strong) Grade *grade;
    @end
    
    @interface Grade : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *teacher;
    @end
    
    NS_ASSUME_NONNULL_END
    
    //Person.m
    
    @implementation Person
    
    @end
    
    @implementation School
    
    @end
    
    
    @implementation Grade
    
    @end
    
    #import "NSObject+Model.h"
    #import <objc/message.h>
    
    @implementation NSObject (Model)
    
    + (instancetype)modelWithDictionary:(NSDictionary *)dictionary{
        NSObject *object = [[self alloc] init];
        [object configModelWithDictionary:dictionary];
        return object;
    }
    
    - (void)configModelWithDictionary:(NSDictionary *)dictionary{
        
        Class cls = [self class];
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(cls, &count);
        for (unsigned int i = 0; i < count; i ++) {
            
            Ivar ivar = ivars[i];
            //获取成员变量的名字
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //去除下划线
            key = [key substringFromIndex:1];
            // 取出字典的值
            id value = dictionary[key];
            if (value == nil) continue;
            // 利用KVC将字典中的值设置到模型上
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            
            /*
            NSLog(@"type = %@",type);
            2020-05-12 15:04:58.817721+0800 Business[11356:937324] type = @"NSString"
            2020-05-12 15:04:58.817830+0800 Business[11356:937324] type = @"NSString"
            2020-05-12 15:04:58.817905+0800 Business[11356:937324] type = @"NSNumber"  
            
            这就要去除@"",取得NSString、NSNumber
            */
            // 如果属性是对象类型(字典)
           NSRange range = [type rangeOfString:@"@"];
           if (range.location != NSNotFound) {
               // 那么截取对象的名字(比如@"School",截取为School)
               type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
               
               // 排除系统的对象类型(如果人为的设置自定义的类带@”NS“如:NSSchool,则会出现错误)
               if (![type hasPrefix:@"NS"]) {//字典
                   //将对象名转换为对象的类型,将新的对象字典转模型(递归),如Grade,并将其对象grade对应的字典转换成模型
                   Class class = NSClassFromString(type);
                   value = [class modelWithDictionary:value];
               }
           }
            [self setValue:value forKeyPath:key];
        }
        //释放指针
        free(ivars);
    }
    
    @end
    
    NSDictionary *dictionary = @{
         @"name" : @"Xiaoming",
         @"age" : @18,
         @"sex" : @"男",
         @"school" : @{
                         @"name" : @"海淀一中",
                         @"address" : @"海淀区",
                         @"grade" : @{
                                      @"name" : @"九年级",
                                      @"teacher" : @"Mr Li"
                                      }
                        }
        };
    
        Person *p = [Person modelWithDictionary:dictionary];
        NSLog(@"p.name = %@",p.name);
        NSLog(@"p.nageame = %@",p.age);
        NSLog(@"p.sex = %@",p.sex);
        
        School *school = p.school;
        NSLog(@"school.name = %@",school.name);
        NSLog(@"school.nageame = %@",school.address);
        
        Grade *grade = school.grade;
        NSLog(@"grade.name = %@",grade.name);
        NSLog(@"grade.teacher = %@",grade.teacher);
    
    //打印结果
    
    2020-05-12 15:04:58.818413+0800 Business[11356:937324] p.name = Xiaoming
    2020-05-12 15:04:58.818483+0800 Business[11356:937324] p.nageame = 18
    2020-05-12 15:04:58.818545+0800 Business[11356:937324] p.sex = 男
    2020-05-12 15:04:58.818600+0800 Business[11356:937324] p.isMarray = 1
    2020-05-12 15:04:58.818641+0800 Business[11356:937324] school.name = 海淀一中
    2020-05-12 15:04:58.818695+0800 Business[11356:937324] school.nageame = 海淀区
    2020-05-12 15:04:58.818749+0800 Business[11356:937324] grade.name = 九年级
    2020-05-12 15:04:58.818798+0800 Business[11356:937324] grade.teacher = Mr Li
    

    5、KVO、KVC

    5.1 KVC 的原理

    KVC 原理剖析

    5.2 如何手动实现KVO

    Glow技术团队-如何自己动手实现 KVO

    相关文章

      网友评论

          本文标题:Runtime 的概念和使用场景 (二)

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