ObjC是一门运行时语言,了解Runtime机制对于ObjC开发者来说至关重要。ObjC方法调用的本质,就是让对象发送消息:

1
id objc_msgSend ( id self, SEL op, ... );

具体的流程为:

  1. 通过isa指针找到所属类
  2. 查找类的cache列表, 如果没有则下一步
  3. 查找类的”方法列表”
  4. 如果能找到与选择子名称相符的方法, 就跳至其实现代码
  5. 找不到, 就沿着继承体系继续向上查找
  6. 如果能找到与选择子名称相符的方法, 就跳至其实现代码
  7. 找不到, 执行”消息转发”。

了解了这个流程,我们就可以实现但不止以下几种应用:关联对象、给分类增加属性、动态添加方法、Method Swizzling 交换方法、拦截系统方法的调用、自动归档解档、字典转模型、万能控制器跳转、KVO的底层实现、JSPatch热更新。

关联对象,给分类增加属性

1
2
3
4
5
6
// 关联对象
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)

例如给一个名为Person的类的分类添加一个books属性:

1
2
3
4
5
6
7
8
9
10
static const void *KEY_BOOKS = &KEY_BOOKS;

@implementation Person (Books)
- (void)setBooks:(NSArray *)books{
objc_setAssociatedObject(self, KEY_BOOKS, books, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSArray *)books{
return objc_getAssociatedObject(self, KEY_BOOKS);
}
@end

动态添加方法(performSelector)

1
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);

通过performSelector调用某个方法,如果在运行时找不到Selector对应的实现,会执行消息转发,在resolveInstanceMethodresolveClassMethod中将函数与对象的Selector关联起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void eat(id self, SEL sel) {
NSLog(@"%@ %@",self, NSStringFromSelector(sel));
}

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(eat)) {
class_addMethod(self, @selector(eat), eat, "[email protected]:");
}
return [super resolveInstanceMethod:sel];
}

@end

Method Swizzling 交换方法、拦截系统方法的调用

每个类都维护一个方法列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系。

1
2
3
4
5
6
7
8
9
10
11
+ (void)exchangeClassMethodImplementations:(Class)cls selector1:(SEL)selector1 selector2:(SEL)selector2{
Method m1 = class_getClassMethod(cls, selector1);
Method m2 = class_getClassMethod(cls, selector2);
method_exchangeImplementations(m1, m2);
}

+ (void)exchangeInstanceMethodImplementations:(Class)cls selector1:(SEL)selector1 selector2:(SEL)selector2{
Method m1 = class_getInstanceMethod(cls, selector1);
Method m2 = class_getInstanceMethod(cls, selector2);
method_exchangeImplementations(m1, m2);
}

自动归档解档

遍历Model自身所有属性,并对属性进行encode和decode操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (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];
}
}

访问私有变量

通过getIvar可以获取到任意已知name的成员变量的值。

1
2
Ivar ivar = class_getInstanceVariable([Model class], "_str1");
NSString * str1 = object_getIvar(model, ivar);

字典转模型的KVC实现

遍历Model自身所有属性,从json中找到与属性对应的值,通过KVC将其赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Ivar:成员变量 以下划线开头
// Property:属性
+ (instancetype)modelWithDict:(NSDictionary *)dict {
id objc = [[self alloc] init];
unsigned int count = 0;
// 获取成员变量数组
Ivar *ivarList = class_copyIvarList(self, &count);

// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 获取key
NSString *key = [ivarName substringFromIndex:1];

// 去字典中查找对应value
// key:user value:NSDictionary
id value = dict[key];

// 二级转换:判断下value是否是字典,如果是,字典转换层对应的模型
// 并且是自定义对象才需要转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 字典转换成模型 userDict => User模型
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDict:value];
}

// 给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}

return objc;
}

参考资料:http://www.cnblogs.com/jys509/p/5207159.html#autoid-3-4-0

实现万能控制器跳转

利用runtime动态生成对象、属性、方法这特性,我们可以先跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性id、type,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端就可以根据控制器名生成对象,再用kvc给对象赋值。

参考资料:http://www.cocoachina.com/articles/13104

KVO的底层实现

KVO是基于runtime机制实现的,当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。


 评论