Runtime
1、Runtime 介绍
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。就是一个动态的消息转发机制。
2、消息结构
//对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
//类
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
//方法
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
类对象 objc_class
由上面代码可以看出他是一个结构体,并包含很多变量。有实例变量列表、方法列表、缓存、遵守的协议列表等 类对象也有一个isa指针,说明他也是一个对象。他的isa指向元类。
实例isa指向类对象,类对象isa指向元类。元类的isa指向父类的元类
元类 Meta Class
元类(Meta Class)是一个类对象的类。 在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。 为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。 任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。
Method
就是我们平时使用的方法。可以独立完成一段功能
他有三个属性
- SEL method_name 方法名
- char *method_types 方法类型
- IMP method_imp 方法实现
selector
就是个方法选择器,他是SEL类型的。但是是className+method 的组合。是一个字符串。所以同一个类里面的方法名不能重复。
IMP
方法实现的指针
Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。
objc_cache
上面已经提到过。 就是当你搜索方法的时候,他会先搜搜类缓存。
objc_category
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
name:是指 class_name 而不是 category_name。
cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
instanceMethods:category中所有给类添加的实例方法的列表。
classMethods:category中所有添加的类方法的列表。
protocols:category实现的所有协议的列表。
instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。
从上面的category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量。
3、Runtime消息传递
- 查找方法
- 当你对一个对象进行消息转发的时候,会通过对象的isa指针找到他的class
- 在class里面有一个 method list 里面找方法
- 如果找不到则找父类,如果找到了,加入objc_cache提高函数查询的效率(method_name是key,imp是value)。避免每次调用都需要便利method list。
- 如果在class继承树里面都没有找到method,那么进入消息转发
- 消息转发
- 动态方法解析
/** 1、动态方法解析, 就是动态的添加一个方法 **/
void watchTV(id self, SEL _cmd, NSString *name) {
NSLog(@"我在看 %@", name);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// NSString *method = NSStringFromSelector(sel);
// if ([method isEqualToString:@"watchTV:"]) {
// /**
// 1、第一个返回值 void - v
// 2、@ 是第一个参数 id
// 3、方法传:
// 4、NSString 传 @
// **/
// return class_addMethod(self, sel, (IMP)watchTV, "v@:@");
// }
return NO;
}
- 快速转发
// 动态转发机制, 找一个新的对象来接收方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
// NSString *method = NSStringFromSelector(aSelector);
// if ([method isEqualToString:@"watchTV:"]) {
// return [Dog new];
// }
return [super forwardingTargetForSelector:aSelector];
}
- 慢速转发
/** 快速转发找不到
进入慢速转发
1、方法签名 保存方法的具体信息
2、慢速转发
**/
// 签名 帮方法信息保存的NSInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *method = NSStringFromSelector(aSelector);
if ([method isEqualToString:@"watchTV:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
Dog *dog = [Dog new];
if ([dog respondsToSelector:selector]) {
[anInvocation invokeWithTarget:dog];
} else {
[super forwardInvocation:anInvocation];
}
}
4、runtime应用
关联对象给分类增加属性
实现对象的set get 方法
在set方法里面调用
objc_setAssociatedObject
在get方法里面调用
objc_getAssociatedObject
添加方法和替换和KVO实现
例如我们上面所说的,动态方法解析的时候可以给一个类动态添加方法。 这里我们可以用于一些失败的处理,面向切面等。
方法替换
1、例如我们需要给每一个ViewController 的 viewdidload 添加埋点,那就可以使用交换方法来实现,而且不影响原项目
2、例如例如我们要给一个tableView为空数据的时候添加一张默认展示图片,上面的demo 有具体代码实现。
核心思想就是利用 class_getInstanceMethod 获取方法 并用 method_exchangeImplementations 来交换方法。
KVO实现
通过上面demo实现自定义KVO可以帮助我们更好的理解KVO和runtime 核心思想
-
监听对象创建他的子类。
· objc_allocateClassPair // 创建新的class
· objc_registerClassPair // 注册新的class
-
修改isa 指针 object_setClass 让isa指针指向新的class 。实现set方法并发送消息
-
重新setter方法
- class_addMethod 添加setter方法
objc_msgSendSuper 先执行父类方法
关联 Observer。然后在执行父类方法之后获取 Observer 发送消息
objc_setAssociatedObject
objc_getAssociatedObject
objc_msgSend
热更新解决Bug 例如 wax
使用脚本语言利用消息转发的特性,进行动态的替换,添加等方式。实现热更新
实现字典和模型的自动转换(MJExtension)
就是利用runtime来访问model的所有属性来取值和赋值。具体看demo