基本解释
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面向切面编程。
既然是切面,就一定不要忘记,交换完再调回自己。
一定要保证只交换一次,否则就会很乱。
最后,据说这个技术很危险,谨慎使用。
防止数组越界 使用交换方法 越界时动态使用方法 但是谨慎使用