1. strongToWeakObjectsMapTable
NSMapTable 如果用 strongToWeak,也就是key是strong,value是weak的话,当一个key的value小时,虽然NSMapTable已经没有对象了,但是key并不会被release,仍旧有一个奇奇怪怪的东西拿住他:
虽然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就是里面的内容,但是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 的提交