FBKVOController 源碼分析
---------摘取源碼重要知識點-----------
1.有意思的宏定義
/**
This macro ensures that key path exists at compile time.
Given a real receiver with a key path as you would call it, it verifies at compile time that the key path exists, without calling it.
For example:
FBKVOKeyPath(string.length) => @"length"
Or even the complex case:
FBKVOKeyPath(string.lowercaseString.length) => @"lowercaseString.length".
*/
#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
上面這個宏定義什么意思呢票编?我們看注釋大概能明白要表達的意思方妖,就是取出,要觀察的對象的屬性值或者類的屬性值枣申,(void)(NO && ((void)KEYPATH, NO))
這個的意思就是快速返回 NO还棱,減少運算撤摸,因為 NO&&任何都為NO,這里要注意温眉,這兩個 NO缸匪,目的就是為了讓KEYPATH不進行運算,因為有可能執(zhí)行g(shù)et方法类溢,因為目的只是做檢測凌蔬,所以不能讓他進行運算w,后面(void)KEYPATH這個是為了檢查編譯闯冷,是為了檢查KEYPATH砂心,接著 const char *fbkvokeypath = strchr(#KEYPATH, '.');
搞了一個指針 fbkvokeypath
, #KEYPATH
的意思是將傳進來的 KEYPATH
轉(zhuǎn)化為字符串,意思就是 test.name
轉(zhuǎn)化為 "test.name"
,因為轉(zhuǎn)化為字符串就可以方便操作了蛇耀,然后進行 strchr(#KEYPATH, '.')
是為了截取.
后的字符串辩诞,也就是 string.length
->.length
,string.lowercaseString.length
->.lowercaseString.length
, NSCAssert 檢查字符串是否有效,最后 fbkvokeypath + 1 纺涤,也就是說 fbkvokeypath 的指針地址 +1译暂,也就是下一個地址,目的就是去掉 .
, string.length
->.length
->length
撩炊,這就是最終目的外永,最終目的就是監(jiān)聽傳進來的對象或者類的屬性,也就是keyPath衰抑,有點意思象迎。
2. 有意思的 debug 打印
static NSString *describe_option(NSKeyValueObservingOptions option)
{
switch (option) {
case NSKeyValueObservingOptionNew:
return @"NSKeyValueObservingOptionNew";
break;
case NSKeyValueObservingOptionOld:
return @"NSKeyValueObservingOptionOld";
break;
case NSKeyValueObservingOptionInitial:
return @"NSKeyValueObservingOptionInitial";
break;
case NSKeyValueObservingOptionPrior:
return @"NSKeyValueObservingOptionPrior";
break;
default:
NSCAssert(NO, @"unexpected option %tu", option);
break;
}
return nil;
}
static void append_option_description(NSMutableString *s, NSUInteger option)
{
if (0 == s.length) {
[s appendString:describe_option(option)];
} else {
[s appendString:@"|"];
[s appendString:describe_option(option)];
}
}
//https://blog.csdn.net/weixin_33674976/article/details/91478135
static NSUInteger enumerate_flags(NSUInteger *ptrFlags)
{
NSCAssert(ptrFlags, @"expected ptrFlags");
if (!ptrFlags) {
return 0;
}
NSUInteger flags = *ptrFlags;
if (!flags) {
return 0;
}
NSUInteger flag = 1 << __builtin_ctzl(flags);
flags &= ~flag;
*ptrFlags = flags;
return flag;
}
static NSString *describe_options(NSKeyValueObservingOptions options)
{
NSMutableString *s = [NSMutableString string];
NSUInteger option;
while (0 != (option = enumerate_flags(&options))) {
append_option_description(s, option);
}
NSLog(@"%@",s);
return s;
}
首先會調(diào)用 describe_options
方法,這個方法的意思就是呛踊,循環(huán)打印每個枚舉的信息砾淌,因為枚舉是位運算,經(jīng)過位運算之后谭网,最后會形成一個值汪厨,拿著這個值去打印每個的信息,什么意思呢愉择?比如
SCXEnum1 = 1<<0,
SCXEnum2 = 1<<1,
SCXEnum3 = 1<<2,
然后我們最后需要穿的值為 SCXEnum2 | SCXEnum3 劫乱,那么就是最后的值就是6,這個6包含了兩個值锥涕,二進制數(shù)據(jù)為 0110
,當(dāng)我們調(diào)用 describe_options 這個方法的時候衷戈,會調(diào)用 enumerate_flags 這個方法,這個方法什么意思呢层坠?__builtin_ctzl 是找到二進制后右邊第一個不為1的位置殖妇,比如6也就是0110,穿進去返回的是1破花,也就是第一個1出現(xiàn)的位置谦趣,然后 1<<1,這個值不就是SCXEnum2疲吸,然后 flags &= ~flag;
*ptrFlags = flags;,就是將這個值從原來的總和里給去除前鹅,然后繼續(xù)while循環(huán)摘悴,挨個去除我們設(shè)置的值,也就是說把6分成了 SCXEnum2 和 SCXEnum3舰绘,是不是很巧妙蹂喻,學(xué)會了嗎?坐下除盏,以后再打印枚舉的值是不是有騷操作了叉橱?反正我以前是不會。
3.NSDictionary NSHashTable NSMapTable
3.1NSDictionary
在了解 NSHashTable 之前者蠕,讓我們先了解下 NSSet和NSDictionary窃祝,
1. 對于 object 都是強引用
2. NSDIctionary 的 key 需要實現(xiàn) NSCopying 協(xié)議,不實現(xiàn)比較麻煩
3. 使用 hash 獲取 hash 值踱侣,通過 isEqual 判斷是否相等粪小,如果hash相等
NSDictionary 要求 key 不能變,因為NSDIctionary中存儲的object的位置是由key來索引的抡句,并且要求key盡量小探膊,否則key的copy比較耗時,所以NSDIctionary的key不適合我們的自定義對象待榔,所以適用于 key->object 的映射
3.2NSHashTable
而我們 NSHashTable
1. 只有可變的逞壁,沒有不可變
2. 可以對加入的對象弱引用
3. 可以對加入的對象 copy
4. 可以包含任意指針,可以使用指針去判等
1. NSHashTableStrongMemory = NSPointerFunctionsStrongMemory : 強引用對象
2. NSHashTableCopyIn=NSPointerFunctionsCopyIn:加入之前 copy 一份
3. NSHashTableObjectPointerPersonality=NSPointerFunctionsObjectPointerPersonality:使用指針進行isEqual:和 hash锐锣。
4. NSHashTableWeakMemory=NSPointerFunctionsWeakMemory:弱引用腌闯,對象銷毀時,自動銷毀
我們可以把他理解為我們的數(shù)組的高級版雕憔,可以存儲弱引用對象姿骏。
3.3NSMapTable
NSMapTable 是為了解決對象到對象的映射
NSMapTableStrongMemory : 強引用
NSMapTableWeakMemory :弱引用
NSPointerFunctionsObjectPersonality: isEqual和hash比較的是-description方法的值
NSPointerFunctionsObjectPointerPersonality : isEqual和hash比較的是指針的地址
NSMapTableCopyIn :copy
比如小明愛吃糖,小紅愛吃火鍋斤彼,如果是以前分瘦,用 NSDIctionary ,可以我們設(shè)計的是琉苇,
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"糖" forKey:@"xiaoming"];
[dic setObject:@"火鍋" forKey:@"xiaohong"];
但如果我們以后加需求嘲玫,需要存儲每個人,和每個人的所有愛好并扇,如果以后我們想查某個人趁冈,及其這個人所有的愛好,NSDIctionary就滿足不了需求,所以我們可以用 NSMapTable
[dic setObject:person(人對象渗勘,里面存儲姓名年齡等) forKey:愛好(愛好對象,存儲所有愛好)];
hashTable 和 mapTable俩莽,都可以弱引用對象旺坠,比如將一個 obj,添加進去之后扮超,然后 obj = nil取刃,那么map中存儲的對象也會被移除,mapTable 無論是key或者value被移除,相應(yīng)的值都會被移除
4 源碼分析
上面給大家總結(jié)了一些關(guān)鍵知識點和一些有意思的代碼出刷,剩下的我想大家看源碼都能看懂璧疗。