runtime简介
runtime简称运行时,OC是运行时机制,也就是在运行时才做一些处理,是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。
在我们平时编写的OC代码中, 程序运行过程时, 最终都是转成了runtime的C语言代码。例如:C语言在编译的时候就知道要调用哪个方法函数,而OC在编译的时候并不知道要调用哪个方法函数,而是推迟到运行的时候才知道调用的方法函数名称,来找到对应的方法函数进行调用。
runtime消息传递流程
在OC语言中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别,任何对象都有isa指针。
一、isa指针
打开Runtime源码,全局搜索isa_t,锁定objc-private.h类,里面有这么一段代码,isa指针是isa_t的实例。代码如下:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
};
#endif
};
arm64之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在arm64架构之后,apple对isa进行了优化,变成了一个联合体union结构,同时使用位域来存储更多的信息。
对象是通过isa的bits进行位运算,取出响应位置的值。runtime中的isa是被联合体位域优化过的,
它不单单是指向类对象了,而是把64位中的每一位都运用了起来,其中的shiftcls为33位,代表了类对象的地址,其他的位都有各自的用处。
那么我来看看这个isa内部元素代表的是什么含义?
-
nonpointer:表示是否对isa指针开启指针优化
- 0:不开启,表示纯isa指针。
- 1开启,不单单是类对象的地址,isa中包含了类信息和对象的引用计数等。
-
has_assoc:表示是否有关联对象标识位
- 0表示:没有,没有关联对象会释放的更快
- 1表示:有
-
has_cxx_dtor:表示该对象是否有C++或者Objc的析构器
- 如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
-
shiftcls:表示存储类指针class的值。
- 开启指针优化的情况下,在arm64 架构中有33位用来存储类指针。
-
magic:固定值为0xd2
- 用于在调试时分辨对象是否完成初始化
-
weakly_referenced:表示对象是否被指向或者曾经指向一个 ARC 的弱引用变量。
- 没有弱引⽤用的对象可以更更快释放。
-
deallocating:标志对象是否正在释放内存
- 0 表示没有 1表示正在释放内存
has_sidetable_rc:表示当对象的引用计数大于10,以至于无法存储在isa指针中时,用散列表去计数。
-
extra_rc:表示该对象的引用计数,实际上是引用计数值减1
- 例如如果对象的引用计数为10,那么extra_rc为9。如果引用计数⼤于10, 则需要使⽤到has_sidetable_rc
二、类对象(objc_class)
在OC中的类是用Class类型来表示的,它实际上是一个指向objc_class结构体的指针。
typedef struct objc_class *Class;
查看objc/runtime.h中objc_class结构体的定义如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_class结构体定义了很多变量如下:
Class isa : 实现方法调用的关键
Class super_class : 父类
const char * name : 类名
long version : 类的版本信息,默认为0
long info : 类信息,供运行期使用的一些位标识
long instance_size : 该类的实例变量大小
struct objc_ivar_list * ivars : 该类的成员变量链表
struct objc_method_list ** methodLists : 方法定义的链表
struct objc_cache * cache : 方法缓存
struct objc_protocol_list * protocols : 协议链表
objc_class结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例。
三、实例(objc_object)
实例对象的isa指针指向类对象,类对象的isa指针指向元类。类对象和元类的结构都是objc_class类型。
typedef struct objc_object *id;
struct objc_object {
/*
*实例对象的isa指针指向类对象
*实质就是指向objc_class结构体的指针
*/
Class isa;
};
- id表示泛型对象,指向objc_object结构体的指针。objc_object 就是我们平时常用的实例对象的定义,它只包含一个isa指针。
- 一个对象唯一保存的信息就是它的 Class 的地址
- 当我们调用一个对象的方法时,它会通过 isa 去找到对应的objc_class,然后再在objc_class的 methodLists 中找到我们调用的方法,然后执行。
四、元类(Meta Class)
在 Objective-C 中,类也被设计为一个对象。
结构体中也包含isa指针,这个指针指向元类。
调用一个对象的类方法的过程如下:
- 通过对象的 isa 指针找到对应的类。
- 通过类的 isa 指针找到对应元类。
- 在元类的 methodLists 中,找到对应的方法,然后执行。
元类也是一个对象,也有一个isa指针,isa指针指向哪里呢?
为了不让这种结构无限延伸下去,Objective-C 的设计者让所有的元类的isa指向基类(比如 NSObject)的元类。而基类的元类的 isa 指向自己。这样就形成了一个完美的闭环。
因此消息传递整个结构图可以如下表示:
五、objc_msgSend
当对象调用方法时候,[objc test] 会转换成
id objc_msgSend(id objc, SEL op, ...);
// 发送消息给无参数,无返回值的方法
((void (*)(id, SEL)) objc_msgSend)(self, NSSelectorFromString(@"functionName"));
// 发送消息给有参数,有返回值的方法
((NSString *(*)(id, SEL, NSString *)) objc_msgSend)(self, NSSelectorFromString(@"functionName"), @"parameter");
objc_msgSend 会默认传入 id 和 SEL,分别对应两个隐含参数, self 和 _cmd,其中 _cmd 指向方法本身。
六、objc_method_list(方法列表)、objc_cache(方法缓存)
* struct objc_method_list ** methodLists : 方法定义的链表
* struct objc_cache * cache : 方法缓存
methodLists:存着该对象的实例方法(类对象的话存放这类方法)
cache的作用就是:
- 因为methodLists方法有很多,如果每次调用方法都去遍历一遍methodLists的话,效率很低。所以objc_cache方法缓存存在的意义。
- 当methodLists遍历找到方法,找到该方法之后,cache会以map的方式把method_name作为key,method_imp作为value给存起来。
- 下次当你在再调用该方法的时候,会先从cache中去找。
七、objc_method 方法
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; //方法名
char *method_types OBJC2_UNAVAILABLE; //方法类型
IMP method_imp //方法实现
}
在objc_method结构体中中,SEL和IMP都是Method的属性。
一、SEL(objc_selector)
/// 代表一个方法的不透明类型
typedef struct objc_selector *SEL;
//发送消息
id objc_msgSend(id self, SEL op, ...);
objc_msgSend函数第二个参数类型为SEL,selector是方法选择器,可以理解为区分方法的 ID(唯一标识符),而这个ID的数据结构是SEL:
@property SEL selector;
可以看到selector是SEL的一个实例。其实selector就是个映射到方法的C字符串,selector既然是一个string,我觉得应该是类似className+method的组合,命名规则有两条:
- 同一个类,selector不能重复
- 不同的类,selector可以重复
这就是我们OC没有重载的功能,一个方法不能有两个方法名一样,哪怕传的参数个数和类型都不一样。就是因为selector是通过方法名来找到对应方法的。
二、IMP
IMP表示: 指向一个方法实现的指针。
typedef id (*IMP)(id, SEL, ...);
就是指向最终实现程序的内存地址的指针。
在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。
八、Category(objc_category)
Category是表示一个指向分类的结构体的指针,结构体如下:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
- name:是指 class_name 而不是 category_name。
- cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
- instanceMethods:category中所有给类添加的实例方法的列表。
- classMethods:category中所有添加的类方法的列表。
- protocols:category实现的所有协议的列表。
- instanceProperties:表示Category里所有的properties
这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。
从上面的category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量。
Category的加载处理过程是怎样的呢?
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大数组中
- 后面参与编译的Category数据,会在数组的前面
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
最后对象发送消息,整个执行流程是什么样的?
八、Runtime消息传递
一个对象的方法像这样[obj work],编译器转成消息发送objc_msgSend(obj, work),Runtime时执行的流程是这样的:
- 首先通过obj的isa指针找到它的类对象
- class的methodlist找到work函数
- 如果class中没找到work函数方法,会继续类对象的superclass(父类)中去找,一直找到父类是根类
- 一旦知道work这个函数方法就去执行的work的实现IMP
如果只是这么简单的消息传递,效率就非常低了。往往一个class只有20%的函数会被经常调用,可能占总调用次数的80%。每个消息都需要遍历一次objc_method_list并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。objc_cache做的事情如下:
- 当methodLists遍历找到方法,找到该方法之后,cache会以map的方式把method_name作为key,method_imp作为value给存起来。
- 下次当你在再调用该方法的时候,会先从cache中去找。
还有一个问题就是发现类别的方法优先级更好,当一个对象和类别都有一个work方法的时候,发现类别方法的优先级更高,消息传递是怎么执行的呢?
- 就是当你遍历类对象的methodlist以前,会把category里面的instanceMethods(实例方法列表)、classMethods(对象方法列表)会插入到methodlist的最前面。
- 所以当遍历work函数,会优先找到类别的work函数,然后执行它的IMP。
runtime消息转发流程
当向一个对象发送一条消息,但它并没有实现的时候,通过_objc_msgForward尝试做消息转发。_objc_msgForward是 IMP类型。
为了展示消息转发的具体动作,可以尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward是如何进行转发的。
当上面消息传递过程中没有找到方法,消息就会进行转发,如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。消息转发的过程有三次机会。
消息转发的原理如下图:
-
第一步,动态添加响应方法
- Objective-C运行时会调用 实例方法调用+resolveInstanceMethod:
- 类方法调用+resolveClassMethod:
- (void)viewDidLoad {
[super viewDidLoad];
//执行work函数
[self performSelector:@selector(work:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(work:)) {//如果是执行work函数,就动态解析指定新的IMP
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//work函数
void workMethod(id obj, SEL _cmd) {
NSLog(@"Working");
}
打印结果:
Working
表明虽然没有实现work:这个函数,但是我们通过class_addMethod动态添加workMethod函数,并执行workMethod这个函数的IMP。
如果上面方法返回是NO的话,那就做走下一步。
- 第二步转发给响应该方法的对象
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给能响应该方法的对象。
下面通过代码,首先写声明一个能响应该方法的对象Person:
@interface Person: NSObject
- (void)work;
@end
@implementation Person
- (void)work {
NSLog(@"person working");//Person的foo函数
}
@end
其次通过forwardingTargetForSelector方法转发给响应该方法的对象
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//执行work函数
[self performSelector:@selector(work)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;//返回NO,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(work)) {
return [Person new];//转发给Person对象,让Person对象接收这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
打印结果:
person working
如果第二步中没有相应该消息的对象,就会走第三步消息转发,就把整个完整的消息转发流程走一遍了。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;//返回NO,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;//返回nil,进入下一步转发
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"work"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
@end
打印结果
working
从打印结果来看,我们实现了完整的转发。通过签名,Runtime生成了一个对象anInvocation,发送给了forwardInvocation,我们在forwardInvocation方法里面让Person对象去执行了work函数。签名参数v@:看苹果文档Type Encodings
如果这三步都没有成功的话,就会doesNotRecognizeSelector报异常了。
Runtime实际应用
Runtime简直就是做大型框架的利器。它的应用场景非常多,下面就介绍一些常见的应用场景。
- 给分类添加属性(关联对象)
- Method Swizzling交换方法
- KVO原理实现
- 消息转发(热更新)解决Bug(JSPatch)
- 实现NSCoding的自动归档和自动解档
- 实现字典和模型的自动转换(MJExtension)
一、给分类添加属性(关联对象)
关联对象Runtime提供了下面几个接口:
//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)
上面的参数解释:
- id object:被关联的对象
- const void *key:关联的key,要求唯一
- id value:关联的对象
- objc_AssociationPolicy policy:内存管理的策略
objc_AssociationPolicy是一个枚举如下:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //weak nonatomic 属性
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //retain\strong nonatomic 属性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy nonatomic属性
OBJC_ASSOCIATION_RETAIN = 01401, // retain\strong atomic属性
OBJC_ASSOCIATION_COPY = 01403 // copy atomic属性
};
针对枚举如下面解释:
下面就给Person关联一个name属性
@interface Person (Name)
@property (nonatomic, copy) NSString *name;
@end
#import "Person+Name.h"
#import <objc/runtime.h>
static NSString *nameKey = @"namekey";
@implementation Person (Name)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &nameKey);
}
二、方法交换(Method Swizzling)
Method Swizzling也是iOS中AOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。
Method Swizzling是发生在运行时,主要用于运行时将两个Method进行交换,我们可以将Method Swizzle代码写到任何地方,但是只有在Method_Swizzling这段Method Swizzle代码执行完毕之后互换才起作用。
Method Swizzling交换时机:尽可能在+load方法中实现。原因:
+load方法会在加载类的时候就被调用,也就是ios应用启动的时候,就会加载所有的类,main函数之前,就会调用每个类的+load方法。
主要用途:当需求需要在调用系统方法的基础上需要添加额外的功能,然后要把项目中所有的方法都要实现都要改一遍的问题上,不需要动原有的系统方法。并且能使用原有的系统方法功能外,添加一下原有的功能。
例如:当系统需要适配ipad图片的时候,项目中调用imageNamed方法的地方非常多。所以每一处都需要进行更改肯定非常麻烦。现在通过Method_Swizzling就可以sw_imageNamed替换imageNamed并且做一些处理。当以调用imageNamed方法时候实际调用的是sw_imageNamed。
@implementation UIImage (Category)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(imageNamed);
SEL swizzledSelector = @selector(sw_imageNamed);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
/*
* NO:尝试添加需要交换的方法,若添加失败,则说明已经有该方法,直接交换
* YES:如果添加成功,没有该方法,则把刚添加的方法替换为我们需要的方法
*/
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
/*
*这里特别注意,必须调用交换的方法,因为上面已经把方法交换了,所以不会死循环,
*如果调用originalMethod,反而会死循环
*/
- (void)sw_imageNamed {
UIImage * image;
//这个时候的sw_imageNamed已经交换后变成了系统的imageNamed
if( IS_IPHONE ){
// iPhone处理
UIImage * image = [self sw_imageNamed:name];
if (image != nil) {
return image;
} else {
return nil;
}
} else {
// ipad处理,_ipad是自己定义。
UIImage *image = [self sw_imageNamed:[NSString stringWithFormat:@"%@_ipad",name]];
if (image != nil) {
return image;
}else {
image = [self swizze_imageNamed:name];
return image;
}
}
}
@end
可能有些不明白为啥sw_imageNamed方法里面又调用[self swizze_imageNamed:name]竟然没有死循环。主要是因为这两个sw_imageNamed是不一样意义:
- load方法表示在加载类的时候就执行了该方法,就已经交换完毕,交换的是IMP(就是函数实现的内存地址指针)。
- 当对象调用[self imageNamed:name]调用的sw_imageNamed的IMP
- (void)sw_imageNamed {}这个就是sw_imageNamed方法真正实现的IMP。
- 然后在sw_imageNamed的IMP里调用[self swizze_imageNamed:name],[self swizze_imageNamed:name]这个就是selector方法名执行的是imageNamed的IMP。
- (void)sw_imageNamed {}与[self swizze_imageNamed:name]这个两个是有区别的,一个IMP,一个Selector。
原理图如下:
Method Swizzing作用就是让sel2、sel3与IMP2、IMP3交换。
三、热更新及解决线上bug(JSPatch)
JSPatch 是一个iOS动态更新框架,只需在项目中引入极小的引擎,就可以使用JavaScript调用任何Objective-C原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复bug。
JSPatch基础原理:
JSPatch 能做到通过JS调用和改写OC方法最根本的原因是 Objective-C是动态语言,OC上所有方法的调用/类的生成都通过 Objective-C Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法:
Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];
也可以替换某个类的方法为新的实现:
static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");
还可以新注册一个类,为类添加方法:
Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
class_addMethod(cls, selector, implement, typedesc);
objc_registerClassPair(cls);
理论上你可以在运行时通过类名/方法名调用到任何 OC 方法,替换任何类的实现以及新增任意类。
JSPatch的基本原理就是:JS传递字符串给OC,OC通过Runtime 接口调用和替换OC方法。这是最基础的原理,实际实现过程还有很多怪要打。
最后JSPatch被苹果禁止使用,还能用 JSPatch 吗?
- 可以,但请不要自行接入,统一接入 JSPatch 平台。
为什么要统一接入 JSPatch 平台?
若从苹果的审核规则来看,JSPatch 和 React Native 是一样的,但苹果接受 React Native,拒绝 JSPatch,主要是因为JSPatch 可以调用任意原生 API,出于安全和滥用方面的顾虑而拒绝,之前大量 APP 自行接入 JSPatch,导致有几个风险点:
无法保证每个 APP 接入方式都是安全的,容易传输过程被替换,被黑客中间人攻击,成为 iOS 上的一个漏洞。
一些像地图/推送类 SDK 接入 JSPatch 后覆盖大量 APP,若这些 SDK 后台被攻破,可以对这些 APP 下发恶意脚本,造成大面积危害。
开发者可以方便地不经过苹果审查使用 JSPatch 调用私有 API。
若大家还是通过一些混淆手段骗过苹果继续各自接入 JSPatch,出于上述三点考虑,苹果不可能再允许,但若接入 JSPatch 平台,上述三个问题可以帮苹果解决,不再有这类隐患,详见这里,苹果才会考虑允许继续使用。
用JSPatch平台最新的SDK接入可以通过审核吗?
可以通过,已有接入并通过审核的 APP。
为什么平台SDK能通过审核?
- 因为做了简单的类名修改混淆。
用Github上的代码接入能通过审核吗?
- 不能。前面也说了,不要自行接入JSPatch源码,不要自行混淆代码接入
四、NSCoding的自动归档、解档
原理描述:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。
核心方法:在Model的基类中重写方法:
//解档
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[I];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[I];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
字典转模型
首先建立一个NSObject分类,做个简单的字典转模型
#import <Foundation/Foundation.h>
@interface NSObject (Json)
+ (instancetype)pf_objectWithJson:(NSDictionary *)json;
@end
#import "NSObject+Json.h"
#import <objc/runtime.h>
@implementation NSObject (Json)
+ (instancetype)cs_objectWithJson:(NSDictionary *)json {
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 取出位置的成员变量
Ivar ivar = ivars[I];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
// 设值
id value = json[name];
if ([name isEqualToString:@"ID"]) {
value = json[@"id"];
}
[obj setValue:value forKey:name];
}
free(ivars);
return obj;
}
@end
字典转模型的模型及调用:
#import <Foundation/Foundation.h>
@interface Persion : NSObject
@property (assign, nonatomic) int ID;
@property (assign, nonatomic) int weight;
@property (assign, nonatomic) int age;
@property (copy, nonatomic) NSString *name;
@end
// 字典转模型调用
- (void)jsonToModel {
// 字典转模型
NSDictionary *json = @{
@"id" : @20,
@"age" : @20,
@"weight" : @60,
@"name" : @"Jack",
@"no" : @30
};
Persion *persion = [Persion pf_objectWithJson:json];
NSLog(@"id = %d, age = %d, weight = %d, name = %@",persion.ID,persion.age,persion.weight,persion.name);
}
Runtime使用场景很多,在这就不一一举例子了。大家可以看下面的应用图:
结尾
Objective-C语言最重要的特点之一就是运行时语言,可见掌握runtime至关重要。Objective-C是一个全动态语言,它的一切都是基于Runtime实现的平时编写的OC代码, 在程序运行过程中, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者。