持续创作,加速成长!这是我参与「掘金日新计划 · 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
类第一次接收到消息的时候调用:就是第一次调用方法的时候调用
- 查找方法class_getClassMethod 、 class_getInstanceMethod
- lookUpImpOr objc_msgLookup中通过class_initialize(Class cls)判断是否初始化
- objc_msgSend调用initialize方法,先调用父类的initialize后调用本类initialize并设置isInitialized = true
- 如果子类没有实现initialize方法,则调用父类的initialize
方法调用中的runtime
OC中方法调用其实是objc_msgSend函数的调用过程,其具体流程主要包含三大阶段:
- 消息发送:本类cache和方法列表父类cache和方法列表顺着往下找找到之后放到本类cache中然后执行
- 动态方法解析 :resolveInstance(Class)Method可以动态添加方法,retry消息发送
- 消息转发:可以转发给另一个对象或者换成其他方法调用,具体步骤如下:
- forwardingTargetForSelector(SEL)aSelector返回不为空则将aSelector转发给返回值对象去调用改方法。
- forwardingTargetForSelector(SEL)aSelector返回为空则调用:methodSignatureForSelector返回不为空~>forwardInvocation,返回为空则调用doesNotRecognizeSelector方法。
用途
可以做NSNull防崩溃操作,具体操作如下
#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方法调用之后做一些事情,代码自己补充
}
复制代码