Runtime(运行时)

来源:互联网 发布:撮合交易平台源码 编辑:程序博客网 时间:2024/05/18 16:15

基本解释

  • 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_value3
10           * name = value4 attribute = T^i,N,V_value4
11           */
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 ()
@end
3、在实现里面写要添加属性的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 <objc/runtime.h>
@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");
}
0 0
原创粉丝点击