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存在的意義就在於此。

能力有限,有理解偏頗之處望及時指出,感激涕零。

相關文章
相關標籤/搜索