深入了解NSURLSession

Github : Jerry4me, Demo : JRBgSessionDemo

前言

本文主要是結(jié)合官方文檔, 挖掘NSURLSession的類層次結(jié)構(gòu)及其聯(lián)系, 總結(jié)出關(guān)于NSURLSession的一些關(guān)鍵點(diǎn)及其用法.

關(guān)于NSURLSession為什么能取代NSURLConnection, 其優(yōu)勢是什么, 及其NSURLSession API的概述, 見
關(guān)于ATS, HTTP/2, 以及iOS9 NSURLSession新特性 : sharedCookies, streamTask和taskMetrics, 見

以上兩篇文章都是我看wwdc視頻然后總結(jié)出來的文章, 大家感興趣的可以先了解了解. 如果不想知道那么多, 只想知道怎么用NSURLSession, 那就直接看本文的正文.

*了解URL Loading System

目錄


NSURLSession

NSURLSession

  • 支持data, ftp, http(s)協(xié)議, 同時(shí)支持代理服務(wù)器和socks網(wǎng)關(guān).
  • 支持http/1.1, http/2, spdy協(xié)議, 但同時(shí)需要服務(wù)器支持ALPN和NPN.
ALPN與NPN
  • ALPN(Application Layer Protocol Negotiation伯复,應(yīng)用層協(xié)議協(xié)商)
  • NPN(Next Protocol Negotiation结胀,下一代協(xié)議協(xié)商)

NPN是服務(wù)端發(fā)送它支持的HTTP協(xié)議列表, 供客戶端選擇; 而ALPN則相反, 由客戶端發(fā)送它支持的HTTP協(xié)議列表, 供服務(wù)端選擇. 如果缺少NPN/ALPN其中一個(gè), 則無法使用HTTP/2通信. 具體請見為什么我們應(yīng)該盡快支持 ALPN.

NSURLSession相關(guān)類為 :

  • NSURLSession
  • NSURLSessionConfiguration
  • NSURLSessionDelegate
  • NSURLSessionTask
  • NSURLSessionTaskMetrics
  • NSURLSessionTaskTransactionMetrics

他們相互的關(guān)系如下 :

NSURLSession

session分為 :

  • 全局共享單例session : NSURLSession sharedSession, 有一定的局限性
  • 自定義session : 自定義配置文件, 設(shè)置代理, 大部分時(shí)間我們都是用這個(gè)
  • 后臺session : 也是自定義session的一種, 只是他專門用于做后臺上傳/下載任務(wù)

session為哪一種類型完全由其內(nèi)部的Configuration而定.

NSURLSessionConfiguration

配置分為 :

  • defaultSessionConfiguration : 系統(tǒng)默認(rèn)
  • ephemeralSessionConfiguration : 僅內(nèi)存緩存, 不做磁盤緩存的配置
  • backgroundSessionConfiguration : 這里需要指定一個(gè)identifier, identifier用來后臺重連session對象. (做后臺上傳/下載就是這個(gè)config)

另外, 我們還可以給Configuration對象再自定義一些屬性, 例如每端口的最大并發(fā)HTTP請求數(shù)目, 以及是否允許蜂窩網(wǎng)絡(luò), 請求緩存策略, 請求超時(shí), cookies/證書存儲策略等等

NSURLSessionDelegate

session管理的一組tasks共享一個(gè)代理, 不想實(shí)現(xiàn)代理方法時(shí), 代理傳nil即可.
代理協(xié)議分為 :

  • NSURLSessionDelegate : session-level的代理方法
  • NSURLSessionTaskDelegate : task-level面向all的代理方法
  • NSURLSessionDataDelegate : task-level面向data和upload的代理方法
  • NSURLSession?Download?Delegate : task-level的面向download的代理方法
  • NSURLSessionStreamDelegate : task-level的面向stream的代理方法
NSURLSessionTask

session task類型分為 :

  • NSURLSessionTask : Task的抽象基類
  • NSURLSessionDataTask : 以NSData的形式接收一個(gè)URLRequest的內(nèi)容
  • NSURLSessionUploadTask : 上傳NSData或者本地磁盤中的文件, 完成后以NSData的形式接收一個(gè)URLRequest的響應(yīng)
  • NSURLSessionDownloadTask : 下載完成后返回臨時(shí)文件在本地磁盤的URL路徑
  • NSURLSessionStreamTask : 用于建立一個(gè)TCP/IP連接
NSURLSessionTaskMetrics 和 NSURLSessionTaskTransactionMetrics

對發(fā)送請求/DNS查詢/TLS握手/請求響應(yīng)等各種環(huán)節(jié)時(shí)間上的統(tǒng)計(jì). 更易于我們檢測, 分析我們App的請求緩慢到底是發(fā)生在哪個(gè)環(huán)節(jié), 并對此進(jìn)行優(yōu)化提升我們APP的性能.

NSURLSessionTaskMetrics對象與NSURLSessionTask對象一一對應(yīng). 每個(gè)NSURLSessionTaskMetrics對象內(nèi)有3個(gè)屬性 :

  • taskInterval : task從開始到結(jié)束總共用的時(shí)間
  • redirectCount : task重定向的次數(shù)
  • transactionMetrics : 一個(gè)task從發(fā)出請求到收到數(shù)據(jù)過程中派生出的每個(gè)子請求, 它是一個(gè)裝著許多NSURLSessionTaskTransactionMetrics對象的數(shù)組. 每個(gè)對象都代表下圖的一個(gè)子過程

API很簡單, 就一個(gè)方法 : - (void)URLSession: task: didFinishCollectingMetrics:, 當(dāng)收集完成的時(shí)候就會調(diào)用該方法.

身份驗(yàn)證和自定義TLS

  1. 當(dāng)一個(gè)服務(wù)器請求身份驗(yàn)證或TLS握手期間需要提供證書的話, URLSession會調(diào)用他的代理方法URLSession:?did?Receive?Challenge:?completion?Handler:?去處理.

  2. 如果你沒有實(shí)現(xiàn)該代理方法, URLSession就會這么做 :

  • 使用身份認(rèn)證信息作為請求URL的一部分(如果可用的話)
  • 在用戶的keychain中查找網(wǎng)絡(luò)密碼和證書(in macOS), 在app的keychain中查找(in iOS)
  1. 如果證書還是不可用或服務(wù)器拒絕該證書, 就會繼續(xù)缺少身份認(rèn)證的連接.
  • 對于HTTP(S)連接, 請求失敗并返回一個(gè)狀態(tài)碼, 可能會提供一些替代的內(nèi)容, 例如一個(gè)私人網(wǎng)站的公共網(wǎng)頁.
  • 對于其他URL類型(如FTP等), 則連接請求失敗, 直接返回錯(cuò)誤信息

App Transport Security

從iOS9開始支持ATS, 且默認(rèn)ATS只支持發(fā)送HTTPS請求, 不允許發(fā)送不安全的HTTP請求. 如果用戶需要發(fā)送HTTP請求需要在info.plist中配置一些東西.

詳情在文章開頭的iOS9 ATS HTTP/2 NSURLSession中說得很詳細(xì), 想了解的可以進(jìn)去閱讀.

NSURLSession 工作流程

那么如何使用NSURLSession像從前用NSURLConnection那樣發(fā)送一個(gè)請求呢?

// 設(shè)置配置
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
/** 設(shè)置其他配置屬性 **/

// 代理隊(duì)列
NSOperationQueue *queue = [NSOperationQueue mainQueue];

// 創(chuàng)建session
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];

// 利用session創(chuàng)建n個(gè)task
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
// 開始
[task resume];

然后就可以在代理方法中處理各種事情了. 簡單吧? 下面分task說明代理方法的調(diào)用情況..

身份驗(yàn)證或TLS握手

這是所有task都必須經(jīng)歷的一個(gè)過程. 當(dāng)一個(gè)服務(wù)器請求身份驗(yàn)證或TLS握手期間需要提供證書的話, URLSession會調(diào)用他的代理方法URLSession:?did?Receive?Challenge:?completion?Handler:?去處理., 另外, 如果連接途中收到服務(wù)器返回需要身份認(rèn)證的response, 也會調(diào)用該代理方法.

重定位response

這也是所有task都有可能經(jīng)歷的一個(gè)過程, 如果response是HTTP重定位, session會調(diào)用代理的URLSession:?task:?will?Perform?HTTPRedirection:?new?Request:?completion?Handler:方法. 這里需要調(diào)用completionHandler告訴session是否允許重定位, 或者重定位到另一個(gè)URL, 或者傳nil表示重定位的響應(yīng)body有效并返回. 如果代理沒有實(shí)現(xiàn)該方法, 則允許重定位直到達(dá)到最大重定位次數(shù).

DataTask

  1. 對于一個(gè)data task來說, session會調(diào)用代理的URLSession:?data?Task:?did?Receive?Response:?completion?Handler:?方法, 決定是否將一個(gè)data dask轉(zhuǎn)換成download task, 然后調(diào)用completion回調(diào)繼續(xù)接收data或下載data.
  • 如果你的app選擇轉(zhuǎn)換成download task, session會調(diào)用代理的URLSession:?data?Task:?did?Become?Download?Task:?方法并把新的download task對象以方法參數(shù)的形式傳給你. 之后代理不會再收到data task的回調(diào)而是轉(zhuǎn)為收到download task的回調(diào)
  1. 在服務(wù)器傳輸數(shù)據(jù)給客戶端期間, 代理會周期性地收到URLSession:?data?Task:?did?Receive?Data:?回調(diào)

  2. session會調(diào)用URLSession:?data?Task:?will?Cache?Response:?completion?Handler:?詢問你的app是否允許緩存. 如果代理不實(shí)現(xiàn)這個(gè)方法的話, 默認(rèn)使用session綁定的Configuration的緩存策略.

DownloadTask

  1. 對于一個(gè)通過download?Task?With?Resume?Data:?創(chuàng)建的下載任務(wù), session會調(diào)用代理的URLSession:?download?Task:?did?Resume?At?Offset:?expected?Total?Bytes:?方法.

  2. 在服務(wù)器傳輸數(shù)據(jù)給客戶端期間, 調(diào)用URLSession:?download?Task:?did?Write?Data:?total?Bytes?Written:?total?Bytes?Expected?To?Write:給用戶傳數(shù)據(jù)

  • 當(dāng)用戶暫停下載時(shí), 調(diào)用cancel?By?Producing?Resume?Data:?給用戶傳已下好的數(shù)據(jù).
  • 如果用戶想要恢復(fù)下載, 把剛剛的resumeData以參數(shù)的形式傳給download?Task?With?Resume?Data:?方法創(chuàng)建新的task繼續(xù)下載.
  1. 如果download task成功完成了, 調(diào)用URLSession:?download?Task:?did?Finish?Downloading?To?URL:把臨時(shí)文件的URL路徑給你. 此時(shí)你應(yīng)該在該代理方法返回以前讀取他的數(shù)據(jù)或者把文件持久化.

UploadTask

上傳數(shù)據(jù)去服務(wù)器期間, 代理會周期性收到URLSession:?task:?did?Send?Body?Data:?total?Bytes?Sent:?total?Bytes?Expected?To?Send:回調(diào)并獲得上傳進(jìn)度的報(bào)告.

StreamTask

如果任務(wù)的數(shù)據(jù)是由一個(gè)stream發(fā)出的, session就會調(diào)用代理的URLSession:?task:?need?New?Body?Stream:?方法去獲取一個(gè)NSInputStream對象并提供一個(gè)新請求的body data.

task completion

任何task完成的時(shí)候, 都會調(diào)用URLSession:?task:?did?Complete?With?Error:?方法, error有可能為nil(請求成功), 不為nil(請求失敗)

  • 請求失敗, 但是該任務(wù)是可恢復(fù)下載的, 那么error對象的userInfo字典里有一個(gè)NSURLSession?Download?Task?Resume?Data對應(yīng)的value, 你應(yīng)該把這個(gè)值傳給download?Task?With?Resume?Data:?方法重新恢復(fù)下載

  • 請求失敗, 但是任務(wù)無法恢復(fù)下載, 那么應(yīng)該重新創(chuàng)建一個(gè)下載任務(wù)并從頭開始下載.

  • 因?yàn)槠渌?如服務(wù)器錯(cuò)誤等等), 創(chuàng)建并恢復(fù)請求.

Note
NSURLSession不會收到服務(wù)器傳來的錯(cuò)誤, 代理只會收到客戶端出現(xiàn)的錯(cuò)誤, 例如無法解析主機(jī)名或無法連接上主機(jī)等等. 客戶端錯(cuò)誤定義在URL Loading System Error Codes. 服務(wù)端錯(cuò)誤通過HTTP狀態(tài)法進(jìn)行傳輸, 詳情請看NSHTTPURLResponse和NSURLResponse類.

銷毀session

如果你不再需要一個(gè)session了, 一定要調(diào)用它的invalidateAndCancelfinishTasksAndInvalidate方法. (前者是取消所有未完成的任務(wù)然后使session失效, 后者是等待正在執(zhí)行的任務(wù)完成之后再使session失效). 否則的話, 有可能造成內(nèi)存泄漏. 另外, session失效后會調(diào)用URLSession:?did?Become?Invalid?With?Error:方法, 之后session釋放對代理的強(qiáng)引用.

Background Transport

需要注意的是, 在后臺session中, 一些代理方法將失效. 下面說一些使用后臺session的注意點(diǎn) :

  • 后臺session必須提供一個(gè)代理處理突發(fā)事件
  • 只支持HTTP(S)協(xié)議. 其他協(xié)議都不可用.
  • 只支持上傳/下載任務(wù), data任務(wù)不支持.
  • 后臺任務(wù)有數(shù)量限制
  • 當(dāng)任務(wù)數(shù)量到達(dá)系統(tǒng)指定的臨界值的時(shí)候, 一些后臺任務(wù)就會被取消. 也就是說, 一個(gè)需要長時(shí)間上傳/下載的任務(wù)很可能會被系統(tǒng)取消然后有可能過一會再重新開始, 所以支持?jǐn)帱c(diǎn)續(xù)傳很重要.
  • 如果一個(gè)后臺傳輸任務(wù)是在app在后臺的時(shí)候開啟的, 那么這個(gè)任務(wù)很可能會出于對性能的考慮隨時(shí)被系統(tǒng)取消掉. . (相當(dāng)于session的Configuration對象的discretionary屬性為true.)

后臺session限制確實(shí)很多, 所以盡可能使用前臺session做事情.

Note

后臺session最好用來傳輸一些支持?jǐn)帱c(diǎn)續(xù)傳大文件. 或?qū)@個(gè)過程進(jìn)行一些針對性的優(yōu)化

  • 最好把文件先壓縮成zip/tar等壓縮文件再上傳/下載.
  • 把大文件按數(shù)據(jù)段分別發(fā)送, 發(fā)送完之后服務(wù)端再把數(shù)據(jù)拼接起來.
  • 上傳的時(shí)候服務(wù)端應(yīng)該返回一個(gè)標(biāo)識符, 這樣可以追蹤傳輸?shù)臓顟B(tài), 及時(shí)做出傳輸?shù)恼{(diào)整
  • 增加一個(gè)web代理服務(wù)器中間層, 以促進(jìn)上述的優(yōu)化

Usage

那么如何使用這個(gè)后臺傳輸呢?

  • 創(chuàng)建一個(gè)后臺session
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.Jerry4me.backgroundSessionIdentifier"];
    _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];

  • 創(chuàng)建一個(gè)upload or download task

NSURL *URL = [NSURL URLWithString:@"http://www.bz55.com/uploads/allimg/140402/137-140402153504.jpg"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    
self.task = [self.session downloadTaskWithRequest:request];
/**注意 : 后臺任務(wù)不能使用帶有completionHandler的方法創(chuàng)建 **/
/**注意 : 如果任務(wù)只想在app進(jìn)入后臺后才處理, 那么可不調(diào)用[task resume]主動執(zhí)行, 待程序進(jìn)入后臺后會自動執(zhí)行 **/
  • 我們等下載到一半后進(jìn)入后臺, 打開App Switcher過一會可以發(fā)現(xiàn), 圖片下載完之后就會顯示在應(yīng)用程序上. 方法調(diào)用順序?yàn)?: 下面四個(gè)方法全部都是app在后臺時(shí)調(diào)用的
2017-03-24 14:17:09.458415 JRBgSessionDemo[2766:1080861] 下載中 - 58%
2017-03-24 14:17:09.567957 JRBgSessionDemo[2766:1080861] 下載中 - 59%
2017-03-24 14:17:16.916830 JRBgSessionDemo[2766:1080828] -[AppDelegate application:handleEventsForBackgroundURLSession:completionHandler:]
2017-03-24 14:17:16.951185 JRBgSessionDemo[2766:1080977] -[DownloadViewController URLSession:downloadTask:didFinishDownloadingToURL:]
2017-03-24 14:17:16.953951 JRBgSessionDemo[2766:1080977] -[DownloadViewController URLSession:task:didCompleteWithError:]
2017-03-24 14:17:16.954574 JRBgSessionDemo[2766:1080977] -[DownloadViewController URLSessionDidFinishEventsForBackgroundURLSession:]

總結(jié)后臺傳輸

  1. 盡量用真機(jī)進(jìn)行調(diào)試, 模擬器會跳過某一兩個(gè)方法
  2. 只能進(jìn)行upload/download task, 不能進(jìn)行data task
  3. 不能使用帶completionHandler的方法創(chuàng)建task, 否則程序直接掛掉
  4. Applecation里的completionHandler必須存儲起來, 等你處理完所有事情之后再調(diào)用告訴系統(tǒng)可以進(jìn)行Snapshot和掛起app了
  5. 后臺下載最好支持?jǐn)帱c(diǎn)續(xù)傳, 因?yàn)槿蝿?wù)有可能會被系統(tǒng)主動取消(例如系統(tǒng)性能下降了, 資源不夠用的情況下)

后臺傳輸?shù)腄emo在文章頭部的地方, 也可以點(diǎn)這里進(jìn)去

NSURLSession API

  • 創(chuàng)建Session

    • + session?With?Configuration:?? : 創(chuàng)建一個(gè)指定配置的session
    • + session?With?Configuration:??delegate:??delegate?Queue:?? : 創(chuàng)建一個(gè)指定配置, 代理和代理方法執(zhí)行隊(duì)列的session
    • shared?Session : 返回session單例
  • 配置Session

    • configuration : 配置
    • delegate : 代理對象
    • delegateQueue : 代理方法的執(zhí)行隊(duì)列
    • sessionDescription : app定義的對于該session的描述
  • 添加data任務(wù)

    • - dataTaskWithURL: : 獲取指定URL內(nèi)容
    • - dataTaskWithURL:completionHandler: : 獲取指定URL內(nèi)容, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
    • - data?Task?With?Request:?? : 獲取指定URLRequest內(nèi)容
    • - data?Task?With?Request:??completionHandler: : 獲取指定URLRequest內(nèi)容, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
  • 添加download任務(wù)

    • - downloadTaskWithURL: : 下載指定URL內(nèi)容
    • - downloadTaskWithURL:completionHandler: : 下載指定URL內(nèi)容, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
    • - downloadTask?With?Request:?? : 下載指定URLRequest內(nèi)容
    • - downloadTask?With?Request:??completionHandler: : 下載指定URLRequest內(nèi)容, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
    • - downloadTask?With?ResumeData:?? : 創(chuàng)建一個(gè)之前被取消/下載失敗的download task
    • - downloadTask?With?ResumeData:??completionHandler: : 創(chuàng)建一個(gè)之前被取消/下載失敗的download task, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
  • 添加upload任務(wù)

    • - upload?Task?With?Request:??from?Data:?? : 通過HTTP請求發(fā)送data給指定URL
    • - upload?Task?With?Request:??from?Data:completionHandler: : 通過HTTP請求發(fā)送data給指定URL, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
    • - upload?Task?With?Request:??from?File:?? : 通過HTTP請求發(fā)送指定文件給指定URL
    • - upload?Task?With?Request:??from?File:completionHandler: : 通過HTTP請求發(fā)送指定文件給指定URL, 在completionHandler中處理數(shù)據(jù). 該方法會繞過代理方法(除了身份認(rèn)證挑戰(zhàn)的代理方法)
    • upload?Task?With?StreamedRequest : 通過HTTP請求發(fā)送指定URLRequest數(shù)據(jù)流給指定URL
  • 添加stream任務(wù)

    • - streamTask?With?HostName:port:?? : 通過給定的域名和端口建立雙向TCP/IP連接
    • - streamTask?With?NetService: : 通過給定的network service建立雙向TCP/IP連接
  • 管理session

    • finishTasksAndInvalidate : 任務(wù)全部完成后銷毀session
    • flushWithCompletionHandler: : 清除硬盤上的cookies和證書, 清理暫時(shí)的緩存, 確保未來能響應(yīng)一個(gè)新的TCP請求
    • getTasksWithCompletionHandler: : 異步調(diào)用session中所有upload, download, data tasks的completion回調(diào).
    • invalidateAndCancel : 取消所有未完成的任務(wù)并銷毀session
    • resetWithCompletionHandler: : 清空cookies, 緩存和證書存儲, 移除所有磁盤文件, 清理正在執(zhí)行的下載任務(wù), 確保未來能響應(yīng)一個(gè)新的socket請求

API總結(jié)

所有創(chuàng)建task的方法, 只要帶有completionHandler這個(gè)參數(shù)的, 均表示為請求過程中不會觸發(fā)代理方法. 所有不帶有completionHandler這個(gè)參數(shù)的, 均會走代理方法流程.

如果你實(shí)現(xiàn)了URLSession:?did?Receive?Challenge:?completion?Handler:?方法又沒有在該方法調(diào)用completionHandler, 請求就會遭到阻塞

斷點(diǎn)續(xù)傳

  • 下載失敗/暫停/被取消, 可以通過task的- cancel?By?Producing?Resume?Data:?方法保存已下載的數(shù)據(jù), 然后調(diào)用session的download?Task?With?Resume?Data:?方法, 觸發(fā)代理的URLSession:?download?Task:?did?Resume?At?Offset:?expected?Total?Bytes:?方法

Something else Important

NSCopying Behavior

session, task和configuration對象都支持copy操作 :

  • session/task copy : 返回session對象本身
  • configuration copy : 返回一個(gè)無法修改(immutable)的對象.
線程安全

URLSession 的API全部都是線程安全的. 你可以在任何線程上創(chuàng)建session和tasks, task會自動調(diào)度到合適的代理隊(duì)列中運(yùn)行.

Warning

后臺傳輸?shù)拇矸椒?code>URLSession?Did?Finish?Events?For?Background?URLSession:?可能會在其他線程中被調(diào)用. 在該方法中你應(yīng)該回到主線程然后調(diào)用completion handler去觸發(fā)AppDelegate中的application:?handle?Events?For?Background?URLSession:?completion?Handler:?方法.

常量
  • NSURLSession-Specific NSError user?Info Dictionary Keys : NSURLSession API 中出現(xiàn)的NSError的userInfo的keys
  • Background Task Cancellation reasons : 指示系統(tǒng)為什么取消了你的后臺任務(wù)的理由
  • Transfer Size Constant : 指示一個(gè)未知傳輸大小的常量

參考文檔

NSURLSession - Foundation
WWDC 2013 - Session 204 - What's New with Multitasking
WWDC 2013 - Session 705 - What's New in Foundation Networking
WWDC 2015 - Session 711 - Networking with NSURLSession
WWDC 2016 - Session 711 - NSURLSession: New Features and Best Practices

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末在抛,一起剝皮案震驚了整個(gè)濱河市乓序,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜀铲,死亡現(xiàn)場離奇詭異,居然都是意外死亡属百,警方通過查閱死者的電腦和手機(jī)记劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來族扰,“玉大人厌丑,你說我怎么就攤上這事∮婧牵” “怎么了怒竿?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扩氢。 經(jīng)常有香客問我耕驰,道長,這世上最難降的妖魔是什么录豺? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任朦肘,我火速辦了婚禮,結(jié)果婚禮上巩检,老公的妹妹穿的比我還像新娘厚骗。我一直安慰自己,他們只是感情好兢哭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夫嗓,像睡著了一般迟螺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舍咖,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天矩父,我揣著相機(jī)與錄音,去河邊找鬼排霉。 笑死窍株,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播球订,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼后裸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冒滩?” 一聲冷哼從身側(cè)響起微驶,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎开睡,沒想到半個(gè)月后因苹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡篇恒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年扶檐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁艰。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡款筑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝗茁,到底是詐尸還是另有隱情醋虏,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布哮翘,位于F島的核電站颈嚼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏饭寺。R本人自食惡果不足惜阻课,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望艰匙。 院中可真熱鬧限煞,春花似錦、人聲如沸员凝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽健霹。三九已至旺上,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糖埋,已是汗流浹背宣吱。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞳别,地道東北人征候。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓杭攻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疤坝。 傳聞我的和親對象是個(gè)殘疾皇子兆解,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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