zoukankan      html  css  js  c++  java
  • IOS-RunTime应用

    什么是Runtime

    总结起来,iOS中的RunTime的作用有以下几点:

    1.发送消息(obj_msgSend)

    2.方法交换(method_exchangeImplementations)

    3.消息转发

    4.动态添加方法

    5.给分类添加属性

    6.获取到类的成员变量及其方法

    7.动态添加类

    8.解档与归档

    9.字典转模型

    runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。

    在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者.例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));

    OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。

    例如: OC就是典型的运行时机制,OC属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用.而C语言中函数在编译的时候就会决定调用哪个函数.

    相关的定义

     1 /// 描述类中的一个方法
     2 typedef struct objc_method *Method;
     3 
     4 /// 实例变量
     5 typedef struct objc_ivar *Ivar;
     6 
     7 /// 类别Category
     8 typedef struct objc_category *Category;
     9 
    10 /// 类中声明的属性
    11 typedef struct objc_property *objc_property_t;

    类在runtime中的表示

     1 //类在runtime中的表示
     2 struct objc_class {
     3     Class isa;//指针,顾名思义,表示是一个什么,
     4     //实例的isa指向类对象,类对象的isa指向元类
     5 
     6 #if !__OBJC2__
     7     Class super_class;  //指向父类
     8     const char *name;  //类名
     9     long version;
    10     long info;
    11     long instance_size
    12     struct objc_ivar_list *ivars //成员变量列表
    13     struct objc_method_list **methodLists; //方法列表
    14     struct objc_cache *cache;//缓存
    15     //一种优化,调用过的方法存入缓存列表,下次调用先找缓存
    16     struct objc_protocol_list *protocols //协议列表
    17     #endif
    18 } OBJC2_UNAVAILABLE;
    19 /* Use `Class` instead of `struct objc_class *` */

     

    获取列表

    有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。
    我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。

     1 //
     2 //  RunTimeTool.m
     3 //  IOS_0423_RunTime
     4 //
     5 //  Created by ma c on 16/4/23.
     6 //  Copyright © 2016年 博文科技. All rights reserved.
     7 //
     8 
     9 #import "RunTimeTool.h"
    10 #import <objc/runtime.h>
    11 #import "Person.h"
    12 
    13 @implementation RunTimeTool
    14 
    15 //获得成员变量
    16 + (void)accessToMemberVariable
    17 {
    18     unsigned int count;
    19     //获得成员变量结构体
    20     Ivar *ivars = class_copyIvarList([Person class], &count);
    21     for (int i = 0; i < count; i++) {
    22         Ivar ivar = ivars[i];
    23         
    24         //根据Ivar获得成员变量的名称
    25         const char *nameC = ivar_getName(ivar);
    26         //C的字符串转成OC字符串
    27         NSString *nameOC = [NSString stringWithUTF8String:nameC];
    28         NSLog(@"%@",nameOC);
    29     }
    30     free(ivars);
    31 }
    32 //获得属性
    33 + (void)accessToProperty
    34 {
    35     unsigned int count;
    36     //获得指向该类所有属性的指针
    37     objc_property_t *properties = class_copyPropertyList([Person class], &count);
    38     
    39     for (int i = 0; i < count; i++) {
    40         //获得该类一个属性的指针
    41         objc_property_t property = properties[i];
    42         
    43         //获得属性的名称
    44         const char *nameC = property_getName(property);
    45         //C的字符串转成OC字符串
    46         NSString *nameOC = [NSString stringWithUTF8String:nameC];
    47         NSLog(@"%@",nameOC);
    48     }
    49     free(properties);
    50 }
    51 //获得方法
    52 + (void)accessToMethod
    53 {
    54     unsigned int count;
    55     //获得指向该类所有方法的指针
    56     Method *methods = class_copyMethodList([Person class], &count);
    57     
    58     for (int i = 0; i < count; i++) {
    59         
    60         //获得该类的一个方法指针
    61         Method method = methods[i];
    62         //获取方法
    63         SEL methodSEL = method_getName(method);
    64         //将方法名转化成字符串
    65         const char *methodC = sel_getName(methodSEL);
    66         //C的字符串转成OC字符串
    67         NSString *methodOC = [NSString stringWithUTF8String:methodC];
    68         //获得方法参数个数
    69         int arguments = method_getNumberOfArguments(method);
    70         NSLog(@"%@方法的参数个数:%d",methodOC, arguments);
    71     }
    72     free(methods);
    73 }
    74 //获得协议
    75 + (void)accessToProtocol
    76 {
    77     unsigned int count;
    78     //获取指向该类遵循的所有协议的指针
    79     __unsafe_unretained Protocol **protocols = class_copyProtocolList([Person class], &count);
    80     
    81     for (int i = 0; i < count; i++) {
    82         //获取指向该类遵循的一个协议的指针
    83         Protocol *protocol = protocols[i];
    84         
    85         //获得属性的名称
    86         const char *nameC = protocol_getName(protocol);
    87         //C的字符串转成OC字符串
    88         NSString *nameOC = [NSString stringWithUTF8String:nameC];
    89         NSLog(@"%@",nameOC);
    90 
    91     }
    92     free(protocols);
    93 }
    94 
    95 
    96 @end

     

    发送消息

    objc_msgSend,只有对象才能发送消息,因此以objc开头.

    使用消息机制的前提:导入#improt<objc/message.h>

     1 // 创建person对象
     2     Person *p = [[Person alloc] init];
     3 
     4     // 调用对象方法
     5     [p eat];
     6 
     7     // 本质:让对象发送消息
     8     objc_msgSend(p, @selector(eat));
     9 
    10     // 调用类方法的方式:两种
    11     // 第一种通过类名调用
    12     [Person eat];
    13     // 第二种通过类对象调用
    14     [[Person class] eat];
    15 
    16     // 用类名调用类方法,底层会自动把类名转换成类对象调用
    17     // 本质:让类对象发送消息
    18     objc_msgSend([Person class], @selector(eat));

    消息机制原理:对象根据方法编号(SEL)去映射表查找对应的方法实现

    动态添加方法

    对象在收到无法解读的消息后,首先会调用所属类的 + (BOOL)resolveInstanceMethod:(SEL)sel

    这个方法在运行时,没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。

    首先从外部隐式调用一个不存在的方法:

     [person performSelector:@selector(sleep:) withObject:@"8小时"];

    然后,在person对象内部重写拦截调用的方法,动态添加方法。

     1 //动态添加方法
     2 + (BOOL)resolveInstanceMethod:(SEL)sel
     3 {
     4     if ([NSStringFromSelector(sel) isEqualToString:@"sleep:"]) {
     5         class_addMethod(self, sel, (IMP)sleepMethod, "v@:*");
     6         return YES;
     7     }
     8     return [super resolveInstanceMethod:sel];
     9 }
    10 void sleepMethod(id self, SEL _cmd, NSString *string)
    11 {
    12     NSLog(@"睡了%@",string);
    13 }

    其中class_addMethod的四个参数分别是:

    1.Class cls 给哪个类添加方法,本例中是self

    2.SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。

    3.IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。

    4."v@:" 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;

      “v@:@@” 意思是,两个参数的没有返回值。

      "v@:*"意思是,代表有一个参数的方法

     

    消息转发

    如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法

    消息继续往下传递到- (id)forwardingTargetForSelector:(SEL)aSelector看看是不是有对象可以执行这个方法

     1 + (BOOL)resolveInstanceMethod:(SEL)sel {
     2  
     3     return [super resolveInstanceMethod:sel];
     4 }
     5 
     6 
     7 //消息转发
     8 - (id)forwardingTargetForSelector:(SEL)aSelector
     9 {
    10     Class class = NSClassFromString(@"Chinese");
    11     Person *person = [[class alloc] init];
    12     if (aSelector == NSSelectorFromString(@"study")) {
    13         return person;
    14     }
    15     return [super forwardingTargetForSelector:aSelector];
    16 }

     

    动态交换方法

     1 //动态交换方法
     2 + (void)load
     3 {
     4     /*
     5      load方法会在类第一次加载时调用
     6      交换方法应该保证,在程序中只被执行一次
     7      */
     8     
     9     SEL runSEL = @selector(run);
    10     SEL eatSEL = @selector(eat);
    11     
    12     //两个方法的Method方法地址
    13     Method runMethod = class_getInstanceMethod([self class], runSEL);
    14     Method eatMethod = class_getInstanceMethod([self class], eatSEL);
    15     
    16     //首先动态的添加方法,实现是被交换的方法,返回值表示添加成功还是失败
    17     BOOL isAdd = class_addMethod(self, eatSEL, method_getImplementation(runMethod), "v@:");
    18     
    19     if (isAdd) {
    20         //如果成功说明类中不存在这个方法实现,将被交换的方法实现替换这个并不存在的实现
    21         class_replaceMethod(self, runSEL, method_getImplementation(eatMethod), "v@:");
    22     } else {
    23         method_exchangeImplementations(runMethod, eatMethod);
    24     }
    25 }

    给分类添加属性

     1 #import "Person.h"
     2 
     3 @interface Person (Ext)
     4 
     5 @property (nonatomic, strong) NSString *IDCard;
     6 
     7 @end
     8 
     9 
    10 //
    11 //  Person+Ext.m
    12 //  IOS_0423_RunTime
    13 //
    14 //  Created by ma c on 16/4/24.
    15 //  Copyright © 2016年 博文科技. All rights reserved.
    16 //
    17 
    18 #import "Person+Ext.h"
    19 #import <objc/message.h>
    20 
    21 @implementation Person (Ext)
    22 
    23 static const char *key = "identifier";
    24 
    25 - (void)setIDCard:(NSString *)IDCard
    26 {
    27     // 第一个参数:给哪个对象添加关联
    28     // 第二个参数:关联的key,通过这个key获取
    29     // 第三个参数:关联的value
    30     // 第四个参数:关联的策略
    31     objc_setAssociatedObject(self, key, IDCard, OBJC_ASSOCIATION_COPY_NONATOMIC);
    32 
    33 }
    34 
    35 - (NSString *)IDCard
    36 {
    37     // 根据关联的key,获取关联的值。
    38     return objc_getAssociatedObject(self, key);
    39 }
    40 
    41 @end

     

    动态创建类

      1 #import "AppDelegate.h"
      2 #import <objc/runtime.h>
      3 
      4 @interface AppDelegate ()
      5 
      6 @end
      7 
      8 @implementation AppDelegate
      9 
     10 
     11 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     12     // 延时,等待所有控件加载完
     13     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
     14         [self test];
     15     });
     16     return YES;
     17 }
     18 
     19 - (void)test
     20 {
     21     // 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
     22     NSDictionary *userInfo = @{
     23                                @"class": @"BowenViewController",
     24                                @"property": @{
     25                                        @"ID": @"123",
     26                                        @"type": @"12"
     27                                        }
     28                                };
     29     
     30     [self push:userInfo];
     31 }
     32 
     33 #pragma mark 接收推送消息
     34 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
     35 {
     36 
     37     [self push:userInfo];
     38 }
     39 
     40 /**
     41  *  跳转界面
     42  */
     43 - (void)push:(NSDictionary *)params
     44 {
     45     // 类名
     46     NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
     47     const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
     48     
     49     // 从一个字串返回一个类
     50     Class newClass = objc_getClass(className);
     51     if (!newClass)
     52     {
     53         // 创建一个类
     54         Class superClass = [NSObject class];
     55         newClass = objc_allocateClassPair(superClass, className, 0);
     56         // 注册你创建的这个类
     57         objc_registerClassPair(newClass);
     58     }
     59     // 创建对象
     60     id instance = [[newClass alloc] init];
     61     
     62     NSDictionary *propertys = params[@"property"];
     63     [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
     64         // 检测这个对象是否存在该属性
     65         if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
     66             // 利用kvc赋值
     67             [instance setValue:obj forKey:key];
     68         }
     69     }];
     70 
     71     
     72     // 获取导航控制器
     73     UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
     74     UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
     75     
     76     // 跳转到对应的控制器
     77     [pushClassStance pushViewController:instance animated:YES];
     78 }
     79 
     80 /**
     81  *  检测对象是否存在该属性
     82  */
     83 - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
     84 {
     85     unsigned int outCount, i;
     86     
     87     // 获取对象里的属性列表
     88     objc_property_t * properties = class_copyPropertyList([instance
     89                                                            class], &outCount);
     90     
     91     for (i = 0; i < outCount; i++) {
     92         objc_property_t property =properties[i];
     93         //  属性名转成字符串
     94         NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
     95         // 判断该属性是否存在
     96         if ([propertyName isEqualToString:verifyPropertyName]) {
     97             free(properties);
     98             return YES;
     99         }
    100     }
    101     free(properties);
    102     
    103     return NO;
    104 }
    105 
    106 @end

    解档与归档

     1 //归档
     2 - (void)encodeWithCoder:(NSCoder *)aCoder
     3 {
     4     unsigned int count;
     5     //获得指向该类所有属性的指针
     6     objc_property_t *properties = class_copyPropertyList([self class], &count);
     7     
     8     for (int i = 0; i < count; i++) {
     9         //获得该类一个属性的指针
    10         objc_property_t property = properties[i];
    11         
    12         //获得属性的名称
    13         const char *nameC = property_getName(property);
    14         //C的字符串转成OC字符串
    15         NSString *nameOC = [NSString stringWithUTF8String:nameC];
    16         
    17         //通过关键字取值
    18         NSString *propertyValue = [self valueForKey:nameOC];
    19         //编码属性
    20         [aCoder encodeObject:propertyValue forKey:nameOC];
    21     }
    22     free(properties);
    23 
    24     
    25 }
    26 
    27 //解档
    28 - (instancetype)initWithCoder:(NSCoder *)aDecoder
    29 {
    30     unsigned int count;
    31     //获得指向该类所有属性的指针
    32     objc_property_t *properties = class_copyPropertyList([self class], &count);
    33     
    34     for (int i = 0; i < count; i++) {
    35         //获得该类一个属性的指针
    36         objc_property_t property = properties[i];
    37         
    38         //获得属性的名称
    39         const char *nameC = property_getName(property);
    40         //C的字符串转成OC字符串
    41         NSString *nameOC = [NSString stringWithUTF8String:nameC];
    42         //解码属性值
    43         NSString *propertyValue = [aDecoder decodeObjectForKey:nameOC];
    44         [self setValue:propertyValue forKey:nameOC];
    45     }
    46     free(properties);
    47     
    48     return self;
    49 }

    字典转模型

    思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。

    步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。

      1 @implementation ViewController
      2 
      3 - (void)viewDidLoad {
      4     [super viewDidLoad];
      5     // Do any additional setup after loading the view, typically from a nib.
      6 
      7     // 解析Plist文件
      8     NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
      9 
     10     NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
     11 
     12     // 获取字典数组
     13     NSArray *dictArr = statusDict[@"statuses"];
     14 
     15     // 自动生成模型的属性字符串
     16 //    [NSObject resolveDict:dictArr[0][@"user"]];
     17 
     18 
     19     _statuses = [NSMutableArray array];
     20 
     21     // 遍历字典数组
     22     for (NSDictionary *dict in dictArr) {
     23 
     24         Status *status = [Status modelWithDict:dict];
     25 
     26         [_statuses addObject:status];
     27 
     28     }
     29 
     30     // 测试数据
     31     NSLog(@"%@ %@",_statuses,[_statuses[0] user]);
     32 
     33 
     34 }
     35 
     36 @end
     37 
     38 @implementation NSObject (Model)
     39 
     40 + (instancetype)modelWithDict:(NSDictionary *)dict
     41 {
     42     // 思路:遍历模型中所有属性-》使用运行时
     43 
     44     // 0.创建对应的对象
     45     id objc = [[self alloc] init];
     46 
     47     // 1.利用runtime给对象中的成员属性赋值
     48 
     49     // class_copyIvarList:获取类中的所有成员属性
     50     // Ivar:成员属性的意思
     51     // 第一个参数:表示获取哪个类中的成员属性
     52     // 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
     53     // 返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
     54     /* 类似下面这种写法
     55 
     56      Ivar ivar;
     57      Ivar ivar1;
     58      Ivar ivar2;
     59      // 定义一个ivar的数组a
     60      Ivar a[] = {ivar,ivar1,ivar2};
     61 
     62      // 用一个Ivar *指针指向数组第一个元素
     63      Ivar *ivarList = a;
     64 
     65      // 根据指针访问数组第一个元素
     66      ivarList[0];
     67 
     68      */
     69     unsigned int count;
     70 
     71     // 获取类中的所有成员属性
     72     Ivar *ivarList = class_copyIvarList(self, &count);
     73 
     74     for (int i = 0; i < count; i++) {
     75         // 根据角标,从数组取出对应的成员属性
     76         Ivar ivar = ivarList[i];
     77 
     78         // 获取成员属性名
     79         NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
     80 
     81         // 处理成员属性名->字典中的key
     82         // 从第一个角标开始截取
     83         NSString *key = [name substringFromIndex:1];
     84 
     85         // 根据成员属性名去字典中查找对应的value
     86         id value = dict[key];
     87 
     88         // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
     89         // 判断下value是否是字典
     90         if ([value isKindOfClass:[NSDictionary class]]) {
     91             // 字典转模型
     92             // 获取模型的类对象,调用modelWithDict
     93             // 模型的类名已知,就是成员属性的类型
     94 
     95             // 获取成员属性类型
     96            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
     97           // 生成的是这种@"@"User"" 类型 -》 @"User"  在OC字符串中 " -> ",是转义的意思,不占用字符
     98             // 裁剪类型字符串
     99             NSRange range = [type rangeOfString:@"""];
    100 
    101            type = [type substringFromIndex:range.location + range.length];
    102 
    103             range = [type rangeOfString:@"""];
    104 
    105             // 裁剪到哪个角标,不包括当前角标
    106           type = [type substringToIndex:range.location];
    107 
    108 
    109             // 根据字符串类名生成类对象
    110             Class modelClass = NSClassFromString(type);
    111 
    112 
    113             if (modelClass) { // 有对应的模型才需要转
    114 
    115                 // 把字典转模型
    116                 value  =  [modelClass modelWithDict:value];
    117             }
    118 
    119 
    120         }
    121 
    122         // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
    123         // 判断值是否是数组
    124         if ([value isKindOfClass:[NSArray class]]) {
    125             // 判断对应类有没有实现字典数组转模型数组的协议
    126             if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
    127 
    128                 // 转换成id类型,就能调用任何对象的方法
    129                 id idSelf = self;
    130 
    131                 // 获取数组中字典对应的模型
    132                 NSString *type =  [idSelf arrayContainModelClass][key];
    133 
    134                 // 生成模型
    135                Class classModel = NSClassFromString(type);
    136                 NSMutableArray *arrM = [NSMutableArray array];
    137                 // 遍历字典数组,生成模型数组
    138                 for (NSDictionary *dict in value) {
    139                     // 字典转模型
    140                   id model =  [classModel modelWithDict:dict];
    141                     [arrM addObject:model];
    142                 }
    143 
    144                 // 把模型数组赋值给value
    145                 value = arrM;
    146 
    147             }
    148         }
    149 
    150 
    151         if (value) { // 有值,才需要给模型的属性赋值
    152             // 利用KVC给模型中的属性赋值
    153             [objc setValue:value forKey:key];
    154         }
    155 
    156     }
    157 
    158     return objc;
    159 }
    160 
    161 @end

    其他:http://www.jianshu.com/p/58c985408b75

  • 相关阅读:
    你应该知道的77条 Windows 7小技巧
    Platform Builder: Build Tools Intro
    JavaScript面向对象编程实现研究
    优秀驾驶员开车技巧
    WinCE BSP中的Dirs文件和Sources文件
    WinCE BSP中的Dirs文件和Sources文件
    WIX资源
    男性减肥方法!!!!!(转)
    Platform Builder: Sources.cmn
    批处理中的字符串处理详解
  • 原文地址:https://www.cnblogs.com/oc-bowen/p/5426968.html
Copyright © 2011-2022 走看看