假期学习-- iOS runtime的简朴了解

打印 上一主题 下一主题

主题 754|帖子 754|积分 2262

iOS RunTime

RunTime介绍

RunTime简称运行时,顾名思义,就是oc在运行时侯的一些机制,比如最主要的消息机制 ;
对于oc语言来说,它所调用的方法会在运行时才会决定,这就取决于RunTime的消息发送和消息转发机制 ;
RunTime消息机制

oc中调用任何方法,本质上都是发送消息,
如:
  1. Person *p = [[Person alloc] init];
  2. [p eat];
复制代码
[p eat];终极在底层会转换为 objc_msgSend(p, @selector(eat));
objc_msgSend ()的底层实现就是一个消息发送的过程(方法查找);
*@selector (SEL):是一个SEL方法选择器。
SEL其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。
SEL其本身是一个int范例的地点,地点中存放着方法的名字,因为SEL是根据方法名天生的,所以相同的方法名称只能对应一个SEL,一个类中不能存在两个名称相同的方法 ;
运行时发送消息的底层实现 每一个类都有一个方法列表 Method List,保存这类内里所有的方法,根据SEL传入的方法编号找到方法,相当于value - key的映射。然后找到方法的实现。
**那么内部动态查找方法的过程是怎么样的呢 **?
首先先看一下类的结构,因为实例对象的方法是在类对象中探求的:
  1. struct objc_class {
  2.   Class isa; // 指向metaclass
  3.   
  4.   Class super_class ; // 指向其父类
  5.   const char *name ; // 类名
  6.   long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
  7.   long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
  8.   long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
  9.   struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
  10.   struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
  11.   struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
  12.   struct objc_protocol_list *protocols; // 存储该类遵守的协议
  13. }
复制代码
实例方法[p eat];底层调用[p performSelectorselector(eat)];方法,编译器在将代码转化为objc_msgSend(p, @selector(eat));
在objc_msgSend函数中。首先通过p的isa指针找到p对应的class。在Class中先去cache中通过SEL查找对应函数method,假如找到则通过method中的函数指针跳转到对应的函数中去实行。
若cache中未找到。再去methodList中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去实行。
若methodlist中未找到,则去superClass中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去实行。
上面只是消息发送的简朴形貌,比较具体的过程,可以看之前写过的iOS 消息发送和消息转发 ;
使用RunTime互换方法

当系统自带的方法功能不够,须要给系统自带的方法扩展一些功能,并且保持原有的功能时,可以使用RunTime互换方法实现。
比如实现image添加图片的时间,主动判断image是否为空,假如为空则提示图片不存在。
首先我们可以使用分类,但分类每次使用都须要引用头文件 ,假如要修改或扩展的方法比较多会很麻烦 ;
这时就可以使用runtime互换方法,互换方法的本质是互换方法的实现;
如调换xx_ccimageNamed和imageName方法,达到调用xx_ccimageNamed其实就是调用imageNamed方法的目标,首先须要明白方法在那里互换,因为互换只须要进行一次,所以在分类的load方法中,当加载分类的时间互换方法即可。
  1. +(void)load
  2. {
  3.     // 获取要交换的两个方法
  4.     // 获取类方法  用Method 接受一下
  5.     // class :获取哪个类方法
  6.     // SEL :获取方法编号,根据SEL就能去对应的类找方法。
  7.     Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
  8.     // 获取第二个类方法
  9.     Method xx_ccimageNameMrthod = class_getClassMethod([UIImage class], @selector(xx_ccimageNamed:));
  10.     // 交换两个方法的实现 方法一 ,方法二。
  11.     method_exchangeImplementations(imageNameMethod, xx_ccimageNameMrthod);
  12.     // IMP其实就是 implementation的缩写:表示方法实现。
  13. }
复制代码
简朴的说:用SEL找到方法实现,然后互换方法实现 ;

注意:互换方法时间 xx_ccimageNamed方法中就不能再调用imageNamed方法了,因为调用imageNamed方法实质上相当于调用 xx_ccimageNamed方法,会循环引用造成死循环。
此时,当调用imageNamed:方法的时间就会调用xx_ccimageNamed:方法,为image添加图片,并判断图片是否存在,假如不存在则提示图片不存在。
动态添加方法

假如一个类方法非常多,其中大概很多方法暂时用不到。而加载类方法到内存的时间须要给每个方法天生映射表,又比较泯灭资源。此时可以使用RunTime动态添加方法
动态添加方法的方法: 首先我们先不实现对象方法,当调用performSelector: 方法的时间,再去动态加载方法。
如:
  1. Person *p = [[Person alloc]init];
  2. // 当调用 P中没有实现的方法时,动态加载方法
  3. [p performSelector:@selector(eat)];
复制代码
此时编译的时间是不会报错的,程序运行时才会报错,因为Person类中并没有实现eat方法,当去类中的Method List中发现找不到eat方法,会报错找不到eat方法。
也就是,在运行时,会先实行runtime的消息发送机制探求方法实现,当发现找不到方法实现就会调用**+(BOOL)resolveInstanceMethodSEL)sel方法 / +(BOOL)resolveClassMethodSEL)sel。 这个时间就进入了消息转发** ;
这两个方法的调用也就是消息转发的第一步动态方法解析
通过这两个方法就可以知道哪些方法没有实现,从而动态添加方法。参数sel即表示没有实现的方法。
一个objective - C方法终极都是一个C函数,默认任何一个方法都有两个参数。 self : 方法调用者 _cmd : 调用方法编号。我们可以使用函数class_addMethod为类添加一个方法以及实现。
  1. +(BOOL)resolveInstanceMethod:(SEL)sel
  2. {
  3.     // 动态添加eat方法
  4.     // 首先判断sel是不是eat方法 也可以转化成字符串进行比较。   
  5.     if (sel == @selector(eat)) {
  6.     /**
  7.      第一个参数: cls:给哪个类添加方法
  8.      第二个参数: SEL name:添加方法的编号
  9.      第三���参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)
  10.      第四个参数: types :方法类型,需要用特定符号,参考API
  11.      */
  12.       class_addMethod(self, sel, (IMP)eat , "v@:");
  13.         // 处理完返回YES
  14.         return YES;
  15.     }
  16.     return [super resolveInstanceMethod:sel];
  17. }
复制代码
对于
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)

  • cls : 表示给哪个类添加方法,这里要给Person类添加方法,self即代表Person。
  • SEL name : 表示添加方法的编号。因为这里只有一个方法须要动态添加,并且之前通过判断确定sel就是eat方法,所以这里可以使用sel。
  • IMP imp : 表示方法的实现,函数入口,函数名可与方法名不同(发起与方法名相同)须要自己来实现这个函数。每一个方法都默认带有两个隐式参数 self : 方法调用者 _cmd : 调用方法的标号,可以写也可以不写。
  1. void eat(id self ,SEL _cmd)
  2. {
  3.       // 实现内容
  4.       NSLog(@"%@的%@方法动态实现了",self,NSStringFromSelector(_cmd));
  5. }
复制代码

  • types : 表示方法范例,须要用特定符号。系统提供的例子中使用的是**"v@:",我们来到API中看看"v@:"**指定的方法是什么范例的。

v -> void 表示无返回值 @ -> object 表示id参数 : -> method selector 表示SEL
动态添加有参数的方法 假如是有参数的方法,须要对方法的实现和class_addMethod方法内方法范例参数做一些修改。
runtime 动态添加属性

这里给NSObject添加name属性,创建NSObject的分类 我们可以使用@property给分类添加属性
  1. @property(nonatomic,strong)NSString *name;
复制代码
虽然在分类中可以写@property 添加属性,但是不会主动天生私有属性,也不会天生set,get方法的实现,只会天生set,get的声明,须要我们自己去实现。
关于方法实现有两种方式:

  • 我们可以通过使用静态全局变量给分类添加属性
  1. static NSString *_name;
  2. -(void)setName:(NSString *)name
  3. {
  4.     _name = name;
  5. }
  6. -(NSString *)name
  7. {
  8.     return _name;
  9. }
复制代码
但是这样_name静态全局变量与类并没有关联,无论对象创建与烧毁,只要程序在运行_name变量就存在,并不是真正意义上的属性。

  • 使用RunTime动态添加属性 RunTime提供了动态添加属性和获得属性的方法
  1. -(void)setName:(NSString *)name
  2. {
  3.     objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  4. }
  5. //参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
  6. //参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在**objc_getAssociatedObject中通过次key获得属性的值并返回。
  7. //参数三:id value** : 关联的值,也就是set方法传入的值给属性去保存。
  8. //参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
  9. -(NSString *)name
  10. {
  11.     return objc_getAssociatedObject(self, @"name");   
  12. }
  13. //参数一:id object : 获取哪个对象里面的关联的属性。 参数二:void * == id key : 什么属性,与**objc_setAssociatedObject**中的key相对应,即通过key值取出value。
复制代码
注意一下:这两个方法可以实现属性的动态添加,但不会增长属性声明 ;
RunTime字典转模子

首先来看一下KVC字典转模子和RunTime字典转模子的区别
KVC:KVC字典转模子实现原理是遍历字典中所有Key,然后去模子中查找相对应的属性名,要求属性名与Key必须一一对应,字典中所有key必须在模子中存在。
RunTime:RunTime字典转模子实现原理是遍历模子中的所有属性名,然后去字典查找相对应的Key,也就是以模子为准,模子中有哪些属性,就去字典中找那些属性。
RunTime字典转模子的优点:当服务器返回的数据过多,而我们只使用其中很少一部分时,没有效的属性就没有须要界说成属性浪费不须要的资源。只保存最有效的属性即可。(Runtime字典转模子,数据的继承取决于模子的属性)
首先须要了解,属性界说在类内里,那么类内里就有一个属性列表,属性列表以数组的形式存在,根据属性列表就可以获得类内里的所有属性名,所以遍历属性列表,也就可以遍历模子中的所有属性名。所以RunTime字典转模子过程就很清楚了。(简朴的说就是先获取模子类的属性列表,然后遍历这个列表获取模子的属性名,在字典中获取属性名对应的value) ;
  1. #import <Foundation/Foundation.h>
  2. #import <objc/runtime.h>
  3. @interface Person : NSObject
  4. @property (nonatomic, strong) NSString *name;
  5. @property (nonatomic, assign) NSInteger age;
  6. @end
  7. @implementation Person
  8. @end
  9. // 字典转模型方法
  10. - (Person *)modelFromDictionary:(NSDictionary *)dictionary {
  11.     Person *person = [[Person alloc] init];
  12.    
  13.     // 获取 Person 类的属性列表
  14.     unsigned int count;
  15.     objc_property_t *properties = class_copyPropertyList([Person class], &count);
  16.    
  17.     for (int i = 0; i < count; i++) {
  18.       //获取属性名
  19.         objc_property_t property = properties[i];
  20.         const char *propertyName = property_getName(property);
  21.         NSString *key = [NSString stringWithUTF8String:propertyName];
  22.         
  23.         // 根据属性名从字典中取值
  24.         id value = [dictionary objectForKey:key];
  25.         
  26.         if (value) {
  27.             // 使用 KVC 设置属性值
  28.             [person setValue:value forKey:key];
  29.         }
  30.     }
  31.    
  32.     free(properties);
  33.    
  34.     return person;
  35. }
  36. int main(int argc, const char * argv[]) {
  37.     @autoreleasepool {
  38.         NSDictionary *data = @{@"name": @"Alice", @"age": @30};
  39.         
  40.         Person *person = [self modelFromDictionary:data];
  41.         
  42.         NSLog(@"Name: %@, Age: %ld", person.name, (long)person.age);
  43.     }
  44.    
  45.     return 0;
  46. }
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表