文章分享至我的个人技术博客:https://cainluo.github.io/15034954202343.html
我们已经来到了装逼技术学习的第三部分, 如果没有看到前面部分的朋友, 可以去看玩转iOS开发:iOS开发中的装逼技术 - RunTime(二).
这次说的是啥呢? 下面我们就来看看
转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.
自动归档
我们平常撸归档的时候, 是怎么弄的呢? 看看代码:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self = [super init]) {
NSObject *obj = [decoder decodeObjectForKey:@"keyName"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
NSObject *obj = [[NSObject alloc] init];
[coder encodeObject:obj
forKey:@"keyName"];
}
看上去好像没什么问题, 但如果我们有一百个归档怎么办, 那我们就要一个一个的去写, 各一百个, 写的手软.
既然讲到这里, 那肯定有更好的方法去解决了, Runtime也可以通过几个步骤之后, 进行归档:
- 通过
class_copuIvarList方法获取当前的Model的所有成员变量. - 通过
ivar_getName方法来获取成员变量的名称. - 通过
KVC来读取Model的属性值, 最后就给Model的属性赋值, 就完事.
举个栗子🌰,新建了一个名为CoderModel的Model类,详细代码为:
#import "CoderModel.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation CoderModel
- (void)encodeWithCoder:(NSCoder *)coder{
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[coder encodeObject:value
forKey:key];
}
free(vars);
}
- (nullable instancetype)initWithCoder:(NSCoder *)decoder{
if (self = [super init]) {
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(vars);
}
return self;
}
@end
- 耍过
KVC的老铁们都知道,KVC的特性, 若能找到key的属性setter方法, 就会调用setter方法. - 如果找不到
setter, 就会查找成员变量key或者成员变量_key - 所以我们在这里不需要再另外处理前缀有
_前缀的成员变量名
这里我们还需要写一个转模型的Category, 自己在工程里找吧, 最终实现:
- (void)coderModel {
CoderModel *coderModel = [CoderModel objectWithKeyValues:self.dictionary];
NSLog(@"%@, %ld, %ld", coderModel.name, coderModel.age, coderModel.phoneNumber);
NSDictionary *dictionary = [coderModel keyValuesWithObject];
NSLog(@"dictionary is %@", dictionary);
}
2017-08-23 22:27:51.349 1.RunTime[36904:3052474] 小明, -5764607523034234590, -5764607302232026877
2017-08-23 22:27:51.349 1.RunTime[36904:3052474] dictionary is {
age = 18;
name = "\U5c0f\U660e";
phoneNumber = 13800138000;
}
字典与模型互转
刚刚讲完归档之后, 就顺便把字典与模型的互转给说完吧.
我们以前给模型赋值的时候是:
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (self = [super init]) {
self.age = dictionary[@"age"];
self.name = dictionary[@"name"];
}
return self;
}
看起来貌似没啥问题, 但实际上是和归档遇到的问题一个样, 后来用过MJMJExtension, YYModel之后, 就发现, 原来模型解析还可以有别的方式.
我们来看看流程:
- 字典转模型的时候
- 根据字典的
key生成setter方法 - 使用
objc_msgSend调用setter方法为Model的属性赋值或者用KVC
- 根据字典的
+ (id)objectWithKeyValues:(NSDictionary *)dictionary {
id objc = [[self alloc] init];
for (NSString *key in dictionary.allKeys) {
id value = dictionary[key];
// 判断当前属性是否属于Model
objc_property_t property = class_getProperty(self, key.UTF8String);
unsigned int outCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
objc_property_attribute_t attribute = attributeList[0];
NSString *typeString = [NSString stringWithUTF8String:attribute.value];
if ([typeString isEqualToString:@"@\"CoderModel\""]) {
value = [self objectWithKeyValues:value];
}
// 生成setter方法,并用objc_msgSend调用
NSString *methodName = [NSString stringWithFormat:@"set%@%@:", [key substringToIndex:1].uppercaseString, [key substringFromIndex:1]];
SEL setter = sel_registerName(methodName.UTF8String);
if ([objc respondsToSelector:setter]) {
((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
free(attributeList);
}
return objc;
}
- 模型转字典的时候
- 调用
class_copyPropertyList方法获取当前Model的所有属性 - 调用 property_getName 获取属性名称
- 根据属性名称生成
getter方法 - 使用
objc_msgSend调用getter方法获取属性值或者用KVC
- 调用
- (NSDictionary *)keyValuesWithObject {
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
for (int i = 0; i < outCount; i ++) {
objc_property_t property = propertyList[i];
//生成getter方法,并用objc_msgSend调用
const char *propertyName = property_getName(property);
SEL getter = sel_registerName(propertyName);
if ([self respondsToSelector:getter]) {
id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
// 判断当前属性是否属于Model
if ([value isKindOfClass:[self class]] && value) {
value = [value keyValuesWithObject];
}
if (value) {
NSString *key = [NSString stringWithUTF8String:propertyName];
[mutableDictionary setObject:value
forKey:key];
}
}
}
free(propertyList);
return mutableDictionary;
}
动态方法解析
说到这里基本上就已经结束了, 最后就是对前面没讲的补充.
一般我们调用了对象里不存在里的方法就会直接崩掉, 然后给一个错误信息:
unrecognized selector sent to instance 0x0bcde09abc0
但是在崩掉之前, Runtime会给一个机会我们去进行动态解析, 流程大致是:
- 检测
selector需不需要忽略的, 比如在macOS上的开发, 有了ARC就不会理会什么retain,release这些方法了. - 检测
target是不是为nil,Objective-C是允许对一个nil对象随便执行一个方法都不会崩掉, 所以就会被忽略掉了, 但是在Swift就会出问题了. - 如果上面两个都过了, 那就会去查找这个类的
IMP, 先从cache里面找, 找到了就跳到对应的函数去执行.- 如果
cache找不到就找一下方法分发表
- 如果
- 如果在分发表里找不到, 那就会去超类的分发表里去查找, 一直找到
NSObject. - 如果还找不到就会开始进入消息转发
转发的大致过程:
图片是在网上的某个博文里偷过来的, 懒得画了.
- 进入了
resolveInstanceMethod:方法后, 指定是否是动态添加方法.- 如果返回是
NO, 就会进入下一步 - 如果返回
YES, 就会通过class_addMethod函数动态的添加方法, 消息就会得到处理, 这个过程就会结束了.
- 如果返回是
resolveInstanceMethod:方法返回NO时, 就会进入forwardingTargetForSelector方法, 这是Runtime给我们的第二次机会, 用于指定哪个对象响应这个selector.- 如果返回
nil, 就会进入下一步, 返回某个对象, 则会调用该对象的方法.
- 如果返回
forwardingTargetForSelector:返回是nil的话, 我们就要通过methodSignatureForSelector:来指定方法签名.- 如果
methodSignatureForSelector:返回nil, 就表示不处理, 返回方法签名, 就会进入下一步.
- 如果
- 当
methodSignatureForSelector:方法返回签名后, 就会调用forwardInvocation:方法, 我们可以通过anInvocation对象做处理, 比如修改实现方法,修改响应对象等等. - 如果到最后这里, 消息都还是没有得到响应或处理, 那么就会崩掉了.
我们来看看代码:
#import "BicycleModel.h"
#import "SportsCarModel.h"
#import <objc/runtime.h>
@implementation BicycleModel
- (void)ridingSpeed {
NSLog(@"Slow Ride");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
#if 0
return NO;
#else
class_addMethod(self, sel, class_getMethodImplementation(self, sel_registerName("ridingSpeed")), "v@:");
return [super resolveInstanceMethod:sel];
#endif
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
#if 0
return nil;
#else
return [[SportsCarModel alloc] init];
#endif
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
return [anInvocation invokeWithTarget:[[SportsCarModel alloc] init]];
}
#import "SportsCarModel.h"
@implementation SportsCarModel
- (void)rapidAcceleration {
NSLog(@"High speed");
}
@end
执行方法:
- (void)dynamicAnalysis {
BicycleModel *bicycle = [[BicycleModel alloc] init];
((void (*) (id, SEL)) objc_msgSend) (bicycle, sel_registerName("rapidAcceleration"));
}
打印出来的结果:
2017-08-24 00:09:15.238 1.RunTime[37657:3122648] Slow Ride
工程地址
项目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/RunTime/Three