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不进行运算,因为有可能执行get方法,因为目的只是做检测,所以不能让他进行运算w,后面(void)KEYPATH这个是为了检查编译,是为了检查KEYPATH,接着 const char *fbkvokeypath = strchr(#KEYPATH, '.');
搞了一个指针 fbkvokeypath
, #KEYPATH
的意思是将传进来的 KEYPATH
转化为字符串,意思就是 test.name
转化为 "test.name"
,因为转化为字符串就可以方便操作了,然后进行 strchr(#KEYPATH, '.')
是为了截取.
后的字符串,也就是 string.length
->.length
,string.lowercaseString.length
->.lowercaseString.length
, NSCAssert 检查字符串是否有效,最后 fbkvokeypath + 1 ,也就是说 fbkvokeypath 的指针地址 +1,也就是下一个地址,目的就是去掉 .
, string.length
->.length
->length
,这就是最终目的,最终目的就是监听传进来的对象或者类的属性,也就是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;
}
首先会调用 describe_options
方法,这个方法的意思就是,循环打印每个枚举的信息,因为枚举是位运算,经过位运算之后,最后会形成一个值,拿着这个值去打印每个的信息,什么意思呢?比如
SCXEnum1 = 1<<0,
SCXEnum2 = 1<<1,
SCXEnum3 = 1<<2,
然后我们最后需要穿的值为 SCXEnum2 | SCXEnum3 ,那么就是最后的值就是6,这个6包含了两个值,二进制数据为 0110
,当我们调用 describe_options 这个方法的时候,会调用 enumerate_flags 这个方法,这个方法什么意思呢?__builtin_ctzl 是找到二进制后右边第一个不为1的位置,比如6也就是0110,穿进去返回的是1,也就是第一个1出现的位置,然后 1<<1,这个值不就是SCXEnum2,然后 flags &= ~flag;
*ptrFlags = flags;,就是将这个值从原来的总和里给去除,然后继续while循环,挨个去除我们设置的值,也就是说把6分成了 SCXEnum2 和 SCXEnum3,是不是很巧妙,学会了吗?坐下,以后再打印枚举的值是不是有骚操作了?反正我以前是不会。
3.NSDictionary NSHashTable NSMapTable
3.1NSDictionary
在了解 NSHashTable 之前,让我们先了解下 NSSet和NSDictionary,
1. 对于 object 都是强引用
2. NSDIctionary 的 key 需要实现 NSCopying 协议,不实现比较麻烦
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:弱引用,对象销毁时,自动销毁
我们可以把他理解为我们的数组的高级版,可以存储弱引用对象。
3.3NSMapTable
NSMapTable 是为了解决对象到对象的映射
NSMapTableStrongMemory : 强引用
NSMapTableWeakMemory :弱引用
NSPointerFunctionsObjectPersonality: isEqual和hash比较的是-description方法的值
NSPointerFunctionsObjectPointerPersonality : isEqual和hash比较的是指针的地址
NSMapTableCopyIn :copy
比如小明爱吃糖,小红爱吃火锅,如果是以前,用 NSDIctionary ,可以我们设计的是,
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被移除,相应的值都会被移除
4 源码分析
上面给大家总结了一些关键知识点和一些有意思的代码,剩下的我想大家看源码都能看懂。
网友评论