(20160602)開源第三方學習之SDWebImage


這個類庫提供一個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 會有我的微信號,如果有問題你也可以在那找到我;當然不感興趣無視此信息;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM