OC底层那些事儿:无处不在的runtime
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
启动中的runtime
处理category
- Category编译之后底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息,这里注意,Category并不存储成员变量。
- 在程序启动阶段,runtime会将Category中的数据合并到类信息中,对象方法、属性、协议等信息合并到类对象,类方法合并到元类对象。
- 另外我们平时在Class的.m文件中有再写interface,并添加一些属性和方法,这个是Extension,也是对类进行扩展,它和Category的本质不同在于:Extension在编译阶段就进行了合并,而Category的合并是发生在运行启动pre-main阶段
+(void)load
- 在runtime加载类、分类时调用
- 不使用objc_messageSend方式调用,而是放在load_method中使用方法地址调用
- 在程序启动pre-main阶段调用,call_class_load(void)循环调用load_method
- 先调用load_method调用类的+load方法,调用子类的+load之前会先调用父类的+load
- 根据分类编译先后顺序调用分类load方法
+(void)initialize
类第一次接收到消息的时候调用:就是第一次调用方法的时候调用 1. 查找方法class_getClassMethod 、 class_getInstanceMethod 2. lookUpImpOr objc_msgLookup中通过class_initialize(Class cls)判断是否初始化 3. objc_msgSend调用initialize方法,先调用父类的initialize后调用本类initialize并设置isInitialized = true 4. 如果子类没有实现initialize方法,则调用父类的initialize
方法调用中的runtime
OC中方法调用其实是objc_msgSend函数的调用过程,其具体流程主要包含三大阶段: - 消息发送:本类cache和方法列表父类cache和方法列表顺着往下找找到之后放到本类cache中然后执行 - 动态方法解析 :resolveInstance(Class)Method可以动态添加方法,retry消息发送 - 消息转发:可以转发给另一个对象或者换成其他方法调用,具体步骤如下:
- forwardingTargetForSelector(SEL)aSelector返回不为空则将aSelector转发给返回值对象去调用改方法。
- forwardingTargetForSelector(SEL)aSelector返回为空则调用:methodSignatureForSelector返回不为空~>forwardInvocation,返回为空则调用doesNotRecognizeSelector方法。
用途
可以做NSNull防崩溃操作,具体操作如下
```js
import "NSNull+NotFoundMethodException.h"
@implementation NSNull (NotFoundMethodException)
-(void)forwardInvocation:(NSInvocation *)anInvocation{
//使用nil调用方法会被直接忽略防止崩溃
anInvocation.target = nil;
[anInvocation invoke];
}
- (NSMethodSignature )methodSignatureForSelector:(SEL*)aSelector{
//在常见OC类型中寻找方法响应者,并获取方法签名
NSMethodSignature signature = [super* methodSignatureForSelector:aSelector];
if (!signature)
{
for (Class someClass in @[
[NSMutableArray class],
[NSMutableDictionary class],
[NSMutableString class],
[NSNumber class],
[NSString class],
[NSDate class],
[NSData class]
]){
@try
{
if([someClass instancesRespondToSelector:aSelector])
{
signature = [someClass instanceMethodSignatureForSelector:aSelector];
break;
}
}
@catch (__unused NSException *unused) {}
}
}
if(!signature) {//如果依然没有找到合适的方法签名则默认返回一个众所周知的 NSObject init方法签名
signature = [NSObject instanceMethodSignatureForSelector: @selector(init)];
} return signature; } ```
KVO中的runtime
- 利用Runtime API动态生成一个子类,并让instance对象isa指向这个子类。重写了class、dealloc,添加isKVOA方法
- 当修改instance对象的属性时,会调用Foundation框架的_NSSetXXXValueAndNotify函数而非setXXX方法
- 该方法先调用willChangeValueForKey:
- 然后调用本类原来的setter
- 最后调用didChangeValueForKey:
- will和did两个方法配合会触发监听器(Observer)的监听方法(objserveValueForKeyPath:ofObject:change:context:)
给Category添加属性(成员变量)
iOS动态关联属性(objc_setAssociatedObject,objc_getAssocicatedObject)的实现原理: 由于Category本身不能存放成员变量,所以需要runtime提供的关联对象API来管理这些成员变量。 - AssociationsManager 是顶级的对象,维护了一个从spinlock_t 锁到AssociationsHashMap哈希表的单例键值对映射; - AssociationsHashMap是一个无序的哈希表,维护了从对象地址到 ObjectAssociationMap的映射; - ObjectAssociationMap 是一个 C 中的 map ,维护了从 key 到 ObjcAssociation 的映射,即关联记录; - ObjcAssociation是一个C的类,表示一个具体的关联结构,主要包括两个实例变量,_policy 表示关联策略,_value 表示关联对象。
OC中的反射机制
- 使用runtime提供的API获取模型中所有属性这一特性,来对要进行转换的字典进行遍历
- 利用KVC的
- (nullable id)valueForKeyPath:(NSString *)keyPath;
方法去取出模型属性并作为字典中相对应的key读取字典中的值 - 再利用KVC的
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
方法把value赋值给模型属性。
Hook
程序启动后,为了实现监听用户跳转页面的功能,可以将viewDidLoad进行hook操作 ```
-
(void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad)); Method swizzledMethod = class_getInstanceMethod([UIViewController class], @selector(swizzling_viewDidLoad));
method_exchangeImplementations(originalMethod, swizzledMethod);
}); }
-
(void)swizzling_viewDidLoad { //在 viewDidLoad之前做一些事情,代码自己补充 [self swizzling_viewDidLoad]; //在 viewDidLoad方法调用之后做一些事情,代码自己补充 } ```