美文网首页
[iOS] NSMapTable & git tip

[iOS] NSMapTable & git tip

作者: 木小易Ying | 来源:发表于2021-09-04 08:00 被阅读0次
1. strongToWeakObjectsMapTable

NSMapTable 如果用 strongToWeak,也就是key是strong,value是weak的话,当一个key的value小时,虽然NSMapTable已经没有对象了,但是key并不会被release,仍旧有一个奇奇怪怪的东西拿住他:

map

虽然map里面只有一个键值对了,但是却还是持有了多个key哦,所以其实key不会自动释放的。

于是这个问题就到了 NSMapTable 的实现原理,根据这篇文章我找到了源码http://www.ziyingtech.com/yk4sck.html#nsmaptable-%E6%BA%90%E7%A0%81

然后自己开始看起来0.0 讲真没想到如此复杂...

+ (void) initialize
{
  if (abstractClass == 0)
    {
      abstractClass = [NSMapTable class];
      concreteClass = [NSConcreteMapTable class];
    }
}

其实 NSMapTable 的实现都是走的 NSConcreteMapTable,后者是前者的子类哈。先看init叭:

// NSMapTable
- (id) initWithKeyOptions: (NSPointerFunctionsOptions)keyOptions
         valueOptions: (NSPointerFunctionsOptions)valueOptions
             capacity: (NSUInteger)initialCapacity
{
  NSPointerFunctions    *k;
  NSPointerFunctions    *v;
  id            o;

  k = [[NSPointerFunctions alloc] initWithOptions: keyOptions];
  v = [[NSPointerFunctions alloc] initWithOptions: valueOptions];
  o = [self initWithKeyPointerFunctions: k
          valuePointerFunctions: v
                   capacity: initialCapacity];
  [k release];
  [v release];
  return o;
}

其实最后调到的还是 NSConcreteMapTable 的,子类里干了什么呢?开始其实就是去配置那个weak还是strong的option,如果没有需要创建default的,然后就做了一个分配空间的事情:

GSIMapInitWithZoneAndCapacity(self, zone, initialCapacity);

GS_STATIC_INLINE void 
GSIMapInitWithZoneAndCapacity(GSIMapTable map, NSZone *zone, uintptr_t capacity)
{
  map->zone = zone;
  map->nodeCount = 0;
  map->bucketCount = 0;
  map->buckets = 0;
  map->nodeChunks = 0;
  map->freeNodes = 0;
  map->chunkCount = 0;
  map->increment = 300000;   // choosen so the chunksize will be less than 4Mb
  GSIMapRightSizeMap(map, capacity);
  GSIMapMoreNodes(map, capacity);
}

可以看出来 map 有好多属性啊,来到声明里面康康:

@interface  NSConcreteMapTable : NSMapTable
{
@public
  NSZone    *zone;
  size_t    nodeCount;  /* Number of used nodes in map. */
  size_t    bucketCount;    /* Number of buckets in map.    */
  GSIMapBucket  buckets;    /* Array of buckets.        */
  GSIMapNode    freeNodes;  /* List of unused nodes.    */
  GSIMapNode    *nodeChunks;    /* Chunks of allocated memory.  */
  size_t    chunkCount; /* Number of chunks in array.   */
  size_t    increment;  /* Amount to grow by.       */
  unsigned long version;    /* For fast enumeration.    */
  BOOL      legacy;     /* old style callbacks?     */
  union {
    struct {
      PFInfo    k;
      PFInfo    v;
    } pf;
    struct {
      NSMapTableKeyCallBacks k;
      NSMapTableValueCallBacks v;
    } old;
  }cb;
}
@end

真的是一个 map 为啥要这么复杂,于是他有很多node和bucket,这俩分别是啥呢?

struct  _GSIMapNode {
  GSIMapNode    nextInBucket;   /* Linked list of bucket.   */
  GSIMapKey key;
#if GSI_MAP_HAS_VALUE
  GSIMapVal value;
#endif
};

struct  _GSIMapBucket {
  uintptr_t nodeCount;  /* Number of nodes in bucket.   */
  GSIMapNode    firstNode;  /* The linked list of nodes.    */
};
bucket和node的关系

大概就是bucket就像一个链表头,node就是里面的内容,但是map持有了一个 bucket 的array,为啥要array呢?现在来看看 set 的时候做了什么:

- (void) setObject: (id)anObject forKey: (id)aKey
{
  GSIMapNode    node;

  if (aKey == nil)
    {
      [NSException raise: NSInvalidArgumentException
          format: @"[%@-%@:] given nil argument",
        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
    }
  node = GSIMapNodeForKey(self, (GSIMapKey)aKey);
  if (node)
    {
      if (GSI_MAP_READ_VALUE(self, &node->value).obj != anObject)
    {
          GSI_MAP_RELEASE_VAL(self, node->value);
          GSI_MAP_WRITE_VAL(self, &node->value, (GSIMapVal)anObject);
          GSI_MAP_RETAIN_VAL(self, node->value);
      version++;
    }
    }
  else
    {
      GSIMapAddPair(self, (GSIMapKey)aKey, (GSIMapVal)anObject);
      version++;
    }
}

首先如果它找不到 key 绑定的 node,就会创建一个新的,如果找得到,就会释放之前 node 里面的 value,然后写入一个新的 value。那么它是如何找到 node 的呢?

首先找 node 需要先找到一个 bucket,然后再找 node 就很容易了,其实就是链表的遍历。but 如何找到 bucket 有点神奇:

GS_STATIC_INLINE GSIMapNode 
GSIMapNodeForKey(GSIMapTable map, GSIMapKey key)
{
  GSIMapBucket  bucket;
  GSIMapNode    node;

  if (map->nodeCount == 0)
    {
      return 0;
    }
  bucket = GSIMapBucketForKey(map, key);
  node = GSIMapNodeForKeyInBucket(map, bucket, key);
  return node;
}

GS_STATIC_INLINE GSIMapBucket
GSIMapBucketForKey(GSIMapTable map, GSIMapKey key)
{
  return GSIMapPickBucket(GSI_MAP_HASH(map, key),
    map->buckets, map->bucketCount);
}

GS_STATIC_INLINE GSIMapBucket
GSIMapPickBucket(unsigned hash, GSIMapBucket buckets, uintptr_t bucketCount)
{
  return buckets + hash % bucketCount;
}

拿 bucket 的方式一看就是 map->buckets 指向一个地址,然后连续有几个 bucket,当我们放一个key的时候,先拿他的 hash 看看放到哪个 bucket。(一旦您把第一个元素的地址存储在 p 中,您就可以使用 p、(p+1)、*(p+2) 等来访问数组元素)

那么这些bucket是咋创建的呢?

new_buckets = (GSIMapBucket)NSZoneCalloc(map->zone, size,
    sizeof(GSIMapBucket_t));

if (new_buckets != 0)
  {
    GSIMapRemangleBuckets(map, map->buckets, map->bucketCount, new_buckets,
size);
    if (map->buckets != 0)
{
  NSZoneFree(map->zone, map->buckets);
}
    map->buckets = new_buckets;
    map->bucketCount = size;

果然是根据数量 alloc [count * bucket size] 的大小,然后把旧的 bucket array里面的东西拷贝到新的bucket array。

如果之前木有 node,要如何新加呢?

GS_STATIC_INLINE GSIMapNode
GSIMapAddPair(GSIMapTable map, GSIMapKey key, GSIMapVal value)
{
  GSIMapNode    node = map->freeNodes;

  if (node == 0)
    {
      GSIMapMoreNodes(map, map->nodeCount < map->increment ? 0: map->increment);
      node = map->freeNodes;
    }
  map->freeNodes = node->nextInBucket;
  GSI_MAP_WRITE_KEY(map, &node->key, key);
  GSI_MAP_RETAIN_KEY(map, node->key);
  GSI_MAP_WRITE_VAL(map, &node->value, value);
  GSI_MAP_RETAIN_VAL(map, node->value);
  node->nextInBucket = 0;
  GSIMapRightSizeMap(map, map->nodeCount);
  GSIMapAddNodeToMap(map, node);
  return node;
}

如果在freeNodes里面找不到空node,就创建一个然后将 key 和 value 都设置上,设置的方式我们可以看到,首先需要 GSI_MAP_WRITE_KEY 然后还需要 GSI_MAP_RETAIN_KEY。这里就涉及到如何实现的 weak 指针啦。

write和retain的宏真的是不太容易懂,大概是酱紫的:

#define GSI_MAP_WRITE_KEY(M, addr, x) \
    if (M->legacy) \
          *(addr) = x;\
    else\
      (IS_WEAK_KEY(M) ? pointerFunctionsAssign(&M->cb.pf.k, (void**)addr, (x).obj) : (*(id*)(addr) = (x).obj));
#define GSI_MAP_RETAIN_KEY(M, X)\
 (M->legacy ? M->cb.old.k.retain(M, X.ptr) \
  : IS_WEAK_KEY(M) ? nil : pointerFunctionsAcquire(&M->cb.pf.k, &X.ptr, X.ptr))

write看起来就是把数据写进去了,然后retain会增加引用计数,让对象不会被释放,因为retain最后会调到这段:

static inline void pointerFunctionsAssign(PFInfo *PF, void **addr, void *value)
{
  if (memoryType(PF->options, NSPointerFunctionsWeakMemory))
    {
      ARC_WEAK_WRITE(addr, value);
    }
  else if (memoryType(PF->options, NSPointerFunctionsZeroingWeakMemory))
    {
      WEAK_WRITE(addr, value);
    }
  else if (memoryType(PF->options, NSPointerFunctionsStrongMemory))
    {
      STRONG_WRITE(addr, value);
    }
  else
    {
      *addr = value;
    }
}

#    define STRONG_WRITE(addr, x) objc_storeStrong((id*)addr, (id)x)

objc_storeStrong就是runtime来强引用的一个方式,所以如果你用strong option去持有 key 或者 value,他会真的持有它,即使它配对的键值对已经被weak释放了,也不会导致strong的释放。

因为其实key value都是作为 node 的一部分来存储的,当weak option 导致对象释放的时候,其实node不会自动清空,它里面甚至好像还有指针指向那一块内存,因为它不知道你的key或者value已经没了,但是某些节点会去check这个节点能不能被清空回收放到 freeNodes 里面。

上面只是我的理解哈,这个 NSMapTable 实在是有一丢丢复杂一堆奇奇怪怪的宏,我也不没能太明白为啥搞得如此复杂0.0 anyway可以看出来的是,如果strong持有,即使键值对没了还是会持有的哦,无论是 key 还是 value。

2. 找到修改某个函数的commit

refer: https://segmentfault.com/a/1190000020099456

如果你知道要找的代码具体写的是什么,或者知道某个特别的关键字,你就可以用它来搜索。

git log -S "config.menu_items"

本例中会查找所有包含 config.menu_items 的提交

相关文章

网友评论

      本文标题:[iOS] NSMapTable & git tip

      本文链接:https://www.haomeiwen.com/subject/vwjlbltx.html