這個類庫提供一個UIImageView類別以支持加載來自網絡的遠程圖片。具有緩存管理、異步下載、同一個URL下載次數控制和優化等特征。
地址:https://github.com/rs/SDWebImage
原理圖:
各個類的交互圖:
一 插件的運用
針對這部分的理論知識可以查看文章《SDWebImage 圖片下載緩存框架 常用方法及原理》,已經針對SDWebImage的相關知識點都有相應介紹;並且把相關的類都有注解,接下來將會簡單介紹一些屬性及小知識點:
1.1 設置存儲路徑
可以在項目AppDelegate設置存儲路徑,SDWebImage默認使用磁盤緩存,在 沙盒/Library/Cache中可以找到帶WebImageCache字眼的目錄,可以找到緩存的圖片,下面這個可以設置一個只讀的存儲路徑:
NSString *bundledPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"CustomPathImages"]; [[SDImageCache sharedImageCache] addReadOnlyCachePath:bundledPath];
1.2 SDWebImage的最大並發數是多少
在類SDWebImageDownloader中,默認並發數為6(_downloadQueue.maxConcurrentOperationCount = 6);也可以修改maxConcurrentDownloads設置其下載並發數;
1.3 SDWebImage緩存周期
SDWebImage緩存周期為一周,可以在類SDImageCache里面有kDefaultCacheMaxCacheAge常量,定義的緩存時間;static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
1.4 SDWebImage 緩存圖片命名規則
為了防止名稱重復,對其進行 md5 運算;
1.5 默認下載的超時時長是多少?
默認為15秒,可以在類SDWebImageDownloader中設置downloadTimeout
1.6 SDWebImage用什么類型緩存圖片?
NSCache,SDImageCache內處理內存警告,以通知的方式,clearMemory
1.7 cleanDisk的執行過程
i. 先遍歷所有的緩存文件,記錄過期的文件,計算緩存文件的總大小 ii. 刪除過期的文件 iii. 判斷maxCacheSize的值是否>0,如果大於0再判斷緩存的文件總大小是否大於maxCacheSize iv.如果緩存文件的總大小超過maxCacheSize,刪除最早的文件 注意:.jpg、.gif等文件需要把擴展名填上,png不需要
1.8 SDWebImage獲得緩存大小,並對它進行清除
float tmpSize = [[SDImageCache sharedImageCache] getSize]; NSString *clearCacheName =@"當前緩存已清理"; if (tmpSize>0) { clearCacheName=tmpSize >= 1 ? [NSString stringWithFormat:@"成功清理緩存(%.2fM)",tmpSize] : [NSString stringWithFormat:@"成功清理緩存(%.2fK)",tmpSize * 1024]; } [[SDImageCache sharedImageCache] clearDisk];
1.9 SDWebImages是如何識別圖片
NSData+ImageContentType.m中,根據圖片文件十六進制數據的第一個字節判斷
+ (NSString *)sd_contentTypeForImageData:(NSData *)data { uint8_t c; [data getBytes:&c length:1]; switch (c) { case 0xFF: return @"image/jpeg"; case 0x89: return @"image/png"; case 0x47: return @"image/gif"; case 0x49: case 0x4D: return @"image/tiff"; case 0x52: // R as RIFF for WEBP if ([data length] < 12) { return nil; } NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return @"image/webp"; } return nil; } return nil; }
1.10 SDImageCacheType 緩存類型
SDImageCacheTypeNone 永不緩存,但是從網上下載
SDImageCacheTypeDisk 只緩存到磁盤上
SDImageCacheTypeMemory 只緩存到內存中
1.11 SDWebImageDownloaderProgressBlock 下載進度
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); progress 參數: receivedSize 接收到的字節數 expectedSize 期望下載的字節數 //乘1.0是為了轉換成float類型 float progress = receivedSize * 1.0 / expectedSize;
NSURL *url = [NSURL URLWithString:@"http://picview01.baomihua.com/photos/20120624/m_14_634761470842343750_15728444.jpg"]; [self.imageView sd_setImageWithURL:url placeholderImage:nil options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { //乘1.0是為了轉換成float類型 float progress = receivedSize * 1.0 / expectedSize; NSLog(@"下載進度 %f",progress); } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { NSLog(@"完成"); }];
1.12 SDWebImageOptions 屬性
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
// 默認情況下,當URL下載失敗時,URL會被列入黑名單,導致庫不會再去重試,該標記用於禁用黑名單
SDWebImageRetryFailed = 1 << 0,
// 默認情況下,圖片下載開始於UI交互,該標記禁用這一特性,這樣下載延遲到UIScrollView減速時
SDWebImageLowPriority = 1 << 1,
// 該標記禁用磁盤緩存
SDWebImageCacheMemoryOnly = 1 << 2,
// 該標記啟用漸進式下載,圖片在下載過程中是漸漸顯示的,如同瀏覽器一下。
// 默認情況下,圖像在下載完成后一次性顯示
SDWebImageProgressiveDownload = 1 << 3,
// 即使圖片緩存了,也期望HTTP響應cache control,並在需要的情況下從遠程刷新圖片。
// 磁盤緩存將被NSURLCache處理而不是SDWebImage,因為SDWebImage會導致輕微的性能下載。
// 該標記幫助處理在相同請求URL后面改變的圖片。如果緩存圖片被刷新,則完成block會使用緩存圖片調用一次
// 然后再用最終圖片調用一次
SDWebImageRefreshCached = 1 << 4,
// 在iOS 4+系統中,當程序進入后台后繼續下載圖片。這將要求系統給予額外的時間讓請求完成
// 如果后台任務超時,則操作被取消
SDWebImageContinueInBackground = 1 << 5,
// 通過設置NSMutableURLRequest.HTTPShouldHandleCookies = YES;來處理存儲在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
// 允許不受信任的SSL認證
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
// 默認情況下,圖片下載按入隊的順序來執行。該標記將其移到隊列的前面,
// 以便圖片能立即下載而不是等到當前隊列被加載
SDWebImageHighPriority = 1 << 8,
// 默認情況下,占位圖片在加載圖片的同時被加載。該標記延遲占位圖片的加載直到圖片已以被加載完成
SDWebImageDelayPlaceholder = 1 << 9,
// 通常我們不調用動畫圖片的transformDownloadedImage代理方法,因為大多數轉換代碼可以管理它。
// 使用這個票房則不任何情況下都進行轉換。
SDWebImageTransformAnimatedImage = 1 << 10,
};
運用如下:
運用上面的兩個 SDWebImageRetryFailed : 下載失敗后,會自動重新下載 SDWebImageLowPriority : 當正在進行UI交互時,自動暫停內部的一些下載操作 SDWebImageRetryFailed | SDWebImageLowPriority : 擁有上面2個功能 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"app"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } // 取出模型 HMApp *app = self.apps[indexPath.row]; // 設置基本信息 cell.textLabel.text = app.name; cell.detailTextLabel.text = app.download; // 下載圖片 NSURL *url = [NSURL URLWithString:app.icon]; UIImage *placeholder = [UIImage imageNamed:@"placeholder"]; SDWebImageOptions options = SDWebImageRetryFailed | SDWebImageLowPriority; [cell.imageView sd_setImageWithURL:url placeholderImage:placeholder options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { // 這個block可能會被調用多次 NSLog(@"下載進度:%f", (double)receivedSize / expectedSize); } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { NSLog(@"----圖片加載完畢---%@", image); }]; return cell; }
1.1.3 下載順序SDWebImageDownloaderExecutionOrder
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { // 以隊列的方式,按照先進先出的順序下載。這是默認的下載順序 SDWebImageDownloaderFIFOExecutionOrder, // 以棧的方式,按照后進先出的順序下載。 SDWebImageDownloaderLIFOExecutionOrder };
1.1.4 SDWebImageDecoder類異步對圖像進行了一次解壓
由於UIImage的imageWithData函數是每次畫圖的時候才將Data解壓成ARGB的圖像,所以在每次畫圖的時候,會有一個解壓操作,這樣效率很低,但是只有瞬時的內存需求。為了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓,然后畫在另外一張圖片上,這樣這張新圖片就不再需要重復解壓了。這種做法是典型的空間換時間的做法。
1.1.5 SDImageCache是怎么做數據管理的
SDImageCache分兩個部分,一個是內存層面的,一個是硬盤層面的。內存層面的相當是個緩存器,以Key-Value的形式存儲圖片。當內存不夠的時候會清除所有緩存圖片。用搜索文件系統的方式做管理,文件替換方式是以時間為單位,剔除時間大於一周的圖片文件。當SDWebImageManager向SDImageCache要資源時,先搜索內存層面的數據,如果有直接返回,沒有的話去訪問磁盤,將圖片從磁盤讀取出來,然后調SDWebImageDecoder做Decoder,將圖片對象放到內存層面做備份,再返回調用層。
1.1.6 SDWebImage內部實現過程
1. 入口 setImageWithURL:placeholderImage:options: 會先把 placeholderImage 顯示,然后 SDWebImageManager 根據 URL 開始處理圖片。 2. 進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經下載 queryDiskCacheForKey:delegate:userInfo:. 3. 先從內存圖片緩存查找是否有圖片,如果內存中已經有圖片緩存,SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。 4. SDWebImageManagerDelegate 回調 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。 5. 如果內存緩存中沒有,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經緩存。 6. 根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結果回調 notifyDelegate:。 7. 如果上一操作從硬盤讀取到了圖片,將圖片添加到內存緩存中(如果空閑內存過小,會先清空內存緩存)。SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo:。進而回調展示圖片。 8. 如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調 imageCache:didNotFindImageForKey:userInfo:。 9. 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。 10. 圖片下載由 NSURLConnection 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。 11. connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。 12. connectionDidFinishLoading: 數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。 13. 圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。 14. 在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調給 SDWebImageDownloader。 15. imageDownloader:didFinishWithImage: 回調給 SDWebImageManager 告知圖片下載完成。 16. 通知所有的 downloadDelegates 下載完成,回調給需要的地方展示圖片。 17. 將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。 18. SDImageCache 在初始化的時候會注冊一些消息通知,在內存警告或退到后台的時候清理內存圖片緩存,應用結束的時候清理過期圖片。 19. SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。 20. SDWebImagePrefetcher 可以預先下載圖片,方便后續使用。
二 知識點
2.1 typedef定義Block
這樣定義方法時直接用typedef的類型,只要有引入此頭文件,都可以使用到,在block里面進行主線程操作,這樣就可以省得在調用時每個地方都寫;
SDImageCache.h文件: typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType); @interface SDImageCache : NSObject - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock; @end SDImageCache.m文件 - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { if (!doneBlock) { return nil; } if (!key) { doneBlock(nil, SDImageCacheTypeNone); return nil; } // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil; } NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); } }); return operation; }
2.2 SDWebImage 有兩個宏 來判斷程序在主線程運行(sync同步 async異步)
#define dispatch_main_sync_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_sync(dispatch_get_main_queue(), block);\ } #define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ }
運用如下:
if ([self showActivityIndicatorView]) { [self addActivityIndicator]; } __weak __typeof(self)wself = self; id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { [wself removeActivityIndicator]; if (!wself) return; dispatch_main_sync_safe(^{ if (!wself) return; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { wself.image = image; [wself setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
2.3 三元?:符號的運用
[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];
若?后面的值 lastPreviousCachedImage則可以這么簡單寫
2.4 SDWebImage 部分清除緩存的原理
部分清理則是根據我們設定的一些參數值來移除一些文件,這里主要有兩個指標:文件的緩存有效期及最大緩存空間大小。文件的緩存有效期可以通過maxCacheAge屬性來設置,默認是1周的時間。如果文件的緩存時間超過這個時間值,則將其移除。而最大緩存空間大小是通過maxCacheSize屬性來設置的,如果所有緩存文件的總大小超過這一大小,則會按照文件最后修改時間的逆序,以每次一半的遞歸來移除那些過早的文件,直到緩存的實際大小小於我們設置的最大使用空間。清理的操作在-cleanDiskWithCompletionBlock:方法中,其實現如下:
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // 1. 該枚舉器預先獲取緩存文件的有用的屬性 NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge]; NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; NSUInteger currentCacheSize = 0; // 2. 枚舉緩存文件夾中所有文件,該迭代有兩個目的:移除比過期日期更老的文件;存儲文件屬性以備后面執行基於緩存大小的清理操作 NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; for (NSURL *fileURL in fileEnumerator) { NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // 3. 跳過文件夾 if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; } // 4. 移除早於有效期的老文件 NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // 5. 存儲文件的引用並計算所有文件的總大小,以備后用 NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } for (NSURL *fileURL in urlsToDelete) { [_fileManager removeItemAtURL:fileURL error:nil]; } // 6.如果磁盤緩存的大小大於我們配置的最大大小,則執行基於文件大小的清理,我們首先刪除最老的文件 if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { // 7. 以設置的最大緩存大小的一半作為清理目標 const NSUInteger desiredCacheSize = self.maxCacheSize / 2; // 8. 按照最后修改時間來排序剩下的緩存文件 NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // 9. 刪除文件,直到緩存總大小降到我們期望的大小 for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { NSDictionary *resourceValues = cacheFiles[fileURL]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) { break; } } } } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } }); }
2.5 查看IOS沙盒中文件的屬性(修改日期,創建日期,大小等)
NSString *strPath =[[NSBundle mainBundle] pathForResource:@"lomo.jpg" ofType:nil]; NSLog(@"path:%@", strPath); NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *path = strPath;//@"/tmp/List"; NSError *error = nil; NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:path error:&error]; if (fileAttributes != nil) { NSNumber *fileSize = [fileAttributes objectForKey:NSFileSize]; NSString *fileOwner = [fileAttributes objectForKey:NSFileOwnerAccountName]; NSDate *fileModDate = [fileAttributes objectForKey:NSFileModificationDate]; NSDate *fileCreateDate = [fileAttributes objectForKey:NSFileCreationDate]; if (fileSize) { NSLog(@"File size: %qi\n", [fileSize unsignedLongLongValue]); } if (fileOwner) { NSLog(@"Owner: %@\n", fileOwner); } if (fileModDate) { NSLog(@"Modification date: %@\n", fileModDate); } if (fileCreateDate) { NSLog(@"create date:%@\n", fileModDate); } } else { NSLog(@"Path (%@) is invalid.", path); }
2.6 遍歷文件NSDirectoryEnumerator
需要獲得目錄的內容列表,使用enumeratorAtPath:方法或者directoryC ontentsAtPath:方法,可以完成枚舉過程。如果使用第一種enumeratorAtPath:方法,一次可以枚舉指定目錄中的每個文件。默認情況下,如果其中一個文件為目錄,那么也會遞歸枚舉它的內容。在這個過程中,通過向枚舉對象發送一條skipDescendants消息,可以動態地阻止遞歸過程,從而不再枚舉目錄中的內容。對於directoryContentsAtPath:方法,使用這個方法,可以枚舉指定目錄的內容,並在一個數組中返回文件列表。如果這個目錄中的任何文件本身是個目錄,這個方法並不遞歸枚舉它的內容。
NSString *path; NSFileManager *fm; NSDirectoryEnumerator *dirEnum; NSArray *dirArray; fm = [NSFileManager defaultManager]; //獲取當前的工作目錄的路徑 path = [fm currentDirectoryPath]; //遍歷這個目錄的第一種方法:(深度遍歷,會遞歸枚舉它的內容) dirEnum = [fm enumeratorAtPath:path]; NSLog(@"1.Contents of %@:",path); while ((path = [dirEnum nextObject]) != nil) { NSLog(@"%@",path); } //遍歷目錄的另一種方法:(不遞歸枚舉文件夾種的內容) dirArray = [fm directoryContentsAtPath:[fm currentDirectoryPath]]; NSLog(@"2.Contents using directoryContentsAtPath:"); for(path in dirArray) NSLog(@"%@",path);
通過以上程序變例如下文件路徑:
如果對上述代碼while循環做如下修改,可以阻止任何子目錄中的枚舉
while ((path = [dirEnum nextObject]) != nil) { NSLog(@"%@",path); BOOL flag; [fm fileExistsAtPath:path isDirectory:&flag]; if(flag == YES) [dirEnum skipDescendants]; }
這里flag是一個BOOL類型的變量。如果指定的路徑是目錄,則fileExistsAtPath:在flag中存儲YES,否則存儲NO。
2.7 SDImageCache 后台運行通知注冊
先說說iOS 應用程序5個狀態;
停止運行-應用程序已經終止,或者還未啟動。
不活動-應用程序處於前台但不再接收事件(例如,用戶在app處於活動時鎖住了設備)。
活動-app處於“使用中”的狀態。
后台-app不再屏幕上顯示,但它仍然執行代碼。
掛起-app仍然駐留內存但不再執行代碼。
按下Home鍵時,app從活動狀態轉入后台,絕大部分app通常在幾秒內就從后台變成了掛起。從 iOS 4 開始,應用就可以在退到后台后,繼續運行一小段時間(10 分鍾);iOS 7 需要注意的區別:iOS 7 以前,應用進入后台繼續運行時,如果用戶鎖屏了,那么 iOS 會等待應用運行完,才進入睡眠狀態。而在 iOS 7 上,系統會很快進入睡眠狀態,那些后台應用也就暫停了。如果收到事件被喚醒(例如定時事件、推送、位置更新等),后台應用才能繼續運行一會。因為處理過程變成了斷斷續續的。更多關於后台運行的知識可以查看這文章或這文章;
a:如果是在程序的AppDelegate里面,就可以直接在這個方法里面處理;
// 當應用程序掉到后台時,執行該方法 - (void)applicationDidEnterBackground:(UIApplication *)application { }
b:如果是自個的類或控制器要處理,比如進入后台當前頁或類的一些信息要進行保存;那么就要注冊系統的通知;下面SDImageCache 里面的實現代碼進行講解;
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory { if ((self = [super init])) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundCleanDisk) name:UIApplicationDidEnterBackgroundNotification object:nil]; } return self; } - (void)backgroundCleanDisk { Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ //完成后,要告訴iOS,任務完成,提交完成申請“好借好還” //標記指定的后台任務完成 [application endBackgroundTask:bgTask]; //銷毀后台任務標識符 bgTask = UIBackgroundTaskInvalid; }]; // 調用本地的方法,在完成后再進行進入掛起,完成后,要告訴iOS,任務完成,提交完成申請“好借好還” [self cleanDiskWithCompletionBlock:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; } //記得刪除通知 - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
注意:UIApplication這邊是通過運行時獲取;UIBackgroundTaskIdentifier知識點可以看介紹;當一個 iOS 應用被送到后台,它的主線程會被暫停。你用 NSThread 的 detachNewThreadSelector:toTar get:withObject:類方法創建的線程也被掛起了。如果你想在后台完成一個長期任務,就必須調用 UIApplication 的beginBackgroundTaskWithExpirationHandler:實例方法,來向 iOS 借點時間。默認情況下,如果在這個期限內,長期任務沒有被完成,iOS 將終止程序。怎么辦?可以使用 beginBackgroundTaskWithExpirationHandler:實例方法,來向 iOS 再借點時間。借和換必須成雙成對!程序提前完成了,也可以提前結束(當然上面還是要用application):
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
最近有個妹子弄的一個關於擴大眼界跟內含的訂閱號,每天都會更新一些深度內容,在這里如果你感興趣也可以關注一下(嘿對美女跟知識感興趣),當然可以關注后輸入:github 會有我的微信號,如果有問題你也可以在那找到我;當然不感興趣無視此信息;