初识RunTime

作者: 葱花思鸡蛋 | 来源:发表于2016-12-09 18:42 被阅读0次

    初次接触RunTime,记录下自己的学习心得,为后来者铺平道路,提供一个学习的切入点。

    首先简单的介绍下RunTime,它就是传说中的运行时,为什么是传说中的呢?因为我们首次听到运行时大多都是从他人口中,只知道很底层的东西,非常难以理解。好了,上面都是废话,我们先要了解苹果的Object-C是面向运行时的语言,是动态的,它把编译和链接期所做的事推迟到运行时才进行处理,给了我们很大的灵活性,可以进行消息的转发,替换方法等。

    理论上的东西说的再多,不会用都是瞎掰,我相信用的多了,慢慢自己也会深究其理,自然也就懂了。那么RunTime可以用来做什么呢?

    一、动态添加方法的实现

    先简单了解一下方法的调用机制,以Person类为例:

    以对象方法为例当调用eat方法时[p eat],底层会调用[p performSelector:@selector(eat)]方法,编译器再将代码转化为objc_msgSend(p, @selector(eat));

    当使用类方法调用时,eat为类方法,[Person eat]会转化为[[Person class] performSelector:@selector(eat)]方法,然后编译器再将代码转化为objc_msgSend(p, @selector(eat));

    那么当我们只是对方法进行了声明,而没有实现时,编译时会有警告而不会报错。我们都知道在运行时系统找不到相应的方法实现,程序就会崩溃。但系统给了我们机会来防止崩溃,在崩溃之前系统会来到拦截调用。拦截调用就是系统在找不到调用的方法,程序崩溃之前调用的方法。

    当调用了没有实现的对象方法时,就会调用+(BOOL)resolveInstanceMethod:(SEL)sel方法。当调用了没有实现的类方法时,就会调用+(BOOL)resolveClassMethod:(SEL)sel方法。通过这两个方法就可以知道哪些方法没有实现,从而动态添加方法。参数sel即表示没有实现的方法。

    一个objective - C方法最终都是一个C函数,默认任何一个方法都有两个参数。self : 方法调用者 _cmd : 调用方法编号。我们可以使用函数class_addMethod为类添加一个方法以及实现。

    代码实现:需要在Person类的实现方法中调用

    +(BOOL)resolveInstanceMethod:(SEL)sel{

    // 动态添加eat方法

    // 首先判断sel是不是eat方法 也可以转化成字符串进行比较。

    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {

    /**

    来看一下class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types) 的参数:

    第一个参数: cls:给哪个类添加方法

    第二个参数: SEL name:添加方法的编号

    第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)

    第四个参数: types :方法类型,需要用特定符号,参考API

    示例:v -> void 表示无返回值, @ -> object 表示id参数,: -> method selector 表示SEL

    */

    class_addMethod(self, sel, (IMP)eat , "v@:");

    // 处理完返回YES

    return YES;

    }

    return [super resolveInstanceMethod:sel];

    }

     void eat (id self ,SEL _cmd){// 实现内容

    NSLog(@"%@的%@方法动态实现了",self,NSStringFromSelector(_cmd));

    }

    二、动态为分类添加属性

    RunTime提供了动态添加属性和获得属性的方法:

    设置关联属性的值:

    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);  参数介绍:参数1   为哪个对象关联属性 

                       参数2  可以自定义key值,用来获取被关联属性的值

                      参数3   关联的值,也就是set方法传入的值给属性去保存。

                      参数4   属性的保存方式,常用OBJC_ASSOCIATION_RETAIN_NONATOMIC

    获取关联属性的值:

    objc_getAssociatedObject(id object, const void *key);

    参数介绍:参数1   为哪个对象关联属性

                       参数2   通过自定义的key值获取被关联属性的值

    示例:为UIImageview的分类添加属性@property(copy,nonatomic)NSString *imageName;

    // 给分类添加属性

    - (void)setImageName:(NSString *)imageName {

    //使用关联对象设置值

    objc_setAssociatedObject(self, @"name", imageName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    - (NSString *)imageName {

    //获取关联对象设置的值

    NSString *imgName = objc_getAssociatedObject(self, @"name");

    return imgName;

    }

    三、动态获取一个类中的成员变量、属性、方法、协议

    需要创建NSObject的分类,这样就可以获取所有类的员变量、属性、方法、协议,并且与KVC配合可以修改系统的类

    1)获取属性列表

    const void * kAssociateObjectKey = @"kAssociateObjectKey";// key值用来做标识的

    + (NSArray *)yk_propertyArray

    {//关联属性

    NSArray *prolist = objc_getAssociatedObject(self, kAssociateObjectKey);

    if (prolist != nil) {

    return prolist;

    }

    unsigned int count ;

    objc_property_t *protertylist =  class_copyPropertyList([self class], &count);

    //临时可变数组

    NSMutableArray *arrM = [NSMutableArray array];

    for (NSInteger i = 0; i < count ; i ++ ){

         //取出属性

         objc_property_t proterty =  protertylist[i];

          //获取属性名

          const char *name = property_getName(proterty);

          NSString *nameStr = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];

           [arrM addObject:nameStr];

          }

    // 设置关联属性

    objc_setAssociatedObject(self, kAssociateObjectKey, arrM.copy,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //释放内存

    free(protertylist);

    return arrM.copy;

    }

    2)获取成员变量列表

    // 获取成员变量

    +(NSArray *)yk_ivarArray

    {

    unsigned int count;

    Ivar *ivarList = class_copyIvarList([self class], &count);

    NSMutableArray *arr = [NSMutableArray array];

    for (NSInteger i = 0; i < count ; i ++ ) {

          // 取出成员变量

          Ivar ivar = ivarList[i];

         // 获取成员变量名

          const char *name = ivar_getName(ivar);

         // 将C语言字符串转化为OC字符串

         NSString *ivarStr = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];

          NSLog(@"ivar = %@",ivarStr);

          [arr addObject:ivarStr];

          }

    free(ivarList);

    //    NSLog(@"%zd",count);

    return arr.copy;

    }

    3)获取方法列表

    //获取方法名

    + (NSArray *)yk_methodList{

    unsigned int count;

    Method *list = class_copyMethodList([self class], &count);

    NSMutableArray *arr = [NSMutableArray array];

    for (NSInteger i = 0; i < count ; i++ ){

            // 获取方法结构体

           Method m = list[i];

           // 获取方法名

           SEL selector = method_getName(m);

            // 转化为字符串

           NSString *name = NSStringFromSelector(selector);

           [arr addObject:name];

           }

    free(list);

    return arr.copy;

    }

    4) 获取协议列表

    // 获取协议名

    + (NSArray *)yk_protocolList{

     unsigned int count;

    // 返回值需要标记

    __unsafe_unretained  Protocol ** proList = class_copyProtocolList([self class], &count);

    NSMutableArray *arr = [NSMutableArray array];

    //遍历求协议名

    for (NSInteger i = 0; i < count ; i ++ ){

             Protocol *pro = proList[i];

             const char *name = protocol_getName(pro);

              NSString *nameStr = [NSString stringWithCString:name             encoding:NSUTF8StringEncoding];

              [arr addObject:nameStr];

    }

    free(proList);

    return arr.copy;

    }

    四、在获取属性列表的基础上实现简单的字典转模型

    // 字典数组转模型 以字典转模型为基础的

    + (NSArray *)modelWithDictArray:(NSArray *)array

    {

    if (array.count == 0) {

    NSLog(@"数组为空!");

    return nil;

    }

    NSAssert([array[0] isKindOfClass:[NSDictionary class]], @"数组内容必须为字典");

    NSMutableArray *arr = [NSMutableArray array];

    for (NSDictionary *dict in array) {

            // 创建模型对象

            id instance = [self modelWithDict:dict];

           [arr addObject:instance];

             }

    return arr.copy;

    }

    // 字典转模型

    +(instancetype)modelWithDict:(NSDictionary *)dict

    {

    id instance = [[self alloc] init];

    //获取属性列表 不能每次都用调用获取属性列表的方法 效率太低

    NSArray *prolist = [self yk_propertyArray];

    //遍历字典

    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

    // 判断字典属性是否在属性列表中

    if ([prolist containsObject:key]) {

    [instance setValue:obj forKey:key];

    }

    }];

    return instance;

    }

    提供GitHub上demo的下载地址:链接

    相关文章

      网友评论

        本文标题:初识RunTime

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