iOS FBKVOController 源码分析

2020-03-17  本文已影响0人  孙掌门

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 源码分析

上面给大家总结了一些关键知识点和一些有意思的代码,剩下的我想大家看源码都能看懂。

上一篇 下一篇

猜你喜欢

热点阅读