美文网首页
《重读SDWebImage》-SDMemoryCache中的NS

《重读SDWebImage》-SDMemoryCache中的NS

作者: 我是繁星 | 来源:发表于2019-03-08 23:08 被阅读0次

    带着问题学习

    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存在的意义就在于此。

    相关文章

      网友评论

          本文标题:《重读SDWebImage》-SDMemoryCache中的NS

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