面試驅(qū)動技術(shù)合集(初中級iOS開發(fā))环鲤,關(guān)注倉庫,及時獲取更新 Interview-series
Class 結(jié)構(gòu)詳解
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;//已經(jīng)緩存的方法數(shù)量
}
struct bucket_t {
cache_key_t _key;//@selecter(xxx) 作為key
MethodCacheIMP _imp;//函數(shù)的執(zhí)行地址
}
-
buckets
散列表古徒,是一個數(shù)組爆雹,數(shù)組里面的每一個元素就是一個bucket_t
,bucket_t
里面存放兩個-
_key
SEL作為key -
_imp
函數(shù)的內(nèi)存地址
-
-
_mask
散列表的長度 -
_occupied
已經(jīng)緩存的方法數(shù)量
- 函數(shù)調(diào)用底層走的是objc_msgSend
正常的流程:
- 對象通過isa,找到函數(shù)所在的類對象
- 這時候先做緩存查找勺三,如果緩存的函數(shù)列表中沒找到該方法
- 就去類的class_rw中的methods中找雷滚,如果找到了,調(diào)用并緩存該方法
- 如果類的class_rw中沒找到該方法吗坚,通過superclass到父類中祈远,走的邏輯還是先查緩存,緩存沒有查類里面的方法商源。
- 最終如果在父類中調(diào)用到了车份,會將方法緩存到當(dāng)前類的方法緩存列表中
方法緩存
如何進(jìn)行緩存查找->使用散列表(散列表 - 空間換時間)
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 0x100000be0
2019-03-13 22:11:42.912946+0800 beauty 0x100000c10
2019-03-13 22:11:42.912970+0800 (null) 0x0
2019-03-13 22:11:42.913002+0800 init 0x7fff4f98ff4d
發(fā)現(xiàn)緩存中已經(jīng)有三個方法了,分別是初始化調(diào)用的init牡彻,第一次調(diào)用的beauty和第二次調(diào)用的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索引,數(shù)組同index就
注意缎除,不一定每次都能準(zhǔn)確的index索引严就,算出來的index取出來的內(nèi)容不一定是想要的,但是經(jīng)常是比較接近器罐,最差的情況下梢为,也只是一邊的循環(huán)遍歷
索引散列表效率遠(yuǎn)高于數(shù)組!
方法查找的源碼: 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);
// hack
Class 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 <= 原來的值
哈希表的算法也有用求余的铸董,和&類似
實現(xiàn)如下:
(i = cache_next(i, m)) != begin
查找流程梳理: 比如起始下標(biāo)是4, 總長度是6肴沫,目標(biāo)不在列表中
- 取出index = 4的值袒炉,發(fā)現(xiàn)不是想要的,i - - 變成3
- 3 依次 - - 到0樊零,然后mask長度開始 = 6繼續(xù)
- 當(dāng)6 又 - - 到起始index = 4的時候我磁,說明已經(jīng)遍歷一圈了,還是沒找到驻襟,方法緩存查找結(jié)束
OC的消息機(jī)制
三個階段
消息發(fā)送
動態(tài)方法解析
消息轉(zhuǎn)發(fā)
消息發(fā)送
當(dāng)前類查找順序
- 排序好的列表夺艰,采用二分查找算法查找對應(yīng)的執(zhí)行函數(shù)
- 未排序的列表,采用一般遍歷的方法查找對象執(zhí)行函數(shù)
父類逐級查找
動態(tài)方法解析
@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));
//動態(tài)添加interview方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
----------------------------------------------
//調(diào)用
IOSer *ios = [[IOSer alloc]init];
[ios interview];
---------------------------------------------
結(jié)果沉衣,不會crash郁副,進(jìn)入了動態(tài)添加的方法了
2019-03-17 21:33:51.475717+0800 Runtime-TriedResolverDemo[11419:9277997] -[IOSer test]
消息轉(zhuǎn)發(fā)流程
- 消息轉(zhuǎn)發(fā)流程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作為消息轉(zhuǎn)發(fā)的接收者
return [[IOSer alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
---------------------------------------------------------------
調(diào)用
Forwarding *obj = [[Forwarding alloc]init];
[obj interview];
---------------------------------------------
結(jié)果,不會crash豌习,進(jìn)入了動態(tài)添加的方法了
2019-03-17 22:57:45.130805+0800 Runtime-TriedResolverDemo[13776:9355195] -[IOSer interview]
- 消息轉(zhuǎn)發(fā)流程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 - 方法調(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//設(shè)置方法調(diào)用者
[anInvocation invokeWithTarget:[[IOSer alloc]init]];
}
@end
- NSInvocation 其實封裝了一個方法調(diào)用存谎,包括:
- 方法名 -
anInvocation.selector
- 方法調(diào)用 -
anInvocation.target
- 方法參數(shù) -
anInvocation getArgument: atIndex:
- 方法名 -
冷門知識補(bǔ)充
//類方法的消息轉(zhuǎn)發(fā)
[Forwarding test];
類方法也可以實現(xiàn)消息轉(zhuǎn)發(fā)肥隆,但是用的是+ (id)forwardingTargetForSelector:(SEL)aSelector
函數(shù)
因為__forwarding底層既荚,是用receiver去發(fā)送
forwardingTargetForSelector
消息,如果是類方法栋艳,receiver是類對象恰聘,所以要調(diào)用的是 “+” 方法
小tips:默認(rèn)是沒有+ (id)forwardingTargetForSelector:(SEL)aSelector
方法,可以先打- (id)forwardingTargetForSelector:(SEL)aSelector
吸占,“-” 替換成“+”晴叨,完成~
友情演出:小馬哥MJ
參考資料: