Swift 5.x - 并發(fā)(中文文檔)

引言

繼續(xù)學習Swift文檔碴里,從上一章節(jié):錯誤處理签赃,我們學習了Swift錯誤處理相關(guān)的內(nèi)容哨坪,主要有使用throwing函數(shù),throw拋出錯誤轿腺、使用do-catch來處理錯誤两嘴、將錯誤轉(zhuǎn)為可選值(使用try?)、禁用錯誤傳遞(使用try!)族壳、延遲操作處理(使用defer關(guān)鍵詞)等這些內(nèi)容°颈瑁現(xiàn)在,我們學習Swift并發(fā)的相關(guān)內(nèi)容仿荆。由于篇幅較長贰您,這里分篇來記錄坏平,接下來,F(xiàn)ighting锦亦!

Swift 內(nèi)置支持以結(jié)構(gòu)化方式編寫異步和并行代碼舶替。 盡管一次只執(zhí)行一段程序,異步代碼可以掛起并稍后恢復運行。 掛起和恢復程序中的代碼可以讓它在短時間內(nèi)繼續(xù)操作杠园,例如更新其 UI坎穿,同時繼續(xù)處理長時間運行的操作,例如通過網(wǎng)絡獲取數(shù)據(jù)或解析文件返劲。 并行代碼意味著多段代碼同時運行——例如玲昧,具有四核處理器的計算機可以同時運行四段代碼,每個核執(zhí)行一項任務篮绿。 使用并行和異步代碼同時執(zhí)行多個操作的程序孵延; 它可以將正在等待外部系統(tǒng)的操作掛起,并可以更容易地以內(nèi)存安全的方式編寫此代碼亲配。

并行或異步代碼帶來的額外的靈活性調(diào)度也伴隨著復雜性增加的代價尘应。 Swift 允許您以某種方式表達您的意圖,從而啟用一些編譯時檢查——例如吼虎,您可以使用 actor 來安全地訪問可變狀態(tài)犬钢。 但是,向緩慢或有缺陷的代碼添加并發(fā)并不能保證它會變得快速或正確思灰。 事實上玷犹,添加并發(fā)甚至可能會使您的代碼更難調(diào)試。 但是洒疚,在需要并發(fā)的代碼中使用支持并發(fā)的Swift語言,可以幫助您在編譯時發(fā)現(xiàn)問題歹颓。

本章的其余部分使用并發(fā)術(shù)語來指代異步和并行代碼的這種常見組合。

注意
如果您之前編寫過并發(fā)代碼油湖,您可能習慣于使用線程巍扛。 Swift 中的并發(fā)模型建立在線程之上,但您不直接與它們交互乏德。 Swift 中的異步函數(shù)可以放棄它正在運行的線程撤奸,這讓另一個異步函數(shù)在該線程上運行而第一個函數(shù)被阻塞。

盡管可以在不使用 Swift 語言的情況下編寫并發(fā)代碼喊括,但該代碼往往更難閱讀胧瓜。 例如,以下代碼下載照片名稱列表瘾晃,下載該列表中的第一張照片贷痪,并向用戶顯示該照片:

listPhotos(inGallery: "Summer Vacation") { photoNames in
    let sortedNames = photoNames.sorted()
    let name = sortedNames[1]
    downloadPhoto(named: name) { photo in
        show(photo)
    }
}

即使在這種簡單的情況下,您最終必須編寫嵌套閉包的代碼來完成一系列處理程序蹦误。 在這種風格中劫拢,具有深層嵌套的更復雜的代碼很快就會變得笨拙肉津。

定義和調(diào)用異步函數(shù)

異步函數(shù)或異步方法是一種特殊的函數(shù)或方法,可以在執(zhí)行過程中掛起舱沧。 這與普通的同步函數(shù)和方法形成對比妹沙,它們要么運行完成,要么拋出錯誤熟吏,要么永不返回距糖。 異步函數(shù)或方法仍然會做這三件事中的一件,但它也可以在等待某事時在中間掛起牵寺。 在異步函數(shù)或方法的主體內(nèi)悍引,您標記每個可以掛起執(zhí)行的位置。

為了表明一個函數(shù)或方法是異步的帽氓,你可以在它的參數(shù)后面的聲明中寫一個 async 關(guān)鍵字趣斤,類似于你如何使用 throws 來標記一個拋出函數(shù)。 如果函數(shù)或方法返回一個值黎休,則在返回箭頭 (->) 之前寫入 async浓领。 例如,以下是獲取圖庫中照片名稱的方法:

func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

對于既是異步又是拋出的函數(shù)或方法势腮,可以在throws之前編寫async联贩。

調(diào)用異步方法時,執(zhí)行會掛起捎拯,直到該方法返回泪幌。 您在調(diào)用前寫入 await 以標記可能的暫停點。 這就像在調(diào)用拋出函數(shù)時編寫 try 一樣玄渗,如果出現(xiàn)錯誤座菠,則標記程序流程可能發(fā)生的變化。 在異步方法中藤树,只有在調(diào)用另一個異步方法時才會掛起執(zhí)行流程——掛起永遠不會是隱式或搶占式的——這意味著每個可能的暫停點都被標記為 await。

例如拓萌,下面的代碼獲取圖庫中所有圖片的名稱岁钓,然后顯示第一張圖片:

let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[1]
let photo = await downloadPhoto(named: name)
show(photo)

因為 listPhotos(inGallery:) 和 downloadPhoto(named:) 函數(shù)都需要進行網(wǎng)絡請求,所以它們可能需要相對較長的時間才能完成微王。 通過在返回箭頭之前編寫 async 使它們都異步屡限,讓應用程序的其余代碼在等待圖片準備好時繼續(xù)運行。

為了理解上面例子的并發(fā)特性炕倘,這里說說大概的執(zhí)行順序:

  1. 代碼從第一行開始運行钧大,一直運行到第一個 await。 它調(diào)用 listPhotos(inGallery:) 函數(shù)并在等待該函數(shù)返回時暫停執(zhí)行罩旋。
  2. 當此代碼的執(zhí)行暫停時啊央,同一程序中的其他一些并發(fā)代碼會運行眶诈。 例如,一個長時間運行的后臺任務可能會繼續(xù)更新新照片畫廊的列表瓜饥。 該代碼也一直運行到下一個掛起點逝撬,由 await 標記,或者直到它完成乓土。
  3. listPhotos(inGallery:) 返回后宪潮,此代碼從該點開始繼續(xù)執(zhí)行。 它將返回的值分配給 photoNames趣苏。
  4. 定義 sortedNames 和 name 的這兩行是常見的同步代碼狡相。 因為在這些行上沒有標記 await,所以沒有任何的暫停點食磕。
  5. 下一個 await 標記對 downloadPhoto(named:) 函數(shù)的調(diào)用尽棕。 此代碼再次暫停執(zhí)行,直到該函數(shù)返回芬为,從而為其他并發(fā)代碼提供運行的機會萄金。
  6. downloadPhoto(named:)返回后,它的返回值被賦值給photo媚朦,然后在調(diào)用show(_:)時作為參數(shù)傳遞氧敢。

代碼中標有 await 的掛起點表示當前代碼段可能會在等待異步函數(shù)或方法返回時暫停執(zhí)行。 這也稱為讓出線程询张,因為在后臺孙乖,Swift 會暫停您在當前線程上執(zhí)行的代碼,并在該線程上運行其他一些代碼份氧。 因為帶有 await 的代碼需要能夠暫停執(zhí)行唯袄,所以只有程序中的某些地方可以調(diào)用異步函數(shù)或方法:

  • 異步函數(shù)、方法或?qū)傩缘闹黧w中的代碼蜗帜。
  • 用@main 標記的結(jié)構(gòu)恋拷、類或枚舉的靜態(tài) main() 方法中的代碼。
  • 分離的子任務中的代碼厅缺,如下面的Unstructured Concurrency
    中所示蔬顾。

注意
Task.sleep(_:) 方法在編寫簡單代碼以了解并發(fā)工作原理時很有用。 此方法什么都不做湘捎,但在返回之前至少等待給定的納秒數(shù)诀豁。 這是 listPhotos(inGallery:) 函數(shù)的一個版本,它使用 sleep() 來模擬等待網(wǎng)絡操作:

func listPhotos(inGallery name: String) async -> [String] {
    await Task.sleep(2 * 1_000_000_000)  // Two seconds
    return ["IMG001", "IMG99", "IMG0404"]
}

異步隊列

上一節(jié)中的 listPhotos(inGallery:) 函數(shù)在數(shù)組的所有元素都準備就緒后一次性異步返回整個數(shù)組窥妇。 另一種方法是使用異步隊列一次等待集合的一個元素舷胜。 以下是對異步隊列進行迭代的樣子:

import Foundation

let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}

上面的例子沒有使用普通的 for-in 循環(huán),而是在它后面寫了 for with await活翩。 就像調(diào)用異步函數(shù)或方法時一樣烹骨,寫 await 表示可能的暫停點翻伺。 for-await-in 循環(huán)可能會在每次迭代開始時暫停執(zhí)行,等待下一個可用的元素展氓。

就像您可以通過添加對 Sequence 協(xié)議的一致性在 for-in 循環(huán)中使用自己的類型一樣穆趴,您可以通過添加對 AsyncSequence 協(xié)議的一致性在 for-await-in 循環(huán)中使用自己的類型。

并行調(diào)用異步函數(shù)

使用 await 調(diào)用異步函數(shù)一次只運行一段代碼遇汞。 當異步代碼運行時未妹,調(diào)用者會等待該代碼完成,然后再繼續(xù)運行下一行代碼空入。 例如络它,要從圖庫中獲取前三張照片,您可以等待對 downloadPhoto(named:) 函數(shù)的三個調(diào)用歪赢,如下所示:

let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])

let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

這種方法有一個重要的缺點:雖然下載是異步的化戳,并且在下載過程中允許其他工作發(fā)生,但一次只運行一次對 downloadPhoto(named:) 的調(diào)用埋凯。 每張照片都下載完成了才會開始下載下一張照片点楼。 但是,以下操作無需等待——每張照片都可以獨立下載白对,甚至可以同時下載掠廓。

要調(diào)用異步函數(shù)并讓它與其周圍的代碼并行運行,請在定義常量時在 let 前面寫上 async 甩恼,然后在每次使用常量時寫上 await 蟀瞧。

async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

在此示例中,對 downloadPhoto(named:) 的所有三個調(diào)用都開始而不等待前一個調(diào)用完成条摸。 如果有足夠的系統(tǒng)資源可用悦污,它們可以同時運行。 這些函數(shù)調(diào)用都沒有標記為 await钉蒲,因為代碼不會掛起以等待函數(shù)的結(jié)果切端。 相反,執(zhí)行會一直持續(xù)到定義照片的那一行——在這一點上顷啼,程序需要這些異步調(diào)用的結(jié)果帆赢,所以可以編寫 await 來暫停執(zhí)行,直到所有三張照片都完成下載线梗。

以下是您如何思考這兩種方法之間的差異:

  • 當以下行中的代碼取決于該函數(shù)的結(jié)果時,使用 await 調(diào)用異步函數(shù)怠益。 這將創(chuàng)建按順序執(zhí)行的工作仪搔。
  • 當您在代碼中稍后才需要結(jié)果時,請使用 async-let 調(diào)用異步函數(shù)蜻牢。 這將創(chuàng)建可以并行執(zhí)行的工作烤咧。
  • await 和 async-let 都允許其他代碼在掛起時運行偏陪。
  • 在這兩種情況下,您都用 await 標記可能的暫停點以指示執(zhí)行將暫停(如果需要)煮嫌,直到異步函數(shù)返回笛谦。

您還可以在同一代碼中混合使用這兩種方法。

任務和任務組

任務是一個工作單元昌阿,可以作為程序的一部分異步運行饥脑。 所有異步代碼都作為某些任務的一部分運行。 上一節(jié)中描述的 async-let 語法為您創(chuàng)建了一個子任務懦冰。 您還可以創(chuàng)建一個任務組并將子任務添加到該組中灶轰,這使您可以更好地控制優(yōu)先級和取消操作,并讓您創(chuàng)建動態(tài)數(shù)量的任務刷钢。

任務按層次結(jié)構(gòu)排列笋颤。 任務組中的每個任務都有相同的父任務,每個任務可以有子任務内地。 由于任務和任務組之間存在顯式關(guān)系伴澄,因此這種方法稱為結(jié)構(gòu)化并發(fā)。 盡管在代碼的正確性無法保證阱缓,但任務之間顯式的父子關(guān)系讓 Swift可以 處理一些行為非凌,例如為您進行取消操作,并讓 Swift 在編譯時檢測到一些錯誤茬祷。

await withTaskGroup(of: Data.self) { taskGroup in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        taskGroup.async { await downloadPhoto(named: name) }
    }
}

有關(guān)任務組更多的信息,請移步TaskGroup清焕。

非結(jié)構(gòu)化并發(fā)

除了前面幾節(jié)中描述的結(jié)構(gòu)化并發(fā)方法之外,Swift 還支持非結(jié)構(gòu)化并發(fā)祭犯。 與屬于任務組的任務不同秸妥,非結(jié)構(gòu)化任務沒有父任務。 您可以完全靈活地以任何程序需要的方式管理非結(jié)構(gòu)化任務沃粗,但您也對它們的正確性完全負責粥惧。 要創(chuàng)建在當前 actor 上運行的非結(jié)構(gòu)化任務,請調(diào)用 async(priority:operation:) 函數(shù)最盅。 要創(chuàng)建不屬于當前參與者的非結(jié)構(gòu)化任務突雪,更具體地說,稱為分離任務涡贱,請調(diào)用 asyncDetached(priority:operation:)咏删。 這兩個函數(shù)都返回一個任務操作,讓您可以與任務進行交互——例如问词,等待其結(jié)果或取消它督函。

let newPhoto = // ... some photo data ...
let handle = async {
    return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.get()

有關(guān)管理分離任務的更多信息,請參閱Task.Handle

任務取消

Swift 并發(fā)使用協(xié)作取消模型辰狡。 每個任務檢查它是否在其執(zhí)行的適當點被取消锋叨,并以任何適當?shù)姆绞巾憫∠?根據(jù)您所做的工作,這通常意味著以下情況之一:

  • 像CancellationError一樣拋出錯誤
  • 返回nil或者空的集合
  • 退回部分完成的工作

要檢查取消宛篇,請調(diào)用 Task.checkCancellation()
娃磺,如果任務被取消,它會拋出 CancellationError叫倍,或者檢查 Task.isCancelled
的值并在您自己的代碼中處理取消偷卧。 例如,從圖庫下載照片的任務可能需要刪除部分下載并關(guān)閉網(wǎng)絡連接段标。

要手動取消涯冠,請調(diào)用Task.Handle.cancel()

Actors

和類一樣逼庞,actor 也是引用類型蛇更,所以 Classes Are Reference Types 中值類型和引用類型的比較既適用于 actor,也適用于類赛糟。 與類不同派任,actor 一次只允許一個任務訪問其可變狀態(tài),這使得多個任務中的代碼可以安全地與同一個 actor 實例交互璧南。 例如掌逛,這是一個記錄溫度的 actor:

actor TemperatureLogger {
   let label: String
   var measurements: [Int]
   private(set) var max: Int

   init(label: String, measurement: Int) {
       self.label = label
       self.measurements = [measurement]
       self.max = measurement
   }
}

你用 actor 關(guān)鍵字引入一個 actor類,然后用一對大括號來定義它司倚。 TemperatureLogger 角色具有角色外部的其他代碼可以訪問的屬性豆混,并限制了 max 屬性,因此只有角色內(nèi)部的代碼才能更新最大值动知。

您可以使用與結(jié)構(gòu)體和類相同的初始化器語法來創(chuàng)建 actor 的實例皿伺。 當你訪問一個 actor 的屬性或方法時,你使用 await 來標記潛在的暫停點——例如:

let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(await logger.max)
// Prints "25"

在這個例子中盒粮,訪問 logger.max 是一個掛起的地方鸵鸥。 因為 actor 一次只允許一個任務訪問其可變狀態(tài),如果來自另一個任務的代碼已經(jīng)與logger交互丹皱,則該代碼在等待訪問該屬性時掛起妒穴。

相比之下,actor 的一部分代碼在訪問 actor 的屬性時不會編寫 await摊崭。 例如讼油,這是一個使用新溫度更新 TemperatureLogger 的方法:

extension TemperatureLogger {
    func update(with measurement: Int) {
        measurements.append(measurement)
        if measurement > max {
            max = measurement
        }
    }
}

update(with:) 方法已經(jīng)在 actor 上運行,所以它不會用 await 標記它對 max 等屬性的訪問呢簸。 此方法還顯示了 Actor 一次只允許一項任務與其可變狀態(tài)交互的原因之一:對 Actor 狀態(tài)的某些更新會暫時更改不可變量汁讼。 TemperatureLogger actor 會跟蹤溫度列表和最高溫度淆攻,并在您記錄新測量值時更新最高溫度。 在更新過程中嘿架,在添加新測量值之后,但在更新 max 之前,溫度記錄器處于臨時不一致狀態(tài)啸箫。 防止多個任務同時與同一個實例交互可以防止出現(xiàn)類似以下事件隊列的問題:

  • 您的代碼調(diào)用 update(with:) 方法耸彪。 它首先更新測量數(shù)組。
  • 在您的代碼可以更新 max 之前忘苛,其他地方的代碼會讀取最大值和溫度數(shù)組蝉娜。
  • 您的代碼通過更改 max 來完成更新。

在這種情況下扎唾,在別處運行的代碼會讀取不正確的信息召川,因為它對 actor 的訪問在調(diào)用 update(with:) 的過程中交錯進行,而數(shù)據(jù)暫時是無效的胸遇。 您可以在使用 Swift actor 時防止出現(xiàn)此問題荧呐,因為它們一次只允許對其狀態(tài)進行一次操作,并且因為該代碼只能在 await 標記暫停點的地方中斷纸镊。 因為 update(with:) 不包含任何暫停點倍阐,所以沒有其他代碼可以在更新過程中訪問數(shù)據(jù)。

如果您嘗試從 actor 外部訪問這些屬性逗威,就像使用類的實例一樣峰搪,您將收到編譯時錯誤; 例如:

print(logger.max)  // Error

在不寫入 await 的情況下訪問 logger.max 會失敗凯旭,因為 actor 的屬性是該 actor 隔離的本地狀態(tài)的一部分概耻。 Swift 保證只有 Actor 內(nèi)部的代碼才能訪問 Actor 的本地狀態(tài)。 這種保證稱為參與者隔離罐呼。

總結(jié)

Swift支持使用異步和并行代碼來處理耗時操作,就像OC中使用GCD或NSOperation編寫多線程一樣鞠柄;相比之下,Swift語法要簡單的多弄贿。

  • 使用async關(guān)鍵字來實現(xiàn)異步操作春锋,寫法有兩種,如下:
func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}
async let firstPhoto = downloadPhoto(named: photoNames[0])
  • 使用await關(guān)鍵字來實現(xiàn)等待耗時操作完成后差凹,再執(zhí)行后面的操作期奔,相當于在一個線程上同步運行任務,如下代碼危尿,只會同步調(diào)用下載任務呐萌,這種操作比較耗時。
let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])

let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
  • asyncawait配合使用實現(xiàn)多線程操作谊娇,并行調(diào)用方法肺孤,等待任務全部完成后,再執(zhí)行之后的任務,如:
async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
  • 這里簡單的說了一下任務和任務組的概念赠堵,將任務加入到任務組小渊,可以方便的控制任務優(yōu)先級和進行取消操作,創(chuàng)建動態(tài)的任務數(shù)量茫叭,更多詳細的內(nèi)容酬屉,請看TaskGroup
  • 任務處理的相關(guān)操作,如任務取消揍愁,請參閱Task.Handle呐萨。
  • 使用actor關(guān)鍵字來避免數(shù)據(jù)競爭,相當于OC中的鎖莽囤。

并發(fā)的內(nèi)容就這些了谬擦,最后的最后,以上總結(jié)若有錯誤朽缎,請指正惨远!喜歡的朋友也麻煩您點個贊喲~

上一章節(jié):錯誤處理

參考文檔:[Swift - Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饵沧,隨后出現(xiàn)的幾起案子锨络,更是在濱河造成了極大的恐慌,老刑警劉巖狼牺,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡儿,死亡現(xiàn)場離奇詭異,居然都是意外死亡是钥,警方通過查閱死者的電腦和手機掠归,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悄泥,“玉大人虏冻,你說我怎么就攤上這事〉簦” “怎么了厨相?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸥鹉。 經(jīng)常有香客問我蛮穿,道長,這世上最難降的妖魔是什么毁渗? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任践磅,我火速辦了婚禮,結(jié)果婚禮上灸异,老公的妹妹穿的比我還像新娘府适。我一直安慰自己羔飞,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布檐春。 她就那樣靜靜地躺著逻淌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喇聊。 梳的紋絲不亂的頭發(fā)上恍风,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音誓篱,去河邊找鬼。 笑死凯楔,一個胖子當著我的面吹牛窜骄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播摆屯,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼邻遏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了虐骑?” 一聲冷哼從身側(cè)響起准验,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎廷没,沒想到半個月后糊饱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡颠黎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年另锋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狭归。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡夭坪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出过椎,到底是詐尸還是另有隱情室梅,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布疚宇,位于F島的核電站亡鼠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灰嫉。R本人自食惡果不足惜拆宛,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讼撒。 院中可真熱鬧浑厚,春花似錦股耽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至敢艰,卻和暖如春诬乞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钠导。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工震嫉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牡属。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓票堵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逮栅。 傳聞我的和親對象是個殘疾皇子悴势,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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

  • 引言 繼續(xù)學習Swift文檔,從上一章節(jié):可選鏈接措伐,我們學習了Swift可選鏈接相關(guān)的內(nèi)容√叵耍現(xiàn)在,我們學習Swif...
    shiyueZ閱讀 1,005評論 0 1
  • 引言 繼續(xù)學習Swift文檔侥加,從上一章節(jié):繼承捧存,我們學習了Swift繼承相關(guān)的內(nèi)容,如繼承的作用官硝、重寫父類的方法和...
    shiyueZ閱讀 2,066評論 0 2
  • 引言 繼續(xù)學習Swift文檔矗蕊,從上一章節(jié):析構(gòu)函數(shù),我們學習了Swift析構(gòu)函數(shù)相關(guān)的內(nèi)容∏饧埽現(xiàn)在傻咖,我們學習Swif...
    shiyueZ閱讀 638評論 0 0
  • 引言 繼續(xù)學習Swift文檔,從上一章節(jié):函數(shù)岖研,我們學習了Swift函數(shù)相關(guān)的內(nèi)容卿操,如函數(shù)的定義和使用、函數(shù)參數(shù)孙援、...
    shiyueZ閱讀 1,873評論 0 1
  • 引言 繼續(xù)學習Swift文檔害淤,從上一章節(jié):開篇 ,我們了解Swift基本的知識點拓售,現(xiàn)在我們還是從詳細的基礎知識...
    shiyueZ閱讀 5,963評論 3 8