美文网首页iOS知识
iOS Runtime(一)-简介及使用示例

iOS Runtime(一)-简介及使用示例

作者: 搬砖的crystal | 来源:发表于2021-07-27 13:59 被阅读0次

    OC语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。

    这种特性意味着OC不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于OC来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。

    1. 简介

    (1)简称运行时,是一套比较底层的纯C语言API。
    (2)Runtime是指将数据类型的确定由编译时推迟到了运行时。
    (3)OC代码,在程序运行过程中,最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者。
    (4)OC需要Runtime来创建类和对象,进行消息发送和转发。

    2.使用

    (1)基本使用

    • 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法
    • 遍历一个类中所有的成员变量、属性以及所有方法
    • 消息传递和转发

    (2)典型使用

    • 给系统分类添加属性、方法
    • 方法交换
    • 获取对象的属性、私有属性
    • 字典转模型
    • KVC、KVO
    • 归档(编码、解码)
    • block
      ......
    3.使用示例
    (1)动态交换两个方法

    应用场景:当第三方框架或者系统原生功能不能满足我们的时候,可以保持系统原有方法功能基础上,添加额外的功能。

    #import "ViewController.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        /**
         加载一张图片,提示是否加载成功
         */
        //调用系统加载图片的方法
        UIImage *image = [UIImage imageNamed:@"44"];
    }
    @end
    
    //UIImage分类
    #import "UIImage+ImageLoad.h"
    #import <objc/message.h>
    @implementation UIImage (ImageLoad)
    /**
     load方法:把类加载进内存的时候调用,只会调用一次
     方法应先交换,再去调用
     */
    + (void)load {
        //1.获取imageNamed方法地址
        //class_getClassMethod 获取某个类的方法
        Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
        //2.获取in_imageNamed方法地址
        Method in_imageNamedMethpd = class_getClassMethod(self, @selector(in_imageNamed:));
        //3.交换方法地址,相当于交换实现方法
        method_exchangeImplementations(imageNamedMethod, in_imageNamedMethpd);
    }
    /**
     不会出现死循环
     调用imageNamed: -> in_imageNamed:
     调用in_imageNamed: -> imageNamed:
     */
    + (UIImage *) in_imageNamed:(NSString *)name{
        //实际上调用的是系统的imageNamed:
        UIImage *image = [UIImage in_imageNamed:name];
        if (image) {
            NSLog(@"加载成功");
        }else{
            NSLog(@"加载失败");
        }
        return image;
    }
    /**
     不能在分类中重写系统方法imageNamed:,会把系统的功能覆盖掉,而且分类中不能调用super
    + (UIImage *)imageNamed:(NSString *)name{
        
    }
    */
    @end
    
    (2)给分类动态添加属性

    原理:给一个类添加属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。(应用场景:给系统的类添加属性的时候,可以使用runtime【系统 NSObject 添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的成员变量和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。】)

    #import <Foundation/Foundation.h>
    @interface NSObject (Property)
    //@property在分类中,只会生产get、set、方法声明,不会生产实现,也不会生产带下划线的成员属性
    @property (nonatomic,strong)NSString *name;
    @end
    
    #import "NSObject+Property.h"
    #import <objc/message.h>
    @implementation NSObject (Property)
    -(void)setName:(NSString *)name{
        /**
         objc_setAssociatedObject将某个值跟某个对象关联起来,将某个值存储到某个对象中
         */
        objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    -(NSString *)name{
        return objc_getAssociatedObject(self, @"name");
    }
    @end
    
    #import "ViewController.h"
    #import "NSObject+Property.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSObject *objc = [[NSObject alloc]init];
        objc.name = @"zhangsan";
        NSLog(@"%@",objc.name);
    }
    
    @end
    
    (3)字典转模型

    字典转模型的方式:
    1)一个一个属性赋值
    2)字典转模型KVC实现
    KVC字典转模型必须保证模型中属性和字典中的key一一对应,可以重写重写对象的setValue:forUndefinedKey:,把系统的方法覆盖

    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    
    @property (nonatomic,strong)NSString *name;
    @property (nonatomic,assign)NSInteger age;
    #pragma mark - 模型构造函数
    +(instancetype)personWithDict:(NSDictionary *)dict;
    -(instancetype)initWithDict:(NSDictionary *)dict;
    @end
    
    #import "Person.h"
    
    @implementation Person
    
    +(instancetype)personWithDict:(NSDictionary *)dict{
        return [[self alloc]initWithDict:dict];
    }
    
    -(instancetype)initWithDict:(NSDictionary *)dict{
        self = [super init];
        if (self) {
            //方法一:直接设置
            _name = dict[@"name"];
            _age = [dict[@"age"] integerValue];
            //方法二:使用KVC设置
            [self setValue:dict[@"name"] forKey:@"name"];
            [self setValue:dict[@"age"] forKey:@"age"];
            //方法三:遍历字典设置
            for (NSString *key in dict) {
                id value = dict[key];
                [self setValue:value forKey:key];
            }
            //方法四:简化方法三
            [self setValuesForKeysWithDictionary:dict];
        }
        return self;
    }
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
        
    }
    @end
    

    3)字典转模型runtime实现

    #import <Foundation/Foundation.h>
    @protocol ModelDelegate<NSObject>
    @optional
    //提供一个协议,只要准备这个协议的类,都能把数组中的字典转成模型(返回字典为数组属性名:模型名)
    +(NSDictionary *)arrayContainModelClass;
    @end
    @interface NSObject (Model)
    //字典转模型
    +(instancetype)objectWithDoct:(NSDictionary *)dict;
    @end
    
    #import "NSObject+Model.h"
    #import <objc/message.h>
    
    @implementation NSObject (Model)
    
    +(instancetype)objectWithDoct:(NSDictionary *)dict{
        //创建模型对象
        id objc = [[self alloc]init];
        unsigned int count = 0;
        //获取成员属性数组
        Ivar *ivarList = class_copyIvarList(self, &count);
        //遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
        for (int i = 0; i < count; i++) {
            //获取成员属性
            Ivar ivar = ivarList[i];
            //获取成员属性名 c -> oc字符串
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //_成员属性名 -> 字典key
            NSString *key = [ivarName substringFromIndex:1];
            //字典取出对应value给模型属性赋值
            id value = dict[key];
            //获取成员属性类型
            NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            //二级转换,字典中还有字典,也需要把字典转换成模型
            //判断value是不是字典
            if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
                //是字典对象,并且属性名对应类型是自定义类型
                //处理类型字符串 @\"User\" -> User
                ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
                ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
                //自定义对象,并且值是字典
                Class modelClass = NSClassFromString(ivarType);
                if (modelClass) {
                    //字典转模型
                    value = [modelClass objectWithDoct: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 objectWithDoct:dict];
                        [arrM addObject:model];
                    }
                    //把模型数组赋值给value
                    value = arrM;
                }
            }
            if (value) {
                //KVC字典转模型
                [objc setValue:value forKey:key];
            }
        }
        //返回对象
        return objc;
    }
    @end
    

    测试:

    #import <Foundation/Foundation.h>
    #import "CarType.h"
    @interface Car : NSObject
    @property (nonatomic,strong)CarType *carType;
    @property (nonatomic,assign)NSInteger speed;
    @property (nonatomic,strong)NSArray * CarColorArr;
    @end
    
    #import "Car.h"
    #import "NSObject+Model.h"
    @interface Car()<ModelDelegate>
    @end
    @implementation Car
    //实现协议方法
    + (NSDictionary *)arrayContainModelClass{
        return @{@"CarColorArr":@"CarColor"};
    }
    @end
    
    #import <Foundation/Foundation.h>
    @interface CarType : NSObject
    @property (nonatomic,strong)NSString *type;
    @end
    
    #import <Foundation/Foundation.h>
    @interface CarColor : NSObject
    @property (nonatomic,strong)NSString *color;
    @end
    
    #import "ViewController.h"
    #import "NSObject+Model.h"
    #import "Car.h"
    #import "CarType.h"
    #import "CarColor.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSDictionary * dict = @{
                                @"carType":@{@"type":@"BMW"},
                                @"speed":@120,
                                @"CarColorArr":@[@{@"color":@"red"},@{@"color":@"blue"}]
                                };
        Car *car = [Car objectWithDoct:dict];
        NSLog(@"type == %@,speed == %ld",car.carType.type,car.speed);
        CarColor *color = car.CarColorArr[0];
        NSLog(@"color == %@",color.color);
        
    }
    @end
    
    (4)动态添加方法

    应用场景:如果一个类的方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类添加方法解决。

    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    @end
    
    #import "Person.h"
    #import <objc/message.h>
    @implementation Person
    //没有返回值,一个参数
    //void,(id,SEL)
    void aaa(id self,SEL _cmd,NSNumber *meter){
        NSLog(@"走了%@米",meter);
    }
    /**
     任何方法默认都有两个隐式参数,self和_cmd(当前方法的方法编号)
     resolveInstanceMethod:只要一个对象调用了一个未实现的方法,就会调用此方法进行处理(消息转发机制)
     作用:动态添加方法,处理未实现方法
     */
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == NSSelectorFromString(@"walk:")) {
            class_addMethod(self, sel, (IMP)aaa, "v@:@");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    @end
    
    #import "ViewController.h"
    #import "Person.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *p = [[Person alloc]init];
        [p performSelector:@selector(walk:) withObject:@100];
    }
    @end
    
    (5)动态变量控制
    #import "ViewController.h"
    #import "Person.h"
    #import <objc/message.h>
    @interface ViewController ()
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *xiaoming = [[Person alloc]init];
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([xiaoming class], &count);
        for (int i = 0; i<count; i++) {
            Ivar ivar = ivarList[i];
            const char *ivarName = ivar_getName(ivar);
            NSString *name = [NSString stringWithUTF8String:ivarName];
            if ([name isEqualToString:@"_age"]) {
                object_setIvar(xiaoming, ivar, @"22");
                break;
            }
        }
        NSLog(@"%@",xiaoming.age);
    }
    @end
    

    相关文章

      网友评论

        本文标题:iOS Runtime(一)-简介及使用示例

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