MST

星途 面试题库

面试题:Objective-C图片加载策略的缓存机制优化

假设你正在开发一个iOS应用,频繁需要加载网络图片。请阐述如何设计一个高效的图片缓存机制,包括内存缓存和磁盘缓存,在Objective-C中如何实现以及需要注意哪些性能问题和内存管理细节。
49.9万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

高效图片缓存机制设计思路

  1. 内存缓存:使用NSCache类,它是线程安全的,并且会在系统内存警告时自动释放内存。NSCache类似NSDictionary,可以用来存储图片对象,以图片的URL作为键值。
  2. 磁盘缓存:使用文件系统来存储图片。可以计算图片URL的哈希值作为文件名,将图片数据写入磁盘文件。为了管理磁盘空间,需要设置一定的清理策略,例如定期删除长时间未使用的图片,或者当磁盘占用超过一定阈值时进行清理。

在Objective - C中的实现

  1. 内存缓存实现
#import <Foundation/Foundation.h>

@interface ImageCache : NSObject

@property (nonatomic, strong) NSCache *memoryCache;

+ (instancetype)sharedCache;
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (void)storeImage:(UIImage *)image inMemoryCacheForKey:(NSString *)key;

@end

@implementation ImageCache

+ (instancetype)sharedCache {
    static ImageCache *sharedCache = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedCache = [[ImageCache alloc] init];
        sharedCache.memoryCache = [[NSCache alloc] init];
    });
    return sharedCache;
}

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memoryCache objectForKey:key];
}

- (void)storeImage:(UIImage *)image inMemoryCacheForKey:(NSString *)key {
    if (image && key) {
        [self.memoryCache setObject:image forKey:key];
    }
}

@end
  1. 磁盘缓存实现
#import <Foundation/Foundation.h>

@interface DiskCache : NSObject

+ (instancetype)sharedCache;
- (void)storeImageData:(NSData *)imageData forKey:(NSString *)key;
- (NSData *)imageDataFromDiskCacheForKey:(NSString *)key;
- (void)cleanDiskCache;

@end

@implementation DiskCache

+ (instancetype)sharedCache {
    static DiskCache *sharedCache = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedCache = [[DiskCache alloc] init];
    });
    return sharedCache;
}

- (NSString *)cachePath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cacheDir = paths[0];
    return [cacheDir stringByAppendingPathComponent:@"ImageCache"];
}

- (void)createCacheDirectoryIfNeeded {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:[self cachePath]]) {
        NSError *error = nil;
        [fileManager createDirectoryAtPath:[self cachePath] withIntermediateDirectories:YES attributes:nil error:&error];
        if (error) {
            NSLog(@"Error creating cache directory: %@", error);
        }
    }
}

- (void)storeImageData:(NSData *)imageData forKey:(NSString *)key {
    if (imageData && key) {
        [self createCacheDirectoryIfNeeded];
        NSString *fileName = [self cachePathForKey:key];
        NSError *error = nil;
        [imageData writeToFile:fileName options:NSDataWritingAtomic error:&error];
        if (error) {
            NSLog(@"Error writing image data to disk: %@", error);
        }
    }
}

- (NSData *)imageDataFromDiskCacheForKey:(NSString *)key {
    NSString *fileName = [self cachePathForKey:key];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:fileName]) {
        NSError *error = nil;
        NSData *imageData = [NSData dataWithContentsOfFile:fileName options:NSDataReadingMappedIfSafe error:&error];
        if (error) {
            NSLog(@"Error reading image data from disk: %@", error);
        }
        return imageData;
    }
    return nil;
}

- (NSString *)cachePathForKey:(NSString *)key {
    NSString *fileName = [key stringByAppendingPathExtension:@"png"];
    return [[self cachePath] stringByAppendingPathComponent:fileName];
}

- (void)cleanDiskCache {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:[self cachePath] error:nil];
    for (NSString *fileName in cacheFiles) {
        NSString *filePath = [[self cachePath] stringByAppendingPathComponent:fileName];
        NSError *error = nil;
        [fileManager removeItemAtPath:filePath error:&error];
        if (error) {
            NSLog(@"Error removing file from disk cache: %@", error);
        }
    }
}

@end
  1. 综合使用
#import "ImageCache.h"
#import "DiskCache.h"
#import <UIKit/UIKit.h>

@interface ImageLoader : NSObject

+ (void)loadImageWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion;

@end

@implementation ImageLoader

+ (void)loadImageWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion {
    NSString *key = url.absoluteString;
    UIImage *image = [[ImageCache sharedCache] imageFromMemoryCacheForKey:key];
    if (image) {
        completion(image);
        return;
    }
    NSData *imageData = [[DiskCache sharedCache] imageDataFromDiskCacheForKey:key];
    if (imageData) {
        image = [UIImage imageWithData:imageData];
        [[ImageCache sharedCache] storeImage:image inMemoryCacheForKey:key];
        completion(image);
        return;
    }
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data &&!error) {
            image = [UIImage imageWithData:data];
            [[ImageCache sharedCache] storeImage:image inMemoryCacheForKey:key];
            [[DiskCache sharedCache] storeImageData:data forKey:key];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(image);
        });
    }];
    [task resume];
}

@end

性能问题和内存管理细节

  1. 性能问题
    • 缓存命中率:合理设计缓存键值,确保相同图片不会重复加载。可以对图片URL进行标准化处理,避免因URL参数顺序等差异导致缓存未命中。
    • 磁盘I/O性能:尽量减少磁盘读写操作。批量处理磁盘清理等操作,避免在主线程进行磁盘I/O。
  2. 内存管理细节
    • 内存缓存NSCache会自动释放内存,但也要注意缓存对象的生命周期。避免缓存对象持有其他大量内存对象导致内存无法及时释放。
    • 磁盘缓存:及时清理磁盘缓存,防止磁盘空间占用过多。可以在应用启动、进入后台等时机进行磁盘缓存清理。同时,在写入磁盘数据时,要注意处理文件系统相关的错误。