SDMemoryCache中的NSMapTable

带着问题学习Lgit

NSMapTable看名字是一个映射表,官方文档描述为:相似于字典的集合,但具备更普遍的可用内存语义。github

问题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上来,官方文档描述以下:bash

映射表的模型和NSDictionary具备如下的差别:ide

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

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

NSMapTable * table = [[NSMapTable alloc] initWithKeyOptions: NSPointerFunctionsCopyIn valueOptions:NSPointerFunctionsStrongMemory capacity:2];
复制代码

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

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

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处理。 那么咱们知道了这些。this

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

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

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

能力有限,有理解偏颇之处望及时指出,感激涕零。

相关文章
相关标签/搜索