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

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 阮小二买彩票
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 企业奖金发放
    Java实现 蓝桥杯VIP 算法提高 企业奖金发放
    让程序后台隐藏运行
    只要你喜欢,并且可以养家糊口,就是好的
  • 原文地址:https://www.cnblogs.com/oc-bowen/p/5426968.html
Copyright © 2011-2022 走看看