SDWebImage源代碼解析(二)Downloader

前言

CSDN地址:http://blog.csdn.net/game3108/article/details/52598835
本文的中文注釋代碼demo更新在我的github上毛萌。

上篇文章講解的了SDWebImage的Cache部分晃痴,這篇講講一下Download部分缀蹄。

Download

Download部分主要包含如下2個(gè)方法

  • SDWebImageDownloader
    圖片下載控制類
  • SDWebImageDownloaderOperation
    NSOperation子類

SDWebImageDownloader

SDWebImageDownloader主要包含以下內(nèi)容:

  • 初始化信息
  • 相關(guān)請(qǐng)求信息設(shè)置與獲取
  • 請(qǐng)求圖片方法
  • NSURLSession相關(guān)回調(diào)

初始化信息

SDWebImageDownloader.h中的property聲明

//下載完成執(zhí)行順序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    //先進(jìn)先出
    SDWebImageDownloaderFIFOExecutionOrder,
    //先進(jìn)后出
    SDWebImageDownloaderLIFOExecutionOrder
};

@interface SDWebImageDownloader : NSObject
//是否壓縮圖片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//最大下載數(shù)量
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//當(dāng)前下載數(shù)量
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//下載超時(shí)時(shí)間
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//下載策略
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

SDWebImageDownloader.m中的Extension

@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//下載的queue
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
//上一個(gè)添加的操作
@property (weak, nonatomic) NSOperation *lastAddedOperation;
//操作類
@property (assign, nonatomic) Class operationClass;
//url請(qǐng)求緩存
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
//請(qǐng)求頭
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
//這個(gè)queue為了能否序列化處理所有網(wǎng)絡(luò)結(jié)果的返回
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;

// The session in which data tasks will run
//數(shù)據(jù)runsession
@property (strong, nonatomic) NSURLSession *session;

@end

相關(guān)方法實(shí)現(xiàn)如下:

//判斷用了SDNetworkActivityIndicator藻雌,去替換該方法的內(nèi)容
+ (void)initialize {
    // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
    // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
    if (NSClassFromString(@"SDNetworkActivityIndicator")) {

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop

        // Remove observer in case it was previously added.
        [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                 selector:NSSelectorFromString(@"startActivity")
                                                     name:SDWebImageDownloadStartNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                 selector:NSSelectorFromString(@"stopActivity")
                                                     name:SDWebImageDownloadStopNotification object:nil];
    }
}

//單例
+ (SDWebImageDownloader *)sharedDownloader {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

//初始化方法
- (id)init {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES;
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLCallbacks = [NSMutableDictionary new];
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;

        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        sessionConfig.timeoutIntervalForRequest = _downloadTimeout;

        /**
         *  Create the session for this task
         *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
         *  method calls and completion handler calls.
         */
        //session初始化
        self.session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                     delegate:self
                                                delegateQueue:nil];
    }
    return self;
}

- (void)dealloc {
    //停止session
    [self.session invalidateAndCancel];
    self.session = nil;

    //停止所有downloadqueue
    [self.downloadQueue cancelAllOperations];
    SDDispatchQueueRelease(_barrierQueue);
}

從這邊也可以看出匙握,SDWebImageDownloader的下載方法就是用NSOperation。使用NSOperationQueue去控制最大操作數(shù)量和取消所有操作涎才,在NSOperation中運(yùn)行NSUrlSession方法請(qǐng)求參數(shù)溪食。

相關(guān)請(qǐng)求信息設(shè)置與獲取

相關(guān)方法如下:

//設(shè)置hedaer頭
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
    if (value) {
        self.HTTPHeaders[field] = value;
    }
    else {
        [self.HTTPHeaders removeObjectForKey:field];
    }
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    return self.HTTPHeaders[field];
}

//設(shè)置多大并發(fā)數(shù)量
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}

- (NSUInteger)currentDownloadCount {
    return _downloadQueue.operationCount;
}

- (NSInteger)maxConcurrentDownloads {
    return _downloadQueue.maxConcurrentOperationCount;
}

//設(shè)置operation的類型,可以自行再繼承子類去實(shí)現(xiàn)
- (void)setOperationClass:(Class)operationClass {
    _operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
}

//暫停下載
- (void)setSuspended:(BOOL)suspended {
    [self.downloadQueue setSuspended:suspended];
}
//取消所有下載
- (void)cancelAllDownloads {
    [self.downloadQueue cancelAllOperations];
}

這一部分唯一提一下的是setOperationClass:方法半火,是為了可以自己實(shí)現(xiàn)NSOperation的子類去完成相關(guān)請(qǐng)求的設(shè)置越妈。

請(qǐng)求圖片方法

相關(guān)方法實(shí)現(xiàn)如下:

//請(qǐng)求圖片方法
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;

    //處理新請(qǐng)求的封裝
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
        //為了防止?jié)撛诘闹貜?fù)cache
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        //設(shè)置是否處理cookies
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        //有header處理block,則調(diào)用block钮糖,返回headerfieleds
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
        //這邊的創(chuàng)建方式允許自己定義SDWebImageDownloaderOperation的子類進(jìn)行替換
        operation = [[wself.operationClass alloc] initWithRequest:request
                                                        inSession:self.session
                                                          options:options
                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                                             SDWebImageDownloader *sself = wself;
                                                             if (!sself) return;
                                                             __block NSArray *callbacksForURL;
                                                             //這里用barrierQueue可以確保數(shù)據(jù)一致性
                                                             dispatch_sync(sself.barrierQueue, ^{
                                                                 //獲取所有的同url的請(qǐng)求
                                                                 callbacksForURL = [sself.URLCallbacks[url] copy];
                                                             });
                                                             //遍歷創(chuàng)建信息函數(shù)
                                                             for (NSDictionary *callbacks in callbacksForURL) {
                                                                 dispatch_async(dispatch_get_main_queue(), ^{
                                                                     //獲取progressblock并調(diào)用
                                                                     SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                                                                     if (callback) callback(receivedSize, expectedSize);
                                                                 });
                                                             }
                                                         }
                                                        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                                                            SDWebImageDownloader *sself = wself;
                                                            if (!sself) return;
                                                            __block NSArray *callbacksForURL;
                                                            //等待新請(qǐng)求插入和獲取參數(shù)完結(jié)梅掠,再finish
                                                            dispatch_barrier_sync(sself.barrierQueue, ^{
                                                                callbacksForURL = [sself.URLCallbacks[url] copy];
                                                                //如果完結(jié)了,刪除所有url緩存
                                                                if (finished) {
                                                                    [sself.URLCallbacks removeObjectForKey:url];
                                                                }
                                                            });
                                                            //調(diào)用completeblock
                                                            for (NSDictionary *callbacks in callbacksForURL) {
                                                                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                                                                if (callback) callback(image, data, error, finished);
                                                            }
                                                        }
                                                        cancelled:^{
                                                            SDWebImageDownloader *sself = wself;
                                                            if (!sself) return;
                                                            //刪除url
                                                            dispatch_barrier_async(sself.barrierQueue, ^{
                                                                [sself.URLCallbacks removeObjectForKey:url];
                                                            });
                                                        }];
        //是否壓縮圖片
        operation.shouldDecompressImages = wself.shouldDecompressImages;
        
        //是否包含url的驗(yàn)證
        if (wself.urlCredential) {
            operation.credential = wself.urlCredential;
        } else if (wself.username && wself.password) {
            //有賬號(hào)和密碼則設(shè)置請(qǐng)求
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
        }
        //設(shè)置operation請(qǐng)求優(yōu)先級(jí)
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }

        //開始請(qǐng)求[operation start]
        [wself.downloadQueue addOperation:operation];
        //如果是先進(jìn)后出店归,鏈接依賴
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [wself.lastAddedOperation addDependency:operation];
            wself.lastAddedOperation = operation;
        }
    }];

    return operation;
}

//用來處理新請(qǐng)求的封裝函數(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;
    }
    
    //添加progress的過程都會(huì)等待queue中的前面完成后進(jìn)行
    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        //如果url不存在在self.URLCallbacks中阎抒,說明該url第一次下載
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }

        // Handle single download of simultaneous download request for the same URL
        //處理同時(shí)同樣的url請(qǐng)求情況
        //將progressblock和completeblock存入dictionary,再添加到self.URLCallbacks[url]的array中消痛,這樣可以在運(yùn)行完成后一起運(yùn)行
        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();
        }
    });
}

可以理解分為兩塊:

  • 1.請(qǐng)求封裝參數(shù)
    這一塊的主要目的是存儲(chǔ)請(qǐng)求的progressBlock(進(jìn)度回調(diào))和completeBlock(完成回調(diào))到self.URLCallbacks中且叁,并且對(duì)于同時(shí)多個(gè)請(qǐng)求同一個(gè)url圖片的情況進(jìn)行了處理。
  • 2.新請(qǐng)求獲取圖片
    當(dāng)url為第一次請(qǐng)求時(shí)候秩伞,構(gòu)建請(qǐng)求request與operation逞带,并開始運(yùn)行operation

NSURLSession相關(guān)回調(diào)

相關(guān)方法如下:

#pragma mark Helper methods

//獲取operationQueue中的包含此task的operation
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
    SDWebImageDownloaderOperation *returnOperation = nil;
    for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
        if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
            returnOperation = operation;
            break;
        }
    }
    return returnOperation;
}

#pragma mark NSURLSessionDataDelegate

//收到返回結(jié)果
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];

    [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}

//收到返回?cái)?shù)據(jù)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {

    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];

    [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}

//是否緩存reponse
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {

    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];

    [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}

#pragma mark NSURLSessionTaskDelegate

//task已經(jīng)完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];

    [dataOperation URLSession:session task:task didCompleteWithError:error];
}

//task請(qǐng)求驗(yàn)證方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];

    [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}

實(shí)際上相關(guān)處理都已經(jīng)放到NSOperation進(jìn)行處理欺矫。

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation主要包含以下內(nèi)容:

  • 初始化相關(guān)信息
  • 相關(guān)參數(shù)設(shè)置
  • 開始請(qǐng)求與取消請(qǐng)求
  • NSURLSession相關(guān)回調(diào)

初始化相關(guān)信息

SDWebImageDownloaderOperation.h的property聲明

@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//operation的請(qǐng)求
@property (strong, nonatomic, readonly) NSURLRequest *request;
//operation的task
@property (strong, nonatomic, readonly) NSURLSessionTask *dataTask;
//是否壓縮圖片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//當(dāng)收到`-connection:didReceiveAuthenticationChallenge:`的驗(yàn)證
@property (nonatomic, strong) NSURLCredential *credential;
//下載方式
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
//文件大小
@property (assign, nonatomic) NSInteger expectedSize;
//operation的返回
@property (strong, nonatomic) NSURLResponse *response;

SDWebImageDownloaderOperation.m中的extension

@interface SDWebImageDownloaderOperation ()

@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;

@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic) NSMutableData *imageData;

// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
// the task associated with this operation
//因?yàn)槭潜籨ownloader持有,所以是weak
@property (weak, nonatomic) NSURLSession *unownedSession;
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
//如果不用設(shè)置的nsurlsession展氓,需要自己去吃油一個(gè)
@property (strong, nonatomic) NSURLSession *ownedSession;
//sessiontask
@property (strong, nonatomic, readwrite) NSURLSessionTask *dataTask;
//當(dāng)前線程
@property (strong, atomic) NSThread *thread;

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif

@end

@implementation SDWebImageDownloaderOperation {
    //圖片長(zhǎng),寬
    size_t width, height;
    //圖片方向
    UIImageOrientation orientation;
    //是否圖片是從cache讀出
    BOOL responseFromCached;
}

初始化相關(guān)方法:

- (id)initWithRequest:(NSURLRequest *)request
            inSession:(NSURLSession *)session
              options:(SDWebImageDownloaderOptions)options
             progress:(SDWebImageDownloaderProgressBlock)progressBlock
            completed:(SDWebImageDownloaderCompletedBlock)completedBlock
            cancelled:(SDWebImageNoParamsBlock)cancelBlock {
    if ((self = [super init])) {
        _request = [request copy];
        _shouldDecompressImages = YES;
        _options = options;
        _progressBlock = [progressBlock copy];
        _completedBlock = [completedBlock copy];
        _cancelBlock = [cancelBlock copy];
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        _unownedSession = session;
        responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
    }
    return self;
}

同樣穆趴,從初始化的參數(shù)可以看出,在SDWebImageDownloaderOperation中遇汞,通過NSURLRequest去生成一個(gè)NSURLSessionTask未妹,然后建立鏈接,獲取信息空入。

相關(guān)參數(shù)設(shè)置

相關(guān)實(shí)現(xiàn)如下:

//重寫set方法络它,并且設(shè)置kvo的觀察回調(diào)
- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

//重寫excute方法,并且設(shè)置kvo的觀察回調(diào)
- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

//是否允許并發(fā)
- (BOOL)isConcurrent {
    return YES;
}

這里重寫的時(shí)候還注意到了原來相關(guān)屬性的KVO值执庐。

開始請(qǐng)求與取消請(qǐng)求

主要就是重寫NSOperation的startcancel方法,其中cancel方法還是SDWebImageOperation的回調(diào)导梆。
相關(guān)方法如下:

//重新父類start開始方法
- (void)start {
    //加鎖
    @synchronized (self) {
        //如果已經(jīng)取消轨淌,則取消
        if (self.isCancelled) {
            self.finished = YES;
            //重置數(shù)據(jù)
            [self reset];
            return;
        }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        //在iOS4以上,會(huì)去運(yùn)行后臺(tái)方法看尼,等待完成后递鹉,取消相關(guān)下載
        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
        NSURLSession *session = self.unownedSession;
        //沒有unowndSession,則創(chuàng)建一個(gè)
        if (!self.unownedSession) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        //創(chuàng)建NSURLSessionDataTask
        self.dataTask = [session dataTaskWithRequest:self.request];
        //代表是否在執(zhí)行
        self.executing = YES;
        //創(chuàng)建thread藏斩,目的是能夠在該方法開始后躏结,取消方法在開始方法結(jié)束進(jìn)行調(diào)用
        self.thread = [NSThread currentThread];
    }
    
    //開始執(zhí)行task
    [self.dataTask resume];
    
    if (self.dataTask) {
        //初始化的進(jìn)度block
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        //發(fā)一個(gè)開始下載的notification
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    }
    else {
        //不存在request就直接返回下載失敗
        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
    //直接停止后臺(tái)下載動(dòng)作
    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
}

//SDWebImageOperation的deelgate
- (void)cancel {
    @synchronized (self) {
        //有self.thread說明該operation已經(jīng)開始下載了,需要把cancel方法放到下載的同一個(gè)線程狰域,并且等待下載完成后cancel
        if (self.thread) {
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
        else {
            [self cancelInternal];
        }
    }
}
//線程cancel方法
- (void)cancelInternalAndStop {
    if (self.isFinished) return;
    [self cancelInternal];
}

//停止下載
- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel];
    if (self.cancelBlock) self.cancelBlock();

    //數(shù)據(jù)請(qǐng)求取消
    if (self.dataTask) {
        [self.dataTask cancel];
        //發(fā)送stop notification
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });

        // As we cancelled the connection, its callback won't be called and thus won't
        // maintain the isFinished and isExecuting flags.
        //停止請(qǐng)求后媳拴,需要去設(shè)置相關(guān)方法
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }

    [self reset];
}

//下載完畢
- (void)done {
    self.finished = YES;
    self.executing = NO;
    [self reset];
}

//重置信息
- (void)reset {
    self.cancelBlock = nil;
    self.completedBlock = nil;
    self.progressBlock = nil;
    self.dataTask = nil;
    self.imageData = nil;
    self.thread = nil;
    if (self.ownedSession) {
        [self.ownedSession invalidateAndCancel];
        self.ownedSession = nil;
    }
}

這里比較有趣的是self.thread的妙用,如果存在self.thread說明operation已經(jīng)start兆览,那么cancel已經(jīng)無效屈溉,必須等待請(qǐng)求完成后,才能去cancel它抬探,防止出現(xiàn)問題子巾。這邊用self.thread就可以達(dá)成這樣的目的。

NSURLSession相關(guān)回調(diào)

相關(guān)方法如下:

#pragma mark NSURLSessionDataDelegate

//收到請(qǐng)求回復(fù)
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    
    //'304 Not Modified' is an exceptional one
    //除了304以外<400的工程情況
    if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
        //設(shè)置接受數(shù)據(jù)大小
        self.expectedSize = expected;
        //初始化progress過程
        if (self.progressBlock) {
            self.progressBlock(0, expected);
        }
        
        //創(chuàng)建能夠接受的圖片大小數(shù)據(jù)
        self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
        self.response = response;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
        });
    }
    else {
        NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];
        
        //This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
        //In case of 304 we need just cancel the operation and return cached image from the cache.
        //304說明圖片沒有變化小压,停止operation然后返回cache
        if (code == 304) {
            [self cancelInternal];
        } else {
            //其他情況停止請(qǐng)求
            [self.dataTask cancel];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });
        //完成block线梗,返回錯(cuò)誤
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
        }
        //結(jié)束
        [self done];
    }
    
    //回調(diào)response允許調(diào)用
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}

//收到請(qǐng)求數(shù)據(jù),分進(jìn)度
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    //數(shù)據(jù)添加到imageData緩存
    [self.imageData appendData:data];
    
    //如果需要顯示進(jìn)度
    if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
        // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
        // Thanks to the author @Nyx0uf

        // Get the total bytes downloaded
        //獲得下載的圖片大小
        const NSInteger totalSize = self.imageData.length;

        // Update the data source, we must pass ALL the data, not just the new bytes
        //更新數(shù)據(jù)怠益,需要把所有數(shù)據(jù)一同更新
        CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
        
        //為0說明是第一段數(shù)據(jù)
        if (width + height == 0) {
            CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
            if (properties) {
                NSInteger orientationValue = -1;
                CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
                //獲得圖片高
                if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
                val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
                //獲得圖片寬
                if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
                val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
                //獲得圖片旋轉(zhuǎn)方向
                if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
                CFRelease(properties);

                // When we draw to Core Graphics, we lose orientation information,
                // which means the image below born of initWithCGIImage will be
                // oriented incorrectly sometimes. (Unlike the image born of initWithData
                // in didCompleteWithError.) So save it here and pass it on later.
                //當(dāng)我們繪制Core Graphic仪搔,我們會(huì)失去圖片方向信息
                //著意味著用initWithCGIImage將會(huì)有的時(shí)候并不正確,(不像在didCompleteWithError里用initWithData),所以保存信息
                orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
            }

        }
        
        //不是第一段數(shù)據(jù)蜻牢,并且沒有下載完畢
        if (width + height > 0 && totalSize < self.expectedSize) {
            // Create the image
            //創(chuàng)建圖片來源
            CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);

#ifdef TARGET_OS_IPHONE
            // Workaround for iOS anamorphic image
            //處理iOS失真圖片僻造,這邊的處理方式有寫看不大懂
            if (partialImageRef) {
                const size_t partialHeight = CGImageGetHeight(partialImageRef);
                CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
                CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
                CGColorSpaceRelease(colorSpace);
                if (bmContext) {
                    CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
                    CGImageRelease(partialImageRef);
                    partialImageRef = CGBitmapContextCreateImage(bmContext);
                    CGContextRelease(bmContext);
                }
                else {
                    CGImageRelease(partialImageRef);
                    partialImageRef = nil;
                }
            }
#endif
            //如果有了圖片數(shù)據(jù)
            if (partialImageRef) {
                //獲取圖片
                UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
                //獲得key
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                //獲得適合屏幕的圖片
                UIImage *scaledImage = [self scaledImageForKey:key image:image];
                if (self.shouldDecompressImages) {
                    //壓縮圖片
                    image = [UIImage decodedImageWithImage:scaledImage];
                }
                else {
                    image = scaledImage;
                }
                CGImageRelease(partialImageRef);
                //返回完成結(jié)果
                dispatch_main_sync_safe(^{
                    if (self.completedBlock) {
                        self.completedBlock(image, nil, nil, NO);
                    }
                });
            }
        }

        CFRelease(imageSource);
    }
    
    //顯示階段
    if (self.progressBlock) {
        self.progressBlock(self.imageData.length, self.expectedSize);
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {

    //說明結(jié)果沒有從cache讀取
    responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
    NSCachedURLResponse *cachedResponse = proposedResponse;

    //如果是放棄cache的模式
    if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
        // Prevents caching of responses
        cachedResponse = nil;
    }
    //回調(diào)結(jié)果cachedResponse
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

#pragma mark NSURLSessionTaskDelegate

//完成請(qǐng)求
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    @synchronized(self) {
        //完成后的數(shù)據(jù)設(shè)置
        self.thread = nil;
        self.dataTask = nil;
        //停止notification
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
            if (!error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
            }
        });
    }
    
    //如果有error憋他,將error返回
    if (error) {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, error, YES);
        }
    } else {
        //如果存在完成block
        SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
        
        if (completionBlock) {
            /**
             *  See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
             *  Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
             *    and images for which responseFromCached is YES (only the ones that cannot be cached).
             *  Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication 
             */
            //查看#1608和#1623的pull request。顯然髓削,這里會(huì)有一個(gè)罕見的NSURLCache的crash
            //限制了調(diào)用cachedResponseForRequest竹挡,只有當(dāng)responseFromCached為yes的時(shí)候,我們應(yīng)該去忽略緩存的reponse和圖片
            //記錄:當(dāng)responseFromCached在`willCacheResponse:`設(shè)置為no立膛。這個(gè)方法不會(huì)在大型圖片和驗(yàn)證圖片的時(shí)候到靠用
            if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
                completionBlock(nil, nil, nil, YES);
            } else if (self.imageData) {
                //初始化圖片
                UIImage *image = [UIImage sd_imageWithData:self.imageData];
                //緩存的url的key
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                //適應(yīng)屏幕設(shè)置
                image = [self scaledImageForKey:key image:image];
                
                // Do not force decoding animated GIFs
                //如果不是GIF
                if (!image.images) {
                    if (self.shouldDecompressImages) {
                        //壓縮圖片
                        image = [UIImage decodedImageWithImage:image];
                    }
                }
                //圖片大小為0揪罕,則報(bào)錯(cuò)
                if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                    completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
                }
                else {
                    //完成整個(gè)圖片處理
                    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];
}

//處理請(qǐng)求特殊權(quán)限驗(yàn)證
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    
    //需要信任該服務(wù)
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        //不需要信任,則走默認(rèn)處理
        if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        } else {
            //設(shè)置站點(diǎn)為信任
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            disposition = NSURLSessionAuthChallengeUseCredential;
        }
    } else {
        //有一些錯(cuò)誤
        if ([challenge previousFailureCount] == 0) {
            if (self.credential) {
                //使用credential
                credential = self.credential;
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    //回調(diào)驗(yàn)證信息
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

#pragma mark Helper methods

//返回圖片方向
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
    switch (value) {
        case 1:
            return UIImageOrientationUp;
        case 3:
            return UIImageOrientationDown;
        case 8:
            return UIImageOrientationLeft;
        case 6:
            return UIImageOrientationRight;
        case 2:
            return UIImageOrientationUpMirrored;
        case 4:
            return UIImageOrientationDownMirrored;
        case 5:
            return UIImageOrientationLeftMirrored;
        case 7:
            return UIImageOrientationRightMirrored;
        default:
            return UIImageOrientationUp;
    }
}

- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
    return SDScaledImageForKey(key, image);
}

//是否應(yīng)該在后臺(tái)運(yùn)行
- (BOOL)shouldContinueWhenAppEntersBackground {
    return self.options & SDWebImageDownloaderContinueInBackground;
}

這邊的代碼比較多宝泵,比較有意思的地方主要在于以下兩點(diǎn):

  • 1.圖片的處理
    在下載過程當(dāng)中好啰,就對(duì)已經(jīng)接受數(shù)據(jù)的信息并且轉(zhuǎn)換成了模糊化的圖片,并且回調(diào)了self.completedBlock(image, nil, nil, NO);儿奶,說明可以做到圖片逐漸清晰的這種效果框往。
  • 2.驗(yàn)證信息的方式
    didReceiveChallenge方法代表在請(qǐng)求時(shí)候,遇到一些驗(yàn)證闯捎。

總結(jié)

總的來說椰弊,Downloader有以下優(yōu)點(diǎn):

  • 1.設(shè)計(jì)上支持并發(fā)的下載同一個(gè)url的圖片,并且有正確的回調(diào)
  • 2.通過barrier去確保獲取數(shù)據(jù)不會(huì)與設(shè)置數(shù)據(jù)有沖突
  • 3.妙用thread去達(dá)到nsoperation無法取消但可以等待完成后取消的結(jié)果
  • 4.神奇的圖片處理瓤鼻,一開始獲取圖片的高秉版、寬、方向茬祷,然后通過神奇的方法清焕,進(jìn)行圖片展示,這個(gè)方法到現(xiàn)在為止我還是沒看懂它是如何處理的祭犯,如果有懂的人麻煩指點(diǎn)一下
  • 5.NSURLSession的使用(在這之前我只用過NSURLConnection秸妥,對(duì)ios7出來的NSURLSession確實(shí)了解不多)
  • 6.對(duì)于驗(yàn)證請(qǐng)求的處理

更新:關(guān)于圖片處理這塊,在utils的解碼中沃粗,有詳細(xì)一些的標(biāo)注筛峭,可以稍微用來理解這里的圖片處理。

參考資料

1.SDWebImage源碼淺析
2.Apple Guide:URL Session Programming Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陪每,一起剝皮案震驚了整個(gè)濱河市影晓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌檩禾,老刑警劉巖挂签,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盼产,居然都是意外死亡饵婆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門戏售,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侨核,“玉大人草穆,你說我怎么就攤上這事〈暌耄” “怎么了悲柱?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)些己。 經(jīng)常有香客問我豌鸡,道長(zhǎng),這世上最難降的妖魔是什么段标? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任涯冠,我火速辦了婚禮,結(jié)果婚禮上逼庞,老公的妹妹穿的比我還像新娘蛇更。我一直安慰自己,他們只是感情好赛糟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布派任。 她就那樣靜靜地躺著,像睡著了一般虑灰。 火紅的嫁衣襯著肌膚如雪吨瞎。 梳的紋絲不亂的頭發(fā)上痹兜,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天穆咐,我揣著相機(jī)與錄音,去河邊找鬼字旭。 笑死对湃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遗淳。 我是一名探鬼主播拍柒,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼屈暗!你這毒婦竟也來了拆讯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤养叛,失蹤者是張志新(化名)和其女友劉穎种呐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃甥,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爽室,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淆攻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阔墩。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘿架,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啸箫,到底是詐尸還是另有隱情耸彪,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布筐高,位于F島的核電站搜囱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柑土。R本人自食惡果不足惜蜀肘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稽屏。 院中可真熱鬧扮宠,春花似錦、人聲如沸狐榔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薄腻。三九已至收捣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庵楷,已是汗流浹背罢艾。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尽纽,地道東北人咐蚯。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像弄贿,于是被迫代替她去往敵國(guó)和親春锋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

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