Swift閉包

前情提要

Swift的閉包和OC的Block是一回事,是一種特殊的函數-帶有自動變量的匿名函數。

分別從語法和原理上講解閉包

語法

Swift中,函數和閉包都是引用類型

無論你什么時候賦值一個函數或者閉包給常量或者變量,你實際上都是將常量和變量設置為對函數和閉包的引用

如果你分配了一個閉包給類型的屬性,并且閉包通過引用該實例或者它的成員來捕獲實例,你將在閉包和實例間產生循環引用

閉包表達式:

閉包表達式語法能夠使用常量形式參數,變量形式參數,輸入輸出形式參數,以及可變形式參數,但在使用可變形式參數時需要在形式參數列表的最后面使用。參數不能提供默認值。元組也可被用來作為形式參數和返回類型

{? ? (parameter) -> (return type) in

? ? statements

}

從sorted(by:)函數說起,它會根據你提供的排序閉包將已知類型的數組的值進行排序。一旦完成,sorted方法會返回排序好的新數組并且不會改變原數組。

不用閉包:

let names = ["zhangsan", "lisi", "wang2", "liu5"]

func backward(_ s1: String, _ s2: String) -> Bool {

? ? return s1 > s2

}

var reversedNames = names.sorted(by: backward)

print(reversedNames)

使用閉包:

let names = ["zhangsan", "lisi", "wang2", "liu5"]

let?reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in?

? ? return s1 > s2

?})

print(reversedNames)

因為排序閉包作為實際參數來傳遞給函數,所以Swift能推斷出它的形式參數類型和返回值類型

sorted(by:)方法期望它的形式參數是一個(String, String) -> Bool類型的函數。這意味著(String, String)和Bool類型不需要被寫成閉包表達式定義中的一部分,因為所有的類型都能被推斷,返回箭頭(->)和圍繞在形式參數名周圍的括號也能被省略。

let names = ["zhangsan", "lisi", "wang2", "liu5"]

let?reversedNames = names.sorted(by: { s1, s2 in return s1 > s2?})

print(reversedNames)

單表達式的閉包能夠通過從它們的聲明中刪掉return關鍵字來隱式返回它們單個表達式的結果(函數也可以,重點是單表達式)

let names = ["zhangsan", "lisi", "wang2", "liu5"]

let?reversedNames = names.sorted(by: { s1, s2 in s1 > s2?})

print(reversedNames)

Swift自動對行內閉包提供簡寫實際參數名,可以通過$0,$1,$2等名字來引用閉包的實際參數值。

let names = ["zhangsan", "lisi", "wang2", "liu5"]

let?reversedNames = names.sorted(by: {? $0 > $1?})

print(reversedNames)

Swift的String類型定義了關于大于號(>)的特定字符串實現,讓其作為一個有兩個String類型形式參數的函數并返回一個Bool類型的值。這正好與sorted(by:)方法相匹配。因此,你能簡單地傳遞一個大于號,并且Swift將推斷你想使用大于號特殊字符串函數實現。

let names = ["zhangsan", "lisi", "wang2", "liu5"]

let?reversedNames = names.sorted(by: >?)

print(reversedNames)



尾隨閉包

如果你需要將一個很長的閉包表達式作為函數最后一個實際參數傳遞給函數,使用尾隨閉包將增強函數的可讀性。尾隨閉包是一個被書寫在函數形式參數的括號外面的閉包表達式,例:XXX(_ xx: X, ...)尾隨閉包

let names = ["zhangsan", "lisi", "wang2", "liu5"]

let?reversedNames = names.sorted{? $0 > $1?}

print(reversedNames)

捕獲值

一個閉包能夠從上下文捕獲已被定義的常量和變量。即使定義這些常量和變量的原作用于已經不存在,閉包仍能夠在其函數體內引用和修改這些值

作為一種優化,如果一個值沒有改變或者在閉包的外面(閉包沒用它),Swift不會去捕獲它

Swift也處理了變量的內存管理操作,當變量不再需要時會被釋放

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

? ? var runningTotal = 0

? ? func incrementer() -> Int {

// 捕獲了runnigTotal 并在這個閉包(內嵌函數)內,申請了一塊新的內存空間給變量runningTotal

? ? ? ? runningTotal += amount

? ? ? ? retun runningTotal

????}

? ? return incrementer

}

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen() --10

incrementByTen() --20

incrementByTen() --30

如果你有一個新的變量引用了函數makeIncrementer

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen() --10

incrementByTen() --20

incrementByTen() --30

let incrementBySeven =makeIncrementer(forIncrement:7)

let incrementBySeven() --7

let incrementBySeven() --14

let incrementBySeven() --21

通過內存地址來分析這一過程都發生了什么?

分析代碼:

fileprivate?func?makeIncrementer(forIncrement amount:Int) -> () ->Int{

? ? ? ? var?runningTotal =0

? ? ? ? print("-------------------")

? ? ? ? print("內嵌函數外的runningTotal地址:")

? ? ? ? print(Unmanaged.passUnretained(runningTotalasAnyObject).toOpaque())

? ? ? ? print("-------------------")

? ? ? ? func?incrementer() ->Int{

? ? // 捕獲了runnigTotal 并在這個閉包(內嵌函數)內,申請了一塊新的內存空間給變量runningTotal

? ? ? ? ? ? runningTotal+=amount

? ? ? ? ? ? print("內嵌函數內的runningTotal地址:")

? ? ? ? ? ? print(Unmanaged.passUnretained(runningTotalasAnyObject).toOpaque())


? ? ? ? ? ? return?runningTotal

? ? ? ? }

? ? ? ? return incrementer

? ? }



? ? ? ?leti ncrementByTen =makeIncrementer(forIncrement:10)

? ? ? ? print(incrementByTen())

? ? ? ? print(incrementByTen())

? ? ? ? print(incrementByTen())

? ? ? ? let?incrementBySeven =makeIncrementer(forIncrement:7)

? ? ? ? print(incrementBySeven())

? ? ? ? print(incrementBySeven())

? ? ? ? print(incrementBySeven())


? ? ? ? print(makeIncrementer(forIncrement:10)())

? ? ? ? print(makeIncrementer(forIncrement:10)())

? ? ? ? print(makeIncrementer(forIncrement:11)())

代碼對應的print:

-------------------

內嵌函數外的runningTotal地址:

0xbd27871c8d0870d3

-------------------

內嵌函數內的runningTotal地址:

0xbd27871c8d08707310

內嵌函數內的runningTotal地址:

0xbd27871c8d08719320

內嵌函數內的runningTotal地址:

0xbd27871c8d08713330

-------------------

內嵌函數外的runningTotal地址:

0xbd27871c8d0870d3

-------------------

內嵌函數內的runningTotal地址:

0xbd27871c8d0870a37

內嵌函數內的runningTotal地址:

0xbd27871c8d08703314

內嵌函數內的runningTotal地址:

0xbd27871c8d08718321

-------------------

內嵌函數外的runningTotal地址:

0xbd27871c8d0870d3

-------------------

內嵌函數內的runningTotal地址:

0xbd27871c8d08707310

-------------------

內嵌函數外的runningTotal地址:

0xbd27871c8d0870d3

-------------------

內嵌函數內的runningTotal地址:

0xbd27871c8d08707310

-------------------

內嵌函數外的runningTotal地址:

0xbd27871c8d0870d3

-------------------

內嵌函數內的runningTotal地址:

0xbd27871c8d08706311

分析

1.變量incrementByTen和incrementBySeven引用的函數makeIncrementer是相同的,makeIncrementer(forIncrement:10)和makeIncrementer(forIncrement:7)所返回的內嵌函數(閉包)地址也是相同的,并沒有創建兩個blcok;

2.在執行賦值(let?incrementByTen = makeIncrementer(forIncrement:10))的過程中,內嵌函數外的runningTotal地址也是相同的;

3.在執行incrementByTen()時,內嵌函數內的runningTotal地址每一次都是不同的,意味著runningtotal被blcok所捕獲,每執行一次都會在前一次的基礎上把runningtotal復制到一個新地址上再執行相加。

4.累加,是因為變量incrementByTen/incrementBySeven維護著自己block捕獲的變量。當單獨執行makeIncrementer(forIncrement:10)()時,無論多少次,也不會累加,因為沒有變量去維護block捕獲的變量,執行完就釋放了。

5.第一次執行incrementByTen()和直接執行makeIncrementer(forIncrement:10)(),的內嵌函數runningTotal地址一樣,這是內存行為,提高了效率。

逃逸閉包

當閉包作為一個實際參數傳遞給一個函數的時候,并且它會在函數返回之后調用(比如封裝的網絡請求),我們就說這個閉包逃逸了。當你聲明一個接受閉包作為形式參數的函數時(比如封裝的帶閉包的網絡請求),你可以在形式參數前寫@escaping來明確閉包是允許逃逸的。

閉包可以逃逸的一種方法是被存儲在定義于函數外的變量里。比如說,很多函數接收閉包實際參數來作為啟動異步任務的回調。函數在啟動任務后返回,但是閉包要直到任務完成--閉包需要逃逸,以便稍后調用

讓閉包@escaping意味著你必須在閉包中顯示地引用self

var completionHandlers: [() -> void] = []

func someFunctionWithEscapingClousure(completionHandler: @escaping () -> void) {

? ? completionHandlers.append(completionHandler)

}

func someFuncWithNonEscapingClosure(closure: () -> void) {

? ? closure()

}

class SomeClass {

? ? var x = 10

? ? func doSomething() {

????????someFunctionWithEscapingClousure {self.x = 100}

? ? ? ? ?someFuncWithNonEscapingClosure {x = 200}

????}

}

let instance = someClass()

instance.doSomething()

print(instance.x) -- 200

completionHandlers.first?()

print(instance.x) -- 100

自動閉包

自動閉包是一種自動創建的用來把作為實際參數傳遞給函數的表達式打包的閉包。它不接受任何實際參數,并且當它被調用時,它會返回內部打包的表達式的值。

這個語法的好處在于通過寫普通表達式代替顯示閉包而使你省略包圍函數形式參數的括號

public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticsString = #file, line: Uint = #line)

let number = 3

assert(number > 3, "number 不大于3")

自動閉包允許你延遲處理,因此閉包內部的代碼直到你調用它的時候才會運行。對于有副作用或者占用資源的代碼來說很有用,因為它可以允許你控制代碼何時才進行求值

var customersInLine = ["熬", "2", "3", "4", "5"]

print(customersInLine.count) -- 5

let customerProvider = { customersInLine.remove(at: 0) }

print(customersInLine.count) -- 5

print("Now serving \(customerProvider())!") -- 熬

print(customersInLine.count) -- 4

當你傳一個閉包作為實際參數到函數的時候,你會得到與延遲處理相同的行為

fileprivate?func?serve(customer customerProvider: () ->String) {

? ? ? ? print("Now serving \(customerProvider())!")

? ? }

serve(customer: {customersInLine.remove(at: 0)}) --?Now serving 熬!

通過@autoclosure標志標記他的形式參數使用了自動閉包。現在你可以調用函數就像它接收了一個String實際參數而不是閉包。實際參數自動地轉換為閉包,因為customerProvider形式參數的類型被標記為@autoclosure。

fileprivate?func?serve(customer customerProvider: @autoclosure () ->String) {

? ? ? ? print("Now serving \(customerProvider())!")

? ? }

serve(customer: customersInLine.remove(at: 0)) --Now serving 熬!

同時使用逃逸閉包和自動閉包

? ??????func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) ????????????{

? ? ? ????????? customerProvides.append(customerProvider)

? ????????? }

? ??????var customerProvides: [() -> String] = []

? ??????var?customersInLine = ["熬","2","3","4","5"]

? ? ? ? collectCustomerProviders(customersInLine.remove(at:0))

? ? ? ? collectCustomerProviders(customersInLine.remove(at:0))

? ? ? ? print("Collected\(customerProvides.count)closures.")

? ? ? ? for?customerProvider?in?customerProvides{

? ? ? ? ? ? print("Now serving \(customerProvider())!")

? ? ? ? }

? ? ? ? print("Remainning\(customerProvides.count)closures.")



原理

閉包/Block是一個,帶有自動變量值(可以截獲自動變量值)的匿名函數;

截獲的含義是保存改自動變量的瞬間值

OC中如果要改變block截獲的外部自動變量的值需要在改值前加上__block修飾符,Swift不用,系統會自動處理這些問題。延伸到對象,對于Swift來說系統也會處理但OC不同。

-OC下,截獲一個對象(NSMutableArray)mArray,在block內調用NSMutableArray的方法(addObject:)沒問題,因為Block捕獲的是NSMutableArray類對象用的結構體實例指針,但對其進行操作,比如賦值,就會產生編譯錯誤,要在變量前加上__block。Swift下直接用就行了。

從C代碼的角度分析closure/block。OC可以直接用 clang -rewrite-objc 原文件名 來分析,swift下的clang我沒有找到對應的命令,所以只看oc的。在分析之前,先介紹兩個小概念。

1.OC&Swift中的self

OC Code:

- (void) method:(int)arg {

? ? NSLog(@"%p %d\n", self, arg);

}

MyObject *obj = [[MyObject alloc] init];

[obj method: 10];

轉換成C Code:

void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg) {

? ??NSLog(@"%p %d\n", self, arg);

}

MyObject *obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc"));

obj = objc_msgSend(obj, sel_registerName("init"));

objc_msgSend(obj, sel_registerName("method:"), 10);

最后一條執行語句,也就是[obj method: 10] ->?objc_msgSend(obj, sel_registerName("method:"), 10)。objc_msgSend(obj, sel_registerName("method:"), 10)函數根據指定的對象和函數名,從對象持有類(MyObject)的結構體中檢索method:10對應的函數void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)的指針并調用。此時,objc_msgSend函數的第一個參數obj作為_I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)的第一個參數self進行傳遞。即self就是Myobject類的對象-obj自己。



2.OC中的id

id在C語言中的聲明:

typedef struct objc_object {

? ? Class isa;

} *id;

-id為objc_object結構體的指針類型。

Class在C語言中的聲明:

typedef struct objc_class {

????Class isa;

} *Class;

-Class為objc_class結構體的指針類型。

這與objc_object結構體相同。然而,objc_object結構體和objc_class結構體歸根結底是在各個對象(id)和類(Class)的實現中使用的最基本的結構體。

例:

類MyObject

@interface MyObject : NSObject {

? ? int val0;

? ? int val1;

}

@end

他是結構體,一個基于objc_class結構體的class_t結構體。

struct class_t {

? ? struct class_t *isa;

? ? struct class_t *superclass;

? ? Cache cache;

? ? IMP *vtable;

? ? uintptr_t data_NEVER_USE;

};

而基于MyObject創建的對象(MyObject1/2/3/4/5)的結構體:

struct MyObject1/2/3/4/5 {

? ? Class isa;

? ? int val0;

? ? int val1;

}

MyObject類的實例變量val0,val1被直接聲明為對象的結構體成員。OC中由類生成對象就意味著,生成一個基于該類的結構體實例,這些結構體實例(對象)通過isa保持該類的結構體實例指針(這個例子就是*class_t)。舉個例子就是:有3個類,C動物->C狗->C柯基,基于柯基生成的一個對象叫做KK,那么KK這個結構體就會有一個isa指向C柯基,而這個C柯基類的結構體中還會有一個isa指向C狗,同理C狗的isa指向C動物,一層一層地指向父類(super),直到Class,而Class中的isa指向的就是自己,也就到頭了。

class_t中持有,聲明的成員變量,方法的名稱,方法的實現(函數指針),屬性以及父類的指針,并被OC運行時庫所使用的。



介紹完兩個小概念,開始從C代碼的角度分析Block:

.m文件下的代碼

int?main() {

? ? void(^blk)(void) = ^{printf("Block\n");};

? ? blk();

? ? return0;

}



clang -rewrite-objc 項目文件.m之后,摘取有用的內容:

```c

struct?__block_impl {

? void*isa;

? intFlags;

? intReserved;

? void*FuncPtr;

};

struct?__main_block_impl_0 {

? struct?__block_impl impl;

? struct?__main_block_desc_0* Desc;

? __main_block_impl_0(void?*fp,?struct?__main_block_desc_0 *desc,?int?flags=0) {

? ? impl.isa = &_NSConcreteStackBlock;

? ? impl.Flags = flags;

? ? impl.FuncPtr = fp;

? ? Desc = desc;

? }

};

static?void__main_block_func_0(struct__main_block_impl_0 *__cself) {printf("Block\n");}

static?struct__main_block_desc_0 {

? size_t reserved;

? size_t Block_size;

}

__main_block_desc_0_DATA= {0,?sizeof(struct?__main_block_impl_0)};

int?main() {

? ? void(*blk)(void) = ((void(*)(void))&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));

? ? ((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

? ? return0;

}

```

粗略來看大概有5部分,一步一步看;

1.源碼中的^{printf("Block\n");};轉換后的代碼是static?void__main_block_func_0(struct __main_block_impl_0 *__cself) {

printf("Block\n");}

通過轉換后的代碼可知,通過Block使用的匿名函數實際上被作為簡單的C語言函數來處理。

另外根據Block語法所屬的函數名(此處為main)和該Block語法在該函數出現的順序值(此處為0)來給經clang變換的函數命名(void__main_block_func_0)。

該函數的參數*__cself就是指向Block值的變量,就是他自己。相當于OC實例方法中指向對象自身的變量self。上面的第一個小概念有詳細解釋。來看看該參數,__cself是__main_block_impl_0結構體的指針。

那我們來看看__main_block_impl_0結構體,去掉構造函數后:

struct?__main_block_impl_0 {

? struct?__block_impl impl;

? struct?__main_block_desc_0* Desc;

};

第一個成員變量是impl,他的結構體聲明是:

struct__block_impl {

? void?*isa;

? int?Flags;

? int?Reserved;

? void?*FuncPtr;

};

impl是虛函數列表,該結構體內存放了一個標志,包括今后版本升級所需的區域及函數指針。

第二個成員變量是Desc指針,他的結構體的聲明是:

static?struct__main_block_desc_0 {

? size_t reserved;

? size_t Block_size;

}__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0)};

這些也如同其成員名稱所示,其結構為今后版本升級所需的區域和Block大小。

這兩個成員變量看完之后,再來看一下初始化含有這些結構體的__main_block_impl_0結構體的構造函數,就是我們剛才忽略的那一部分代碼,這個代碼執行的完成意味著_cself初始化完成:

__main_block_impl_0(void?*fp,struct__main_block_desc_0 *desc,intflags=0) {

? ? impl.isa = &_NSConcreteStackBlock;

? ? impl.Flags = flags;

? ? impl.FuncPtr = fp;

? ? Desc = desc;

? }

在分析這個結構體之前,先看一下這個構造函數的調用(在 int main() {...}中):

void(*blk)(void) = ((void(*)(void))&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));

去掉轉換部分 簡化成兩行代碼:

struct __main_block_impl_0 tmp =?__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

struct?__main_block_impl_0 *blk = &tmp;

該源代碼將__main_block_impl_0結構體類型的自動變量(棧上生成的__main_block_impl_0結構體實例的指針),賦值給__main_block_impl_0結構體指針類型的變量blk,對應未轉換前的代碼就是這個賦值操作:

void(^blk)(void) = ^{printf("Block\n");};

即將Block語法生成的Block賦給Block類型變量blk。該源代碼中的Block就是__main_block_impl_0結構體類型的自動變量,即棧上生成的__main_block_impl_0結構體實例。然后指針賦值。

接下來看看__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);結構體實例構造參數。__main_block_func_0和&__main_block_desc_0_DATA。

__main_block_func_0是一個由Block語法轉換的C語言函數指針;

&__main_block_desc_0_DATA是作為靜態全局變量初始化的__main_block_desc_0結構體實例指針。其初始化代碼如下:

__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0)};

由此可知,改源代碼使用Block,即__main_block_impl_0結構體實例的大小,進行初始化。

下面綜合來看一下,棧上的Block即__main_block_impl_0結構體實例的初始化是怎樣完成的(展開):

struct__main_block_impl_0 {

? ? void *isa;

? ? Int Flags;

? ? int Reserved;

? ? void &FuncPtr;

? ? struct?__main_block_desc_0* Desc;

}

該結構體會根據構造函數進行如下的初始化:

isa = &_NSConcreteStackBlock;

Flags = 0;

Reserved = 0;

FuncPtr =?__main_block_func_0;

Desc = &__main_block_desc_0_DATA;

即,將__main_block_func_0函數指針賦值給成員變量FunPtr。



源碼中使用blk的部分blk()對應轉換后的代碼是:

((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

去掉轉換部分:

(*blk->impl.FuncPtr)(blk);

這就是簡單地使用函數指針調用函數。正如我們剛才所確認的,有Block語法轉換的__main_block_func_0函數的指針被賦值成員變量FuncPtr中。另外也說明了,__main_block_func_0函數的參數__cself指向Block值。在調用改函數的源代碼中可以看出Block正是作為參數進行了傳遞。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372