SDWebImage 源碼分析之 SDMemoryCache 和 SDDiskCache 源碼分析

SDMemoryCache 源碼分析

上次我們分析過 AFN 的源碼,也分析過 AFN 的 內(nèi)存緩存,這里再分析下 SD 的緩存,AFN 是 自己設(shè)計的緩存晚唇,然后超過了預(yù)期的大小之后,開始循環(huán)清除盗似,直到剩下預(yù)期可接受的大小為止哩陕,這里 SD 的緩存策略怎么設(shè)計的呢?

/**
 A memory cache which auto purge the cache on memory warning and support weak cache.
 */
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache>

@property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config;

@end

這里是集成系統(tǒng) NSCache 的赫舒,并且是個泛型類悍及,實現(xiàn) SDMemoryCache 這個協(xié)議,這個協(xié)議也就告訴我們接癌,我們可以自定義實現(xiàn)內(nèi)存緩存心赶,實現(xiàn)協(xié)議的這些方法就可以。

}

- (void)commonInit {
    SDImageCacheConfig *config = self.config;
    self.totalCostLimit = config.maxMemoryCost;
    self.countLimit = config.maxMemoryCount;

    [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
    [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];

#if SD_UIKIT
    self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
    self.weakCacheLock = dispatch_semaphore_create(1);

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveMemoryWarning:)
                                                 name:UIApplicationDidReceiveMemoryWarningNotification
                                               object:nil];
#endif
}

SDImageCacheConfig 是一個配置缺猛,可以設(shè)置緩存的數(shù)量限制和成本內(nèi)存限制缨叫,當(dāng)前類是繼承自NSCache這個類的,所以直接設(shè)置給父類就可以荔燎,并且當(dāng)前緩存類還建立了一個弱緩存耻姥,是基于 NSMapTable 數(shù)據(jù)結(jié)構(gòu)的,key 是 strong 湖雹,value 是 weak 的,然后監(jiān)聽系統(tǒng)的內(nèi)存警告曙搬,NSCache 是不能自動監(jiān)聽內(nèi)存警告的摔吏,當(dāng)收到內(nèi)存警告的時候,會清楚系統(tǒng) nscache 存儲的所有數(shù)據(jù)

- (void)didReceiveMemoryWarning:(NSNotification *)notification {
    // Only remove cache, but keep weak cache
    [super removeAllObjects];
}

// `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
        SD_LOCK(self.weakCacheLock);
        [self.weakCache setObject:obj forKey:key];
        SD_UNLOCK(self.weakCacheLock);
    }
}

- (id)objectForKey:(id)key {
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        // Check weak cache
        SD_LOCK(self.weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        SD_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;
}

- (void)removeObjectForKey:(id)key {
    [super removeObjectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key) {
        // Remove weak cache
        SD_LOCK(self.weakCacheLock);
        [self.weakCache removeObjectForKey:key];
        SD_UNLOCK(self.weakCacheLock);
    }
}

- (void)removeAllObjects {
    [super removeAllObjects];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    // Manually remove should also remove weak cache
    SD_LOCK(self.weakCacheLock);
    [self.weakCache removeAllObjects];
    SD_UNLOCK(self.weakCacheLock);
}

添加刪除纵装,都是通過信號量來控制線程安全的征讲,可以看到?jīng)]有給父類 nscache 加鎖,因為系統(tǒng)的nscache是 線程安全的橡娄,我們可以看到設(shè)置值會先給父類設(shè)置诗箍,如果配置沒有設(shè)置弱引用緩存直接返回,如果設(shè)置了挽唉,就往弱引用緩存里面添加一份滤祖,獲取值的時候先從父類里面取,如果配置沒有設(shè)置從弱引用取的話瓶籽,直接返回匠童,如果設(shè)置了,接著判斷塑顺,nscache是否能夠獲取到汤求,因為俏险,nscache 會根據(jù)系統(tǒng)自己的策略,自己清楚緩存扬绪,這時候就從我們的弱引用緩存中獲取竖独,獲取到了之后,再往nscache里面放一份挤牛,只有從nscache沒有獲取到才會執(zhí)行此操作莹痢,可以看出,set方法是nscache和自己的弱引用緩存都存儲赊颠,get的時候格二,如果nscache沒有,才從弱引用緩存中去竣蹦,然后存儲到nscache中顶猜,

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if (context == SDMemoryCacheContext) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) {
            self.totalCostLimit = self.config.maxMemoryCost;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) {
            self.countLimit = self.config.maxMemoryCount;
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

對于 kvo ,監(jiān)聽totalCostLimit和maxMemoryCost的設(shè)置痘括,然后動態(tài)設(shè)置給nscache长窄。

這就是 SD 的內(nèi)存緩存,要比 AFN 還要簡單纲菌,但我會提出如下幾點疑問挠日,歡迎評論區(qū)發(fā)表意見

  1. 為什么要用 NSCache 和 自己再創(chuàng)建一個弱引用緩存,要存儲兩份翰舌?就用弱引用緩存不行嗎嚣潜?
  2. 信號量這里面是單讀單寫的,沒有多讀單寫性能好椅贱,這里是有什么考慮嗎懂算?

SDDiskCache 源碼分析

最開始我理解的內(nèi)存緩存和磁盤緩存,我以為磁盤緩存會像 YYCache 一樣庇麦,存儲到數(shù)據(jù)庫计技,過段時間寫兩片 YYCache 的文章,結(jié)果看到代碼后山橄,我發(fā)現(xiàn)我錯了垮媒,我們看他的初始化,你就明白了


- (void)commonInit {
    if (self.config.fileManager) {
        self.fileManager = self.config.fileManager;
    } else {
        self.fileManager = [NSFileManager new];
    }
}

如果看到這里你還不明白怎么存儲航棱,那就往后看吧


- (BOOL)containsDataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    BOOL exists = [self.fileManager fileExistsAtPath:filePath];
    
    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    if (!exists) {
        exists = [self.fileManager fileExistsAtPath:filePath.stringByDeletingPathExtension];
    }
    
    return exists;
}

- (NSData *)dataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    
    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    data = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    
    return nil;
}

- (void)setData:(NSData *)data forKey:(NSString *)key {
    NSParameterAssert(data);
    NSParameterAssert(key);
    if (![self.fileManager fileExistsAtPath:self.diskCachePath]) {
        [self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    // get cache Path for image key
    NSString *cachePathForKey = [self cachePathForKey:key];
    // transform to NSURL
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    
    [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
    
    // disable iCloud backup
    if (self.config.shouldDisableiCloud) {
        // ignore iCloud backup resource value error
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

- (NSData *)extendedDataForKey:(NSString *)key {
    NSParameterAssert(key);
    
    // get cache Path for image key
    NSString *cachePathForKey = [self cachePathForKey:key];
    
    NSData *extendedData = [SDFileAttributeHelper extendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
    
    return extendedData;
}

- (void)setExtendedData:(NSData *)extendedData forKey:(NSString *)key {
    NSParameterAssert(key);
    // get cache Path for image key
    NSString *cachePathForKey = [self cachePathForKey:key];
    
    if (!extendedData) {
        // Remove
        [SDFileAttributeHelper removeExtendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
    } else {
        // Override
        [SDFileAttributeHelper setExtendedAttribute:SDDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil];
    }
}

- (void)removeDataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    [self.fileManager removeItemAtPath:filePath error:nil];
}

- (void)removeAllData {
    [self.fileManager removeItemAtPath:self.diskCachePath error:nil];
    [self.fileManager createDirectoryAtPath:self.diskCachePath
            withIntermediateDirectories:YES
                             attributes:nil
                                  error:NULL];
}

這里是對數(shù)據(jù)的增刪查的處理睡雇,get 方法是通過 dataWithContentsOfFile api,set 是通過 [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil]; 這個 api饮醇,然后根據(jù)配置判斷是否進(jìn)行 iCloud 存儲

    // ignore iCloud backup resource value error
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }

刪除是通過 [self.fileManager removeItemAtPath:filePath error:nil]; api ,都是通過系統(tǒng) api入桂,接下來我們來看看是怎么處理過期數(shù)據(jù)的

// 移除過期數(shù)據(jù)
- (void)removeExpiredData {
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    
    // Compute content date key to be used for tests
    // 過期時間的依據(jù),訪問時間驳阎,內(nèi)容修改時間抗愁,創(chuàng)建時間或者屬性修改時間
    NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
    switch (self.config.diskCacheExpireType) {
        case SDImageCacheConfigExpireTypeAccessDate:
            cacheContentDateKey = NSURLContentAccessDateKey;
            break;
        case SDImageCacheConfigExpireTypeModificationDate:
            cacheContentDateKey = NSURLContentModificationDateKey;
            break;
        case SDImageCacheConfigExpireTypeCreationDate:
            cacheContentDateKey = NSURLCreationDateKey;
            break;
        case SDImageCacheConfigExpireTypeChangeDate:
            cacheContentDateKey = NSURLAttributeModificationDateKey;
            break;
        default:
            break;
    }
    // 想要了解的屬性
    NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
    
    // This enumerator prefetches useful properties for our cache files.
    // 遍歷文件馁蒂,跳過隱藏文件
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];
    // 過期時間
    NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
    NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
    NSUInteger currentCacheSize = 0;
    
    // Enumerate all of the files in the cache directory.  This loop has two purposes:
    //
    //  1. Removing files that are older than the expiration date.
    //  2. Storing file attributes for the size-based cleanup pass.
    NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
    for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        // 根據(jù) url 獲取返回結(jié)果,從中獲取想要或崎嶇的屬性
        NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
        
        // Skip directories and errors.
        if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }
        
        // Remove files that are older than the expiration date;
        // 獲取修改時間
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        // 過期了
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }
        
        // Store a reference to this file and account for its total size.
        // 將文件總大小存儲下來
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;
    }
    // 將過期文件刪除
    for (NSURL *fileURL in urlsToDelete) {
        [self.fileManager removeItemAtURL:fileURL error:nil];
    }
    
    // If our remaining disk cache exceeds a configured maximum size, perform a second
    // size-based cleanup pass.  We delete the oldest files first.
    NSUInteger maxDiskSize = self.config.maxDiskSize;
    
    // 如果當(dāng)前磁盤存儲文件的總大小大于配置的大小
    if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
        // Target half of our maximum cache size for this cleanup pass.
        const NSUInteger desiredCacheSize = maxDiskSize / 2;
        
        // Sort the remaining cache files by their last modification time or last access time (oldest first).
        // 將剩余的文件按照訪問時間進(jìn)行排序,最老的放在最前面
        NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                     return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                 }];
        
        // Delete files until we fall below our desired cache size.
        for (NSURL *fileURL in sortedFiles) {
            if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                // 挨個刪除蜘腌,知道大小達(dá)到我們預(yù)期的值沫屡,也就是設(shè)置的最大值的一半
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }
}

先是根據(jù)配置,設(shè)置選擇文件的訪問時間撮珠,是以何種策略來淘汰文件沮脖,是根據(jù)創(chuàng)建時間超時,還是內(nèi)容修改時間超時等依據(jù)芯急,然后創(chuàng)建一個 數(shù)據(jù)勺届,想要查看文件的哪些屬性 NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];,這里是查看文件是否為目錄,訪問時間屬性娶耍,和文件的總大小屬性免姿,然后拿出所有文件的 url

 // 遍歷文件,跳過隱藏文件
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];

拿到總的結(jié)果之后榕酒,然后根據(jù)配置算出真正的過期時間

  // 過期時間
    NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
 for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        // 根據(jù) url 獲取返回結(jié)果,從中獲取想要或崎嶇的屬性
        NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
        
        // Skip directories and errors.
        if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }
        
        // Remove files that are older than the expiration date;
        // 獲取修改時間
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        // 過期了
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }
        
        // Store a reference to this file and account for its total size.
        // 將文件總大小存儲下來
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;
    }

然后根據(jù)遍歷的 url 結(jié)果胚膊,遍歷每一個文件,在從每一個文件中取出我們想要的三個屬性想鹰,// 根據(jù) url 獲取返回結(jié)果,從中獲取想要或崎嶇的屬性 NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error]; 這里是取出我們關(guān)心的屬性紊婉,然后接下來,判斷下辑舷,如果這個文件是目錄額話喻犁,那么就continue跳過,然后再獲取修改時間屬性何缓,

 // 獲取修改時間
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        // 過期了
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }

如果這個屬性存在肢础,并且和我們餓過期時間比較,如果文件的屬性和過期時間比較歌殃,因為過期時間是根據(jù)當(dāng)前時間算的乔妈,根據(jù)時間差算的蝙云,所以如果文件的修改時間比當(dāng)前過期時間小的話氓皱,說明過期時間的定位的那個時間點跨度沒有包含文件的修改時間,也就是說時間間隔太短了勃刨,換句話說也就是過期了波材,要不然肯定會把修改時間包含進(jìn)去,然后就把這個文件放到數(shù)組跳過身隐,

// Store a reference to this file and account for its total size.
        // 將文件總大小存儲下來
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;

緊接著計算文件的總大小廷区,后面用得到


// 將過期文件刪除
    for (NSURL *fileURL in urlsToDelete) {
        [self.fileManager removeItemAtURL:fileURL error:nil];
    }

將上面的過期文件刪除

  NSUInteger maxDiskSize = self.config.maxDiskSize;
    
    // 如果當(dāng)前磁盤存儲文件的總大小大于配置的大小
    if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
        // Target half of our maximum cache size for this cleanup pass.
        const NSUInteger desiredCacheSize = maxDiskSize / 2;
        
        // Sort the remaining cache files by their last modification time or last access time (oldest first).
        // 將剩余的文件按照訪問時間進(jìn)行排序,最老的放在最前面
        NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                     return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                 }];
        
        // Delete files until we fall below our desired cache size.
        for (NSURL *fileURL in sortedFiles) {
            if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                // 挨個刪除贾铝,知道大小達(dá)到我們預(yù)期的值隙轻,也就是設(shè)置的最大值的一半
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }

這段代碼的意思就是取出配置設(shè)置的磁盤允許緩存的總的文件大小埠帕,因為已經(jīng)刪除過期的文件了,接下來就是刪除超出總大小的文件玖绿,這里是將剩下的文件按照時間排序敛瓷,然后遍歷剩下的文件,將最老的文件刪除斑匪,然后拿出刪除額這個文件的大小呐籽,將總大小減去這個大小和配置文件大小作比較,這里面有個策略蚀瘸,就是超出總大小之后狡蝶,刪除配置設(shè)置的總大小的一一半,也就是說每次留下配置設(shè)置的總大小緩存額一半贮勃。

// 獲取存儲的文件的總大小
- (NSUInteger)totalSize {
    NSUInteger size = 0;
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    for (NSString *fileName in fileEnumerator) {
        NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
        NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil];
        size += [attrs fileSize];
    }
    return size;
}
// 獲取緩存的總個數(shù)
- (NSUInteger)totalCount {
    NSUInteger count = 0;
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    count = fileEnumerator.allObjects.count;
    return count;
}

這里就是獲取文件的總大小和總數(shù)量

// 移動緩存
- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath {
    NSParameterAssert(srcPath);
    NSParameterAssert(dstPath);
    // Check if old path is equal to new path
    if ([srcPath isEqualToString:dstPath]) {
        return;
    }
    BOOL isDirectory;
    // Check if old path is directory
    if (![self.fileManager fileExistsAtPath:srcPath isDirectory:&isDirectory] || !isDirectory) {
        return;
    }
    // Check if new path is directory
    if (![self.fileManager fileExistsAtPath:dstPath isDirectory:&isDirectory] || !isDirectory) {
        if (!isDirectory) {
            // New path is not directory, remove file
            [self.fileManager removeItemAtPath:dstPath error:nil];
        }
        NSString *dstParentPath = [dstPath stringByDeletingLastPathComponent];
        // Creates any non-existent parent directories as part of creating the directory in path
        if (![self.fileManager fileExistsAtPath:dstParentPath]) {
            [self.fileManager createDirectoryAtPath:dstParentPath withIntermediateDirectories:YES attributes:nil error:NULL];
        }
        // New directory does not exist, rename directory
        [self.fileManager moveItemAtPath:srcPath toPath:dstPath error:nil];
    } else {
        // New directory exist, merge the files
        NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtPath:srcPath];
        NSString *file;
        while ((file = [dirEnumerator nextObject])) {
            [self.fileManager moveItemAtPath:[srcPath stringByAppendingPathComponent:file] toPath:[dstPath stringByAppendingPathComponent:file] error:nil];
        }
        // Remove the old path
        [self.fileManager removeItemAtPath:srcPath error:nil];
    }
}

這個就是文件路徑的移動切換贪惹,也很簡單

問題總結(jié)

 為什么用 nscache,和 mapTable衙猪,我的疑問是馍乙,為什么nscache和maptable一起使用,maptable是弱引用

額垫释,那么nscache和maptable都存儲一份丝格,那么nacache里面的經(jīng)過系統(tǒng)自動清理緩存之后,maptable中不也

清除了嗎?有什么用苦蒿?為什么要這樣用熙揍,用一個maptable不也能解決嗎,帶著這個問題曼尊,我在一些 ios 技術(shù)

討論群里面拋出了這個問題,最后有一個兄弟的話點醒了我脏嚷,因為可能會存在這種場景骆撇,比如我們的

nsacache和maptable都存儲一張圖片,界面上的imageview也用了這張圖片父叙,對這張圖片進(jìn)行強引用神郊,當(dāng)

nscache中的數(shù)據(jù)被清理之后,如果下次再來照這張圖片趾唱,那么如果沒有maptable二次存儲涌乳,這時候內(nèi)存緩存就不能命中了,就需要去磁盤緩存中取了甜癞,有了這個弱引用存儲夕晓,如若nacache中找不到,那么就去maptable中找悠咱,如果找到了蒸辆,就nscache再設(shè)置一次征炼,這樣就增加了命中率,提高了效率躬贡,不用去磁盤中取了柒室,說到這里,是不是豁然開朗逗宜,但是雄右,我還是有疑問,為什么不只用maptable一個數(shù)據(jù)結(jié)構(gòu)去存儲呢纺讲?為什么存儲兩份擂仍?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熬甚,隨后出現(xiàn)的幾起案子逢渔,更是在濱河造成了極大的恐慌,老刑警劉巖乡括,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肃廓,死亡現(xiàn)場離奇詭異,居然都是意外死亡诲泌,警方通過查閱死者的電腦和手機盲赊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敷扫,“玉大人哀蘑,你說我怎么就攤上這事】冢” “怎么了绘迁?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卒密。 經(jīng)常有香客問我缀台,道長,這世上最難降的妖魔是什么哮奇? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任膛腐,我火速辦了婚禮,結(jié)果婚禮上屏镊,老公的妹妹穿的比我還像新娘依疼。我一直安慰自己痰腮,他們只是感情好而芥,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膀值,像睡著了一般棍丐。 火紅的嫁衣襯著肌膚如雪误辑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天歌逢,我揣著相機與錄音巾钉,去河邊找鬼。 笑死秘案,一個胖子當(dāng)著我的面吹牛砰苍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阱高,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼赚导,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赤惊?” 一聲冷哼從身側(cè)響起吼旧,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎未舟,沒想到半個月后圈暗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡裕膀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年员串,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昼扛。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡昵济,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出野揪,到底是詐尸還是另有隱情访忿,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布斯稳,位于F島的核電站海铆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挣惰。R本人自食惡果不足惜卧斟,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憎茂。 院中可真熱鬧珍语,春花似錦、人聲如沸竖幔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至募逞,卻和暖如春蛋铆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背放接。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工刺啦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纠脾。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓玛瘸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親苟蹈。 傳聞我的和親對象是個殘疾皇子捧韵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348