[iOS] NSMapTable & git tip

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 的提交

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 实现弱引用 本文将整理 弱引用、强引用的定义 为什么会出现“弱引用” weak 实现原理 实现弱引用的N种方法 如...
    安处幽篁兮阅读 2,312评论 0 1
  • 目录: NSProxy 字典集合对成员的引用方式 class判断 block变量捕获 1. NSProxy NSP...
    木小易Ying阅读 916评论 1 8
  • 级别: ★★☆☆☆标签:「iOS 」「避免常见崩溃」「FBKVOController」「KVO」作者: WYW[...
    QiShare阅读 3,031评论 2 26
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53