ObjC是一门运行时语言,了解Runtime机制对于ObjC开发者来说至关重要。ObjC方法调用的本质,就是让对象发送消息:
id objc_msgSend ( id self, SEL op, ... );
|
具体的流程为:
- 通过isa指针找到所属类
- 查找类的cache列表, 如果没有则下一步
- 查找类的”方法列表”
- 如果能找到与选择子名称相符的方法, 就跳至其实现代码
- 找不到, 就沿着继承体系继续向上查找
- 如果能找到与选择子名称相符的方法, 就跳至其实现代码
- 找不到, 执行”消息转发”。
了解了这个流程,我们就可以实现但不止以下几种应用:关联对象、给分类增加属性、动态添加方法、Method Swizzling 交换方法、拦截系统方法的调用、自动归档解档、字典转模型、万能控制器跳转、KVO的底层实现、JSPatch热更新。
关联对象,给分类增加属性
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属性:
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
|
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
|
通过 performSelector
调用某个方法,如果在运行时找不到 Selector
对应的实现,会执行消息转发,在 resolveInstanceMethod
或 resolveClassMethod
中将函数与对象的 Selector
关联起来。
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, "v@:"); } return [super resolveInstanceMethod:sel]; }
@end
|
Method Swizzling 交换方法、拦截系统方法的调用
每个类都维护一个方法列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系。
+ (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操作。
- (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的成员变量的值。
Ivar ivar = class_getInstanceVariable([Model class], "_str1"); NSString * str1 = object_getIvar(model, ivar);
|
字典转模型的KVC实现
遍历Model自身所有属性,从json中找到与属性对应的值,通过KVC将其赋值。
+ (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)]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""]; NSString *key = [ivarName substringFromIndex:1];
id value = dict[key];
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) { 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方法。