iOS源碼解析—AFNetworking(URLConnection)

概述

AFNetwokring目前是3.x版本,基于NSURLSession的功能進行封裝,而2.x版本是基于NSURLConnection。由于NSURLConnection逐漸被NSURLSession所取代,2.x版本逐漸被3.x取代。本篇分析一下2.x版本,因為該版本涉及的一些代碼值得學習,例如NSOperation、KVO的使用。

AFHTTPRequestOperationManager

AFHTTPRequestOperationManager是AFN封裝的管理HTTP請求的類,首先初始化方法中設置了一些參數值,代碼注釋如下:

- (instancetype)initWithBaseURL:(NSURL *)url {
    ...
    self.baseURL = url;
    self.requestSerializer = [AFHTTPRequestSerializer serializer]; //序列化
    self.responseSerializer = [AFJSONResponseSerializer serializer];//反序列化
    self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //默認的安全策略
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];//監聽網絡狀態
    self.operationQueue = [[NSOperationQueue alloc] init]; //任務隊列
    self.shouldUseCredentialStorage = YES;
    return self;
}

初始化方法設置了請求報文序列化/反序列化對象,以及默認的安全策略,網絡監聽對象,任務隊列。

AFHTTPRequestOperationManager提供了一系列HTTP請求相關的方法,例如GET、POST、PATCH等,內部實現相同,只是method參數值不同,以GET請求方法為例,代碼注釋如下:

- (AFHTTPRequestOperation *)GET:(NSString *)URLString
                     parameters:(id)parameters
                        success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                        failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
    [self.operationQueue addOperation:operation];
    return operation;
}

首先創建一個AFHTTPRequestOperation類型的NSOperation對象,然后將NSOperation對象加入operationQueue隊列中,開始執行operation。在創建AFHTTPRequestOperation對象的方法中,首先通過requestSerializer構建NSURLRequest對象,然后設置相關屬性,設置completionBlock,代碼注釋如下:

- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
                                                    success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                    failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    ... //設置相關屬性
    [operation setCompletionBlockWithSuccess:success failure:failure]; //設置operation結束時的completionBlock
    operation.completionQueue = self.completionQueue; //執行completionBlock的隊列
    operation.completionGroup = self.completionGroup; //執行completionBlock的group
    return operation;
}

AFHTTPRequestOperation繼承AFURLConnectionOperation,AFURLConnectionOperation真正負責網絡請求的發出以及處理,AFHTTPRequestOperation設置completionBlock的方法如下:

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
    self.completionBlock = ^{
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }
        //在異步隊列中執行
        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {
                if (failure) { //網絡請求失敗
                    //在completionQueue或者主線程隊列中執行失敗block
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else { //網絡請求成功
                //反序列化報文數據
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        //反序列化報文數據失敗,在completionQueue或者主線程隊列中執行失敗block
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        //反序列化報文數據成功,在completionQueue或者主線程隊列中執行成功block
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }
            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

該方法定義一個void (^)(void)類型的block,設置給父類的completionBlock屬性,當執行completionBlock時,首先會判斷網絡請求是否錯誤,如果出錯,直接在completionQueue或者主線程中調用failure的block拋給調用層。如果網絡請求成功,則調用responseObject方法反序列化響應報文數據responseData,代碼注釋如下:

- (id)responseObject {
    [self.lock lock];
    if (!_responseObject && [self isFinished] && !self.error) {
        NSError *error = nil;
        //反序列化響應報文數據
        self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
        if (error) {
            self.responseSerializationError = error;
        }
    }
    [self.lock unlock];
    return _responseObject;
}

由于該方法在http_request_operation_processing_queue()中執行,不影響主線程的性能。如果反序列化成功,調用success的blcok將error拋給調用層,如果失敗,調用failure的block將反序列化后的對象拋給調用層。

AFURLConnectionOperation

AFURLConnectionOperation負責發送網絡請求,處理delegate回調方法。AFURLConnectionOperation繼承NSOperation,眾所周知,當實現一個自定義的NSOperation時,需要重寫NSOperation的相關方法,以確保operation機制的正常運行。

初始化方法

初始化方法設置了相關參數,代碼注釋如下:

- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    NSParameterAssert(urlRequest);
    self = [super init];
    if (!self) {
        return nil;
    }
    _state = AFOperationReadyState; //狀態設置為準備執行
    self.lock = [[NSRecursiveLock alloc] init]; //創建遞歸鎖
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest; //設置urlRequest
    self.shouldUseCredentialStorage = YES;
    self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //設置securityPolicy
    return self;
}

該方法設置用于網絡請求的request,初始化了遞歸鎖,安全策略對象securityPolicy,同時設置了狀態為AFOperationReadyState(準備執行)。

狀態機制

當operation加入到operationQueue中時,operationQueue會通過KVO的方式監聽operation的狀態,NSOperation有幾種狀態,分別對應以下屬性:

isReady(是否準備執行)

isExecuting(是否正在執行)

isCancelled(是否取消)

isPaused(是否暫停)

isFinished(是否完成)

上面的屬性狀態決定operation的生命周期,operationQueue監聽operation的狀態屬性,當operation的isFinished屬性為YES時,說明operation生命周期結束,operation會在隊列中被釋放。AFURLConnectionOperation實現了自定義的operation,重寫了以下屬性方法:

- (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}
- (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}

通過getter方法訪問operation的屬性時,返回的狀態值會根據AFNetworking維護的枚舉值來確定。下面是枚舉類型AFOperationState的代碼:

typedef NS_ENUM(NSInteger, AFOperationState) {
    AFOperationPausedState      = -1, //暫停
    AFOperationReadyState       = 1, //準備執行
    AFOperationExecutingState   = 2, //正在執行
    AFOperationFinishedState    = 3, //完成
};

分別對應operation的狀態屬性,同時實現-setState:方法來更新AFOperationState的枚舉值,下面是代碼注釋:

- (void)setState:(AFOperationState)state {
    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }
    [self.lock lock];
    NSString *oldStateKey = AFKeyPathFromOperationState(self.state); //原狀態
    NSString *newStateKey = AFKeyPathFromOperationState(state); //新狀態
    [self willChangeValueForKey:newStateKey]; //手動發送KVO通知
    [self willChangeValueForKey:oldStateKey]; //手動發送KVO通知
    _state = state; //切換狀態
    [self didChangeValueForKey:oldStateKey]; //手動發送KVO通知
    [self didChangeValueForKey:newStateKey]; //手動發送KVO通知
    [self.lock unlock];
}

首先通過AFKeyPathFromOperationState方法將新舊AFOperationState枚舉值映射成operation的狀態屬性名,然后更新AFOperationState枚舉值,當外界訪問operation的狀態屬性時,狀態已經改變。同時手動發送KVO通知,通知operationQueue,operation的狀態屬性發生了改變。

start方法

如果實現自定義的NSOperation,需要重寫start方法,下面是代碼注釋:

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) { //如果operation之前被取消,調用cancelConnection方法取消connection
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) { //如果operation準備執行,調用operationDidStart方法開始構建connection,發送網絡請求
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

該方法首先判斷operation當前的狀態,如果之前已經被取消了,則調用cancelConnection方法進一步處理,該方法放在后文分析。如果新建operation,在初始化方法中,設置初始狀態是AFOperationReadyState,即operation的狀態isReady=YES,調用operationDidStart方法開始進行網絡請求。同時AFNetworking創建了一個常駐線程來執行connection相關的方法。常駐線程由類方法networkRequestThread創建,代碼如下:

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; //創建常駐線程
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; //為runloop添加port,使runloop永不退出
        [runLoop run];
    }
}

首先在dispatch_once()中創建一個線程,由于dispatch_once中的block只會執行一次,所以線程只會創建一次,且_networkRequestThread是static類型的,所以會常駐內存不被釋放。每次調用networkRequestThread方法都會返回該線程指針。由于是手動創建的子線程,需要手動開啟它的runloop,并且在runloop中添加port,使其永不退出。我們將這個常駐線程稱為AFN線程。在AFN線程中執行operationDidStart方法,下面是代碼注釋:

- (void)operationDidStart {
    [self.lock lock];
    if (![self isCancelled]) {
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        [self.outputStream open];
        [self.connection start];
    }
    [self.lock unlock];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
    });
}

首先創建了connection,設置當前operation對象為connection對象的delegate,處理網絡請求回調的各個方法。如果startImmediately參數為YES,connection會立刻啟動,開始下載數據,且connection在當前線程的runloop中執行,如果為NO,暫不開始請求數據,需要調用start方法手動開始,同時調用scheduleInRunLoop:forMode:方法把NSURLConnection加入到指定線程的run loop中去運行,否則會加入當前線程的runloop中去,使用outputStream來接收網絡請求回來的數據。

NSURLConnectionDelegate

在connection網絡請求的過程中,將回調方法拋給delegate執行,下面分析一下主要方法:

  1. -(void)connection:willSendRequestForAuthenticationChallenge:方法

    當客戶端發送HTTPS請求給服務端時,會進行SSL握手,在握手的過程中,服務端需要客戶端進行授權的響應,客戶端對服務端發來的信息進行校驗,在iOS代碼中,抽象為系統拋出delegate方法給上層代碼,同時傳入一個challenge對象,封裝了需要驗證的信息,下面是代碼部分注釋:

    - (void)connection:(NSURLConnection *)connection
    willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        ...
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
        {
         //對serverTrust對象和host進行校驗
         if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                 //校驗通過,生成一個憑證對象credential
                NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                 //使用credential給系統
                [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
            } else {
                 //驗證失敗,取消后續SSL連接
                [[challenge sender] cancelAuthenticationChallenge:challenge];
            }
        } else {
            if ([challenge previousFailureCount] == 0) {
                if (self.credential) { //直接用現有的credential給系統
                    [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
                } else {
                    [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
                }
            } else {
                [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
            }
        }
    }
    

    challenge中的屬性protectionSpace是一個NSURLProtectionSpace對象,包含服務器的host、port、isProxy等信息,同時包含authenticationMethod,即校驗方式的類型,如果類型是NSURLAuthenticationMethodServerTrust,則驗證信任對象serverTrust,如果驗證通過,生成一個憑證對象credential返回給服務端,具體的流程可以參考騰訊Bugly的文章。

  2. -(void)connection: didReceiveData:

    當網絡連接建立后,服務器開始向客戶端傳輸數據,系統會回調該方法,上層代碼負責接收并且拼裝response數據。下面是部分代碼注釋:

    - (void)connection:(NSURLConnection __unused *)connection
        didReceiveData:(NSData *)data
    {
        NSUInteger length = [data length]; //需要讀取的字節長度
        while (YES) {
            NSInteger totalNumberOfBytesWritten = 0; //本次一共寫入的字節長度
            if ([self.outputStream hasSpaceAvailable]) { //outputStream有空間寫入
                const uint8_t *dataBuffer = (uint8_t *)[data bytes];
                NSInteger numberOfBytesWritten = 0;
                while (totalNumberOfBytesWritten < (NSInteger)length) {
                    numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; //將dataBuffer中的數據寫入outputStream中
                    if (numberOfBytesWritten == -1) {
                        break;
                    }
                    totalNumberOfBytesWritten += numberOfBytesWritten; //累加寫入的字節長度
                }
                break;
            } else { //outputStream沒有空間寫入
                [self.connection cancel];
                if (self.outputStream.streamError) {
                    [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
                }
                return;
            }
        }
        ...
    }
    

    該方法主要將data數據寫入outputStream中,對于本次數據data,如果一次性寫不全進outputStream,則通過totalNumberOfBytesWritten記錄共寫入的字節長度,通過while循環控制,直到全部寫入。outputStream通過[NSOutputStream outputStreamToMemory]創建,是寫入內存的流對象,如果hasSpaceAvailable返回NO,即后續返回的response數據沒有空間存放,則直接斷開網絡請求。

  3. -(void)connectionDidFinishLoading:

    當網絡請求結束時,調用該方法,獲取最終的response數據,結束本次operation。

    - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
        self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; //獲取response數據
        [self.outputStream close]; //關閉outputStream
        if (self.responseData) {
           self.outputStream = nil;
        }
        self.connection = nil;
        [self finish]; //結束operation
    }
    
  4. -(NSCachedURLResponse *)connection: willCacheResponse:

    如果服務端需要將response數據緩存到客戶端的NSURLCache緩存系統,在緩存到客戶端本地之前,會首先調用該方法,可以修改緩存的數據,默認是接口返回的response數據。

    - (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                      willCacheResponse:(NSCachedURLResponse *)cachedResponse
    {
        if (self.cacheResponse) {
            return self.cacheResponse(connection, cachedResponse); //修改服務端返回緩存數據
        } else {
            if ([self isCancelled]) {
                return nil;
            }
            return cachedResponse;
        }
    }
    

    默認網絡請求的緩存策略是UseProtocolCachePolicy,根據服務端返回的Cache-Control字段來開啟HTTP緩存功能,字段值可能包含 max-age,是公共 public 還是私有 private,或者不緩存no-cache 等信息。關于NSURLCache的相關講解,可以參考這篇文章

    - (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                      willCacheResponse:(NSCachedURLResponse *)cachedResponse
    {
        if (self.cacheResponse) {
            return self.cacheResponse(connection, cachedResponse); //修改需要緩存的數據
        } else {
            if ([self isCancelled]) {
                return nil;
            }
            return cachedResponse;
        }
    }
    
控制生命周期

上文所述,AFURLConnectionOperation通過setState:方法實現了operation狀態的切換,從而控制operation的生命周期。下面分析一下,另外幾個方法:

  1. finish方法

    當網絡請求結束時,該方法被調用,負責結束operation的生命周期:

    - (void)finish {
        [self.lock lock];
        self.state = AFOperationFinishedState; //設置結束狀態,結束operation的生命周期
        [self.lock unlock];
        ...
    }
    

    在setState方法里更改為AFOperationFinishedState狀態并且手動觸發KVO,通知operationQueue,isFinished屬性變化,觸發completionBlock,執行block里面的代碼。

  2. cancel方法

    該方法取消一個cancel這個operation,同時調用cancelConnection方法取消當前的connection連接。

    - (void)cancel {
        [self.lock lock];
        if (![self isFinished] && ![self isCancelled]) {
            [super cancel]; //調用NSOperation的cancel方法
            if ([self isExecuting]) {
                [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
            }
        }
        [self.lock unlock];
    }
    

    注意cancelConnection方法也會在start方法中調用,新建一個請求時,當發現這個operation之前被取消了,進一步判斷connection是否建立,如果connection存在,先取消connection,然后調用finish方法,結束operation的生命周期。

  3. pause方法和resume方法

    AFURLConnectionOperation提供了pause和resume方法,pause方法將狀態改為AFOperationPausedState,同時取消當前的connection。resume方法將狀態重新改為AFOperationReadyState,同時調用start方法,重新請求connection,將狀態改為AFOperationExecutingState。下面是代碼注釋:

    - (void)pause {
        if ([self isPaused] || [self isFinished] || [self isCancelled]) {
            return;
        }
        [self.lock lock];
        if ([self isExecuting]) { //取消當前conneciton
            [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
     ...
        self.state = AFOperationPausedState; //暫停operation
        [self.lock unlock];
    }
      
    - (void)resume {
        if (![self isPaused]) {
            return;
        }
        [self.lock lock];
        self.state = AFOperationReadyState; //operation重置為isReady狀態
        [self start]; //新建connection,重新請求數據,狀態職位isExecuting
        [self.lock unlock];
    }
    

    pause方法只是取消本次網絡請求,不會結束operation的生命周期,當外界調用resume方法時,也只是重新進行網絡請求。

小結

雖然NSURLConnection及其基礎上封裝的AF2.x版本逐漸被廢棄,但是作者關于operation的使用,以及如何實現一個網絡請求的處理流程,對于初學者來說,具有參考和學習的價值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容