Swift學(xué)習(xí)筆記(九)--可選類型鏈與錯(cuò)誤處理

可選鏈和錯(cuò)誤處理在WWDC的視頻里都有說, 有興趣的可以翻出來看看. 貌似是在Advanced Swift里面說的.

可選類型鏈(Optional Chaining)

翻譯為可選類型鏈感覺很奇怪, 但是一時(shí)半會(huì)又找不到更貼切的詞語了, 這是Swift讓我覺得很實(shí)用很方便的一點(diǎn). 簡單說來就是一個(gè)實(shí)例是可選類型的, 它的屬性(或方法返回值)也是可選類型的, 它屬性的屬性還是可選的(可以一直鏈下去)...這種情況如果按之前提到的, 就要一直if let, 持續(xù)好幾次. 這個(gè)可選類型鏈就是為了解決這一類問題.

同時(shí), 我們已經(jīng)知道在Swift里面給nil發(fā)消息, 那么會(huì)導(dǎo)致crash, 但是用可選類型鏈就可以規(guī)避這個(gè)問題. 所以就有了下一節(jié)這個(gè)標(biāo)題.

可選類型鏈就強(qiáng)制拆包的一種替代(Optional Chaining as an Alternative to Forced Unwrapping)

如果一個(gè)對(duì)象的類型是可選的, 按之前提到的我們?cè)谑褂弥岸家酶袊@號(hào)(!)進(jìn)行強(qiáng)制拆包, 而可選類型鏈在處理可能為nil的對(duì)象的時(shí)候, 要優(yōu)雅很多, 它會(huì)在對(duì)象為nil的時(shí)候返回nil(這個(gè)時(shí)候是不是很像ObjC里面的機(jī)制了呢?), 所以, 我們?cè)谑褂玫臅r(shí)候要記住一點(diǎn), 如果用了可選類型鏈來賦值或者獲得返回值, 那么這個(gè)值會(huì)是可選類型的(也就是原本返回Int, 用了可選類型鏈就會(huì)變成返回Int?, 因?yàn)橛锌赡軙?huì)返回nil啊).
值得一提的是, 即使原本返回Void的方法, 也會(huì)變成Void?. 同時(shí), 如果本身就是可選類型的, 那么也不會(huì)再嵌套一層可選類型, 比如為Int??類型(可選類型是可以嵌套的, 可以看看唐巧公眾號(hào)上分享的那篇文章).

我們直接看一段代碼:

var str :String?
var length:Int? = str?.characters.count // 如果改為var length:Int 則會(huì)報(bào)錯(cuò)

官方文檔上也給出了一個(gè)例子來對(duì)比強(qiáng)制拆包和可選類型鏈的區(qū)別, 一起看看:

class Person {
    var residence: Residence?
}
 
class Residence {
    var numberOfRooms = 1
}
let john = Person()
let roomCount = john.residence!.numberOfRooms  // 這里有runtime error
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")  // 執(zhí)行這一行
}

// 給residence賦值
john.residence = Residence()
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")  // 打印這一行
} else {
    print("Unable to retrieve the number of rooms.")
}

可選類型鏈的更多用法

這一節(jié)我覺得講的和上兩節(jié)差不多, 但是官網(wǎng)給出了很多例子, 看看代碼即可. 或者可以直接看最后的那一塊真正講鏈?zhǔn)降牡胤? 那里才是精華, 足夠讓人興奮(如果你不夠興奮, 說明你沒有經(jīng)歷過1.0時(shí)代).

直接以官網(wǎng)的例子來看, 首先定義一大堆的類:

class Person {
    var residence: Residence?  // 可選類型
}

class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    func printNumberOfRooms() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?  // 可選類型
}

class Room {
    let name: String
    init(name: String) { self.name = name }
}

class Address {
    var buildingName: String?  // 可選類型
    var buildingNumber: String?  // 可選類型
    var street: String?  // 可選類型
    func buildingIdentifier() -> String? {   // 可選類型
        if buildingName != nil {
            return buildingName
        } else if buildingNumber != nil && street != nil {
            return "\(buildingNumber) \(street)"
        } else {
            return nil
        }
    }
}

可以看到, Person的residence為可選, Residence的address為可選, Address的大量屬性和方法返回值為可選, 所以, 如果我們要有Person實(shí)例, 想要通過Residence實(shí)例來訪問Address里的屬性, 勢必要經(jīng)過多次檢查.

一. 首先來看用可選類型鏈來訪問屬性的情況:

let john = Person()
// 讀取數(shù)據(jù)
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")  // 打印這一行
}
// 寫入數(shù)據(jù)
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress  // 然而并沒有賦值成功, 因?yàn)閞esidence為nil, 直接被返回了

二. 再來看看用可選類型鏈調(diào)用方法的情況:
之前稍稍提過了一點(diǎn), 就是即使是返回為Void的方法, 如果用可選類型鏈來調(diào)用, 也會(huì)變成Void?, 所以, 判斷一個(gè)返回Void的方法調(diào)用成功與否可以這樣:

if john.residence?.printNumberOfRooms() != nil {
    print("It was possible to print the number of rooms.")
} else {
    print("It was not possible to print the number of rooms.")  // 打印這一行
}

同樣的道理, 如果你想看賦值是否成功, 也可以類似的處理:

if (john.residence?.address = someAddress) != nil {
    print("It was possible to set the address.")
} else {
    print("It was not possible to set the address.")  // 打印這一行
}

三. 用可選類型鏈訪問下標(biāo)
這一節(jié)其實(shí)就是一個(gè)規(guī)定, 這個(gè)規(guī)定也很符合常理, 就是訪問可選類型鏈中一個(gè)對(duì)象的下標(biāo)的時(shí)候, 問號(hào)(?)要加在[]之前, 如:

if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.") // 打印這一行
}

這很容易理解, 畢竟我們需要先判斷這個(gè)實(shí)例是否為nil, 才能進(jìn)行下一步的操作.
繼續(xù)看下面的代碼:

john.residence?[0] = Room(name: "Bathroom") // 并不會(huì)賦值成功
// 這回給上residence
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse
 
if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).") // 執(zhí)行這一行
} else {
    print("Unable to retrieve the first room name.")
}

再來看看對(duì)可選類型的訪問下標(biāo)的操作:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
testScores["Brian"]?[0] = 72
// 結(jié)果為:
// "Dave" array = [91, 82, 84] 
// "Bev"   array = [80, 94, 81]

// 多套一層可以這樣:
var dict : Dictionary<String, Array<Int>>?
dict = {"Ryan":[1,2,3], "Chris":[4,5,6]}
dict?["Ryan"]?[0] = 0
dict?["Chris"]?[2] = 10
// 結(jié)果為:
// "Ryan"為0,2,3
// "Chris"為4,5,10

多層鏈接

看了上面的例子可能沒有太多的感受, 最后也還是要用if let, 感覺沒有太明顯的優(yōu)勢. 這是因?yàn)槲覀冞€沒有講到鏈, 鏈這個(gè)字就意味著我們可以持續(xù)寫下去, 只要得到的值是可選類型, 我們就能鏈上來.

還是用上面的那些類和實(shí)例, 來感受一下鏈?zhǔn)降拇a:

// 因?yàn)橹百x值的address并沒有成功, 所以此時(shí)還是空的
if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.") // 執(zhí)行這一行
}

// 賦上address
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
 
if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")  // 執(zhí)行這一行
} else {
    print("Unable to retrieve the address.")
}

我們可以看到, 在多層次的可選類型鏈中, 只需要一個(gè)if let就能取出最終的返回值來進(jìn)行判斷, 可以省略很多的中間步驟, 直達(dá)我們的目的地, 最后以官網(wǎng)用鏈?zhǔn)将@得返回值的例子結(jié)尾:

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
}
// 打印 "John's building identifier is The Larches."

if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
        if beginsWithThe {
            print("John's building identifier begins with \"The\".")
        } else {
            print("John's building identifier does not begin with \"The\".")
        }
}
// 打印 "John's building identifier begins with "The"."

至此, 可選類型鏈就已經(jīng)結(jié)束, 相信我們都會(huì)在以后的代碼中頻繁使用這一機(jī)制, 畢竟它能夠讓我們的代碼更安全, 更簡潔. 具體細(xì)節(jié)還是慣例查看官方文檔

錯(cuò)誤處理(Error Handling)

不管是什么語言, 只要是認(rèn)真開發(fā)一個(gè)作品都是需要面對(duì)錯(cuò)誤處理的. 之前稍稍提過一點(diǎn), Swift和ObjC不一樣, Swift的標(biāo)準(zhǔn)錯(cuò)誤處理靠拋出錯(cuò)誤(嗯, 就是錯(cuò)誤, 畢竟它的protocol叫ErrorType而不是ExceptionType), 而不是靠返回值或者參數(shù). 同時(shí)之前也提過一點(diǎn)Swift是面向協(xié)議編程的, 所以, 如果要拋出錯(cuò)誤的話, 那么就要讓拋出的對(duì)象實(shí)現(xiàn)ErrorType協(xié)議.

其實(shí)只要你愿意的話, 繼續(xù)依賴返回值來控制錯(cuò)誤也是可以的, 因?yàn)槲覀冇辛嗽M(tuple)這個(gè)利器. 至于為什么用throws來拋出錯(cuò)誤, 主要還是讓接口的調(diào)用方能夠很明確這個(gè)接口可能出現(xiàn)的錯(cuò)誤是什么.

官方給出一個(gè)自動(dòng)售貨機(jī)買小吃的例子, 可能出現(xiàn)的錯(cuò)誤包括: 不存在該食品, 錢不夠和庫存不足.

錯(cuò)誤的展示和拋出(Representing and Throwing Errors)

如官網(wǎng)所說, ErrorType只是一個(gè)空的協(xié)議, 其主要作用還是一個(gè)指示性的作用, 說明這里可能會(huì)出現(xiàn)錯(cuò)誤. 同時(shí), 官方比較推薦的做法是用枚舉來指示錯(cuò)誤, 例如下面這個(gè)例子:

enum VendingMachineError: ErrorType {
    case InvalidSelection
    case InsufficientFunds(coinsNeeded: Int)
    case OutOfStock
}
throw VendingMachineError.InsufficientFunds(coinsNeeded: 5)

處理錯(cuò)誤

上一節(jié)說了拋出錯(cuò)誤, 那么拋出了就要處理, Swift有四種方法來處理錯(cuò)誤:
1). 傳遞給函數(shù)的調(diào)用方
2). 用do-catch語句來處理
3). 把錯(cuò)誤當(dāng)做optional值來處理
4). 斷言判定錯(cuò)誤不發(fā)生.
下面的小節(jié)會(huì)分別講述這4種處理方法. 特別講一下自己拋出或者處理異常的時(shí)候,
還要用到try(或者try?, try!), 這個(gè)稍后會(huì)細(xì)講.

用可拋出錯(cuò)誤函數(shù)來傳遞錯(cuò)誤(Propagating Errors Using Throwing Functions)

翻譯起來很奇怪, 其實(shí)就是說一個(gè)函數(shù)拋出錯(cuò)誤說明它不處理這個(gè)錯(cuò)誤, 也就要傳遞出去, 讓調(diào)用方來處理. 如果函數(shù)沒有聲明為可拋出, 那么所有內(nèi)部的錯(cuò)誤都要自己處理掉.
聲明的語法如下:

func canThrowErrors() throws -> String
 
func cannotThrowErrors() -> String

先看一個(gè)拋出異常的例子:

struct Item {
    var price: Int
    var count: Int
}
 
class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    func dispenseSnack(snack: String) {
        print("Dispensing \(snack)")
    }
    
    func vend(itemNamed name: String) throws {
        guard var item = inventory[name] else {
            throw VendingMachineError.InvalidSelection
        }
        
        guard item.count > 0 else {
            throw VendingMachineError.OutOfStock
        }
        
        guard item.price <= coinsDeposited else {
            throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }
        
        coinsDeposited -= item.price
        --item.count
        inventory[name] = item
        dispenseSnack(name)
    }
}

如上面的代碼所示, vend(itemNamed:) 這個(gè)方法會(huì)根據(jù)傳入的參數(shù)拋出3種錯(cuò)誤(這里的guard的優(yōu)勢體現(xiàn)的很明顯, 可以試試用if let來寫會(huì)是什么情況...), 所以我們調(diào)用這個(gè)函數(shù)大概就是這個(gè)德行:

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {  // throws說明繼續(xù)拋出
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)  
}

這里用try而不是throw, 因?yàn)閠hrow后面要接具體的異常, 而后面的方法并不是一定返回異常的, 所以用try.

用do-catch處理異常

如果要自己消化掉錯(cuò)誤, 就要用到do-catch(涉及到具體的語句還是得try). 先看下具體的語法吧:

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}

注意, 這里try和別的地方不一樣, 只能try一條語句, 而不可以用try{}來包圍起來. 官方給出下面的例子, 在我的Xcode7.2上會(huì)報(bào)枚舉沒有窮盡的問題, 但是實(shí)際上已經(jīng)窮盡了, 不明白是什么問題, 為了繼續(xù)走下去我加上了一個(gè)分支:

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.InvalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.OutOfStock {
    print("Out of Stock.")
} catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.") // 觸發(fā)這個(gè)error
} catch { // 兜底的catch
    print("UNKNOW ERROR")
}

錯(cuò)誤轉(zhuǎn)換為可選值(Converting Errors to Optional Values)

之前提過, try還有兩種變種, try?和try!. 如果用try?來處理錯(cuò)誤的話, 會(huì)把錯(cuò)誤轉(zhuǎn)換為一個(gè)可選值. 所以, 如果在執(zhí)行try?語句的時(shí)候拋出了錯(cuò)誤, 那么語句執(zhí)行的結(jié)果就是nil(即使原本沒有返回值也會(huì)返回nil, 這個(gè)之前的章節(jié)講過). try!則是不向調(diào)用方拋出錯(cuò)誤了. 這個(gè)下一小節(jié)講.

先來看官方的例子:

func someThrowingFunction() throws -> Int {
    // ...
}
 
let x = try? someThrowingFunction()  // x的類型為Int?, 而不是Int
 
let y: Int?  // 常量不會(huì)在聲明的時(shí)候自動(dòng)置為nil, 糾正一下在構(gòu)造器里的錯(cuò)誤描述
do {
    y = try someThrowingFunction()
} catch {
    y = nil  
}

官方文檔來給出了一種別的用法:

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}
去除錯(cuò)誤傳遞(Disabling Error Propagation)

如果你知道一個(gè)聲明為可拋出錯(cuò)誤的函數(shù)或方法, 實(shí)際上不會(huì)拋出錯(cuò)誤, 就會(huì)用到這種處理方法. 所以如果你判斷失誤, 就會(huì)有runtime error了.

官方給出的例子是需要從給定路徑加載圖片, 如果加載失敗就拋出錯(cuò)誤. 有的時(shí)候, 你很確定這張圖片是肯定存在的, 比如隨應(yīng)用一起下載的圖片, 在這種情況就比較適合去除錯(cuò)誤傳遞了.

let photo = try! loadImage("./Resources/John Appleseed.jpg")

執(zhí)行清理行為(Specifying Cleanup Actions)

在處理錯(cuò)誤的時(shí)候可能要關(guān)閉一些資源, 例如關(guān)閉掉打開的文件(又是這個(gè)例子). 有時(shí)候可能代碼寫多了就會(huì)忘記寫, 導(dǎo)致出現(xiàn)一些問題, 所以Swift里面引入了一個(gè)叫defer(延遲)的關(guān)鍵字, 這個(gè)關(guān)鍵字可以在代碼塊要結(jié)束的時(shí)候執(zhí)行(所謂代碼塊簡單來說就是用大括號(hào)包起來的代碼). 所以, 不管是拋出錯(cuò)誤, 還是return, break, 一旦要離開就開始執(zhí)行defer的代碼.
注意: 如果多個(gè)defer在一起, 我這邊測試情況是, 先執(zhí)行后面的, 再執(zhí)行前面的, 并不是按照編碼的順序. 因此, 可以推斷Swift應(yīng)該是把需要defer的代碼push到棧里面, 之后一個(gè)個(gè)執(zhí)行. 至于為什么要這樣, 我猜測了幾種可能性都不能很好地解釋, 估計(jì)是某些情況打開多個(gè)資源, 子元件又互相有依賴, 那么先釋放掉最后創(chuàng)建的肯定是相對(duì)安全的, 因?yàn)閯?chuàng)建資源1的時(shí)候還沒有資源2, 說明資源1,2共存是可以接受的, 但是只有2沒有1則不一定了.

另外需要說明的是, defer里面不能出現(xiàn)break, return或者拋出異常.

說了這么多, 還是先來看看例子吧:

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
}
// 我自己寫了一個(gè)更加簡明的例子:
var x = 0
var y = 0

if (1 < 2){
    x = 1
    y = 2
    defer{
        print("\(x)+\(y)=\(x+y)")
    }
    defer{
        print("\(2*x)+\(2*y)=\(2*x+2*y)")
    }
}

x = 3
y = 4
// 結(jié)果輸出:
2+4=6
1+2=3

錯(cuò)誤處理到這邊差不多了, 比較推薦去看下WWDC上的相關(guān)視頻, 細(xì)節(jié)還是參考官方文檔

2016/03/09補(bǔ)充內(nèi)容:
關(guān)于錯(cuò)誤處理有這么個(gè)情況: 假如你寫一個(gè)函數(shù), 函數(shù)接受一個(gè)閉包, 并執(zhí)行它, 如果這個(gè)閉包會(huì)拋出異常, 那么負(fù)責(zé)拋出呢, 如果是閉包拋出, 那么此函數(shù)的調(diào)用方怎么知道這個(gè)函數(shù)是否拋出異常, 拋出什么異常? 甚至再加一個(gè)條件, 如果這個(gè)函數(shù)異步執(zhí)行這個(gè)閉包呢?
第一個(gè)問題的答案是, 誰出錯(cuò)誰拋出, 但是, 函數(shù)要加一個(gè)rethrows,
例如:

enum NumberError:ErrorType {
    case ExceededInt32Max
}

func functionWithCallback(callback:(Int) throws -> Int) rethrows {
    try callback(Int(Int32.max)+1)
}

do {
 try functionWithCallback({v in 
    if v <= Int(Int32.max) { 
        return v 
    }; 
    throw NumberError.ExceededInt32Max
  })
}
catch NumberError.ExceededInt32Max {
    "Error: exceeds Int32 maximum"
}
catch {
}

至于第二種情況, 參考這篇文章, 里面還涉及到了Promise, Result和Monad, 完全理解有一點(diǎn)難度.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末急鳄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扮叨,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疫铜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來双谆,“玉大人壳咕,你說我怎么就攤上這事⊥绮觯” “怎么了谓厘?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寸谜。 經(jīng)常有香客問我竟稳,道長,這世上最難降的妖魔是什么熊痴? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任他爸,我火速辦了婚禮,結(jié)果婚禮上果善,老公的妹妹穿的比我還像新娘诊笤。我一直安慰自己,他們只是感情好巾陕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布讨跟。 她就那樣靜靜地躺著,像睡著了一般鄙煤。 火紅的嫁衣襯著肌膚如雪晾匠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天梯刚,我揣著相機(jī)與錄音混聊,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛句喜,可吹牛的內(nèi)容都是我干的预愤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼咳胃,長吁一口氣:“原來是場噩夢啊……” “哼植康!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起展懈,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤销睁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后存崖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冻记,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年来惧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冗栗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡供搀,死狀恐怖隅居,靈堂內(nèi)的尸體忽然破棺而出秦忿,到底是詐尸還是另有隱情翁授,我是刑警寧澤家夺,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布世剖,位于F島的核電站,受9級(jí)特大地震影響叁扫,放射性物質(zhì)發(fā)生泄漏地来。R本人自食惡果不足惜乘寒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一的诵、第九天 我趴在偏房一處隱蔽的房頂上張望万栅。 院中可真熱鬧,春花似錦奢驯、人聲如沸申钩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邮偎,卻和暖如春管跺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背禾进。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工豁跑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泻云。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓艇拍,卻偏偏與公主長得像狐蜕,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卸夕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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