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

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 

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 

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
//打印属性

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

4.0 字典转模型

4.1 简单的Dictionary
NSDictionary *dictionary = @{
     @"name" : @"Xiaoming",
     @"age" : @18,
     @"sex" : @"男"
    };
#import "NSObject+Model.h"
#import 

@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 

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 

@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 的概念和使用场景 (二))