IOS網絡圖片緩存詳
在開發移動應用的時候比如Android,IOS,因為手機流量、網速、內存等這些因素,當我們的移動應用是針對互聯網,並要頻繁訪問網絡的話,對網絡優化這塊就顯得尤為重要了。
比如某個應用要經常顯示網絡圖片,就不能每次顯示圖片都去網絡上下載,那太耗費時間也太耗費流量,這時就要對網絡圖片進行緩存了,以下是我對IOS網絡圖片緩存的一些見解,有不足之處,歡迎大家指出來,一起探討。
處理網絡圖片緩存步驟:
1、根據圖片URL查找內存是否有這張圖片,有則返回圖片,沒有則進入第二步
2、查找物理存儲是否有這張圖片,有則返回圖片,沒有則進入第三步
3、從網絡上下載該圖片,下載完後保存到內存和物理存儲上,並返回該圖片
注:因為URL包含特殊字符和長度不確定,要對URL進行MD5處理或其他處理
下面是針對以上步驟的代碼講解:
1、內存緩存圖片處理
使用NSMutableDictionary存儲圖片UIImage,數組的Key為該圖片的URL地址
//緩存圖片到內存上
[plain] view plaincopy
- [memCache setObject:image forKey:key];
2、物理緩存圖片處理
把圖片保持到物理存儲設備上,則直接使用NSFileManager,把URL作為文件名保存
3、網絡圖片下載處理
圖片使用異步下載,下載完後把圖片保持到NSMutableDictionary和物理存儲上
以下是摘自SDWebImageleik網絡圖片緩存處理的一個類,有詳細注釋
.h文件
[plain] view plaincopy
- @interface SDImageCache : NSObject
- {
- NSMutableDictionary *memCache;//內存緩存圖片引用
- NSString *diskCachePath;//物理緩存路徑
- NSOperationQueue *cacheInQueue, *cacheOutQueue;
- }
-
- + (SDImageCache *)sharedImageCache;
-
- //保存圖片
- - (void)storeImage:(UIImage *)image forKey:(NSString *)key;
-
- //保存圖片,並選擇是否保存到物理存儲上
- - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
-
- //保存圖片,可以選擇把NSData數據保存到物理存儲上
- - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
-
- //通過key返回UIImage
- - (UIImage *)imageFromKey:(NSString *)key;
-
- //如果獲取內存圖片失敗,是否可以在物理存儲上查找
- - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk;
-
-
- - (void)queryDiskCacheForKey:(NSString *)key delegate:(id )delegate userInfo:(NSDictionary *)info;
-
- //清除key索引的圖片
- - (void)removeImageForKey:(NSString *)key;
- //清除內存圖片
- - (void)clearMemory;
- //清除物理緩存
- - (void)clearDisk;
- //清除過期物理緩存
- - (void)cleanDisk;
-
- @end
.m文件 [plain] view plaincopy
- @implementation SDImageCache
-
- #pragma mark NSObject
-
- - (id)init
- {
- if ((self = [super init]))
- {
- // Init the memory cache
- memCache = [[NSMutableDictionary alloc] init];
-
- // Init the disk cache
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
- diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@ImageCache] retain];
-
- if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath])
- {
- [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath
- withIntermediateDirectories:YES
- attributes:nil
- error:NULL];
- }
-
- // Init the operation queue
- cacheInQueue = [[NSOperationQueue alloc] init];
- cacheInQueue.maxConcurrentOperationCount = 1;
- cacheOutQueue = [[NSOperationQueue alloc] init];
- cacheOutQueue.maxConcurrentOperationCount = 1;
-
- #if TARGET_OS_IPHONE
- // Subscribe to app events
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(clearMemory)
- name:UIApplicationDidReceiveMemoryWarningNotification
- object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(cleanDisk)
- name:UIApplicationWillTerminateNotification
- object:nil];
-
- #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
- UIDevice *device = [UIDevice currentDevice];
- if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported)
- {
- // When in background, clean memory in order to have less chance to be killed
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(clearMemory)
- name:UIApplicationDidEnterBackgroundNotification
- object:nil];
- }
- #endif
- #endif
- }
-
- return self;
- }
-
- - (void)dealloc
- {
- [memCache release], memCache = nil;
- [diskCachePath release], diskCachePath = nil;
- [cacheInQueue release], cacheInQueue = nil;
-
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-
- [super dealloc];
- }
-
- #pragma mark SDImageCache (class methods)
-
- + (SDImageCache *)sharedImageCache
- {
- if (instance == nil)
- {
- instance = [[SDImageCache alloc] init];
- }
-
- return instance;
- }
-
- #pragma mark SDImageCache (private)
-
- /*
- *創建指定圖片key的路徑
- */
- - (NSString *)cachePathForKey:(NSString *)key
- {
- const char *str = [key UTF8String];
- unsigned char r[CC_MD5_DIGEST_LENGTH];
- CC_MD5(str, (CC_LONG)strlen(str), r);
- NSString *filename = [NSString stringWithFormat:@%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x,
- r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]];
-
- return [diskCachePath stringByAppendingPathComponent:filename];
- }
-
- /*
- *保存key和Data到物理存儲
- *keyAndData[0] ->key
- *keyAndData[1] ->Data
- */
- - (void)storeKeyWithDataToDisk:(NSArray *)keyAndData
- {
- // Can't use defaultManager another thread
- NSFileManager *fileManager = [[NSFileManager alloc] init];
-
- NSString *key = [keyAndData objectAtIndex:0];
- NSData *data = [keyAndData count] > 1 ? [keyAndData objectAtIndex:1] : nil;
-
- //如果有數據,則保存到物理存儲上
- if (data)
- {
- [fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil];
- }
- else
- {
- //如果沒有data,則把UIImage轉換為JPEG,並保存到物理存儲上
- // If no data representation given, convert the UIImage in JPEG and store it
- // This trick is more CPU/memory intensive and doesn't preserve alpha channel
- UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock
- if (image)
- {
- #if TARGET_OS_IPHONE
- [fileManager createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil];
- #else
- NSArray* representations = [image representations];
- NSData* jpegData = [NSBitmapImageRep representationOfImageRepsInArray: representations usingType: NSJPEGFileType properties:nil];
- [fileManager createFileAtPath:[self cachePathForKey:key] contents:jpegData attributes:nil];
- #endif
- [image release];
- }
- }
-
- [fileManager release];
- }
-
- /*
- *查找圖片委托
- */
- - (void)notifyDelegate:(NSDictionary *)arguments
- {
- NSString *key = [arguments objectForKey:@key];
- id delegate = [arguments objectForKey:@delegate];
- NSDictionary *info = [arguments objectForKey:@userInfo];
- UIImage *image = [arguments objectForKey:@image];
-
- if (image)
- {
- [memCache setObject:image forKey:key];
-
- if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
- {
- [delegate imageCache:self didFindImage:image forKey:key userInfo:info];
- }
- }
- else
- {
- if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
- {
- [delegate imageCache:self didNotFindImageForKey:key userInfo:info];
- }
- }
- }
-
- /*
- *查找物理緩存上的圖片
- */
- - (void)queryDiskCacheOperation:(NSDictionary *)arguments
- {
- NSString *key = [arguments objectForKey:@key];
- NSMutableDictionary *mutableArguments = [[arguments mutableCopy] autorelease];
-
- UIImage *image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
- if (image)
- {
- #ifdef ENABLE_SDWEBIMAGE_DECODER
- UIImage *decodedImage = [UIImage decodedImageWithImage:image];
- if (decodedImage)
- {
- image = decodedImage;
- }
- #endif
- [mutableArguments setObject:image forKey:@image];
- }
-
- [self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO];
- }
-
- #pragma mark ImageCache
-
- /*
- *緩存圖片
- *
- **/
- - (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk
- {
- if (!image || !key)
- {
- return;
- }
-
- //緩存圖片到內存上
- [memCache setObject:image forKey:key];
-
- //如果需要緩存到物理存儲上,並data不為空,則把data緩存到物理存儲上
- if (toDisk)
- {
- if (!data) return;
- NSArray *keyWithData;
- if (data)
- {
- keyWithData = [NSArray arrayWithObjects:key, data, nil];
- }
- else
- {
- keyWithData = [NSArray arrayWithObjects:key, nil];
- }
- //後台線程緩存圖片到物理存儲上
- [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self
- selector:@selector(storeKeyWithDataToDisk:)
- object:keyWithData] autorelease]];
- }
- }
-
- /*
- *保存圖片到內存上,不保存到物理存儲上
- */
- - (void)storeImage:(UIImage *)image forKey:(NSString *)key
- {
- [self storeImage:image imageData:nil forKey:key toDisk:YES];
- }
- /*
- *保存圖片到內存上,不保存到物理存儲上
- */
- - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
- {
- [self storeImage:image imageData:nil forKey:key toDisk:toDisk];
- }
-
- /*
- *通過key返回指定圖片
- */
- - (UIImage *)imageFromKey:(NSString *)key
- {
- return [self imageFromKey:key fromDisk:YES];
- }
-
- /*
- *返回一張圖像
- *key:圖像的key
- *fromDisk:如果內存中沒有圖片,是否在物理存儲上查找
- *return 返回查找到的圖片,如果沒有則返回nil
- */
- - (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk
- {
- if (key == nil)
- {
- return nil;
- }
-
- UIImage *image = [memCache objectForKey:key];
-
- if (!image && fromDisk) //如果內存沒有圖片,並且可以在物理存儲上查找,則返回物理存儲上的圖片
- {
- image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
- if (image)
- {
- [memCache setObject:image forKey:key];
- }
- }
-
- return image;
- }
-
- - (void)queryDiskCacheForKey:(NSString *)key delegate:(id )delegate userInfo:(NSDictionary *)info
- {
- if (!delegate)
- {
- return;
- }
-
- if (!key)
- {
- if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
- {
- [delegate imageCache:self didNotFindImageForKey:key userInfo:info];
- }
- return;
- }
-
- // First check the in-memory cache...
- UIImage *image = [memCache objectForKey:key];
- if (image)
- {
- // ...notify delegate immediately, no need to go async
- if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
- {
- [delegate imageCache:self didFindImage:image forKey:key userInfo:info];
- }
- return;
- }
-
- NSMutableDictionary *arguments = [NSMutableDictionary dictionaryWithCapacity:3];
- [arguments setObject:key forKey:@key];
- [arguments setObject:delegate forKey:@delegate];
- if (info)
- {
- [arguments setObject:info forKey:@userInfo];
- }
- [cacheOutQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(queryDiskCacheOperation:) object:arguments] autorelease]];
- }
-
- /*
- *從內存和物理存儲上移除指定圖片
- */
- - (void)removeImageForKey:(NSString *)key
- {
- if (key == nil)
- {
- return;
- }
-
- [memCache removeObjectForKey:key];
- [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil];
- }
- /*
- *清除內存緩存區的圖片
- */
- - (void)clearMemory
- {
- [cacheInQueue cancelAllOperations]; // won't be able to complete
- [memCache removeAllObjects];
- }
-
- /*
- *清除物理存儲上的圖片
- */
- - (void)clearDisk
- {
- [cacheInQueue cancelAllOperations];
- [[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil];
- [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath
- withIntermediateDirectories:YES
- attributes:nil
- error:NULL];
- }
- /*
- *清除過期緩存的圖片
- */
- - (void)cleanDisk
- {
- NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-cacheMaxCacheAge];
- NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath];
- for (NSString *fileName in fileEnumerator)
- {
- NSString *filePath = [diskCachePath stringByAppendingPathComponent:fileName];
- NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
- if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate])
- {
- [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
- }
- }
- }
-
- @end