SDWebImage 解析筆記

項(xiàng)目中一直都有使用SDWebImage,對(duì)這個(gè)框架有一定的了解哩牍,但是體系卻未能貫通,因此特地整理下,主要參考:

iOS 源代碼分析 --- SDWebImage

SDWebImage源碼剖析(-)

SDWebImage源碼剖析(二)

蝶.jpg

一. 簡(jiǎn)介

SDWebImage提供了一個(gè)異步下載圖片并且支持緩存的UIImageView分類(lèi)疗垛。
主要邏輯為:

  • 查看緩存罢洲,如果緩存中存在圖片就返回圖片并且更新UIImageView.
  • 緩存中不存在圖片就異步下載圖片,加入緩存塑崖,更新UIImageView.

主要用到的對(duì)象:

1七冲、UIImageView (WebCache)類(lèi)別,入口封裝规婆,實(shí)現(xiàn)讀取圖片完成后的回調(diào)

2澜躺、SDWebImageManager,對(duì)圖片進(jìn)行管理的中轉(zhuǎn)站抒蚜,記錄那些圖片正在讀取掘鄙。
向下層讀取Cache(調(diào)用SDImageCache),或者向網(wǎng)絡(luò)請(qǐng)求下載對(duì)象(調(diào)用SDWebImageDownloader) 嗡髓。
實(shí)現(xiàn)SDImageCacheSDWebImageDownloader的回調(diào)操漠。

3、SDImageCache饿这,根據(jù)URL的MD5生成key對(duì)圖片進(jìn)行存儲(chǔ)和讀嚷帷(實(shí)現(xiàn)存在內(nèi)存中或者存在硬盤(pán)上兩種實(shí)現(xiàn))
實(shí)現(xiàn)圖片和內(nèi)存清理工作。

4蛹稍、SDWebImageDownloader吧黄,根據(jù)URL向網(wǎng)絡(luò)讀取數(shù)據(jù)(實(shí)現(xiàn)部分讀取和全部讀取后再通知回調(diào)兩種方式)

其他類(lèi):
SDWebImageDecoder,異步對(duì)圖像進(jìn)行了一次解壓唆姐。

具體流程圖:


SDWebImage實(shí)現(xiàn)流程圖.png

SDWebImage 加載圖片的流程 :

  1. 入口 setImageWithURL:placeholderImage:options:會(huì)先把placeholderImage顯示拗慨,然后 SDWebImageManager 根據(jù) URL 開(kāi)始處理圖片。

  2. 進(jìn)入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:奉芦,交給 SDImageCache從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:.

  3. 先從內(nèi)存圖片緩存查找是否有圖片赵抢,如果內(nèi)存中已經(jīng)有圖片緩存,SDImageCacheDelegate回調(diào) imageCache:didFindImage:forKey:userInfo:SDWebImageManager声功。

4.SDWebImageManagerDelegate回調(diào) webImageManager:didFinishWithImage:UIImageView+WebCache 等前端展示圖片烦却。

  1. 如果內(nèi)存緩存中沒(méi)有,生成NSInvocationOperation 添加到隊(duì)列開(kāi)始從硬盤(pán)異步查找圖片是否已經(jīng)緩存先巴。

  2. 根據(jù) URLKey 在硬盤(pán)緩存目錄下嘗試讀取圖片文件其爵。這一步是在 NSOperation 進(jìn)行的操作冒冬,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:

  3. 如果上一操作從硬盤(pán)讀取到了圖片摩渺,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過(guò)小简烤,會(huì)先清空內(nèi)存緩存)。SDImageCacheDelegate回調(diào)imageCache:didFindImage:forKey:userInfo:摇幻。進(jìn)而回調(diào)展示圖片横侦。

  4. 如果從硬盤(pán)緩存目錄讀取不到圖片,說(shuō)明所有緩存都不存在該圖片绰姻,需要下載圖片枉侧,回調(diào) imageCache:didNotFindImageForKey:userInfo:

  5. 共享或重新生成一個(gè)下載器 SDWebImageDownloader 開(kāi)始下載圖片狂芋。

  6. 圖片下載由 NSURLConnection 來(lái)做榨馁,實(shí)現(xiàn)相關(guān) delegate 來(lái)判斷圖片下載中、下載完成和下載失敗银酗。

  7. connection:didReceiveData: 中利用ImageIO 做了按圖片下載進(jìn)度加載效果。

  8. connectionDidFinishLoading:數(shù)據(jù)下載完成后交給 SDWebImageDecoder做圖片解碼處理徒像。

  9. 圖片解碼處理在一個(gè) NSOperationQueue 完成黍特,不會(huì)拖慢主線程 UI。如果有需要對(duì)下載的圖片進(jìn)行二次處理锯蛀,最好也在這里完成灭衷,效率會(huì)好很多。

  10. 在主線程 notifyDelegateOnMainThreadWithInfo:宣告解碼完成旁涤,imageDecoder:didFinishDecodingImage:userInfo:回調(diào)給 SDWebImageDownloader翔曲。

  11. imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成。

  12. 通知所有的 downloadDelegates 下載完成劈愚,回調(diào)給需要的地方展示圖片瞳遍。

  13. 將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤(pán)緩存同時(shí)保存菌羽。寫(xiě)文件到硬盤(pán)也在以單獨(dú) NSInvocationOperation 完成掠械,避免拖慢主線程。

  14. SDImageCache 在初始化的時(shí)候會(huì)注冊(cè)一些消息通知注祖,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存猾蒂,應(yīng)用結(jié)束的時(shí)候清理過(guò)期圖片。

  15. SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache是晨,方便使用肚菠。

  16. SDWebImagePrefetcher 可以預(yù)先下載圖片,方便后續(xù)使用罩缴。

二. 架構(gòu)簡(jiǎn)介

A.架構(gòu)圖:

SDWebImageView_relationship.jpeg

UIImageView+WebCacehUIButton+WebCache直接為UIkit框架提供接口蚊逢,而SDWebImageManger負(fù)責(zé)處理和協(xié)調(diào)SDWebImageDownloaderSDWebImageCache并與UIkit層進(jìn)行交互层扶。

三. 具體分析

1.UIImageView+WebCache

A.框架常用入口

// 所有設(shè)置圖片最終都會(huì)調(diào)用這個(gè)方法
- (void)sd_setImageWithURL:(NSURL *)url 
      placeholderImage:(UIImage *)placeholder {
  [self sd_setImageWithURL:url 
        placeholderImage:placeholder 
                 options:0 
                progress:nil 
               completed:nil];
  }

該接口調(diào)用下面這個(gè)方法:

[self   sd_setImageWithURL:placeholderImage:options:progress:completed:]

該方法作為sd_setImageWithURL接口的最終入口,提供了多種參數(shù)时捌。

  • url:遠(yuǎn)程圖片的地址

  • placeholder : 預(yù)顯示圖片

  • optionsSDWebImageOptions

      typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { 
      //下載失敗了會(huì)再次嘗試下載 
      SDWebImageRetryFailed = 1 << 0,
    
      //當(dāng)UIScrollView等正在滾動(dòng)時(shí)怒医,延遲下載圖片(放置scrollView滾動(dòng)卡)     
      WebImageLowPriority = 1 << 1,
    
      //只緩存到內(nèi)存中
      SDWebImageCacheMemoryOnly = 1 << 2, 
    
      // 圖片會(huì)邊下邊顯示
      SDWebImageProgressiveDownload = 1 << 3, 
    
     // 將硬盤(pán)緩存交給系統(tǒng)自帶的NSURLCache去處理 
      SDWebImageRefreshCached = 1 << 4,
    
     //后臺(tái)下載 
      SDWebImageContinueInBackground = 1 << 5,
    
      // 通過(guò)設(shè)置NSMutableURLRequest.HTTPShouldHandleCookies = YES來(lái)處理存儲(chǔ)在NSHTTPCookieStore中的cookie 
      SDWebImageHandleCookies = 1 << 6,
    
      // 允許不受信任的SSL證書(shū)。主要用于測(cè)試目的奢讨。 
      SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    
      // 默認(rèn)情況下,image在裝載的時(shí)候是按照他們?cè)陉?duì)列中的順序裝載的(就是先進(jìn)先出).這個(gè)flag會(huì)把他們移動(dòng)到隊(duì)列的前端,并且立刻裝載,而不是等到當(dāng)前隊(duì)列裝載的時(shí)候再裝載
      SDWebImageHighPriority = 1 << 8,   
    
      // 默認(rèn)情況下,占位圖會(huì)在圖片下載的時(shí)候顯示.這個(gè)flag開(kāi)啟會(huì)延遲占位圖顯示的時(shí)間,等到圖片下載完成之后才會(huì)顯示占位圖
      SDWebImageDelayPlaceholder = 1 << 9, 
    
       // 是否transform圖片
      SDWebImageTransformAnimatedImage = 1 << 10,
      };
    
  • progress :下載進(jìn)度

B.代碼分析:

操作的管理:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
     
    // 取消當(dāng)前下載操作
    [self sd_cancelCurrentImageLoad];

    // 動(dòng)態(tài)添加屬性
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // 如果選項(xiàng)非SDWebImageDelayPlaceholder
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            // 設(shè)置占位圖
            self.image = placeholder;
        });
    }



    if (url.absoluteString.length > 0) {

        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
             // 顯示 下載轉(zhuǎn)圈
            [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) {
            // 下載完成回調(diào)
            // 移除下載進(jìn)度轉(zhuǎn)圈
            [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"];
    } else {
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
       });
    }
}

[self sd_cancelCurrentImageLoad];取消當(dāng)前的下載操作稚叹,它表明 SDWebImage 管理操作的方法:
SDWebImage所有的操作實(shí)際都是通過(guò)一個(gè) operationDictionary 的字典管理,這個(gè)字典是動(dòng)態(tài)添加到 UIView 上的一個(gè)屬性拿诸,因?yàn)檫@個(gè)operationDictionary 需要在UIButtonUIImageView 上重用扒袖,所以需要添加到它們的根類(lèi)上。

這行代碼是要保證沒(méi)有當(dāng)前正在進(jìn)行的異步下載操作, 不會(huì)與即將進(jìn)行的操作發(fā)生沖突, 它會(huì)調(diào)用:

// UIImageView+WebCache
// sd_cancelCurrentImageLoad #1
[self  sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]

這行代碼會(huì)取消當(dāng)前這個(gè)UIImageView的所有操作亩码,不會(huì)影響之后進(jìn)行的下載操作季率。

占位圖的實(shí)現(xiàn):

// UIImageView+WebCache
//sd_setImageWithURL:placeholderImage:options:progress:completed: #4
if (!(options & SDWebImageDelayPlaceholder)) { self.image = placeholder;
}

當(dāng)options中沒(méi)有SDWebImageDelayPlaceholder,UIImageView添加一個(gè)占位圖image.

獲取圖片:

 // UIImageView+WebCache
 // sd_setImageWithURL:placeholderImage:options:progress:completed: #8
if (url)

檢測(cè)傳入的URL是否為空描沟,如果非空就調(diào)用全局的SDWebImageManager來(lái)獲取圖片:

[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]

下載完成后調(diào)用(SDWebImageCompletionWithFinishedBlock)completedBlock 為 UIImageView.image 賦值, 添加上最終所需要的圖片.

// UIImageView+WebCache
//sd_setImageWithURL:placeholderImage:options:progress:completed: #10

dispatch_main_sync_safe(^{
   if (!wself) return; 
   if (image) { 
      wself.image = image; 
      [wself setNeedsLayout]; 
    } 
else { 
    if ((options & SDWebImageDelayPlaceholder)) {      
         wself.image = placeholder;
          [wself setNeedsLayout]; 
      }
  } 
  if (completedBlock && finished) { 
      completedBlock(image, error, cacheType, url); 
  }
});

最后在返回 operation的同時(shí), 也會(huì)向 operationDictionary中添加一個(gè)鍵值對(duì), 來(lái)表示操作的正在進(jìn)行:

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #28
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

它將operation 存儲(chǔ)到operationDictionary 中方便以后的cancel操作飒泻。

清晨.jpg

2. SDWebImageManager

這個(gè)類(lèi)主要用于處理異步下載和圖片緩存的類(lèi),也可以直接用SDWebImageManagerdownloadImageWithURL:options:progress:completed:來(lái)直接下載圖片吏廉。
可以看出這個(gè)類(lèi)主要作用就是為了UIImageView+WebCacheSDWebImageDownloader, SDImageCache之間構(gòu)建一個(gè)橋梁泞遗,使它們能夠更好的協(xié)同工作。

A.核心代碼分析:

a.SDWebImageManager

// SDWebImageManager
//- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:

if ([url isKindOfClass:NSString.class]) { 
  url = [NSURL URLWithString:(NSString *)url];
}
if (![url isKindOfClass:NSURL.class]) { 
  url = nil;
}

這塊代碼的功能是確定 url是否被正確傳入, 如果傳入?yún)?shù)的是 NSString類(lèi)型就會(huì)被轉(zhuǎn)換為NSURL, 如果轉(zhuǎn)換失敗, 那么url會(huì)被賦值為空, 這個(gè)下載的操作就會(huì)出錯(cuò).

b. SDWebImageCombinedOperation

當(dāng) url被正確傳入之后, 會(huì)實(shí)例一個(gè)非常奇怪的 operation, 它其實(shí)是一個(gè)遵循 SDWebImageOperation
協(xié)議的 NSObject的子類(lèi). 而這個(gè)協(xié)議也非常的簡(jiǎn)單:

@protocol SDWebImageOperation <NSObject>
 - (void)cancel;
@end

SDWebImageOperation只是看著像NSOperation但是它唯一跟NSOperation相同就是都可以響應(yīng)cancel方法席覆。調(diào)用這個(gè)類(lèi)的cancel方法史辙,會(huì)使得它持有的兩個(gè)operation都被cancel

// SDWebImageCombinedOperation
// cancel #1
- (void)cancel { 
      self.cancelled = YES; 
      if (self.cacheOperation) { 
            [self.cacheOperation cancel]; 
            self.cacheOperation = nil; 
      } 
      if (self.cancelBlock) {
           self.cancelBlock(); 
          _cancelBlock = nil; 
      }
  }

既然獲取了url佩伤,再通過(guò)url獲取對(duì)應(yīng)的key.

NSString *key = [self cacheKeyForURL:url];

接著通過(guò)key在緩存中查找一起是否下載過(guò)相同的圖片

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { ... }];

這里調(diào)用SDImageCache的實(shí)例方法 queryDiskCacheForKey:done:來(lái)嘗試在緩存中獲取圖片的數(shù)據(jù),而這個(gè)方法獲取的就是貨真價(jià)實(shí)的NSOperation.
如果我們?cè)诰彺嬷胁檎业綄?duì)應(yīng)的圖片聊倔,那么我們直接調(diào)用completedBlock回調(diào)塊結(jié)束這一次圖片的下載操作

// SDWebImageManager
// downloadImageWithURL:options:progress:completed: #47
dispatch_main_sync_safe(^{ completedBlock(image, nil, cacheType, YES, url);});

如果沒(méi)有找到就調(diào)用SDWebImageDownLoader的實(shí)例方法去下載該圖片:

id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { ... }];

如果這個(gè)方法返回正確的downloadedImage ,那么我們就在全局緩存中存儲(chǔ)這個(gè)圖片的數(shù)據(jù):

 [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];

并調(diào)用completedBlock對(duì)UIImageView或者UIButton添加圖片生巡。

最后我們將這個(gè)subOperationcancel 操作添加到operation.cancelBlock中耙蔑,方便操作的取消

operation.cancelBlock = ^{ [subOperation cancel]; }

3. SDWebImageCache

維護(hù)了一個(gè)內(nèi)存緩存和一個(gè)可選的磁盤(pán)緩存,首先看下查詢圖片緩存的方法:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

該方法主要功能是異步查詢圖片緩存,先在內(nèi)存中查找

// SDWebImageCache
// queryDiskCacheForKey:done: #9
UIImage *image = [self imageFromMemoryCacheForKey:key];

// 內(nèi)存中查找圖片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];

}

imageFromMemoryCacheForKey:key 方法會(huì)在SDWebImageCache 維護(hù)的緩存memCache 中查找是否有對(duì)應(yīng)的數(shù)據(jù)孤荣,而 memCache 就是一個(gè) NSCache.

NSCache 是一個(gè)類(lèi)似于 NSMutableDictionary 存儲(chǔ) key-value 的容器纵潦,主要有以下幾個(gè)特點(diǎn):

自動(dòng)刪除機(jī)制:當(dāng)系統(tǒng)內(nèi)存緊張時(shí),NSCache會(huì)自動(dòng)刪除一些緩存對(duì)象
線程安全:從不同線程中對(duì)同一個(gè) NSCache 對(duì)象進(jìn)行增刪改查時(shí)垃环,不需要加鎖
不同于 NSMutableDictionary邀层,NSCache存儲(chǔ)對(duì)象時(shí)不會(huì)對(duì)key進(jìn)行 copy 操作

如果在內(nèi)存中沒(méi)有找到圖片的緩存的話,就需要在磁盤(pán)中查找遂庄。

- (UIImage *)diskImageForKey:(NSString *)key {
   NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; 
   if (data) { 
      UIImage *image = [UIImage sd_imageWithData:data];
       image = [self scaledImageForKey:key image:image];
       if (self.shouldDecompressImages) {
           image = [UIImage decodedImageWithImage:image];
          } 
      return image; 
  }
 else { 
  return nil; 
  }
}

得到圖片對(duì)應(yīng)的NSData后還有經(jīng)過(guò):

  • 根據(jù)圖片的不同種類(lèi)寥院,生成對(duì)應(yīng)的UIImage,
  • 根據(jù)key值,調(diào)整imageScale
  • 如果設(shè)置圖片需要解壓縮涛目,需要對(duì)圖片進(jìn)行解碼

對(duì)圖片進(jìn)行存儲(chǔ)需要對(duì)url進(jìn)行MD5加密計(jì)算生成相應(yīng)的key值:

- (NSString *)cachedFileNameForKey:(NSString *)key {
    const char *str = [key UTF8String];
    if (str == NULL) {
        str = "";
    }
    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], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];

    return filename;
}

然后用該key作為圖片文件名存儲(chǔ)在默認(rèn)路徑下:

// 獲取緩存路徑方法(自己寫(xiě)的)
- (NSString*)getCachePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if (paths.count > 0) {
NSString *path = [paths[0] stringByAppendingFormat:@"/com.hackemist.SDWebImageCache.default"];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
return path;
}else{
return nil;
}
}

之前做朋友圈后臺(tái)發(fā)送圖片就是先將小圖命名秸谢,然后根據(jù)獲取到的七牛的domain和token,拼出url凛澎,接著將該url,進(jìn)行md5加密估蹄,加密后存儲(chǔ)到SDWebImage的默認(rèn)存儲(chǔ)路徑下塑煎,然后在主界面顯示存儲(chǔ)的小圖,后臺(tái)去進(jìn)行圖片壓縮上傳任務(wù)臭蚁。

 UIImage *diskImage = [self diskImageForKey:key];
  if (diskImage && self.shouldCacheImagesInMemory) {
       NSUInteger cost = SDCacheCostForImage(diskImage);
       [self.memCache setObject:diskImage forKey:key cost:cost];
   }



如果在磁盤(pán)中找到圖片最铁,就將他復(fù)制到內(nèi)存中,以便下次使用垮兑。

樹(shù).jpg

4.SDWebImageDownloader

專(zhuān)用的并且優(yōu)化的圖片異步下載器,主要用來(lái)下載圖片,下載放在NSOperationQueue中進(jìn)行冷尉,默認(rèn)maxConcurrentOperationCount為6,timeout時(shí)間為15s.

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

該方法直接調(diào)用了下載進(jìn)度回調(diào)函數(shù):

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return;
    }

    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }

        // Handle single download of simultaneous download request for the same URL
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;

        if (first) {
            createCallback();
        }
    });
}

方法會(huì)先查看這個(gè) url是否有對(duì)應(yīng)的 callback, 使用的是 downloader系枪,持有的一個(gè)字典URLCallbacks.
如果是第一次添加回調(diào)的話, 就會(huì)執(zhí)行first = YES, 這個(gè)賦值非常的關(guān)鍵, 因?yàn)?first不為 YES那么 HTTP 請(qǐng)求就不會(huì)被初始化, 圖片也無(wú)法被獲取.
然后, 在這個(gè)方法中會(huì)重新修正在URLCallbacks中存儲(chǔ)的回調(diào)塊.

通過(guò)dispatch_barrier_async函數(shù)提交的任務(wù)會(huì)等它前面的任務(wù)執(zhí)行完才開(kāi)始雀哨,然后它后面的任務(wù)必須等它執(zhí)行完畢才能開(kāi)始. 必須使用dispatch_queue_create創(chuàng)建的隊(duì)列才會(huì)達(dá)到上面的效果.通過(guò)該函數(shù)來(lái)保證每張圖片進(jìn)度順序。

如果是第一次添加回調(diào)塊私爷,那么就會(huì)直接運(yùn)行這個(gè)createCallBack這個(gè)block雾棺,而這個(gè)block,就是我們?cè)?code>downloadImageWithURL:options:progress:completed: 中傳入的回調(diào)塊.

接著分析下NSMutableURLRequest請(qǐng)求:

 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];

request發(fā)送了一個(gè)http請(qǐng)求衬浑,接著又初始化一個(gè)SDWebImageDownloaderOperation實(shí)例捌浩,這個(gè)實(shí)例用于請(qǐng)求網(wǎng)絡(luò)資源的操作,是NSOperation的子類(lèi):

operation = [[wself.operationClass alloc] initWithRequest:request
                                                      options:options
                                                     progress:^(NSInteger receivedSize, NSInteger expectedSize) {

初始化之后嚎卫,將該operation添加到NSOperationQueue中嘉栓。(備注:NSOperation實(shí)例只有在調(diào)用start方法或者加入NSOperationQueue 才會(huì)執(zhí)行)

[wself.downloadQueue addOperation:operation];

5.SDWebImageDownloaderOperation

這個(gè)類(lèi)主要處理HTTP請(qǐng)求宏榕,URL連接的類(lèi)拓诸,當(dāng)這個(gè)類(lèi)的實(shí)例被加入到隊(duì)列之后,start方法被調(diào)用麻昼,start方法首先產(chǎn)生一個(gè)NSURLConnection,通過(guò)NSURLConnection進(jìn)行圖片的下載奠支,為了確保能夠處理下載的數(shù)據(jù),需要在后臺(tái)運(yùn)行runloop,保證程序不被掛起.
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;

            if (sself) {
                    [sself cancel];
                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif

        self.executing = YES;
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        self.thread = [NSThread currentThread];
    }

   [self.connection start];

    if (self.connection) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }

        //在主線程發(fā)通知抚芦,這樣也保證在主線程收到通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });

        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
           // Make sure to run the runloop in our background thread so it can process downloaded data
            // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
            //       not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            CFRunLoopRun();
        }

        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

接下來(lái)這個(gè) connection 就會(huì)開(kāi)始運(yùn)行:

[self.connection start];

它發(fā)出一個(gè)SDWebImageDownloadStartNotification通知,開(kāi)啟狀態(tài)欄的請(qǐng)求加載轉(zhuǎn)圈倍谜。同時(shí)調(diào)用NSURLConnectionDataDelegate代理

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection;

前兩個(gè)代理會(huì)不停的回調(diào) pregressBlock 來(lái)提示下載進(jìn)度。

而最后一個(gè)代理方法會(huì)在圖片下載完成之后調(diào)用completionBlock 來(lái)完成最后 UIImageView.image的更新叉抡,而這里調(diào)用的 progressBlock尔崔,completionBlockcancelBlock都是在之前存儲(chǔ)在 URLCallbacks
字典中的.

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
    @synchronized(self) {
        // 停止 該線程 運(yùn)行時(shí)
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.thread = nil;
        self.connection = nil;
        // 通知停止?fàn)顟B(tài)欄轉(zhuǎn)圈請(qǐng)求
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
        });
    }

    if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
        responseFromCached = NO;
    }

    if (completionBlock) {
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
            completionBlock(nil, nil, nil, YES);
        } else if (self.imageData) {
             // 進(jìn)行緩存
            UIImage *image = [UIImage sd_imageWithData:self.imageData];
            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
            image = [self scaledImageForKey:key image:image];
        
            // Do not force decoding animated GIFs
            if (!image.images) {
                // 進(jìn)行解碼
                if (self.shouldDecompressImages) {
                    image = [UIImage decodedImageWithImage:image];
                }
            }
            if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
            }
            else {
                completionBlock(image, self.imageData, nil, YES);
            }
        } else {
            completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
        }
    }
    self.completionBlock = nil;
    [self done];
}

轉(zhuǎn)換處理圖片和進(jìn)行緩存后褥民,將下載image賦值給控件跳纳。

四. 面試點(diǎn)

1北专、SDImageCache是怎么做數(shù)據(jù)管理的?

  • SDImageCache分成兩部分,一個(gè)是內(nèi)存層面的侣姆,一個(gè)是磁盤(pán)層面的。

  • 內(nèi)存緩存的處理是使用NSCache對(duì)象來(lái)實(shí)現(xiàn)的煮嫌。NSCache是一個(gè)類(lèi)似于集合的容器。它存儲(chǔ)key-value對(duì),這一點(diǎn)類(lèi)似于NSDictionary類(lèi)惫叛,用搜索文件系統(tǒng)的方式做管理,文件替換方式是以時(shí)間為單位逞刷。我們通常用使用緩存來(lái)臨時(shí)存儲(chǔ)短時(shí)間使用但創(chuàng)建昂貴的對(duì)象嘉涌。重用這些對(duì)象可以優(yōu)化性能,因?yàn)樗鼈兊闹挡恍枰匦掠?jì)算亲桥。另外一方面洛心,這些對(duì)象對(duì)于程序來(lái)說(shuō)不是緊要的,在內(nèi)存緊張時(shí)會(huì)被丟棄题篷。

  • 磁盤(pán)緩存的處理則是使用NSFileManager對(duì)象來(lái)實(shí)現(xiàn)的词身。圖片存儲(chǔ)的位置是位于Cache文件夾,文件替換方式是以時(shí)間為單位番枚,剔除時(shí)間大一一周的圖片文件法严。

  • 當(dāng)SDWebImageManagerSDImageCache 要資源時(shí), 先搜索內(nèi)存層面的數(shù)據(jù)葫笼,如果有直接返回深啤,沒(méi)有再訪問(wèn)磁盤(pán),如果有將圖片從磁盤(pán)讀取出來(lái)路星,然后做解壓溯街,將圖片對(duì)象放到內(nèi)存層面做備份,再返回調(diào)用層洋丐。

  1. 為什么圖片要進(jìn)行解壓?
  • 因?yàn)?code>UIImage的imageWithData函數(shù)是每次畫(huà)圖的時(shí)候才將Data解壓成ARGB圖像呈昔,所以在每次畫(huà)圖的時(shí)候,會(huì)有一個(gè)解壓操作友绝,這樣效率很低堤尾,但是只有瞬時(shí)的內(nèi)存需求,為了提高效率迁客,通過(guò)SDWebImageDecoder將包裝在Data下的資源解壓郭宝,然后畫(huà)在另外一張圖片上面,這樣這張圖片就不需要重復(fù)解壓了掷漱,這種做法就是典型的空間換取時(shí)間的做法粘室。

3.SDWebImage 在多線程下載圖片時(shí)防止錯(cuò)亂的策略

  • SDWebImage 會(huì)將ImageView 對(duì)象關(guān)聯(lián)一個(gè)下載列表(列表是給AnimationImages用的,這個(gè)時(shí)候會(huì)下載多張圖片)卜范,當(dāng)tableView滾動(dòng)時(shí)衔统,imageView會(huì)重設(shè)數(shù)據(jù)源url,這時(shí)會(huì)cancel掉下載列表中當(dāng)前對(duì)應(yīng)的下載任務(wù),然后開(kāi)啟一個(gè)新的下載任務(wù),這樣就保證只有當(dāng)前可見(jiàn)的cell對(duì)象的ImageView對(duì)象關(guān)聯(lián)的下載任務(wù)能夠回調(diào)缰冤,不會(huì)發(fā)生Image錯(cuò)亂犬缨。

  • 同時(shí),SDWebImage 管理了一個(gè)全局下載隊(duì)列SDWebDownloadManager棉浸,并發(fā)量設(shè)置為6怀薛,也就表示如果cell的數(shù)目大于6,就會(huì)有部分下載隊(duì)列處于等待狀態(tài)迷郑,而且枝恋,在添加下載任務(wù)到全局的下載隊(duì)列中去的時(shí)候,SDWebImage 默認(rèn)采取的是LIFO(后進(jìn)先出)策略嗡害,具體是添加新的下載任務(wù)的時(shí)候焚碌,將之前的下載任務(wù)添加依賴為新的下載任務(wù)。

另外解決方案:

  • imageView對(duì)象和圖片的url相關(guān)聯(lián)霸妹,在滑動(dòng)時(shí)十电,不取消舊的下載任務(wù),而是在下載任務(wù)完成回調(diào)時(shí)叹螟,進(jìn)行url匹配鹃骂,只有匹配成功的image會(huì)刷新imageView對(duì)象,而其他的image則只做緩存操作罢绽,而不刷新UI畏线。

  • 同時(shí),仍然管理一個(gè)執(zhí)行隊(duì)列良价,為了避免占用太多的資源寝殴,通常會(huì)對(duì)執(zhí)行隊(duì)列設(shè)置一個(gè)最大的并發(fā)量。此外明垢,為了保證LIFO的下載策略蚣常,可以自己維持一個(gè)等待隊(duì)列,每次下載任務(wù)開(kāi)始的時(shí)候袖外,將后進(jìn)入的下載任務(wù)插入到等待隊(duì)列的前面史隆。

  1. SDWebImage的主要任務(wù)就是圖片的下載和緩存魂务。為了支持這些操作曼验,它主要使用了以下知識(shí)點(diǎn):
  • dispatch_barrier_sync函數(shù):該方法用于對(duì)操作設(shè)置屏障,確保在執(zhí)行完任務(wù)后才會(huì)執(zhí)行后續(xù)操作粘姜。該方法常用于確保類(lèi)的線程安全性操作鬓照。

  • NSMutableURLRequest:用于創(chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)象,我們可以根據(jù)需要來(lái)配置請(qǐng)求報(bào)頭等信息孤紧。

NSOperationNSOperationQueue:操作隊(duì)列是Objective-C中一種高級(jí)的并發(fā)處理方法豺裆,現(xiàn)在它是基于GCD來(lái)實(shí)現(xiàn)的。相對(duì)于GCD來(lái)說(shuō),操作隊(duì)列的優(yōu)點(diǎn)是可以取消在任務(wù)處理隊(duì)列中的任務(wù)臭猜,另外在管理操作間的依賴關(guān)系方面也容易一些躺酒。對(duì)SDWebImage中我們就看到了如何使用依賴將下載順序設(shè)置成后進(jìn)先出的順序。

  • NSURLConnection:用于網(wǎng)絡(luò)請(qǐng)求及響應(yīng)處理蔑歌。在iOS7.0后羹应,蘋(píng)果推出了一套新的網(wǎng)絡(luò)請(qǐng)求接口,即NSURLSession類(lèi)次屠。

開(kāi)啟一個(gè)后臺(tái)任務(wù)园匹。

  • NSCache類(lèi):一個(gè)類(lèi)似于集合的容器。它存儲(chǔ)key-value對(duì)劫灶,這一點(diǎn)類(lèi)似于NSDictionary類(lèi)裸违。我們通常用使用緩存來(lái)臨時(shí)存儲(chǔ)短時(shí)間使用但創(chuàng)建昂貴的對(duì)象。重用這些對(duì)象可以優(yōu)化性能本昏,因?yàn)樗鼈兊闹挡恍枰匦掠?jì)算供汛。另外一方面,這些對(duì)象對(duì)于程序來(lái)說(shuō)不是緊要的涌穆,在內(nèi)存緊張時(shí)會(huì)被丟棄紊馏。

  • 清理緩存圖片的策略:特別是最大緩存空間大小的設(shè)置。如果所有緩存文件的總大小超過(guò)這一大小蒲犬,則會(huì)按照文件最后修改時(shí)間的逆序朱监,以每次一半的遞歸來(lái)移除那些過(guò)早的文件,直到緩存的實(shí)際大小小于我們?cè)O(shè)置的最大使用空間原叮。

  • 對(duì)圖片的解壓縮操作:這一操作可以查看SDWebImageDecoder.m+decodedImageWithImage方法的實(shí)現(xiàn)赫编。

  • 對(duì)GIF圖片的處理

  • 對(duì)WebP圖片的處理

  1. 系統(tǒng)級(jí)內(nèi)存警告如何處理
  • 取消當(dāng)前正在進(jìn)行的所有下載操作[[SDWebImageManager sharedManager] cancelAll];
  • 清除緩存數(shù)據(jù):
    內(nèi)存緩存:直接刪除文件,重新創(chuàng)建新的文件
    磁盤(pán)緩存:刪除過(guò)期的文件數(shù)據(jù)奋隶,計(jì)算當(dāng)前未過(guò)期的已經(jīng)下載的文件數(shù)據(jù)的大小擂送,如果發(fā)現(xiàn)該數(shù)據(jù)大小大于我們?cè)O(shè)置的最大緩存數(shù)據(jù)大小,那么程序內(nèi)部會(huì)按照按文件數(shù)據(jù)緩存的時(shí)間從遠(yuǎn)到近刪除唯欣,知道小于最大緩存數(shù)據(jù)為止嘹吨。
  1. 如何播放gif圖片
  • 把用戶傳入的gif圖片->NSData
  • 根據(jù)該Data創(chuàng)建一個(gè)圖片數(shù)據(jù)源(NSData->CFImageSourceRef
  • 計(jì)算該數(shù)據(jù)源中一共有多少幀,把每一幀數(shù)據(jù)取出來(lái)放到圖片數(shù)組中
  • 根據(jù)得到的數(shù)組+計(jì)算的動(dòng)畫(huà)時(shí)間-》可動(dòng)畫(huà)的image
    • [UIImage animatedImageWithImages:images duration:duration];
  1. 如何判斷當(dāng)前圖片類(lèi)型
    + (NSString *)sd_contentTypeForImageData:(NSData *)data;
    圖片的十六進(jìn)制數(shù)據(jù), 的前8個(gè)字節(jié)都是一樣的, 所以可以通過(guò)判斷十六進(jìn)制來(lái)判斷圖片的類(lèi)型

五. 最后

送上一張自己喜歡的圖片:

風(fēng)景.jpeg

個(gè)人小結(jié)境氢,有興趣的朋友可以看一下蟀拷,如果覺(jué)得不錯(cuò),麻煩給個(gè)喜歡或star,若發(fā)現(xiàn)問(wèn)題請(qǐng)及時(shí)反饋萍聊,謝謝问芬!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寿桨,隨后出現(xiàn)的幾起案子此衅,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挡鞍,死亡現(xiàn)場(chǎng)離奇詭異骑歹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)墨微,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)陵刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人欢嘿,你說(shuō)我怎么就攤上這事衰琐。” “怎么了炼蹦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵羡宙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我掐隐,道長(zhǎng)狗热,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任虑省,我火速辦了婚禮匿刮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘探颈。我一直安慰自己熟丸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布伪节。 她就那樣靜靜地躺著光羞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怀大。 梳的紋絲不亂的頭發(fā)上纱兑,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音化借,去河邊找鬼潜慎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蓖康,可吹牛的內(nèi)容都是我干的铐炫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钓瞭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼驳遵!你這毒婦竟也來(lái)了淫奔?” 一聲冷哼從身側(cè)響起山涡,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鸭丛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竞穷,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年鳞溉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘾带。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熟菲,死狀恐怖看政,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抄罕,我是刑警寧澤允蚣,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站呆贿,受9級(jí)特大地震影響嚷兔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜做入,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一冒晰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竟块,春花似錦壶运、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至秫逝,卻和暖如春恕出,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背违帆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工浙巫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刷后。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓的畴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親尝胆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丧裁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容