164,深入理解 Swift 派發(fā)機(jī)制(面試點(diǎn):1电湘,值類型總是會(huì)使用直接派發(fā), 簡單易懂 2,而協(xié)議和類的 extension 都會(huì)使用直接派發(fā) 3鹅经, NSObject 的 extension ...

注:之前看了很多關(guān)于 Swift 派發(fā)機(jī)制的內(nèi)容, 但感覺沒有一篇能夠徹底講清楚這件事情, 看完了這篇文章之后我對 Swift 的派發(fā)機(jī)制才建立起了初步的認(rèn)知.

image.png

一張表總結(jié)引用類型, 修飾符和它們對于 Swift 函數(shù)派發(fā)方式的影響.

函數(shù)派發(fā)就是程序判斷使用哪種途徑去調(diào)用一個(gè)函數(shù)的機(jī)制. 每次函數(shù)被調(diào)用時(shí)都會(huì)被觸發(fā), 但你又不會(huì)太留意的一個(gè)東西. 了解派發(fā)機(jī)制對于寫出高性能的代碼來說很有必要, 而且也能夠解釋很多 Swift 里”奇怪”的行為.

編譯型語言有三種基礎(chǔ)的函數(shù)派發(fā)方式: 直接派發(fā)(Direct Dispatch), 函數(shù)表派發(fā)(Table Dispatch) 和 消息機(jī)制派發(fā)(Message Dispatch), 下面我會(huì)仔細(xì)講解這幾種方式. 大多數(shù)語言都會(huì)支持一到兩種, Java 默認(rèn)使用函數(shù)表派發(fā), 但你可以通過 final 修飾符修改成直接派發(fā). C++ 默認(rèn)使用直接派發(fā), 但可以通過加上 virtual 修飾符來改成函數(shù)表派發(fā). 而 Objective-C 則總是使用消息機(jī)制派發(fā), 但允許開發(fā)者使用 C 直接派發(fā)來獲取性能的提高. 這樣的方式非常好, 但也給很多開發(fā)者帶來了困擾,

派發(fā)方式 (Types of Dispatch )

程序派發(fā)的目的是為了告訴 CPU 需要被調(diào)用的函數(shù)在哪里, 在我們深入 Swift 派發(fā)機(jī)制之前, 先來了解一下這三種派發(fā)方式, 以及每種方式在動(dòng)態(tài)性和性能之間的取舍.

直接派發(fā) (Direct Dispatch)

直接派發(fā)是最快的, 不止是因?yàn)樾枰{(diào)用的指令集會(huì)更少, 并且編譯器還能夠有很大的優(yōu)化空間, 例如函數(shù)內(nèi)聯(lián)等, 但這不在這篇博客的討論范圍. 直接派發(fā)也有人稱為靜態(tài)調(diào)用.

然而, 對于編程來說直接調(diào)用也是最大的局限, 而且因?yàn)槿狈?dòng)態(tài)性所以沒辦法支持繼承.

函數(shù)表派發(fā) (Table Dispatch )

函數(shù)表派發(fā)是編譯型語言實(shí)現(xiàn)動(dòng)態(tài)行為最常見的實(shí)現(xiàn)方式. 函數(shù)表使用了一個(gè)數(shù)組來存儲(chǔ)類聲明的每一個(gè)函數(shù)的指針. 大部分語言把這個(gè)稱為 “virtual table”(虛函數(shù)表), Swift 里稱為 “witness table”. 每一個(gè)類都會(huì)維護(hù)一個(gè)函數(shù)表, 里面記錄著類所有的函數(shù), 如果父類函數(shù)被 override 的話, 表里面只會(huì)保存被 override 之后的函數(shù). 一個(gè)子類新添加的函數(shù), 都會(huì)被插入到這個(gè)數(shù)組的最后. 運(yùn)行時(shí)會(huì)根據(jù)這一個(gè)表去決定實(shí)際要被調(diào)用的函數(shù).

舉個(gè)例子, 看看下面兩個(gè)類:

class ParentClass {
    func method1() {}
    func method2() {}
}
class ChildClass: ParentClass {
    override func method2() {}
    func method3() {}
}

在這個(gè)情況下, 編譯器會(huì)創(chuàng)建兩個(gè)函數(shù)表, 一個(gè)是 ParentClass 的, 另一個(gè)是 ChildClass的:

image.png

這張表展示了 ParentClass 和 ChildClass 虛數(shù)表里 method1, method2, method3 在內(nèi)存里的布局.

let obj = ChildClass()
obj.method2()

當(dāng)一個(gè)函數(shù)被調(diào)用時(shí), 會(huì)經(jīng)歷下面的幾個(gè)過程:
1,讀取對象 0xB00 的函數(shù)表.
2,讀取函數(shù)指針的索引. 在這里, method2 的索引是1(偏移量), 也就是 0xB00 + 1.
3,跳到 0x222 (函數(shù)指針指向0x222)

查表是一種簡單, 易實(shí)現(xiàn), 而且性能可預(yù)知的方式. 然而, 這種派發(fā)方式比起直接派發(fā)還是慢一點(diǎn). 從字節(jié)碼角度來看, 多了兩次讀和一次跳轉(zhuǎn), 由此帶來了性能的損耗. 另一個(gè)慢的原因在于編譯器可能會(huì)由于函數(shù)內(nèi)執(zhí)行的任務(wù)導(dǎo)致無法優(yōu)化. (如果函數(shù)帶有副作用的話)

消息機(jī)制派發(fā) (Message Dispatch )

消息機(jī)制是調(diào)用函數(shù)最動(dòng)態(tài)的方式. 也是 Cocoa 的基石, 這樣的機(jī)制催生了 KVO, UIAppearence 和 CoreData 等功能. 這種運(yùn)作方式的關(guān)鍵在于開發(fā)者可以在運(yùn)行時(shí)改變函數(shù)的行為. 不止可以通過 swizzling 來改變, 甚至可以用 isa-swizzling 修改對象的繼承關(guān)系, 可以在面向?qū)ο蟮幕A(chǔ)上實(shí)現(xiàn)自定義派發(fā).

舉個(gè)例子, 看看下面兩個(gè)類:

class ParentClass {
    dynamic func method1() {}
    dynamic func method2() {}
}
class ChildClass: ParentClass {
    override func method2() {}
    dynamic func method3() {}
}

Swift 會(huì)用樹來構(gòu)建這種繼承關(guān)系:


image.png

這張圖很好地展示了 Swift 如何使用樹來構(gòu)建類和子類.

當(dāng)一個(gè)消息被派發(fā), 運(yùn)行時(shí)會(huì)順著類的繼承關(guān)系向上查找應(yīng)該被調(diào)用的函數(shù). 如果你覺得這樣做效率很低, 它確實(shí)很低! 然而, 只要緩存建立了起來, 這個(gè)查找過程就會(huì)通過緩存來把性能提高到和函數(shù)表派發(fā)一樣快. 但這只是消息機(jī)制的原理, 這里有一篇文章很深入的講解了具體的技術(shù)細(xì)節(jié).

Swift 的派發(fā)機(jī)制

那么, 到底 Swift 是怎么派發(fā)的呢? 我沒能找到一個(gè)很簡明扼要的答案, 但這里有四個(gè)選擇具體派發(fā)方式的因素存在:
1.聲明的位置
2.引用類型
3.特定的行為
4.顯式地優(yōu)化(Visibility Optimizations)
在解釋這些因素之前, 我有必要說清楚, Swift 沒有在文檔里具體寫明什么時(shí)候會(huì)使用函數(shù)表什么時(shí)候使用消息機(jī)制. 唯一的承諾是使用 dynamic 修飾的時(shí)候會(huì)通過 Objective-C 的運(yùn)行時(shí)進(jìn)行消息機(jī)制派發(fā). 下面我寫的所有東西, 都只是我在 Swift 3.0 里測試出來的結(jié)果, 并且很可能在之后的版本更新里進(jìn)行修改.

聲明的位置 (Location Matters)

在 Swift 里, 一個(gè)函數(shù)有兩個(gè)可以聲明的位置: 類型聲明的作用域, 和 extension. 根據(jù)聲明類型的不同, 也會(huì)有不同的派發(fā)方式.

class MyClass {
    func mainMethod() {}
}
extension MyClass {
    func extensionMethod() {}
}

上面的例子里, mainMethod 會(huì)使用函數(shù)表派發(fā), 而 extensionMethod 則會(huì)使用直接派發(fā). 當(dāng)我第一次發(fā)現(xiàn)這件事情的時(shí)候覺得很意外, 直覺上這兩個(gè)函數(shù)的聲明方式并沒有那么大的差異. 下面是我根據(jù)類型, 聲明位置總結(jié)出來的函數(shù)派發(fā)方式的表格.

image.png

這張表格展示了默認(rèn)情況下 Swift 使用的派發(fā)方式.
總結(jié)起來有這么幾點(diǎn):

  • 值類型總是會(huì)使用直接派發(fā), 簡單易懂
  • 而協(xié)議和類的extension都會(huì)使用直接派發(fā)
  • NSObjectextension 會(huì)使用消息機(jī)制進(jìn)行派發(fā)
  • NSObject 聲明作用域里的函數(shù)都會(huì)使用函數(shù)表進(jìn)行派發(fā).
  • 協(xié)議里聲明的, 并且?guī)в心J(rèn)實(shí)現(xiàn)的函數(shù)會(huì)使用函數(shù)表進(jìn)行派發(fā)
引用類型 (Reference Type Matters)

引用的類型決定了派發(fā)的方式. 這很顯而易見, 但也是決定性的差異. 一個(gè)比較常見的疑惑, 發(fā)生在一個(gè)協(xié)議拓展和類型拓展同時(shí)實(shí)現(xiàn)了同一個(gè)函數(shù)的時(shí)候.

protocol MyProtocol {
}
struct MyStruct: MyProtocol {
}
extension MyStruct {
    func extensionMethod() {
        print("結(jié)構(gòu)體")
    }
}
extension MyProtocol {
    func extensionMethod() {
        print("協(xié)議")
    }
}
 
let myStruct = MyStruct()
let proto: MyProtocol = myStruct
 
myStruct.extensionMethod() // -> “結(jié)構(gòu)體”
proto.extensionMethod() // -> “協(xié)議”

剛接觸 Swift 的人可能會(huì)認(rèn)為 proto.extensionMethod()調(diào)用的是結(jié)構(gòu)體里的實(shí)現(xiàn). 但是, 引用的類型決定了派發(fā)的方式, 協(xié)議拓展里的函數(shù)會(huì)使用直接調(diào)用. 如果把extensionMethod 的聲明移動(dòng)到協(xié)議的聲明位置的話, 則會(huì)使用函數(shù)表派發(fā), 最終就會(huì)調(diào)用結(jié)構(gòu)體里的實(shí)現(xiàn). 并且要記得, 如果兩種聲明方式都使用了直接派發(fā)的話, 基于直接派發(fā)的運(yùn)作方式, 我們不可能實(shí)現(xiàn)預(yù)想的 override行為. 這對于很多從 Objective-C 過渡過來的開發(fā)者是反直覺的.

Swift JIRA(缺陷跟蹤管理系統(tǒng)) 也發(fā)現(xiàn)了幾個(gè) bugs, Swfit-Evolution 郵件列表里有一大堆討論, 也有一大堆博客討論過這個(gè). 但是, 這好像是故意這么做的, 雖然官方文檔沒有提過這件事情

指定派發(fā)方式 (Specifying Dispatch Behavior)

Swift 有一些修飾符可以指定派發(fā)方式.

final

final 允許類里面的函數(shù)使用直接派發(fā). 這個(gè)修飾符會(huì)讓函數(shù)失去動(dòng)態(tài)性. 任何函數(shù)都可以使用這個(gè)修飾符, 就算是 extension 里本來就是直接派發(fā)的函數(shù). 這也會(huì)讓 Objective-C 的運(yùn)行時(shí)獲取不到這個(gè)函數(shù), 不會(huì)生成相應(yīng)的 selector.

dynamic

dynamic 可以讓類里面的函數(shù)使用消息機(jī)制派發(fā). 使用 dynamic, 必須導(dǎo)入 Foundation 框架, 里面包括了 NSObjectObjective-C 的運(yùn)行時(shí). dynamic可以讓聲明在 extension 里面的函數(shù)能夠被 override. dynamic 可以用在所有 NSObject 的子類和 Swift 的原聲類.

@objc & @nonobjc

@objc@nonobjc 顯式地聲明了一個(gè)函數(shù)是否能被 Objective-C 的運(yùn)行時(shí)捕獲到. 使用 @objc 的典型例子就是給 selector 一個(gè)命名空間 @objc(abc_methodName), 讓這個(gè)函數(shù)可以被 Objective-C 的運(yùn)行時(shí)調(diào)用. @nonobjc 會(huì)改變派發(fā)的方式, 可以用來禁止消息機(jī)制派發(fā)這個(gè)函數(shù), 不讓這個(gè)函數(shù)注冊到 Objective-C 的運(yùn)行時(shí)里. 我不確定這跟 final 有什么區(qū)別, 因?yàn)閺氖褂脠鼍皝碚f也幾乎一樣. 我個(gè)人來說更喜歡 final, 因?yàn)橐鈭D更加明顯.

final @objc

可以在標(biāo)記為 final 的同時(shí), 也使用 @objc 來讓函數(shù)可以使用消息機(jī)制派發(fā). 這么做的結(jié)果就是, 調(diào)用函數(shù)的時(shí)候會(huì)使用直接派發(fā), 但也會(huì)在 Objective-C 的運(yùn)行時(shí)里注冊響應(yīng)的 selector. 函數(shù)可以響應(yīng) perform(selector:)以及別的 Objective-C 特性, 但在直接調(diào)用時(shí)又可以有直接派發(fā)的性能.

@inline

Swift 也支持 @inline, 告訴編譯器可以使用直接派發(fā). 有趣的是, dynamic @inline(__always) func dynamicOrDirect() {} 也可以通過編譯! 但這也只是告訴了編譯器而已, 實(shí)際上這個(gè)函數(shù)還是會(huì)使用消息機(jī)制派發(fā). 這樣的寫法看起來像是一個(gè)未定義的行為, 應(yīng)該避免這么做.

修飾符總結(jié) (Modifier Overview)
image.png

這張圖總結(jié)這些修飾符對于 Swift 派發(fā)方式的影響.

可見的都會(huì)被優(yōu)化 (Visibility Will Optimize)

Swift 會(huì)盡最大能力去優(yōu)化函數(shù)派發(fā)的方式. 例如, 如果你有一個(gè)函數(shù)從來沒有 override, Swift 就會(huì)檢車并且在可能的情況下使用直接派發(fā). 這個(gè)優(yōu)化大多數(shù)情況下都表現(xiàn)得很好, 但對于使用了 target / action 模式的 Cocoa 開發(fā)者就不那么友好了. 例如:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.rightBarButtonItem = UIBarButtonItem(
        title: "登錄", style: .plain, target: nil,
        action: #selector(ViewController.signInAction)
    )
}
private func signInAction() {}

這里編譯器會(huì)拋出一個(gè)錯(cuò)誤:Argument of '#selector' refers to a method that is not exposed to Objective-C (Objective-C 無法獲取 #selector 指定的函數(shù)). 你如果記得 Swift 會(huì)把這個(gè)函數(shù)優(yōu)化為直接派發(fā)的話, 就能理解這件事情了. 這里修復(fù)的方式很簡單: 加上 @objc 或者dynamic 就可以保證 Objective-C 的運(yùn)行時(shí)可以獲取到函數(shù)了. 這種類型的錯(cuò)誤也會(huì)發(fā)生在UIAppearance 上, 依賴于 proxyNSInvocation 的代碼.

另一個(gè)需要注意的是, 如果你沒有使用 dynamic 修飾的話, 這個(gè)優(yōu)化會(huì)默認(rèn)讓 KVO 失效. 如果一個(gè)屬性綁定了 KVO 的話, 而這個(gè)屬性的 gettersetter 會(huì)被優(yōu)化為直接派發(fā), 代碼依舊可以通過編譯, 不過動(dòng)態(tài)生成的 KVO 函數(shù)就不會(huì)被觸發(fā).

派發(fā)總結(jié) (Dispatch Summary)

這里有一大堆規(guī)則要記住, 所以我整理了一個(gè)表格:


image.png

這張表總結(jié)引用類型, 修飾符和它們對于 Swift 函數(shù)派發(fā)的影響

NSObject 以及動(dòng)態(tài)性的損失 (NSObject and the Loss of Dynamic Behavior)

不久之前還有一群 Cocoa 開發(fā)者討論動(dòng)態(tài)行為帶來的問題. 這段討論很有趣, 提了一大堆不同的觀點(diǎn). 我希望可以在這里繼續(xù)探討一下, 有幾個(gè) Swift 的派發(fā)方式我覺得損害了動(dòng)態(tài)性, 順便說一下我的解決方案.

NSObject 的函數(shù)表派發(fā) (Table Dispatch in NSObject)

上面, 我提到 NSObject 子類定義里的函數(shù)會(huì)使用函數(shù)表派發(fā). 但我覺得很迷惑, 很難解釋清楚, 并且由于下面幾個(gè)原因, 這也只帶來了一點(diǎn)點(diǎn)性能的提升:

  • 大部分 NSObject 的子類都是在 obj_msgSend 的基礎(chǔ)上構(gòu)建的. 我很懷疑這些派發(fā)方式的優(yōu)化, 實(shí)際到底會(huì)給 Cocoa 的子類帶來多大的提升.
  • 大多數(shù) SwiftNSObject 子類都會(huì)使用 extension 進(jìn)行拓展, 都沒辦法使用這種優(yōu)化.

最后, 有一些小細(xì)節(jié)會(huì)讓派發(fā)方式變得很復(fù)雜.

派發(fā)方式的優(yōu)化破壞了 NSObject 的功能 (Dispatch Upgrades Breaking NSObject Features)

性能提升很棒, 我很喜歡 Swift對于派發(fā)方式的優(yōu)化. 但是, UIView 子類顏色的屬性理論上性能的提升破壞了 UIKit 現(xiàn)有的模式.

NSObject 作為一個(gè)選擇 (NSObject as a Choice)

使用靜態(tài)派發(fā)的話結(jié)構(gòu)體是個(gè)不錯(cuò)的選擇, 而使用消息機(jī)制派發(fā)的話則可以考慮 NSObject. 現(xiàn)在, 如果你想跟一個(gè)剛學(xué) Swift 的開發(fā)者解釋為什么某個(gè)東西是一個(gè) NSObject 的子類, 你不得不去介紹 Objective-C 以及這段歷史. 現(xiàn)在沒有任何理由去繼承 NSObject 構(gòu)建類, 除非你需要使用 Objective-C 構(gòu)建的框架.

目前, NSObjectSwift 里的派發(fā)方式, 一句話總結(jié)就是復(fù)雜, 跟理想還是有差距. 我比較想看到這個(gè)修改: 當(dāng)你繼承 NSObject 的時(shí)候, 這是一個(gè)你想要完全使用動(dòng)態(tài)消息機(jī)制的表現(xiàn).

顯式的動(dòng)態(tài)性聲明 (Implicit Dynamic Modification)

另一個(gè) ```Swift 可以改進(jìn)的地方就是函數(shù)動(dòng)態(tài)性的檢測. 我覺得在檢測到一個(gè)函數(shù)被 #selector 和 #keypath 引用時(shí)要自動(dòng)把這些函數(shù)標(biāo)記為 dynamic, 這樣的話就會(huì)解決大部分 UIAppearance 的動(dòng)態(tài)問題, 但也許有別的編譯時(shí)的處理方式可以標(biāo)記這些函數(shù).

Error 以及 Bug (Errors and Bugs)

為了讓我們對 Swift 的派發(fā)方式有更多了解, 讓我們來看一下 Swift 開發(fā)者遇到過的 error.

SR-584

這個(gè) Swift bugSwift 函數(shù)派發(fā)的一個(gè)功能. 存在于 NSObject 子類聲明的函數(shù)(函數(shù)表派發(fā)), 以及聲明在 extension 的函數(shù)(消息機(jī)制派發(fā))中. 為了更好地描述這個(gè)情況, 我們先來創(chuàng)建一個(gè)類:

class Person: NSObject {
    func sayHi() {
        print("Hello")
    }
}
func greetings(person: Person) {
    person.sayHi()
}
greetings(person: Person()) // prints 'Hello'

greetings(person:)函數(shù)使用函數(shù)表派發(fā)來調(diào)用sayHi(). 就像我們看到的, 期望的,“Hello”會(huì)被打印. 沒什么好講的地方, 那現(xiàn)在讓我們繼承 Persion:

class MisunderstoodPerson: Person {}
extension MisunderstoodPerson {
    override func sayHi() {
        print("No one gets me.")
    }
}

greetings(person: MisunderstoodPerson()) // prints 'Hello'

可以看到, sayHi() 函數(shù)是在 extension 里聲明的, 會(huì)使用消息機(jī)制進(jìn)行調(diào)用. 當(dāng)greetings(person:) 被觸發(fā)時(shí), sayHi() 會(huì)通過函數(shù)表被派發(fā)到 Person 對象, 而misunderstoodPerson 重寫之后會(huì)是用消息機(jī)制, 而 MisunderstoodPerson 的函數(shù)表依舊保留了 Person 的實(shí)現(xiàn), 緊接著歧義就產(chǎn)生了.

在這里的解決方法是保證函數(shù)使用相同的消息派發(fā)機(jī)制. 你可以給函數(shù)加上 dynamic 修飾符, 或者是把函數(shù)的實(shí)現(xiàn)從 extension 移動(dòng)到類最初聲明的作用域里.

理解了 Swift 的派發(fā)方式, 就能夠理解這個(gè)行為產(chǎn)生的原因了, 雖然 Swift 不應(yīng)該讓我們遇到這個(gè)問題.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寂呛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瘾晃,更是在濱河造成了極大的恐慌昧谊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酗捌,死亡現(xiàn)場離奇詭異呢诬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)胖缤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門尚镰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哪廓,你說我怎么就攤上這事狗唉。” “怎么了涡真?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵分俯,是天一觀的道長。 經(jīng)常有香客問我哆料,道長缸剪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任东亦,我火速辦了婚禮杏节,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己奋渔,他們只是感情好蒿叠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布统求。 她就那樣靜靜地躺著,像睡著了一般隘马。 火紅的嫁衣襯著肌膚如雪致稀。 梳的紋絲不亂的頭發(fā)上岩馍,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天鳖目,我揣著相機(jī)與錄音泽本,去河邊找鬼。 笑死捻爷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的份企。 我是一名探鬼主播也榄,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼司志!你這毒婦竟也來了甜紫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤骂远,失蹤者是張志新(化名)和其女友劉穎囚霸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體激才,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拓型,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘸恼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劣挫。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖东帅,靈堂內(nèi)的尸體忽然破棺而出压固,到底是詐尸還是另有隱情,我是刑警寧澤靠闭,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布帐我,位于F島的核電站,受9級特大地震影響愧膀,放射性物質(zhì)發(fā)生泄漏拦键。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一檩淋、第九天 我趴在偏房一處隱蔽的房頂上張望矿咕。 院中可真熱鬧,春花似錦、人聲如沸碳柱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莲镣。三九已至福稳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瑞侮,已是汗流浹背的圆。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留半火,地道東北人越妈。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像钮糖,于是被迫代替她去往敵國和親梅掠。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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