[iOS] NSMapTable & git tip

1. strongToWeakObjectsMapTable

NSMapTable 如果用 strongToWeak灼卢,也就是key是strong,value是weak的話萝映,當一個key的value小時,雖然NSMapTable已經(jīng)沒有對象了,但是key并不會被release涉茧,仍舊有一個奇奇怪怪的東西拿住他:

map

雖然map里面只有一個鍵值對了,但是卻還是持有了多個key哦疹娶,所以其實key不會自動釋放的伴栓。

于是這個問題就到了 NSMapTable 的實現(xiàn)原理,根據(jù)這篇文章我找到了源碼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 的實現(xiàn)都是走的 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;
}

其實最后調(diào)到的還是 NSConcreteMapTable 的,子類里干了什么呢额港?開始其實就是去配置那個weak還是strong的option饺窿,如果沒有需要創(chuàng)建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的關(guān)系

大概就是bucket就像一個鏈表頭肠套,node就是里面的內(nèi)容,但是map持有了一個 bucket 的array猖任,為啥要array呢你稚?現(xiàn)在來看看 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,就會創(chuàng)建一個新的刁赖,如果找得到,就會釋放之前 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 指向一個地址症汹,然后連續(xù)有幾個 bucket,當我們放一個key的時候背镇,先拿他的 hash 看看放到哪個 bucket泽裳。(一旦您把第一個元素的地址存儲在 p 中,您就可以使用 p破婆、(p+1)、*(p+2) 等來訪問數(shù)組元素)

那么這些bucket是咋創(chuàng)建的呢祷舀?

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;

果然是根據(jù)數(shù)量 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,就創(chuàng)建一個然后將 key 和 value 都設置上冤吨,設置的方式我們可以看到蒿柳,首先需要 GSI_MAP_WRITE_KEY 然后還需要 GSI_MAP_RETAIN_KEY。這里就涉及到如何實現(xiàn)的 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看起來就是把數(shù)據(jù)寫進去了,然后retain會增加引用計數(shù)爆安,讓對象不會被釋放,因為retain最后會調(diào)到這段:

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扔仓,他會真的持有它,即使它配對的鍵值對已經(jīng)被weak釋放了咖耘,也不會導致strong的釋放翘簇。

因為其實key value都是作為 node 的一部分來存儲的,當weak option 導致對象釋放的時候儿倒,其實node不會自動清空版保,它里面甚至好像還有指針指向那一塊內(nèi)存,因為它不知道你的key或者value已經(jīng)沒了夫否,但是某些節(jié)點會去check這個節(jié)點能不能被清空回收放到 freeNodes 里面彻犁。

上面只是我的理解哈,這個 NSMapTable 實在是有一丟丟復雜一堆奇奇怪怪的宏凰慈,我也不沒能太明白為啥搞得如此復雜0.0 anyway可以看出來的是汞幢,如果strong持有,即使鍵值對沒了還是會持有的哦微谓,無論是 key 還是 value森篷。

2. 找到修改某個函數(shù)的commit

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

如果你知道要找的代碼具體寫的是什么输钩,或者知道某個特別的關(guān)鍵字,你就可以用它來搜索仲智。

git log -S "config.menu_items"

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末买乃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钓辆,更是在濱河造成了極大的恐慌剪验,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岩馍,死亡現(xiàn)場離奇詭異碉咆,居然都是意外死亡,警方通過查閱死者的電腦和手機蛀恩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門疫铜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人双谆,你說我怎么就攤上這事壳咕。” “怎么了顽馋?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵谓厘,是天一觀的道長。 經(jīng)常有香客問我寸谜,道長,這世上最難降的妖魔是什么他爸? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任诊笤,我火速辦了婚禮巾陕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晾匠。我一直安慰自己梯刚,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布预愤。 她就那樣靜靜地躺著咳胃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪销睁。 梳的紋絲不亂的頭發(fā)上存崖,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天来惧,我揣著相機與錄音,去河邊找鬼隅居。 笑死葛虐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的涕蚤。 我是一名探鬼主播的诵,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼奢驯,長吁一口氣:“原來是場噩夢啊……” “哼次绘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起管跺,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤豁跑,失蹤者是張志新(化名)和其女友劉穎泻云,沒想到半個月后狐蜕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卸夕,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡快集,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年个初,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楣嘁。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡马澈,死狀恐怖弄息,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摹量,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布凝果,位于F島的核電站器净,受9級特大地震影響当凡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沿量,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朴则。 院中可真熱鬧,春花似錦汹想、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夏漱。三九已至,卻和暖如春交播,著一層夾襖步出監(jiān)牢的瞬間践付,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工隧土, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曹傀,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓皆愉,卻偏偏與公主長得像幕庐,于是被迫代替她去往敵國和親练链。 傳聞我的和親對象是個殘疾皇子奴拦,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • 實現(xiàn)弱引用 本文將整理 弱引用疚沐、強引用的定義 為什么會出現(xiàn)“弱引用” weak 實現(xiàn)原理 實現(xiàn)弱引用的N種方法 如...
    安處幽篁兮閱讀 2,319評論 0 1
  • 目錄: NSProxy 字典集合對成員的引用方式 class判斷 block變量捕獲 1. NSProxy NSP...
    木小易Ying閱讀 917評論 1 8
  • 級別: ★★☆☆☆標簽:「iOS 」「避免常見崩潰」「FBKVOController」「KVO」作者: WYW[...
    QiShare閱讀 3,034評論 2 26
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭擎厢,有人歡樂有人憂愁,有人驚喜有人失落芬探,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,523評論 28 53