OC底层那些事儿:无处不在的runtime

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

启动中的runtime

处理category

  1. Category编译之后底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息,这里注意,Category并不存储成员变量。
  2. 在程序启动阶段,runtime会将Category中的数据合并到类信息中,对象方法、属性、协议等信息合并到类对象,类方法合并到元类对象。
  3. 另外我们平时在Class的.m文件中有再写interface,并添加一些属性和方法,这个是Extension,也是对类进行扩展,它和Category的本质不同在于:Extension在编译阶段就进行了合并,而Category的合并是发生在运行启动pre-main阶段

+(void)load

  1. 在runtime加载类、分类时调用
  2. 不使用objc_messageSend方式调用,而是放在load_method中使用方法地址调用
  3. 在程序启动pre-main阶段调用,call_class_load(void)循环调用load_method
  4. 先调用load_method调用类的+load方法,调用子类的+load之前会先调用父类的+load
  5. 根据分类编译先后顺序调用分类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消息发送
  • 消息转发:可以转发给另一个对象或者换成其他方法调用,具体步骤如下:
  1. forwardingTargetForSelector(SEL)aSelector返回不为空则将aSelector转发给返回值对象去调用改方法。
  2. 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

  1. 利用Runtime API动态生成一个子类,并让instance对象isa指向这个子类。重写了class、dealloc,添加isKVOA方法
  2. 当修改instance对象的属性时,会调用Foundation框架的_NSSetXXXValueAndNotify函数而非setXXX方法
  3. 该方法先调用willChangeValueForKey:
  4. 然后调用本类原来的setter
  5. 最后调用didChangeValueForKey:
  6. 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中的反射机制

  1. 使用runtime提供的API获取模型中所有属性这一特性,来对要进行转换的字典进行遍历
  2. 利用KVC的- (nullable id)valueForKeyPath:(NSString *)keyPath;方法去取出模型属性并作为字典中相对应的key读取字典中的值
  3. 再利用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方法调用之后做一些事情,代码自己补充
}
复制代码

猜你喜欢

转载自juejin.im/post/7106156453710266398