面试驱动技术合集(初中级iOS开发),关注仓库,及时获取更新 Interview-series
Class 结构详解
struct objc_class : objc_object { Class isa; Class superclass; cache_t cache;--> 方法缓存 class_data_bits_t bits; }
struct cache_t { struct bucket_t *_buckets;//散列表 mask_t _mask;//散列表长度-1 mask_t _occupied;//已经缓存的方法数量 }
struct bucket_t { cache_key_t _key;//@selecter(xxx) 作为key MethodCacheIMP _imp;//函数的执行地址 }
buckets 散列表,是一个数组,数组里面的每一个元素就是一个bucket_t,bucket_t里面存放两个
-
_key SEL作为key
-
_imp 函数的内存地址
_mask 散列表的长度
_occupied已经缓存的方法数量
函数调用底层走的是objc_msgSend
正常的流程:
-
对象通过isa,找到函数所在的类对象
-
这时候先做缓存查找,如果缓存的函数列表中没找到该方法
-
就去类的class_rw中的methods中找,如果找到了,调用并缓存该方法
-
如果类的class_rw中没找到该方法,通过superclass到父类中,走的逻辑还是先查缓存,缓存没有查类里面的方法。
-
最终如果在父类中调用到了,会将方法缓存到当前类的方法缓存列表中
方法缓存
如何进行缓存查找->使用散列表(散列表 – 空间换时间)
MNGirl *girl = [[MNGirl alloc]init];mj_objc_class *girlClass = (__bridge mj_objc_class *)[MNGirl class];[girl beauty];[girl rich];//遍历缓存(散列表长度 = mask + 1)cache_t cache = girlClass->cache;bucket_t *buckets = cache._buckets;for (int i = 0; i < cache._mask + 1; i++) { bucket_t bucket = buckets[i]; NSLog(@"%s %p", bucket,bucket._imp);}----------------------------------------2019-03-13 22:11:42.911494+0800 rich 0x100000be02019-03-13 22:11:42.912946+0800 beauty 0x100000c102019-03-13 22:11:42.912970+0800 (null) 0x02019-03-13 22:11:42.913002+0800 init 0x7fff4f98ff4d
发现缓存中已经有三个方法了,分别是初始化调用的init,第一次调用的beauty和第二次调用的rich
散列表取方法
[girl beauty];[girl rich];//遍历缓存(散列表长度 = mask + 1)cache_t cache = girlClass->cache;bucket_t *buckets = cache._buckets;bucket_t bucket = buckets[(long long)@selector(beauty) & cache._mask];NSLog(@"%s %p", bucket,bucket._imp);-----------------------------------------2019-03-13 22:15:00 beauty 0x100000c60
确实是取方法的时候,不用遍历,通过@selector( ) & mask = index索引,数组同index就
注意,不一定每次都能准确的index索引,算出来的index取出来的内容不一定是想要的,但是经常是比较接近,最差的情况下,也只是一边的循环遍历
索引散列表效率远高于数组!
方法查找的源码: bucket_t * cache_t::find(cache_key_t k, id receiver)
bucket_t * cache_t::find(cache_key_t k, id receiver){assert(k != 0);bucket_t *b = buckets();mask_t m = mask();mask_t begin = cache_hash(k, m);mask_t i = begin;do {if (b[i].key() == 0 || b[i].key() == k) {return &b[i];}} while ((i = cache_next(i, m)) != begin);// hackClass cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));cache_t::bad_cache(receiver, (SEL)k, cls);}
索引值 Index 的计算
static inline mask_t cache_hash(cache_key_t key, mask_t mask) { return (mask_t)(key & mask);}mask_t begin = cache_hash(k, m);
走的是 key & mask的方法, A & B 一定是小于 A的
1111 0010&0011 1111---------- 0011 0010 <= 原来的值
哈希表的算法也有用求余的,和&类似
实现如下:
(i = cache_next(i, m)) != begin
查找流程梳理: 比如起始下标是4, 总长度是6,目标不在列表中
取出index = 4的值,发现不是想要的,i – – 变成3
3 依次 – – 到0,然后mask长度开始 = 6继续
当6 又 – – 到起始index = 4的时候,说明已经遍历一圈了,还是没找到,方法缓存查找结束
OC的消息机制群昵称:ios-Swift/Object C开发上架
群号: 869685378 找ios马甲包开发者合作,有兴趣请添加Q 51259559
三个阶段
-
消息发送
-
动态方法解析
-
消息转发
消息发送
当前类查找顺序
-
排序好的列表,采用二分查找算法查找对应的执行函数
-
未排序的列表,采用一般遍历的方法查找对象执行函数
父类逐级查找
动态方法解析
@interface IOSer : NSObject- (void)interview;@end@implementation IOSer- (void)test{ NSLog(@"%s",__func__); }+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(interview)) { Method method = class_getInstanceMethod(self, @selector(test)); //动态添加interview方法 class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); return YES; } return [super resolveInstanceMethod:sel];}@end----------------------------------------------//调用IOSer *ios = [[IOSer alloc]init];[ios interview];---------------------------------------------结果,不会crash,进入了动态添加的方法了2019-03-17 21:33:51.475717+0800 Runtime-TriedResolverDemo[11419:9277997] -[IOSer test]
消息转发流程
-
消息转发流程1:forwardingTargetForSelector
@implementation IOSer- (void)interview{ NSLog(@"%s",__func__);}@end@interface Forwarding : NSObject- (void)interview;@end@implementation Forwarding- (id)forwardingTargetForSelector:(SEL)aSelector{ if (aSelector == @selector(interview)) { //objc_msgSend([[IOSer alloc]init],aSelector) //由IOSer作为消息转发的接收者 return [[IOSer alloc]init]; } return [super forwardingTargetForSelector:aSelector];}@end---------------------------------------------------------------调用Forwarding *obj = [[Forwarding alloc]init];[obj interview];---------------------------------------------结果,不会crash,进入了动态添加的方法了2019-03-17 22:57:45.130805+0800 Runtime-TriedResolverDemo[13776:9355195] -[IOSer interview]
-
消息转发流程2:forwardingTargetForSelector
@implementation Forwarding//返回方法签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ if (aSelector == @selector(interview)) { //v16@0:8 = void xxx (self,_cmd) return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"]; } return [super methodSignatureForSelector:aSelector];}//NSInvocation - 方法调用- (void)forwardInvocation:(NSInvocation *)anInvocation{ //设置方法调用者 [anInvocation invokeWithTarget:[[IOSer alloc]init]];}@end
NSInvocation 其实封装了一个方法调用,包括:
-
方法名 – anInvocation.selector
-
方法调用 – anInvocation.target
-
方法参数 – anInvocation getArgument: atIndex:
冷门知识补充
//类方法的消息转发[Forwarding test];
类方法也可以实现消息转发,但是用的是+ (id)forwardingTargetForSelector:(SEL)aSelector函数
因为__forwarding底层,是用receiver去发送 forwardingTargetForSelector消息,如果是类方法,receiver是类对象,所以要调用的是 “+” 方法
小tips:默认是没有+ (id)forwardingTargetForSelector:(SEL)aSelector方法,可以先打- (id)forwardingTargetForSelector:(SEL)aSelector,“-” 替换成“+”,完成~
友情演出:小马哥MJ
参考资料:
objc-msgsend
gun
libmalloc
objc4
Objective-C-Message-Sending-and-Forwarding
作者:小蠢驴打代码
群昵称:ios-Swift/Object C开发上架
群号: 869685378 找ios马甲包开发者合作,有兴趣请添加Q 51259559