美文网首页
Runtime(运行时)总结

Runtime(运行时)总结

作者: 攻城狮GG | 来源:发表于2017-06-22 15:11 被阅读0次

    基本解释

    Runtime 是一套比较底层的纯C语言API 它是OC的幕后工作者 我们平时写的OC代码        在运行时都会编译器转为runtime的C语言代码 其中最主要的是消息机制OC的函数调用成为消息发送 属于动态调用过程 在编译的时候并不能决定真正调用哪个函数事实证明在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错而C语言在编译阶段就会报错 只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

    简单实例

    obj doSometing其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成:objc_msgSend(obj,@selector(doSomething);首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method(猜测]cache中method列表是以]EL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中 

    实际应用

    Json到Model的转化

    在开发中相信最常用的就是接口数据需要转化成Model了(当然如果你是直接从Dict取值的话。。。),很多开发者也都使用著名的第三方库如JsonModel、Mantle或MJExtension等,如果只用而不      知其所以然,那真和“搬砖”没啥区别了,下面我们使用runtime去解析json来给Model赋值。原理描述:用runtime提供的函数遍历Model自身所有属性,如在json中有对应的值,则将其赋值。核心方法:在NSObject的分类中添加方法 1  - (instancetype)initWithDict:(NSDictionary *)dict {2    3      if (self = [self init]) {4          //(1)获取类的属性及属性对应的类型5          NSMutableArray * keys = [NSMutableArray array];6          NSMutableArray * attributes = [NSMutableArray array];7          /*8            * 例子9            * name = value3 attribute = T@"NSString",C,N,V_value310          * name = value4 attribute = T^i,N,V_value411          */12          unsigned int outCount;13          objc_property_t * properties = class_copyPropertyList([self class], &outCount);14          for (int i = 0; i < outCount; i ++) {15              objc_property_t property = properties[i];16              //通过property_getName函数获得属性的名字17              NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];18              [keys addObject:propertyName];19              //通过property_getAttributes函数可以获得属性的名字和@encode编码20              NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];21              [attributes addObject:propertyAttribute];22          }23          //立即释放properties指向的内存24          free(properties);25  26          //(2)根据类型给属性赋值27          for (NSString * key in keys) {28              if ([dict valueForKey:key] == nil) continue;29              [self setValue:[dict valueForKey:key] forKey:key];30          }31      }32      return self

    快速归档

    有时候我们要对一些信息进行归档,如用户信息类UserInfo,这将需要重写initWithCoder和encodeWithCoder方法,并对每个属性进行encode和decode操作。那么问题来了:当属性只有几个的时候可以轻松写完,如果有几十个属性呢?那不得写到天荒地老?。。。原理描述:用runtime提供的函数遍历Model自身所有属性,并对性encode和decode操作核心方法:在Model的基类中重写方法:  1  - (id)initWithCoder:(NSCoder *)aDecoder {2      if (self = [super init]) {3          unsigned int outCount;4          Ivar * ivars = class_copyIvarList([self class], &outCount);5          for (int i = 0; i < outCount; i ++) {6              Ivar ivar = ivars[i];7              NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];8              [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];9          }10      }11      return self;12  }13  14  - (void)encodeWithCoder:(NSCoder *)aCoder {15      unsigned int outCount;16      Ivar * ivars = class_copyIvarList([self class], &outCount);17      for (int i = 0; i < outCount; i ++) {18          Ivar ivar = ivars[i];19          NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];20          [aCoder encodeObject:[self valueForKey:key] forKey:key];21      }22  }

    访问私有变量

    我们知道,OC中没有真正意义上的私有变量和方法,要让成员变量私有,要放在m文件中声明,不对外暴露。如果我们知道这个成员变量的名称,可以通过runtime获取成员变量,再通过getIvar来获取它的值。方法:  1.Ivar ivar = class_getInstanceVariable([Model class], "_str1"); 2.NSString * str1 = object_getIvar(model, ivar)

    给分类(Category)添加属性

    遇到一个问题,写了一个分类,但原先类的属性不够用。添加一个属性,调用的时候崩溃了,说是找不到getter、setter方法。查了下文档发现,OC的分类允许给分类添加属性,但不会自动生成getter、setter方法。有没有解决方案呢?有,通过运行时建立关联引用。接下来以添加一个这样的属性为例:@property (nonatomic, copy) NSString *str;在匿名分类或者头文件中添加属性。区别是:匿名分类中添加的是私有属性,只在本类中可以使用,类的实例中不可以使用。头文件中添加的在类的实例中也可以使用。 //分类的头文件@interface ClassName (CategoryName)//我要添加一个实例也可以访问的变量所以就写在这里了@property (nonatomic, strong) NSString *str;@end//匿名分类@interface ClassName ()@end3、在实现里面写要添加属性的getter、setter方法。@implementation ClassName (CategoryName) -(void)setStr:(NSString *)str  {      objc_setAssociatedObject(self, &strKey, str, OBJC_ASSOCIATION_COPY);  }  -(NSString *)str  {      return objc_getAssociatedObject(self, &strKey);  }@end在setStr:方法中使用了一个objc_setAssociatedObject的方法,这个方法有四个参数,分别是:源对象,关联时的用来标记是哪一个属性的key(因为你可能要添加很多属性),关联的对象和一个关联策略。 用来标记是哪一个属性的key常见有三种写法,但代码效果是一样的,如下: //利用静态变量地址唯一不变的特性1、static void *strKey = &strKey;2、static NSString *strKey = @"strKey"; 3、static char strKey;​关联策略是个枚举值,解释如下:enum {    OBJC_ASSOCIATION_ASSIGN = 0, //关联对象的属性是弱引用 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //关联对象的属性是强引用并且关联对象不使用原子性OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //关联对象的属性是copy并且关联对象不使用原子性OBJC_ASSOCIATION_RETAIN = 01401, //关联对象的属性是copy并且关联对象使用原子性OBJC_ASSOCIATION_COPY = 01403 //关联对象的属性是copy并且关联对象使用原子性};​4、完成后的整体代码如下:.h文件//分类的头文件@interface ClassName (CategoryName)@property (nonatomic, strong) NSString *str;@end.m文件//实现文件static void *strKey = &strKey;@implementation ClassName (CategoryName) -(void)setStr:(NSString *)str  {      objc_setAssociatedObject(self, & strKey, str, OBJC_ASSOCIATION_COPY);  }  -(NSString *)str  {      return objc_getAssociatedObject(self, &strKey);  }@end

    Method swizzling(方法交换“黑魔法”)

    方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码,反之亦然。 话不多说,这是参考Mattt大神在NSHipster上的文章自己写的代码。import "UIViewController+swizzling.h"import@implementation UIViewController (swizzling)

    //load方法会在类第一次加载的时候被调用

    //调用的时间比较靠前,适合在这个方法里做方法交换

    + (void)load{

    //方法交换应该被保证,在程序中只会执行一次

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

    //获得viewCo\ntroller的生命周期方法的selector

    SEL systemSel = @selector(viewWillAppear:);

    //自己实现的将要被交换的方法的selector

    SEL swizzSel = @selector(swiz_viewWillAppear:);

    //两个方法的Method

    Method systemMethod = class_getInstanceMethod([self class], systemSel);

    Method swizzMethod = class_getInstanceMethod([self class], swizzSel);

    //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败

    BOOL isAdd = class_addMethod(self, systemSel,

    method_getImplementation(swizzMethod),

    method_getTypeEncoding(swizzMethod));

    if (isAdd) {

    //如果成功,说明类中不存在这个方法的实现

    //将被交换方法的实现替换到这个并不存在的实现

    class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));

    }else{

    //否则,交换两个方法的实现

    method_exchangeImplementations(systemMethod, swizzMethod);

    }

    });

    }

    - (void)swiz_viewWillAppear:(BOOL)animated{

    //这时候调用自己,看起来像是死循环

    //但是其实自己的实现已经被替换了

    [self swiz_viewWillAppear:animated];

    NSLog(@"swizzle");

    }

    @end

    在一个自己定义的viewController中重写viewWillAppear

    - (void)viewWillAppear:(BOOL)animated{

    [super viewWillAppear:animated];

    NSLog(@"viewWillAppear");

    }

    //Run起来看看输出吧!

    我的理解:

    Swizzling应该写在+load方法中,因为+load是在类被初始化时候就被调用的。+initialize是在收到消息之后才调用,如果应用不发送消息给它,它就永远不可能执行。

    Swizzling应该被写在dispatch_once中,保证只被执行一次和线程安全。

    如果类中已经有了可替换的方法,那么就调用method_exchangeImplementations交换,否则调用class_addMethod和class_replaceMethod来完成替换。

    xxx_viewWillAppear:方法的看似会引发死循环,但其实不会。在Swizzling的过程中xxx_viewWillAppear:已经被重新指定到UIViewController类的-viewWillAppear:中。不过如果我们调用的是viewWillAppear:反而会产生无限循环,因为这个方法的实现在运行时已经被重新指定为xxx_viewWillAppear:了。

    方法交换对于我来说更像是实现一种思想的最佳技术:AOP面向切面编程。

    既然是切面,就一定不要忘记,交换完再调回自己。

    一定要保证只交换一次,否则就会很乱。

    最后,据说这个技术很危险,谨慎使用。

    防止数组越界 使用交换方法 越界时动态使用方法 但是谨慎使用

    相关文章

      网友评论

          本文标题:Runtime(运行时)总结

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