iOS之網(wǎng)絡(luò)請求NSURLSession剖析

2013年的WWDC大會上,蘋果推出了NSURLSession护昧,對Foundation URL加載系統(tǒng)進行了徹底的重構(gòu)肢娘,提供了更豐富的API來處理網(wǎng)絡(luò)請求荡灾,如:支持http2.0協(xié)議、直接把數(shù)據(jù)下載到磁盤拓轻、同一session發(fā)送多個請求斯撮、下載是多線程異步處理和提供全局的session并可以統(tǒng)一配置等等,提高了NSURLSession的易用性悦即、靈活性吮成,更加地適合移動開發(fā)的需求橱乱。


NSURLSession的介紹

1. session類型

Default session

+defaultSessionConfiguration 返回一個標(biāo)準(zhǔn)的 configuration,這個配置實際上與 NSURLConnection 的網(wǎng)絡(luò)堆棧(networking stack)是一樣的粱甫,具有相同的共享 NSHTTPCookieStorage泳叠,共享 NSURLCache 和共享 NSURLCredentialStorage

Ephemeral session

+ephemeralSessionConfiguration 返回一個預(yù)設(shè)配置茶宵,這個配置中不會對緩存Cookie 和證書進行持久性的存儲危纫,這對于實現(xiàn)像秘密瀏覽這種功能來說是很理想的。

Background session

+backgroundSessionConfiguration:(NSString *)identifier 的獨特之處在于乌庶,它會創(chuàng)建一個后臺 session种蝶。后臺 session 不同于常規(guī)的,普通的 session瞒大,它甚至可以在應(yīng)用程序掛起螃征,退出或者崩潰的情況下進行上傳和下載任務(wù)。初始化時指定的標(biāo)識符透敌,被用于向任何可能在進程外恢復(fù)后臺傳輸?shù)氖刈o進程盯滚。

2. 配置屬性

基本配置

HTTPAdditionalHeaders 指定了一組默認(rèn)的可以設(shè)置請求(outbound request)的數(shù)據(jù)頭。這對于跨 session 共享信息酗电,如內(nèi)容類型魄藕、語言、用戶代理和身份認(rèn)證撵术,是很有用的背率。

    // 設(shè)置請求的header
    NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password];
    NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
    NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
    NSString *authString = [NSString stringWithFormat:@"Basic %@", base64EncodedCredential];
    NSString *userAgentString = @"AppName/com.example.app (iPhone 5s; iOS 7.0.2; Scale/2.0)";

    configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
                                            @"Accept-Language": @"en",
                                            @"Authorization": authString,
                                            @"User-Agent": userAgentString};
  • networkServiceType 對標(biāo)準(zhǔn)的網(wǎng)絡(luò)流量、網(wǎng)絡(luò)電話嫩与、語音寝姿、視頻,以及由一個后臺進程使用的流量進行了區(qū)分蕴纳。大多數(shù)應(yīng)用程序都不需要設(shè)置這個会油。
  • allowsCellularAccessdiscretionary 被用于節(jié)省通過蜂窩網(wǎng)絡(luò)連接的帶寬。對于后臺傳輸?shù)那闆r古毛,推薦大家使用 discretionary 這個屬性翻翩,而不是 allowsCellularAccess,因為前者會把 WiFi 和電源的可用性考慮在內(nèi)稻薇。
  • timeoutIntervalForRequesttimeoutIntervalForResource 分別指定了對于請求和資源的超時間隔嫂冻。許多開發(fā)人員試圖使用 timeoutInterval 去限制發(fā)送請求的總時間,但其實它真正的含義是:分組(packet)之間的時間塞椎。實際上我們應(yīng)該使用 timeoutIntervalForResource 來規(guī)定整體超時的總時間桨仿,但應(yīng)該只將其用于后臺傳輸,而不是用戶實際上可能想要去等待的任何東西案狠。
  • HTTPMaximumConnectionsPerHostFoundation 框架中 URL 加載系統(tǒng)的一個新的配置選項服傍。它曾經(jīng)被 NSURLConnection 用于管理私有的連接池∏祝現(xiàn)在有了 NSURLSession,開發(fā)者可以在需要時限制連接到特定主機的數(shù)量吹零。
  • HTTPShouldUsePipelining 這個屬性在 NSMutableURLRequest 下也有罩抗,它可以被用于開啟 HTTP 管線化(HTTP pipelining),這可以顯著降低請求的加載時間灿椅,但是由于沒有被服務(wù)器廣泛支持套蒂,默認(rèn)是禁用的。
  • sessionSendsLaunchEvents 是另一個新的屬性茫蛹,該屬性指定該 session 是否應(yīng)該從后臺啟動操刀。
  • connectionProxyDictionary 指定了 session 連接中的代理服務(wù)器。同樣地婴洼,大多數(shù)面向消費者的應(yīng)用程序都不需要代理骨坑,所以基本上不需要配置這個屬性。

Cookie 策略

  • HTTPCookieStorage 存儲了 session 所使用的 cookie柬采。默認(rèn)情況下會使用 NSHTTPCookieShorage+sharedHTTPCookieStorage 這個單例對象卡啰,這與 NSURLConnection 是相同的。
  • HTTPCookieAcceptPolicy 決定了什么情況下 session 應(yīng)該接受從服務(wù)器發(fā)出的 cookie警没。
  • HTTPShouldSetCookies 指定了請求是否應(yīng)該使用 session 存儲的 cookie,即 HTTPCookieSorage 屬性的值振湾。

安全策略

  • URLCredentialStorage 存儲了 session 所使用的證書杀迹。默認(rèn)情況下會使用 NSURLCredentialStorage+sharedCredentialStorage 這個單例對象,這與 NSURLConnection 是相同的押搪。
  • TLSMaximumSupportedProtocolTLSMinimumSupportedProtocol 確定 `session 是否支持 SSL 協(xié)議树酪。

緩存策略

  • URLCachesession 使用的緩存。默認(rèn)情況下會使用 NSURLCache+sharedURLCache 這個單例對象大州,這與 NSURLConnection 是相同的续语。
  • requestCachePolicy 指定了一個請求的緩存響應(yīng)應(yīng)該在什么時候返回。這相當(dāng)于 NSURLRequest-cachePolicy 方法厦画。

自定義協(xié)議

protocolClasses 用來配置特定某個 session 所使用的自定義協(xié)議(該協(xié)議是 NSURLProtocol 的子類)的數(shù)組疮茄。

3. NSURLSessionTask

NSURLsessionTask 是一個抽象類,其下有 3 個實體子類可以直接使用:NSURLSessionDataTask根暑、NSURLSessionUploadTask力试、NSURLSessionDownloadTask。這 3 個子類封裝了現(xiàn)代程序三個最基本的網(wǎng)絡(luò)任務(wù):獲取數(shù)據(jù)排嫌,比如 JSON 或者 XML畸裳,上傳文件和下載文件。

圖1

不同于直接使用 alloc-init 初始化方法淳地,task 是由一個 NSURLSession 創(chuàng)建的怖糊。每個 task 的構(gòu)造方法都對應(yīng)有或者沒有 completionHandler 這個 block 的兩個版本帅容。

4. 代理

針對NSURLsessionTask的代理,根代理為NSURLSessionDelegate伍伤,其它的代理直接或者間接繼承自改代理并徘,如:NSURLSessionTaskDelegateNSURLSessionDataDelegate嚷缭、NSURLSessionDownloadDelegate饮亏。其中根代理NSURLSessionDelegate主要處理鑒權(quán)、后臺下載任務(wù)完成通知等等阅爽,NSURLSessionTaskDelegate主要處理收到鑒權(quán)響應(yīng)路幸、任務(wù)結(jié)束(無論是正常還是異常),NSURLSessionDataDelegate處理數(shù)據(jù)的接收付翁、dataTask轉(zhuǎn)downloadTask简肴、緩存等,NSURLSessionDownloadDelegate主要處理數(shù)據(jù)下載百侧、數(shù)據(jù)進度通知等砰识。

圖2

NSURLSession應(yīng)用

1. NSURLSessionDataTask 發(fā)送 GET 請求

    //確定請求路徑
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520&pwd=520&type=JSON"];
    //創(chuàng)建 NSURLSession 對象
    NSURLSession *session = [NSURLSession sharedSession];

 /**
  根據(jù)對象創(chuàng)建 Task 請求,默認(rèn)在子線程中解析數(shù)據(jù)

  url  方法內(nèi)部會自動將 URL 包裝成一個請求對象(默認(rèn)是 GET 請求)
  completionHandler  完成之后的回調(diào)(成功或失斢犊省)

  param data     返回的數(shù)據(jù)(響應(yīng)體)
  param response 響應(yīng)頭
  param error    錯誤信息
  */
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:
             ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        //解析服務(wù)器返回的數(shù)據(jù)
        NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    }];
    //發(fā)送請求(執(zhí)行Task)
    [dataTask resume];

2. NSURLSessionDataTask 發(fā)送 POST 請求

    //確定請求路徑
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
    //創(chuàng)建可變請求對象
    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
    //修改請求方法
    requestM.HTTPMethod = @"POST";
    //設(shè)置請求體
    requestM.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    //創(chuàng)建會話對象
    NSURLSession *session = [NSURLSession sharedSession];
    //創(chuàng)建請求 Task
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM completionHandler:
             ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        //解析返回的數(shù)據(jù)
        NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    }];
    //發(fā)送請求
    [dataTask resume];

3. NSURLSessionDataTask 設(shè)置代理發(fā)送請求

     //確定請求路徑
     NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
     //創(chuàng)建可變請求對象
     NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
     //設(shè)置請求方法
     requestM.HTTPMethod = @"POST";
     //設(shè)置請求體
     requestM.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
     //創(chuàng)建會話對象辫狼,設(shè)置代理
 /**
  第一個參數(shù):配置信息
  第二個參數(shù):設(shè)置代理
  第三個參數(shù):隊列,如果該參數(shù)傳遞nil 那么默認(rèn)在子線程中執(zhí)行
  */
     NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                              delegate:self delegateQueue:nil];
     //創(chuàng)建請求 Task
     NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM];
     //發(fā)送請求
     [dataTask resume];

代理方法:

-(void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask 
didReceiveResponse:(nonnull NSURLResponse *)response 
completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler {
     //子線程中執(zhí)行
     NSLog(@"接收到服務(wù)器響應(yīng)的時候調(diào)用 -- %@", [NSThread currentThread]);

     self.dataM = [NSMutableData data];
     //默認(rèn)情況下不接收數(shù)據(jù)
     //必須告訴系統(tǒng)是否接收服務(wù)器返回的數(shù)據(jù)
     completionHandler(NSURLSessionResponseAllow);
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {

     NSLog(@"接受到服務(wù)器返回數(shù)據(jù)的時候調(diào)用,可能被調(diào)用多次");
     //拼接服務(wù)器返回的數(shù)據(jù)
     [self.dataM appendData:data];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {

     NSLog(@"請求完成或者是失敗的時候調(diào)用");
     //解析服務(wù)器返回數(shù)據(jù)
     NSLog(@"%@", [[NSString alloc] initWithData:self.dataM encoding:NSUTF8StringEncoding]);
}

設(shè)置代理之后的強引用問題

  • NSURLSession 對象在使用的時候辛润,如果設(shè)置了代理膨处,那么 session 會對代理對象保持一個強引用,在合適的時候應(yīng)該主動進行釋放
  • 可以在控制器調(diào)用 viewDidDisappear 方法的時候來進行處理砂竖,通過調(diào)用 invalidateAndCancel 方法或者是 finishTasksAndInvalidate 方法來釋放對代理對象的強引用真椿。

其中,invalidateAndCancel是直接取消請求然后釋放代理對象乎澄,而finishTasksAndInvalidate是等請求完成之后釋放代理對象突硝。

4. NSURLSessionDownloadTask 簡單下載

    //確定請求路徑
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
    //創(chuàng)建請求對象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //創(chuàng)建會話對象
    NSURLSession *session = [NSURLSession sharedSession];
    //創(chuàng)建會話請求
    //優(yōu)點:該方法內(nèi)部已經(jīng)完成了邊接收數(shù)據(jù)邊寫沙盒的操作,解決了內(nèi)存飆升的問題
    NSURLSessionDownloadTask *downTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        //默認(rèn)存儲到臨時文件夾 tmp 中置济,需要剪切文件到 cache
        NSLog(@"%@", location);//目標(biāo)位置
        NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]  
                         stringByAppendingPathComponent:response.suggestedFilename];

     /**
      fileURLWithPath:有協(xié)議頭
      URLWithString:無協(xié)議頭
      */
        [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];

    }];
    //發(fā)送請求
    [downTask resume];

以上方法無法監(jiān)聽下載進度解恰,如要獲取下載進度,可以使用代理的方式進行下載舟肉。

5. NSURLSessionDownloadTask 代理方式

    NSURL * url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/pic/item/63d0f703918fa0ec14b94082249759ee3c6ddbc6.jpg"];
    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];

    NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url];
    [downloadTask resume];

代理方法:

// 接收數(shù)據(jù)修噪,可能多次被調(diào)用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    float progress = totalBytesWritten * 1.0/totalBytesExpectedToWrite;
    
    // 主線程更新UI
    dispatch_async(dispatch_get_main_queue(),^ {
        [self.process setProgress:progress animated:YES];
    });
}

// 3.下載完成之后調(diào)用該方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSString *catchDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [catchDir stringByAppendingPathComponent:@"app.dmg"];
    
    NSError *fileError = nil;
    NSURL *fileURL = [NSURL fileURLWithPath:filePath];
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&fileError];
    
    if (fileError) {
        NSLog(@"保存下載文件出錯:%@", fileError);
    } else {
        NSLog(@"保存成功:%@", filePath);
    }
}

暫停和恢復(fù)下載:

方式一:

// 暫停下載
- (IBAction)suspendDownload {
    if (self.session) {
        __weak typeof(self) weakSelf = self;
        [self.task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
            weakSelf.receivedData = resumeData;
        }];
    }
}

// 恢復(fù)下載
- (IBAction)resumeDownload {
    if (self.session) {
        self.task = [self.session downloadTaskWithResumeData:self.receivedData];
    }
    
    [self.task resume];
}

方式二:

//暫停
[self.downloadTask suspend];
//恢復(fù)
[self.downloadTask resume];

6. NSURLSessionDownloadTask 后臺下載

// 后臺session
- (NSURLSession* ) backgroundURLSession {
    static NSURLSession * session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString * identifier = @"com.yourcompany.appId.BackgroundSession";
        NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
        session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                delegate:self
                                           delegateQueue:[NSOperationQueue mainQueue]];
    });
    
    return session;
}

// 創(chuàng)建并啟動任務(wù)
- (void)beginDownloadWithUrl:(NSString *)downloadURLString {
    NSURL *downloadURL = [NSURL URLWithString:downloadURLString];
    NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
    NSURLSession *session = [self backgroundURLSession];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
    
    [downloadTask resume];
}

appDelegate中實現(xiàn)application:handleEventsForBackgroundURLSession:completionHandler:方法,在后臺所有的任務(wù)完成后會調(diào)用給方法路媚,但是我一直沒有調(diào)用成功黄琼,原因未知,高手可以告知一下

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {

    NSURLSession *backgroundSession = [self backgroundURLSession];
    
    NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);
    
    // 保存 completion handler 以在處理 session 事件后更新 UI
    [self addCompletionHandler:completionHandler forSession:identifier];
}

handleEventsForBackgroundURLSession 方法是在后臺下載的所有任務(wù)完成后才會調(diào)用。如果后臺任務(wù)完成且應(yīng)用被殺掉脏款,啟動應(yīng)用程序后围苫,該方法會在 application:didFinishLaunchingWithOptions:方法被調(diào)用之后被調(diào)用。

//NSURLSessionDelegate委托方法撤师,會在NSURLSessionDownloadDelegate委托方法后執(zhí)行
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
     NSLog(@"Background URL session %@ finished events.\n", session);
}

之后會調(diào)用接收完成的方法:

/*
 * 在該方法結(jié)束前谍夭,需要處理location指向的文件玉控,因為方法結(jié)束后富腊,臨時文件會被銷毀
 * 如果用模擬器保存琉朽,會出錯,因為模擬器上app退出后再啟動是痒谴,路徑會不一樣衰伯,導(dǎo)致找不到后臺下載的文件;而用真機調(diào)試則無此問題 ;怠R饩ā!
 */
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location 
{}

/*
 * 該方法下載成功和失敗都會回調(diào)尽爆,只是失敗的是error是有值的怎顾,
 * 在下載失敗時,error的userinfo屬性可以通過NSURLSessionDownloadTaskResumeData
 * 這個key來取到resumeData(和上面的resumeData是一樣的)漱贱,再通過resumeData恢復(fù)下載
 */
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error 
{}

7. NSURLSessionUploadTask上傳任務(wù)

    NSURL*URL = [NSURLURLWithString:@"http://example.com/upload"];

    NSURLRequest*request = [NSURLRequestrequestWithURL:URL];

    NSData*data = ...;

    NSURLSession*session = [NSURLSessionsharedSession];

    NSURLSessionUploadTask*uploadTask = [session uploadTaskWithRequest:request fromData:datacompletionHandler:^(NSData*data, NSURLResponse *response,NSError*error) {

        // ...
    }];

[uploadTask resume];

注意事項

1. 后臺下載的配置和限制

作為一個必須實現(xiàn)的委托槐雾,您不能對NSURLSession使用簡單的基于 block的回調(diào)方法。后臺啟動應(yīng)用程序幅狮,是相對耗費較多資源的蚜退,所以總是采用HTTP重定向。后臺傳輸服務(wù)只支持HTTPHTTPS彪笼,你不能使用自定義的協(xié)議。系統(tǒng)會根據(jù)可用的資源進行優(yōu)化蚂且,在任何時候你都不能強制傳輸任務(wù)在后臺進行配猫。

另外,要注意的是在后臺會話中杏死,NSURLSessionDataTasks 是完全不支持的泵肄,你應(yīng)該只出于短期的、小請求等使用這些任務(wù)淑翼,而不是用來下載或上傳腐巢。

2. 后臺啟動新的下載

蘋果會對后臺的下載任務(wù)進行限制,大致流程如下:

  • 蘋果的NSURLSession這個類會維護一個Delay值(即延時執(zhí)行時間)玄括,用于后臺啟動任務(wù)延時執(zhí)行時使用冯丙;
  • 當(dāng)在后臺啟動一個新任務(wù)時,蘋果會對這個任務(wù)進行延時執(zhí)行遭京,延時時間蘋果那邊是有一個默認(rèn)的延時時間胃惜,當(dāng)后臺啟動的任務(wù)數(shù)越多泞莉,這個值就會成2N-1冪倍增長;
  • 比如:假設(shè)蘋果設(shè)定的延時時間為Delay船殉。當(dāng)在后臺啟動了第一個任務(wù)時鲫趁,這個任務(wù)的延時時間為Delay,這個任務(wù)會在Delay時間后開始執(zhí)行利虫;當(dāng)啟動在后臺啟動第二個任務(wù)時挨厚,這個任務(wù)的延時時間為:2 * Delay,當(dāng)啟動第三個任務(wù)是糠惫,該任務(wù)的延時執(zhí)行時間即為:2 * 2 * Delay疫剃;以此類推,在后臺啟動第N個任務(wù)是寞钥,該任務(wù)的延時執(zhí)行時間為:2^(N-1)次方 * Delay慌申;
  • 但是在應(yīng)用從后臺切到前臺或者重新啟動時,這個延時時間會重置理郑。

參考示例:

https://github.com/BirdandLion/NSURLSessionDemo

參考資料:
Life Cycle of a URL Session

http://www.reibang.com/p/63e2ad28459f

http://www.reibang.com/p/b0ddadd34037

http://www.reibang.com/p/1211cf99dfc3

http://www.reibang.com/p/02a5a896c9ed

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹄溉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子您炉,更是在濱河造成了極大的恐慌柒爵,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赚爵,死亡現(xiàn)場離奇詭異棉胀,居然都是意外死亡,警方通過查閱死者的電腦和手機冀膝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門唁奢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窝剖,你說我怎么就攤上這事麻掸。” “怎么了赐纱?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵脊奋,是天一觀的道長。 經(jīng)常有香客問我疙描,道長诚隙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任起胰,我火速辦了婚禮久又,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己籽孙,他們只是感情好烈评,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著犯建,像睡著了一般讲冠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上适瓦,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天竿开,我揣著相機與錄音,去河邊找鬼玻熙。 笑死否彩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嗦随。 我是一名探鬼主播列荔,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枚尼!你這毒婦竟也來了贴浙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤署恍,失蹤者是張志新(化名)和其女友劉穎崎溃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盯质,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡袁串,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了呼巷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囱修。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖王悍,靈堂內(nèi)的尸體忽然破棺而出蔚袍,到底是詐尸還是另有隱情,我是刑警寧澤配名,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站晋辆,受9級特大地震影響渠脉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓶佳,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一芋膘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦为朋、人聲如沸臂拓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胶惰。三九已至,卻和暖如春霞溪,著一層夾襖步出監(jiān)牢的瞬間孵滞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工鸯匹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坊饶,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓殴蓬,卻偏偏與公主長得像匿级,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子染厅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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