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

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 
@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 
@interface NSObject (Property)
//@property在分类中,只会生产get、set、方法声明,不会生产实现,也不会生产带下划线的成员属性
@property (nonatomic,strong)NSString *name;
@end
#import "NSObject+Property.h"
#import 
@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 
@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 
@protocol ModelDelegate
@optional
//提供一个协议,只要准备这个协议的类,都能把数组中的字典转成模型(返回字典为数组属性名:模型名)
+(NSDictionary *)arrayContainModelClass;
@end
@interface NSObject (Model)
//字典转模型
+(instancetype)objectWithDoct:(NSDictionary *)dict;
@end
#import "NSObject+Model.h"
#import 

@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 
#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()
@end
@implementation Car
//实现协议方法
+ (NSDictionary *)arrayContainModelClass{
    return @{@"CarColorArr":@"CarColor"};
}
@end
#import 
@interface CarType : NSObject
@property (nonatomic,strong)NSString *type;
@end
#import 
@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 
@interface Person : NSObject
@end
#import "Person.h"
#import 
@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 
@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
(6)数组越界

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


#import "NSArray+DJSafeIndex.h"
#import 

@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(一)-简介及使用示例)