《重读SDWebImage》-SDMemoryCache中的NSMapTable

带着问题学习

NSMapTable看名字是一个映射表,官方文档描述为:类似于字典的集合,但具有更广泛的可用内存语义。

问题1:NSDictionary内存语义怎么就不广泛了呢?
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;

如上是NSDictionary的赋值方法,明显可以看出key必须要遵循NSCoping协议,那么我们做个小实验。

//teacher遵守了NSCoping协议
Teacher * teacher = [[Teacher alloc] init];
NSMutableDictionary * dictest = [[NSMutableDictionary alloc] initWithCapacity:2];
{
      Student * student = [[Student alloc] init];
      NSLog(@"student:%@",student);
      [dictest setObject:student forKey:teacher];
}
 NSLog(@"dictest:%@\nteacher:%@",dictest,teacher);

//打印结果
student:<Student: 0x600002383e60>
dictest:{
    "<Teacher: 0x6000023d5780>" = "<Student: 0x600002383e60>";
}
teacher:<Teacher: 0x600002383e40>

可以看出作为key的teacher地址变了,而student地址跟原来相同,并且跳出作用于也没有释放,那么结论如下:

  • 其实akey是对原本的key执行了copy。而anObject是对对象进行了强引用。
    这样可以看出来确实NSDictionary的key内存语义只有copy,确实不广泛。

那么我们回到NSMapTable上来,官方文档描述如下:

映射表的模型和NSDictionary具有以下的差异:

  • 可以给键或者值添加弱引用语义,当其中一个对象移除时同时移除该条目
  • 可以给键或者值添加拷贝语义,也可以使用指针标识进行等值判断
  • 作为一个集合类型,它可以包含任意指针(内容不限于对象)

如下:可以给键值设置任意内存语义,常见的有三种NSPointerFunctionsWeakMemory、NSPointerFunctionsStrongMemory、NSPointerFunctionsCopyIn。分别是强引用,弱引用和拷贝。那么下面这样初始化的映射表就跟NSDictionary无异了。

NSMapTable * table = [[NSMapTable alloc] initWithKeyOptions: NSPointerFunctionsCopyIn valueOptions:NSPointerFunctionsStrongMemory capacity:2];

问题2:修改内存key、value的语义对于这种映射的集合类型的差异在于哪呢?

其实就在于查询、删除、赋值这些操作上,看如下的例子:

NSMapTable * table = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory capacity:2];
    NSMutableDictionary * dic = [[NSMutableDictionary alloc] initWithCapacity:2];
    
    Teacher * teacher = [[Teacher alloc] init];
    teacher.name = @"老师";
    teacher.old = @"31";
    
    Student * student1 = [[Student alloc] init];
    student1.name = @"学生1";
    student1.old = @"21";
    
    Student * student2 = [[Student alloc] init];
    student2.name = @"学生2";
    student2.old = @"22";
    
    Student * student3 = [[Student alloc] init];
    student3.name = @"学生3";
    student3.old = @"23";
    
    [dic setObject:@[student1,student2,student3] forKey:teacher];
    [dic setObject:@[student1,student2] forKey:teacher];
    [table setObject:@[student1,student2,student3] forKey:teacher];
    [table setObject:@[student1,student2] forKey:teacher];
    NSLog(@"\n teacher:%@\ndic:%@\n table:%@",teacher,dic,table);

//打印结果
teacher:<Teacher: 0x6000007ea6a0>
dic:{
    "<Teacher: 0x6000007ea980>" =     (
        "<Student: 0x6000007ea820>",
        "<Student: 0x6000007ea8e0>"
    );
    "<Teacher: 0x6000007ea940>" =     (
        "<Student: 0x6000007ea820>",
        "<Student: 0x6000007ea8e0>",
        "<Student: 0x6000007ea840>"
    );
}
 table:NSMapTable {
[5] <Teacher: 0x6000007ea6a0> -> (
    "<Student: 0x6000007ea820>",
    "<Student: 0x6000007ea8e0>"
)
}

在这个例子中,可以看出明显的差别。我们创建了一个NSMutableDictionary对象和一个key是弱引用value是强引用的映射表。都是以teacher为key设置类两遍值。前者dic对于同样一个key生成了两个key-value,后者maptable只要一个。那么这个是为什么呢??
关键在于映射集合在设置key的时候要判断当前集合中是否包含此key,也就是说是否包含key和要设置的key相等,因为key也是一个对象,那么这个问题又回归到判断两个对象是否相等上了,那么判断过程是怎么样的呢?
其实是这样的,首先会判断两个对象的hash值是否相等,如果hash值相等再进入isEqualTo方法判断,以解决散列冲突问题。对于上面例子里面dictionary来说因为key是copy出来的两个对象自然不相等,对于dictionary就是两个不相同的key,对于mapTable来说,key是弱引用而来是相同对象hash值一定是相同的,所以会当作相同key处理。
那么我们知道了这些。

问题3:如何将dictionary改造成跟上面一样呢?

从Teacher类入手,重写hash和isequal方法,如下:

@implementation Teacher
- (id)copyWithZone:(NSZone *)zone{
    Teacher * teacher = [[Teacher alloc] init];
    teacher.name = self.name;
    teacher.old = self.old;
    return teacher;
}
- (BOOL)isEqual:(id)object{
    NSLog(@"是否相等");
    if (![object isKindOfClass:[Teacher class]]){
        return NO;
    }
    if ([((Teacher *)object).name isEqualToString:self.name] && [((Teacher *)object).old isEqualToString:self.old]){
        return YES;
    }
    return NO;
}
- (NSUInteger)hash{
    NSUInteger hash = self.name.hash+self.old.hash;
    NSLog(@"地址%@hash:%@",self,@(hash));
    return hash;
}
@end
接下来我们回到SDMemoryCache中。
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];

如上,SDMemoryCache中存在与一个key强引用,value弱引用的映射表,意思是存储的值销毁的时候,self.weakCache会安全(代码里加了信号量锁)的删除对应的key-value。

// `setObject:forKey:` just call this with 0 cost. Override this is enough
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    [super setObject:obj forKey:key cost:g];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key && obj) {
        // Store weak cache
        LOCK(self.weakCacheLock);
        // Do the real copy of the key and only let NSMapTable manage the key's lifetime
        // Fixes issue #2507 https://github.com/SDWebImage/SDWebImage/issues/2507
        [self.weakCache setObject:obj forKey:[[key mutableCopy] copy]];
        UNLOCK(self.weakCacheLock);
    }
}

- (id)objectForKey:(id)key {
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        // Check weak cache
        LOCK(self.weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        UNLOCK(self.weakCacheLock);
        if (obj) {
            // Sync cache
            NSUInteger cost = 0;
            if ([obj isKindOfClass:[UIImage class]]) {
                cost = [(UIImage *)obj sd_memoryCost];
            }
            [super setObject:obj forKey:key cost:cost];
        }
    }
    return obj;
}

当打开shouldUseWeakMemoryCache的时候赋值的时候可以将值同样付给weakCache,取值的时候如果缓存中没有同样会在weakCache里面找,因为weakCache存储的是引用不会有有额外的内存开销且weak不会影响对象的生命周期,所以在NSCache被清理,且对象没有被释放的情况下,同样可以在weakCache中取到缓存,在一定意义增加了缓存的广度,减少了请求次数。那么weakCache存在的意义就在于此。

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,105评论 1 32
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,418评论 1 14
  • 03 闹鬼的山路 当雄鸡在“咯-咯-咯”叫早的时候,东方刚吐鱼肚白,天刚蒙蒙亮,小雪早早地起床,心情无比激动,今天...
    清水荷_5816阅读 320评论 2 2
  • 2018年2月25日 星期日 正月初十 晴 我是日记星球369号星宝宝曾凡微,正在参加孙老师的日记星球21天蜕变之...
    631cc5f06890阅读 292评论 1 0
  • 你为什么总想要免费 2016年12月12号 今天之所以会写这个东西和我今天遇到的一些事情有关,要从我现在做的事说起...
    我是四妹阅读 392评论 0 0