Alamofire(8)— 終章(網(wǎng)絡(luò)監(jiān)控&通知&下載器封裝)

??????Alamofire專題目錄傅寡,歡迎及時反饋交流 ??????


Alamofire 目錄直通車 --- 和諧學(xué)習诬乞,不急不躁埋哟!


非常高興,這個 Alamofire 篇章馬上也結(jié)束了略水!那么這也作為 Alamofire 的終章价卤,給大家介紹整個 Alamofire 剩余的內(nèi)容,以及下載器封裝渊涝,最后總結(jié)一下慎璧!

一、NetworkReachabilityManager

這個類主要對 SystemConfiguration.framework 中的 SCNetworkReachability 相關(guān)的東西進行封裝的跨释,主要用來管理和監(jiān)聽網(wǎng)絡(luò)狀態(tài)的變化

1??:首先我們來使用監(jiān)聽網(wǎng)絡(luò)狀態(tài)

let networkManager = NetworkReachabilityManager(host: "www.apple.com")

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    /// 網(wǎng)絡(luò)監(jiān)控
    networkManager!.listener = {
        status in
        var message = ""
        switch status {
        case .unknown:
            message = "未知網(wǎng)絡(luò),請檢查..."
        case .notReachable:
            message = "無法連接網(wǎng)絡(luò),請檢查..."
        case .reachable(.wwan):
            message = "蜂窩移動網(wǎng)絡(luò),注意節(jié)省流量..."
        case .reachable(.ethernetOrWiFi):
            message = "WIFI-網(wǎng)絡(luò),使勁造吧..."
        }
        print("***********\(message)*********")
        let alertVC = UIAlertController(title: "網(wǎng)絡(luò)狀況提示", message: message, preferredStyle: .alert)
        alertVC.addAction(UIAlertAction(title: "我知道了", style: .default, handler: nil))
        self.window?.rootViewController?.present(alertVC, animated: true, completion: nil)
    }
    networkManager!.startListening()
    
    return true
}
  • 用法非常簡單胸私,因為考慮到全局監(jiān)聽,一般都會寫在didFinishLaunchingWithOptions
  • 創(chuàng)建 NetworkReachabilityManager 對象
  • 設(shè)置回調(diào)鳖谈,通過回調(diào)的 status 來處理事務(wù)
  • 最后一定要記得開啟監(jiān)聽(內(nèi)部重點封裝)

2??:底層源碼分析

1:我們首先來看看 NetworkReachabilityManager 的初始化

public convenience init?(host: String) {
    guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
    self.init(reachability: reachability)
}

private init(reachability: SCNetworkReachability) {
    self.reachability = reachability
    // 將前面的標志設(shè)置為無保留值岁疼,以表示未知狀態(tài)
    self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)
}
  • 底層源碼里面調(diào)用 SCNetworkReachabilityCreateWithName 創(chuàng)建了 reachability 對象,這也是我們 SystemConfiguration 下非常非常重要的類!
  • 保存在這個 reachability 對象缆娃,方便后面持續(xù)使用
  • 將前面的標志設(shè)置為無保留值捷绒,以表示未知狀態(tài)
  • 其中初始化方法中,也提供了默認創(chuàng)建贯要,該實例監(jiān)視地址 0.0.0.0
  • 可達性將 0.0.0.0地址 視為一個特殊的 token暖侨,它可以監(jiān)視設(shè)備的一般路由狀態(tài),包括 IPv4和IPv6崇渗。

2:open var listener: Listener?

  • 這里也就是對外提供的狀態(tài)回調(diào)閉包

3:networkManager!.startListening() 開啟監(jiān)聽

這里也是這個內(nèi)容點的重點所在

open func startListening() -> Bool {
    // 獲取上下文結(jié)構(gòu)信息
    var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
    context.info = Unmanaged.passUnretained(self).toOpaque()
    // 將客戶端分配給目標字逗,當目標的可達性發(fā)生更改時,目標將接收回調(diào)
    let callbackEnabled = SCNetworkReachabilitySetCallback(
        reachability,
        { (_, flags, info) in
            let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
            reachability.notifyListener(flags)
        },
        &context
    )
    // 在給定分派隊列上為給定目標調(diào)度或取消調(diào)度回調(diào)
    let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
    // 異步執(zhí)行狀態(tài)宅广,以及通知
    listenerQueue.async {
        guard let flags = self.flags else { return }
        self.notifyListener(flags)
    }
    return callbackEnabled && queueEnabled
}
  • 調(diào)用SCNetworkReachabilityContext的初始化扳肛,這個結(jié)構(gòu)體包含用戶指定的數(shù)據(jù)和回調(diào)函數(shù).
  • Unmanaged.passUnretained(self).toOpaque()就是將非托管類引用轉(zhuǎn)換為指針
  • SCNetworkReachabilitySetCallback:將客戶端分配給目標,當目標的可達性發(fā)生更改時乘碑,目標將接收回調(diào)挖息。(這也是只要我們的網(wǎng)絡(luò)狀態(tài)發(fā)生改變時,就會響應(yīng)的原因)
  • 在給定分派隊列上為給定目標調(diào)度或取消調(diào)度回調(diào)
  • 異步執(zhí)行狀態(tài)信息處理兽肤,并發(fā)出通知

4:self.notifyListener(flags) 我們看看狀態(tài)處理以及回調(diào)

  • 調(diào)用了listener?(networkReachabilityStatusForFlags(flags)) 在回調(diào)的時候還內(nèi)部處理了 flags
  • 這也是可以理解的套腹,我們需要不是一個標志位,而是蜂窩網(wǎng)絡(luò)资铡、WIFI电禀、無網(wǎng)絡(luò)!
func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
    guard isNetworkReachable(with: flags) else { return .notReachable }

    var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)

#if os(iOS)
    if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
#endif
    return networkStatus
}
  • 通過 isNetworkReachable 判斷有無網(wǎng)絡(luò)
  • 通過 .reachable(.ethernetOrWiFi) 是否存在 WIFI 網(wǎng)絡(luò)
  • iOS端 還增加了 .reachable(.wwan) 判斷蜂窩網(wǎng)絡(luò)

3??:小結(jié)

網(wǎng)絡(luò)監(jiān)聽處理笤休,還是非常簡單的尖飞!代碼的思路也沒有太惡心,就是通過 SCNetworkReachabilityRef 這個一個內(nèi)部類去處理網(wǎng)絡(luò)狀態(tài),然后通過對 flags 分情況處理政基,確定是無網(wǎng)絡(luò)贞铣、還是WIFI、還是蜂窩

三沮明、AFError錯誤處理

AFError中將錯誤定義成了五個大類型

// 當“URLConvertible”類型無法創(chuàng)建有效的“URL”時返回辕坝。
case invalidURL(url: URLConvertible)
// 當參數(shù)編碼對象在編碼過程中拋出錯誤時返回。
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
// 當多部分編碼過程中的某個步驟失敗時返回荐健。
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
// 當“validate()”調(diào)用失敗時返回酱畅。
case responseValidationFailed(reason: ResponseValidationFailureReason)
// 當響應(yīng)序列化程序在序列化過程中遇到錯誤時返回。
case responseSerializationFailed(reason: ResponseSerializationFailureReason)

這里通過對枚舉拓展了計算屬性江场,來直接對錯誤類型進行 if判斷纺酸,不用在 switch 一個一個判斷了

extension AFError {
    // 返回AFError是否為無效URL錯誤
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }
    // 返回AFError是否是參數(shù)編碼錯誤。
    // 當“true”時址否,“underlyingError”屬性將包含關(guān)聯(lián)的值餐蔬。
    public var isParameterEncodingError: Bool {
        if case .parameterEncodingFailed = self { return true }
        return false
    }
    // 返回AFError是否是多部分編碼錯誤。
    // 當“true”時在张,“url”和“underlyingError”屬性將包含相關(guān)的值。
    public var isMultipartEncodingError: Bool {
        if case .multipartEncodingFailed = self { return true }
        return false
    }
    // 返回“AFError”是否為響應(yīng)驗證錯誤矮慕。
    // 當“true”時帮匾,“acceptableContentTypes”、“responseContentType”和“responseCode”屬性將包含相關(guān)的值痴鳄。
    public var isResponseValidationError: Bool {
        if case .responseValidationFailed = self { return true }
        return false
    }
    // 返回“AFError”是否為響應(yīng)序列化錯誤瘟斜。
    // 當“true”時,“failedStringEncoding”和“underlyingError”屬性將包含相關(guān)的值痪寻。
    public var isResponseSerializationError: Bool {
        if case .responseSerializationFailed = self { return true }
        return false
    }
}

小結(jié)

AFError 錯誤處理螺句,這個類的代碼也是非常簡單的!大家自行閱讀以下應(yīng)該沒有太多疑問,這里也就不花篇幅去啰嗦了橡类!

四蛇尚、Notifications & Validation

Notifications 核心重點

extension Notification.Name {
    /// Used as a namespace for all `URLSessionTask` related notifications.
    public struct Task {
        /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`.
        public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")
        /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`.
        public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")
        /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`.
        public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")
        /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`.
        public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
    }
}
  • Notification.Name 通過擴展了一個 Task 這樣的結(jié)構(gòu)體,把跟 task 相關(guān)的通知都綁定在這個 Task上顾画,因此取劫,在代碼中就可以這么使用:
NotificationCenter.default.post(
                name: Notification.Name.Task.DidComplete,
                object: strongSelf,
                userInfo: [Notification.Key.Task: task]
            )
  • Notification.Name.Task.DidComplete 表達的非常清晰,一般都能知道是 task 請求完成之后的通知研侣。再也不需要惡心的字符串谱邪,需要匹配,萬一寫錯了庶诡,那么也是一種隱藏的危機惦银!

Notification userinfo&key 拓展

extension Notification {
    /// Used as a namespace for all `Notification` user info dictionary keys.
    public struct Key {
        /// User info dictionary key representing the `URLSessionTask` associated with the notification.
        public static let Task = "org.alamofire.notification.key.task"
        /// User info dictionary key representing the responseData associated with the notification.
        public static let ResponseData = "org.alamofire.notification.key.responseData"
    }
}
  • 擴展了Notification,新增了一個 Key結(jié)構(gòu)體,這個結(jié)構(gòu)體用于取出通知中的 userInfo。
  • 使用 userInfo[Notification.Key.ResponseData] = data
NotificationCenter.default.post(
    name: Notification.Name.Task.DidResume,
    object: self,
    userInfo: [Notification.Key.Task: task]
)
  • 設(shè)計的本質(zhì)就是為了更加簡潔扯俱!大家也可以從這種思維得出一些想法運用到實際開發(fā)中: 按照自己的業(yè)務(wù)創(chuàng)建不同的結(jié)構(gòu)體就可以了书蚪。

小結(jié)

  • Notifications 其實是一個 Task結(jié)構(gòu)體,該結(jié)構(gòu)體中定義了一些字符串蘸吓,這些字符串就是所需通知的 key善炫,當網(wǎng)絡(luò)請求 DidResume、DIdSuspend库继、DIdCancel箩艺、DidComplete 都會發(fā)出通知。
  • Validation 主要是用來驗證請求是否成功宪萄,如果出錯了就做相應(yīng)的處理

五艺谆、下載器

這里的下載器筆者是基于 Alamofire(2)— 后臺下載 繼續(xù)給大家分析幾個關(guān)鍵點

1??:暫停&繼續(xù)&取消

//MARK: - 暫停/繼續(xù)/取消
func suspend() {
    self.currentDownloadRequest?.suspend()
}
func resume() {
    self.currentDownloadRequest?.resume()
}
func cancel() {
    self.currentDownloadRequest?.cancel()
}
  • 通過我們的下載事務(wù)管理者:Request 管理 task 任務(wù)的生命周期
  • 其中task事務(wù)就是通過調(diào)用 suspendresume 方法
  • cancel 里面調(diào)用:downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 } 保存了取消時候的 resumeData

2??:斷點續(xù)傳

斷點續(xù)傳的重點:就是保存響應(yīng) resumeData,然后調(diào)用:manager.download(resumingWith: resumeData)

if let resumeData = currentDownloadRequest?.resumeData {
    let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    let fileUrl     = documentUrl?.appendingPathComponent("resumeData.tmp")
    try! resumeData.write(to: fileUrl!)
    currentDownloadRequest = LGDowloadManager.shared.manager.download(resumingWith: resumeData)
}
  • 看到這里大家也就能感受到其實斷點續(xù)傳最重要的是保存resumeData
  • 然后處理文件路徑拜英,保存
  • 最后調(diào)用 download(resumingWith: resumeData) 就可以輕松實現(xiàn)斷點續(xù)傳

3??:應(yīng)用程序被用戶kill的時候

1:準備條件

我們們在前面Alamofire(2)— 后臺下載處理的時候静汤,針對 URLSession 是由要求的

  • 必須使用 background(withIdentifier:) 方法創(chuàng)建 URLSessionConfiguration,其中這個 identifier 必須是固定的居凶,而且為了避免跟 其他App 沖突虫给,建議這個identifier 跟應(yīng)用程序的 Bundle ID相關(guān),保證唯一
  • 創(chuàng)建URLSession的時候侠碧,必須傳入delegate
  • 必須在App啟動的時候創(chuàng)建 Background Sessions抹估,即它的生命周期跟App幾乎一致,為方便使用弄兜,最好是作為 AppDelegate 的屬性药蜻,或者是全局變量。

2:測試反饋

OK替饿,準備好了條件语泽,我們開始測試!當應(yīng)用程序被用戶殺死的時候,再回來视卢!

?? 我們驚人的發(fā)現(xiàn)踱卵,會報錯:load failed with error Error Domain=NSURLErrorDomain Code=-999, 這個BUG 我可是經(jīng)常看見据过,于是飛快定位:

urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)

?? 果然應(yīng)用程序會回到完成代理颊埃,大家如果細心想一想也是可以理解的:應(yīng)用程序被用戶kill,也是舒服用戶取消,這個任務(wù)執(zhí)行失敗暗恪班利! ??

3:處理事務(wù)

if let error = error {
    if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
        LGDowloadManager.shared.resumeData = resumeData
        print("保存完畢,你可以斷點續(xù)傳!")
    }
}
  • 錯誤獲取,然后轉(zhuǎn)成相應(yīng) NSError
  • 通過 error 獲取里面 inifo , 再通過 key 拿到相應(yīng)的 resumeData
  • 因為前面這個已經(jīng)保證了生命周期的單利,就可以啟動應(yīng)用程序的時候保存
  • 下次點擊同一個URL下載的時候榨呆,只要取出對應(yīng)的 task 保存的 resumeData
  • 執(zhí)行download(resumingWith: resumeData) 完美罗标!

當然如果你有特殊封裝也可以執(zhí)行調(diào)用 Alamofire 封裝的閉包

manager.delegate.taskDidComplete = { (session, task, error) in
    print("**************")
    if let error = error {
        if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
            LGDowloadManager.shared.resumeData = resumeData
            print("保存完畢,你可以斷點續(xù)傳!")
        }
    }
    print("**************")
}

4??:APP Crash或者被系統(tǒng)關(guān)閉時候

問題

這里我們在實際開發(fā)過程中庸队,也會遇到各種各樣的BUG,那么在下載的時候 APP Crash 也是完全可能的闯割!問題在于:我們這個時候怎么辦彻消?

思考

我們通過上面的條件,發(fā)現(xiàn)其實 apple 針對下載任務(wù)是有特殊處理的宙拉!我把它理解是在另一進程處理的宾尚!下載程序的代理方法還是會繼續(xù)執(zhí)行!那么我在直接把所有下載相關(guān)代理方法全部斷點

測試結(jié)果

// 告訴委托下載任務(wù)已完成下載
func urlSession(
        _ session: URLSession,
        downloadTask: URLSessionDownloadTask,
        didFinishDownloadingTo location: URL)
// 下載進度也會不斷執(zhí)行
func urlSession(
        _ session: URLSession,
        downloadTask: URLSessionDownloadTask,
        didWriteData bytesWritten: Int64,
        totalBytesWritten: Int64,
        totalBytesExpectedToWrite: Int64)
  • 我們的程序回來谢澈,會在后臺默默執(zhí)行
  • urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 完成也會調(diào)用

問題一:OK煌贴,看似感覺一切都完美(不需要處理),但是錯了:我們用戶不知道你已經(jīng)在后臺執(zhí)行了锥忿,他有可能下次進來有點擊下載(還有UI頁面牛郑,也沒有顯示的進度)

問題二:因為 Alamofirerequest 沒有創(chuàng)建,所以沒有對應(yīng)的 task

思路:重重壓力敬鬓,我找到了一個非常重要的閉包(URLSession 的屬性)-- getTasksWithCompletionHandler 于是有下面這么一段代碼

manager.session.getTasksWithCompletionHandler({ (dataTasks, uploadTasks, downloadTasks) in
    print(dataTasks)
    print(uploadTasks)
    print(downloadTasks)
})
  • 這個閉包能夠監(jiān)聽到當前session里正在執(zhí)行的任務(wù),我們只需要便利找到響應(yīng)的 Task
  • 然后利用緩存把 task 對應(yīng) url 保存起來
  • 下次用戶再點擊相同 url 的時候淹朋,就判斷讀取就OK,如果存在就不需要開啟新的任務(wù)钉答,只要告訴用戶已經(jīng)開始下載就OK础芍,UI頁面處理而已
  • 進度呢?也很簡單畢竟代理在后臺持續(xù)進行数尿,我們只需要在 func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) 代理里面匹配 downloadTask 保存進度仑性,然后更新界面就OK!
  • 細節(jié):didFinishDownloadingTo 記得對下載回來的文件進行路徑轉(zhuǎn)移砌创!

5??:如果應(yīng)用程序creash,但是下載完成

首先這里非常感謝 iOS原生級別后臺下載詳解 提供的測試總結(jié)虏缸!Tiercel2 框架一個非常強大的下載框架鲫懒,推薦大家使用

  • 在前臺:跟普通的 downloadTask 一樣嫩实,調(diào)用相關(guān)的 session代理方法
  • 在后臺:當 Background Sessions 里面所有的任務(wù)(注意是所有任務(wù),不單單是下載任務(wù))都完成后窥岩,會調(diào)用 AppDelegateapplication(_:handleEventsForBackgroundURLSession:completionHandler:) 方法甲献,激活 App,然后跟在前臺時一樣颂翼,調(diào)用相關(guān)的session代理方法晃洒,最后再調(diào)用 urlSessionDidFinishEvents(forBackgroundURLSession:) 方法
  • crash 或者 App被系統(tǒng)關(guān)閉:當 Background Sessions 里面所有的任務(wù)(注意是所有任務(wù),不單單是下載任務(wù))都完成后朦乏,會自動啟動App球及,調(diào)用 AppDelegate的application(_:didFinishLaunchingWithOptions:) 方法,然后調(diào)用 application(_:handleEventsForBackgroundURLSession:completionHandler:) 方法呻疹,當創(chuàng)建了對應(yīng)的Background Sessions 后吃引,才會跟在前臺時一樣,調(diào)用相關(guān)的 session 代理方法,最后再調(diào)用 urlSessionDidFinishEvents(forBackgroundURLSession:) 方法
  • crash 或者 App被系統(tǒng)關(guān)閉镊尺,打開 App 保持前臺朦佩,當所有的任務(wù)都完成后才創(chuàng)建對應(yīng)的 Background Sessions:沒有創(chuàng)建 session 時,只會調(diào)用 AppDelegate的application(_:handleEventsForBackgroundURLSession:completionHandler:) 方法庐氮,當創(chuàng)建了對應(yīng)的 Background Sessions 后语稠,才會跟在前臺時一樣,調(diào)用相關(guān)的 session 代理方法弄砍,最后再調(diào)用 urlSessionDidFinishEvents(forBackgroundURLSession:) 方法
  • crash 或者 App被系統(tǒng)關(guān)閉仙畦,打開 App,創(chuàng)建對應(yīng)的 Background Sessions
    后所有任務(wù)才完成:跟在前臺的時候一樣

到這里输枯,這個篇章就分析完畢了议泵!看到這里估計你也對 Alamofire 有了一定的了解。這個篇章完畢桃熄,我還是會繼續(xù)更新(盡管現(xiàn)在掘進iOS人群不多先口,閱讀量不多)但這是我的執(zhí)著!希望還在iOS行業(yè)奮斗的小伙伴瞳收,繼續(xù)加油碉京,守的云開見日出!??????

就問此時此刻還有誰螟深?45度仰望天空谐宙,該死!我這無處安放的魅力界弧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凡蜻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子垢箕,更是在濱河造成了極大的恐慌划栓,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件条获,死亡現(xiàn)場離奇詭異忠荞,居然都是意外死亡,警方通過查閱死者的電腦和手機帅掘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門委煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人修档,你說我怎么就攤上這事碧绞。” “怎么了吱窝?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵讥邻,是天一觀的道長寓免。 經(jīng)常有香客問我,道長计维,這世上最難降的妖魔是什么袜香? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮鲫惶,結(jié)果婚禮上蜈首,老公的妹妹穿的比我還像新娘。我一直安慰自己欠母,他們只是感情好欢策,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赏淌,像睡著了一般踩寇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上六水,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天俺孙,我揣著相機與錄音,去河邊找鬼掷贾。 笑死睛榄,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的想帅。 我是一名探鬼主播场靴,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼港准!你這毒婦竟也來了旨剥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤浅缸,失蹤者是張志新(化名)和其女友劉穎轨帜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗杉,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡阵谚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年蚕礼,在試婚紗的時候發(fā)現(xiàn)自己被綠了烟具。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡奠蹬,死狀恐怖朝聋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情囤躁,我是刑警寧澤冀痕,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布荔睹,位于F島的核電站,受9級特大地震影響言蛇,放射性物質(zhì)發(fā)生泄漏僻他。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一腊尚、第九天 我趴在偏房一處隱蔽的房頂上張望吨拗。 院中可真熱鬧,春花似錦婿斥、人聲如沸劝篷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娇妓。三九已至,卻和暖如春活鹰,著一層夾襖步出監(jiān)牢的瞬間哈恰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工志群, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蕊蝗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓赖舟,卻偏偏與公主長得像蓬戚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359