美文网首页
iOS runtime的简单用法

iOS runtime的简单用法

作者: 可乐小子 | 来源:发表于2022-11-10 16:32 被阅读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
      (6)数组越界

    动态交换方法,防止数组越界导致崩溃

    import "NSArray+DJSafeIndex.h"

    import <objc/runtime.h>

    @implementation NSArray (DJSafeIndex)

    +(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        // NSArray 是一个类簇,具体有三个子类__NSArray0,__NSSingleObjectArrayI,__NSArrayI,
        
               // 还有一个__NSPlaceholderArray是占位的,不实际使用
               // 对__NSArray0,__NSSingleObjectArrayI来说,下面三种调用的同一个方法objectAtIndex
               // 对__NSArrayI,__NSArrayM来说,objectAtIndex 和 objectAtIndexedSubscript 有不同的实现,
      
        Method method = class_getInstanceMethod(objc_getClass("_NSArray0"), @selector(objectAtIndex:));
        Method changeMethod = class_getInstanceMethod(objc_getClass("_NSArray0"), @selector(emptyObjectIndex:));
        method_exchangeImplementations(method, changeMethod);
        
        
        Method method1 = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(objectAtIndex:));
        Method changeMethod1 = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(singleObjectIndex:));
        method_exchangeImplementations(method1, changeMethod1);
        
        Method method2 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method changeMethod2 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(safe_arrObjectIndex:));
        method_exchangeImplementations(method2, changeMethod2);
        
        
        Method method3 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));
        Method changeMethod3 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(safe_objectAtIndexedSubscript:));
        method_exchangeImplementations(method3, changeMethod3);
        
        
    });
    

    }

    • (id)emptyObjectIndex:(NSInteger)index {

      NSLog(@"__NSArray0 取一个空数组 objectAtIndex , 崩溃") ;

      return nil;

    }

    • (id)singleObjectIndex:(NSInteger)index {

      if (index >= self.count || index < 0) {

       NSLog(@"__NSSingleObjectArrayI 取一个不可变单元素数组时越界 objectAtIndex , 崩溃") ;
      
       return nil;
      

      }

      return [self singleObjectIndex:index];

    }

    • (id)safe_arrObjectIndex:(NSInteger)index{

      if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayI 取不可变数组时越界 objectAtIndex , 崩溃") ;
      
       return nil;
      

      }

      return [self safe_arrObjectIndex:index];
      }

    • (id)safe_objectAtIndexedSubscript:(NSInteger)index{

      if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayI 取不可变数组时越界 objectAtIndexedSubscript , 崩溃") ;
      
       return nil;
      

      }

      return [self safe_objectAtIndexedSubscript:index];
      }

    • (id)mutableArray_safe_objectAtIndexedSubscript:(NSInteger)index{

      if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayM 取可变数组时越界 objectAtIndexedSubscript , 崩溃") ;
      
       return nil;
      

      }

      return [self mutableArray_safe_objectAtIndexedSubscript:index];
      }

    • (id)safeObjectIndex:(NSInteger)index{

      if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayM 取可变数组时越界 objectAtIndex , 崩溃") ;
      
       return nil;
      

      }

      return [self safeObjectIndex:index];

    }

    • (void)safeInsertObject:(id)object atIndex:(NSUInteger)index{

      if (index>self.count) {

       NSLog(@"__NSArrayM 添加元素越界 insertObject:atIndex: , 崩溃") ;
      
       return ;
      

      }

      if (object == nil) {

       NSLog(@"__NSArrayM 添加空元素 insertObject:atIndex: , 崩溃") ;
      
       return ;
      

      }

      [self safeInsertObject:object atIndex:index];

    }

    • (void)safeAddObject:(id)object {

      if (object == nil) {

       NSLog(@"__NSArrayM 添加空元素 addObject , 崩溃") ;
      
       return ;
      

      }
      [self safeAddObject:object];

    }

    @end

    相关文章

      网友评论

          本文标题:iOS runtime的简单用法

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