文章分享至我的个人技术博客: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