一:rumtime原理简单分析
runtime是运行时库(Runtime Library),也简称运行时。
它是一个主要是C和汇编写的库,对C进行了特殊的处理,将结构体视为对象,将函数视为方法,使得C有了面向对象的能力,从而才创造了Objective-C。
这点也可以看出,C是编译时语言,而OC是动态运行时语言,所以在编译阶段,尽管OC中的方法没有实现也不会报错,而C会报错。
在运行时,OC语言才进行方法的处理,比如讲[person eat];转换为objc_msgSend(person, @selector(eat));然后通过person的isa指针找到person对应的class,在class中先去cache中通过SEL方法选择器查找对应的method,若缓存中未找到,再去methodList查找,若还未找到,便去父类中查找,如果这个过程中找到了该方法,便会自动将该方法加入cache中,方便下一次的查找,并且,编译器开始执行找到的这个函数。
上面这段文字可以片面的理解runtime的消息机制,消息机制是runtime最主要的机制,如果要在代码中使用,需要导入<objc/messge.h>。
二:runtime 的基本使用场景
首先,对objc_class结构体的内容做个简单的说明:
- struct objc_class {
- Class isa OBJC_ISA_AVAILABILITY;
-
- #if !__OBJC2__
- Class super_class
- const charchar *name
- long version
- long info
- long instance_size
- struct objc_ivar_list *ivars
- struct objc_method_list **methodLists
- struct objc_cache *cache
- struct objc_protocol_list *protocols
- #endif
-
- } OBJC2_UNAVAILABLE;
在实际开发中,runtime最常用的场景就是交换方法、归档、字典转模型、动态生成属性方法、添加类方法等
(一)runtime交换方法
1.创建一个类的分类
2.导入<objc/runtime.h>
3.实现交换并且自定义方法
- + (void)load {
- Method imageMethodNamed = class_getClassMethod(self, @selector(imageNamed:));
- Method myImageMethodNamed = class_getClassMethod(self, @selector(myImageNamed:));
-
- method_exchangeImplementations(myImageMethodNamed,imageMethodNamed);
- }
- + (instancetype)customImageNamed:(NSString *)name {
-
- UIImage *image = [UIImage customImageNamed:name];
-
- return image;
- }
(二)runtime动态生成getter、setter
由于在类目中添加属性不自动生成setter和getter方法,如果在.m文件中使用@dynamic配置起来就较为繁琐,如果一定要在类目中添加属性,我们可以使用runtime关联方法,简单方便。
比如在类目.h文件中声明了score属性,在.m文件中如下实现:
- voidvoid *key;
- - (void)setScore:(float)score {
-
-
-
-
-
-
-
- objc_setAssociatedObject(self, key, @(score), OBJC_ASSOCIATION_ASSIGN);
- }
- - (float)score {
- id score = objc_getAssociatedObject(self, key);
- return [score floatValue];
- }
(三)runtime归档、解档
归解档可以用kvc模式进行操作,但是如果属性过多的话,这样就显得极为繁琐而且容易出错,所以用runtime实现更为的科学和简洁。
-
- - (void)encodeWithCoder:(NSCoder *)aCoder {
-
- unsigned int count = 0;
- Ivar *ivarList = class_copyIvarList([self class], &count);
-
- for (int i = 0; i < count; i ++) {
- Ivar aIvar = ivarList[i];
-
- const charchar *iVarName = ivar_getName(aIvar);
- id value = [self valueForKey:[NSString stringWithUTF8String:iVarName]];
- if (!value) {
-
- }else {
- [aCoder encodeObject:value forKey:[NSString stringWithUTF8String:iVarName]];
- }
- }
- }
-
- - (instancetype)initWithCoder:(NSCoder *)aDecoder {
- if (self = [super init]) {
- unsigned int count = 0;
- Ivar *ivarList = class_copyIvarList([self class], &count);
- for (int i = 0; i < count; i ++) {
- Ivar aIvar = ivarList[i];
- const charchar *name = ivar_getName(aIvar);
- id value = [aDecoder decodeObjectForKey:[NSString stringWithUTF8String:name]];
- if (!value) {
-
- }else {
- [self setValue:value forKey:[NSString stringWithUTF8String:name]];
- }
- }
-
- }
- return self;
- }
(四)runtime字典转模型
这里需要注意的是,该方法只适用于字典的键和模型的属性一一对应的情况,如果要处理不一一对应的情况,最简单的解决方法是使用三方。
- + (instancetype)modelWithDictionary:(NSDictionary *)dic {
- Student *aStudent = [Student new];
- unsigned int count = 0;
- objc_property_t *propertyList = class_copyPropertyList([self class], &count);
- for (int i = 0; i < count; i ++) {
- objc_property_t aProperty = propertyList[i];
-
- const charchar *name = property_getName(aProperty);
-
- id value = dic[[NSString stringWithUTF8String:name]];
- if (!value) {
-
- }else {
-
- [aStudent setValue:value forKey:[NSString stringWithUTF8String:name]];
- }
- }
- return aStudent;
- }
(五)runtime添加类方法
- void study(id reccevier, SEL sel) {
-
- }
-
-
- + (BOOL)resolveInstanceMethod:(SEL)sel {
- if (sel == @selector(study)) {
- class_addMethod(self, @selector(study), (IMP)study, "v@:");
- }
- return [super resolveInstanceMethod:sel];
- }