UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.11.28 星期三

前言

iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過UIKit進(jìn)行的凶赁。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)

開始

首先看一下寫作環(huán)境

Swift 4.2, iOS 12, Xcode 10

作為開發(fā)人員,您應(yīng)始終努力提供出色的用戶體驗(yàn)哩罪。在顯示列表的應(yīng)用程序中你需要保證出色一種方法是確保滾動(dòng)順滑狞甚。在iOS 10中锁摔,Apple引入了UICollectionView預(yù)取API和相應(yīng)的UITableView預(yù)取API,允許您在Collection Views and Table Views需要之前獲取數(shù)據(jù)哼审。

當(dāng)您遇到滾動(dòng)阻塞感很強(qiáng)的應(yīng)用程序時(shí)谐腰,這通常是由于長(zhǎng)時(shí)間運(yùn)行的進(jìn)程阻塞主UI線程更新。您希望保持主線程可以自由響應(yīng)觸摸事件等事情涩盾。如果您花費(fèi)很長(zhǎng)時(shí)間來獲取和顯示數(shù)據(jù)十气,用戶可以原諒您,但如果您的應(yīng)用沒有響應(yīng)他們的手勢(shì)春霍,他們就不會(huì)寬恕砸西。將繁重的工作轉(zhuǎn)移到后臺(tái)線程是構(gòu)建響應(yīng)式應(yīng)用程序的第一步。

在本教程中址儒,您將開始使用EmojiRater芹枷,這是一個(gè)顯示表情符號(hào)集合的應(yīng)用程序。不幸的是莲趣,它的滾動(dòng)性能還有很多不足之處鸳慈。您將使用預(yù)取API來查找應(yīng)用可能很快顯示的單元格,并在后臺(tái)觸發(fā)相關(guān)數(shù)據(jù)提取喧伞。

打開并運(yùn)行示例應(yīng)用程序走芋,如下所示:

很難受,不是嗎潘鲫? 好消息是你可以解決這個(gè)問題翁逞。

關(guān)于應(yīng)用程序的一點(diǎn)。 該應(yīng)用程序顯示emojis的集合視圖溉仑,您可以向下或向下投票挖函。 要使用,請(qǐng)單擊其中一個(gè)單元格彼念,然后用力按直到您感覺到某些觸覺反饋挪圾。 應(yīng)出現(xiàn)評(píng)級(jí)選擇浅萧。 選擇一個(gè)并在更新的集合視圖中查看結(jié)果:

注意:如果您無法在模擬器中使用3D Touch,則首先需要具有“Force Touch”功能的觸控板的MacMacBook哲思。 然后洼畅,您可以轉(zhuǎn)到System Preferences ? Trackpad并啟用Force Click and haptic feedback。 如果您無法訪問此類設(shè)備或使用3D Touch的iPhone棚赔,您仍然可以獲得本教程的基本知識(shí)帝簇。

看看Xcode中的項(xiàng)目。 這些是主要文件:

  • EmojiRating.swift:表示表情符號(hào)的模型靠益。
  • DataStore.swift:加載一個(gè)表情符號(hào)丧肴。
  • EmojiViewCell.swift:顯示表情符號(hào)的集合視圖單元格。
  • RatingOverlayView.swift:允許用戶對(duì)表情符號(hào)進(jìn)行評(píng)級(jí)的視圖胧后。
  • EmojiViewController.swift:在集合視圖中顯示表情符號(hào)芋浮。

您將向DataStoreEmojiViewController添加功能以增強(qiáng)滾動(dòng)性能。


Understanding Choppy Scrolling - 了解斷續(xù)的滾動(dòng)

您可以通過確保您的應(yīng)用程序滿足每秒60幀(FPS)顯示約束來實(shí)現(xiàn)平滑滾動(dòng)壳快。 這意味著您的應(yīng)用程序需要能夠每秒刷新其UI 60次纸巷,因此每個(gè)幀大約需要16毫秒來呈現(xiàn)內(nèi)容。 系統(tǒng)會(huì)丟棄需要太長(zhǎng)時(shí)間才能顯示內(nèi)容的幀眶痰。

當(dāng)應(yīng)用程序跳過幀并移動(dòng)到下一幀時(shí)瘤旨,這會(huì)導(dǎo)致不穩(wěn)定的滾動(dòng)體驗(yàn)。 丟幀的可能原因是長(zhǎng)時(shí)間運(yùn)行阻塞主線程的操作竖伯。

Apple提供了一些方便的工具來幫助您存哲。 首先,您可以拆分長(zhǎng)時(shí)間運(yùn)行的操作并將它們移動(dòng)到后臺(tái)線程七婴。 這允許您在主線程上處理任何觸摸事件祟偷。 后臺(tái)操作完成后,您可以根據(jù)操作在主線程上進(jìn)行任何所需的UI更新打厘。

以下顯示了丟幀的情況:

將工作移至后臺(tái)后肩袍,事情如下所示:

您現(xiàn)在有兩個(gè)并發(fā)線程正在運(yùn)行以提高應(yīng)用程序的性能。

如果你可以在必須顯示之前開始獲取數(shù)據(jù)婚惫,那會(huì)不會(huì)更好? 這就是UITableViewUICollectionView預(yù)取API的用武之地魂爪。您將在本教程中使用集合視圖API先舷。


Loading Data Asynchronously - 異步加載數(shù)據(jù)

Apple提供了多種方法來為您的應(yīng)用添加并發(fā)性。 您可以使用Grand Central Dispatch (GCD)作為輕量級(jí)機(jī)制來同時(shí)執(zhí)行任務(wù)滓侍。 或者蒋川,您可以使用構(gòu)建在GCD之上的Operation

Operation會(huì)增加更多開銷撩笆,但可以輕松重用和取消操作捺球。 您將在本教程中使用Operation缸浦,以便您可以取消之前開始加載不再需要的表情符號(hào)的操作。

現(xiàn)在是時(shí)候開始研究哪里可以最好地利用EmojiRater中的并發(fā)性氮兵。

打開EmojiViewController.swift并找到數(shù)據(jù)源方法collectionView(_:cellForItemAt :)裂逐。 看下面的代碼:

if let emojiRating = dataStore.loadEmojiRating(at: indexPath.item) {
  cell.updateAppearanceFor(emojiRating, animated: true)
}

這會(huì)在顯示之前從數(shù)據(jù)存儲(chǔ)中加載表情符號(hào)。 讓我們來看看它是如何實(shí)現(xiàn)的泣栈。

打開DataStore.swift并查看加載方法:

public func loadEmojiRating(at index: Int) -> EmojiRating? {
  if (0..<emojiRatings.count).contains(index) {
    let randomDelayTime = Int.random(in: 500..<2000)
    usleep(useconds_t(randomDelayTime * 1000))
    return emojiRatings[index]
  }
  return .none
}

此代碼在隨機(jī)延遲(500ms到2,000ms)之后返回有效的表情符號(hào)卜高。 延遲是在不同條件下對(duì)網(wǎng)絡(luò)請(qǐng)求的仿真模擬。

問題發(fā)現(xiàn)了南片! 表情符號(hào)提取發(fā)生在主線程上掺涛,違反了16ms閾值,觸發(fā)丟幀疼进。 你即將解決這個(gè)問題薪缆。

將以下代碼添加到DataStore.swift的末尾:

class DataLoadOperation: Operation {
  // 1
  var emojiRating: EmojiRating?
  var loadingCompleteHandler: ((EmojiRating) -> Void)?
  
  private let _emojiRating: EmojiRating
  
  // 2
  init(_ emojiRating: EmojiRating) {
    _emojiRating = emojiRating
  }
  
  // 3
  override func main() {
    // TBD: Work it!!
  }
}

Operation是一個(gè)抽象類,您必須使用它的子類才能實(shí)現(xiàn)要從主線程移出的工作伞广。

以下是代碼中一步一步發(fā)生的事情:

  • 1) 創(chuàng)建對(duì)此操作中將使用的表情符號(hào)和完成處理程序的引用拣帽。
  • 2) 創(chuàng)建一個(gè)指定的初始化程序,允許您傳入表情符號(hào)赔癌。
  • 3) 重寫main()方法以執(zhí)行此操作的實(shí)際工作诞外。

現(xiàn)在,將以下代碼添加到main()

// 1
if isCancelled { return }
    
// 2
let randomDelayTime = Int.random(in: 500..<2000)
usleep(useconds_t(randomDelayTime * 1000))

// 3
if isCancelled { return }

// 4
emojiRating = _emojiRating

// 5  
if let loadingCompleteHandler = loadingCompleteHandler {
  DispatchQueue.main.async {
    loadingCompleteHandler(self._emojiRating)
  }
}

下面進(jìn)行細(xì)分

  • 1) 在開始之前檢查取消灾票。 在嘗試長(zhǎng)期或密集的工作之前峡谊,Operations應(yīng)定期檢查是否已取消。
  • 2) 模擬長(zhǎng)時(shí)間運(yùn)行的表情符號(hào)提取刊苍。 這段代碼應(yīng)該很熟悉既们。
  • 3) 檢查操作是否已取消。
  • 4) 分配表情符號(hào)以指示提取已完成正什。
  • 5) 在主線程上調(diào)用完成處理程序啥纸,傳入表情符號(hào)。 然后婴氮,這應(yīng)該觸發(fā)UI更新以顯示表情符號(hào)斯棒。

loadEmojiRating(at :)替換為以下內(nèi)容:

public func loadEmojiRating(at index: Int) -> DataLoadOperation? {
  if (0..<emojiRatings.count).contains(index) {
    return DataLoadOperation(emojiRatings[index])
  }
  return .none
}

原始代碼有兩處更改:

  • 1) 您創(chuàng)建一個(gè)DataLoadOperation()以在后臺(tái)獲取表情符號(hào)。
  • 2) 此方法現(xiàn)在返回DataLoadOperation可選主经,而不是EmojiRating可選荣暮。

您現(xiàn)在需要處理方法簽名更改并使用您的全新operation

打開EmojiViewController.swift罩驻,并在collectionView(_:cellForItemAt :)中刪除以下代碼:

if let emojiRating = dataStore.loadEmojiRating(at: indexPath.item) {
  cell.updateAppearanceFor(emojiRating, animated: true)
}

您將不再啟動(dòng)此數(shù)據(jù)源方法的數(shù)據(jù)提取穗酥。 相反,您將在應(yīng)用程序即將顯示集合視圖單元格時(shí)調(diào)用的委托方法中執(zhí)行此操作。

在類頂部附近添加以下屬性:

let loadingQueue = OperationQueue()
var loadingOperations: [IndexPath: DataLoadOperation] = [:]

第一個(gè)屬性包含操作隊(duì)列砾跃。 loadingOperations是一個(gè)跟蹤數(shù)據(jù)加載操作的數(shù)組骏啰,通過索引路徑將每個(gè)加載操作與其對(duì)應(yīng)的單元相關(guān)聯(lián)。

將以下代碼添加到文件末尾:

// MARK: - UICollectionViewDelegate
extension EmojiViewController {
  override func collectionView(_ collectionView: UICollectionView,  
    willDisplay cell: UICollectionViewCell,
    forItemAt indexPath: IndexPath) {
    guard let cell = cell as? EmojiViewCell else { return }

    // 1
    let updateCellClosure: (EmojiRating?) -> Void = { [weak self] emojiRating in
      guard let self = self else {
        return
      }
      cell.updateAppearanceFor(emojiRating, animated: true)
      self.loadingOperations.removeValue(forKey: indexPath)
    }

    // 2
    if let dataLoader = loadingOperations[indexPath] {
      // 3
      if let emojiRating = dataLoader.emojiRating {
        cell.updateAppearanceFor(emojiRating, animated: false)
        loadingOperations.removeValue(forKey: indexPath)
      } else {
        // 4
        dataLoader.loadingCompleteHandler = updateCellClosure
      }
    } else {
      // 5
      if let dataLoader = dataStore.loadEmojiRating(at: indexPath.item) {
        // 6
        dataLoader.loadingCompleteHandler = updateCellClosure
        // 7
        loadingQueue.addOperation(dataLoader)
        // 8
        loadingOperations[indexPath] = dataLoader
      }
    }
  }
}

這將為UICollectionViewDelegate創(chuàng)建一個(gè)擴(kuò)展抽高,并實(shí)現(xiàn)collectionView(_:willDisplay:forItemAt :)委托方法判耕。 下面進(jìn)行細(xì)說分解:

  • 1) 創(chuàng)建一個(gè)閉包來處理加載數(shù)據(jù)后如何更新單元格。
  • 2) 檢查單元是否有數(shù)據(jù)加載操作厨内。
  • 3) 檢查數(shù)據(jù)加載操作是否已完成祈秕。 如果是這樣,請(qǐng)更新單元格的UI并從跟蹤陣列中刪除操作雏胃。
  • 4) 如果尚未獲取表情符號(hào)请毛,則將閉包分配給數(shù)據(jù)加載完成處理程序。
  • 5) 如果沒有數(shù)據(jù)加載操作瞭亮,請(qǐng)為相關(guān)表情符號(hào)創(chuàng)建一個(gè)新的操作方仿。
  • 6) 將閉包添加到數(shù)據(jù)加載完成處理程序。
  • 7) 將操作添加到操作隊(duì)列统翩。
  • 8) 將數(shù)據(jù)加載器添加到操作跟蹤陣列仙蚜。

從集合視圖中刪除單元格時(shí),您需要確保進(jìn)行一些清理厂汗。

將以下方法添加到UICollectionViewDelegate擴(kuò)展:

override func collectionView(_ collectionView: UICollectionView,
  didEndDisplaying cell: UICollectionViewCell,
  forItemAt indexPath: IndexPath) {
  if let dataLoader = loadingOperations[indexPath] {
    dataLoader.cancel()
    loadingOperations.removeValue(forKey: indexPath)
  }
}

此代碼檢查與cell關(guān)聯(lián)的現(xiàn)有數(shù)據(jù)加載操作委粉。如果存在,則取消下載并從跟蹤操作的陣列中刪除操作娶桦。

構(gòu)建并運(yùn)行應(yīng)用程序贾节。滾動(dòng)表情符號(hào)并注意應(yīng)用程序性能的改進(jìn)。

如果您可以樂觀地獲取數(shù)據(jù)以預(yù)期顯示集合視圖單元格衷畦,那就更好了栗涂。您將使用預(yù)取API來執(zhí)行此操作并為EmojiRater提供額外的提升。


Enabling UICollectionView Prefetching - 啟用UICollectionView預(yù)取

UICollectionViewDataSourcePrefetching協(xié)議為您提前發(fā)出警告祈争,即可能很快需要集合視圖的數(shù)據(jù)斤程。您可以使用此信息開始預(yù)取數(shù)據(jù),以便在單元格可見時(shí)菩混,數(shù)據(jù)可能已經(jīng)可用忿墅。這與你已經(jīng)完成的并發(fā)工作一起工作 - 關(guān)鍵的區(qū)別在于工作開始時(shí)。

下圖顯示了這種情況如何發(fā)揮作用沮峡。用戶在集合視圖上向上滾動(dòng)球匕。黃色單元格應(yīng)該很快進(jìn)入視圖 - 假設(shè)這發(fā)生在Frame 3中,并且您目前處于Frame 1帖烘。

采用prefetch協(xié)議會(huì)通知應(yīng)用程序有關(guān)可能變?yōu)榭梢姷南乱粋€(gè)單元格。 如果沒有prefetch觸發(fā)器橄杨,黃色單元的數(shù)據(jù)提取將在Frame 3開始秘症,并且cell的數(shù)據(jù)在一段時(shí)間后變?yōu)榭梢姟?由于prefetch照卦,單元格數(shù)據(jù)將在單元格可見時(shí)準(zhǔn)備就緒。

打開EmojiViewController.swift并將以下代碼添加到文件末尾:

// MARK: - UICollectionViewDataSourcePrefetching
extension EmojiViewController: UICollectionViewDataSourcePrefetching {
  func collectionView(_ collectionView: UICollectionView,
      prefetchItemsAt indexPaths: [IndexPath]) {
    print("Prefetch: \(indexPaths)")
  }
}

EmojiViewController現(xiàn)在采用UICollectionViewDataSourcePrefetching并實(shí)現(xiàn)所需的委托方法乡摹。 該實(shí)現(xiàn)只是打印出很快就可以看到的索引路徑役耕。

viewDidLoad()中,在調(diào)用super.viewDidLoad()之后添加以下內(nèi)容:

collectionView?.prefetchDataSource = self

這將EmojiViewController設(shè)置為集合視圖的預(yù)取數(shù)據(jù)源聪廉。

構(gòu)建并運(yùn)行應(yīng)用程序瞬痘,在滾動(dòng)之前,檢查Xcode的控制臺(tái)板熊。 你應(yīng)該看到這樣的東西:

Prefetch: [[0, 10], [0, 11], [0, 12], [0, 13], [0, 14], [0, 15]]

這些對(duì)應(yīng)于尚未變得可見的cell框全。 現(xiàn)在,滾動(dòng)更多并像您一樣檢查控制臺(tái)日志干签。 您應(yīng)該看到基于不可見的索引路徑的日志消息津辩。 嘗試向上和向下滾動(dòng),直到您充分了解這一切是如何工作的容劳。

您可能想知道為什么這個(gè)委托方法只是為您提供索引路徑喘沿。 我們的想法是你應(yīng)該從這個(gè)方法開始你的數(shù)據(jù)加載過程,然后在collectionView(_:cellForItemAt :)collectionView(_:willDisplay:forItemAt :)中處理結(jié)果竭贩。 請(qǐng)注意蚜印,當(dāng)立即需要單元格時(shí),不會(huì)調(diào)用委托方法留量。 因此窄赋,您應(yīng)該不依賴于在此方法中將數(shù)據(jù)加載到單元格中。


Prefetching Data Asynchronously - 異步預(yù)取數(shù)據(jù)

EmojiViewController.swift中肪获,通過使用以下內(nèi)容替換print()語句來修改collectionView(_:prefetchItemsAt :)

for indexPath in indexPaths {
  // 1
  if let _ = loadingOperations[indexPath] {
    continue
  }
  // 2
  if let dataLoader = dataStore.loadEmojiRating(at: indexPath.item) {
    // 3
    loadingQueue.addOperation(dataLoader)
    loadingOperations[indexPath] = dataLoader
  }
}

代碼循環(huán)遍歷方法接收的索引路徑并執(zhí)行以下操作:

  • 1) 檢查此cell是否存在現(xiàn)有的加載操作寝凌。 如果有的話,沒有更多的事要做孝赫。
  • 2) 如果找不到加載操作较木,則創(chuàng)建數(shù)據(jù)加載操作。
  • 3) 將操作添加到隊(duì)列并更新跟蹤數(shù)據(jù)加載操作的字典青柄。

傳遞到collectionView(_:prefetchItemsAt :)的索引路徑按優(yōu)先級(jí)排序伐债,基于到集合視圖視圖的單元格幾何距離。 這允許您獲取最有可能需要的單元格致开。

回想一下峰锁,您之前在collectionView(_:willDisplay:forItemAt :)中添加了代碼來處理加載操作的結(jié)果。 請(qǐng)查看以下方法的重點(diǎn):

override func collectionView(_ collectionView: UICollectionView,
    willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  // ...
  let updateCellClosure: (EmojiRating?) -> Void = { [weak self] emojiRating in
    guard let self = self else {
      return
    }
    cell.updateAppearanceFor(emojiRating, animated: true)
    self.loadingOperations.removeValue(forKey: indexPath)
  }
  
  if let dataLoader = loadingOperations[indexPath] {
    if let emojiRating = dataLoader.emojiRating {
      cell.updateAppearanceFor(emojiRating, animated: false)
      loadingOperations.removeValue(forKey: indexPath)
    } else {
      dataLoader.loadingCompleteHandler = updateCellClosure
    }
  } else {
    // ...
  }  
}

創(chuàng)建cell更新閉包后双戳,檢查跟蹤操作的數(shù)組虹蒋。 如果存在即將出現(xiàn)的單元格且表情符號(hào)可用,則更新單元格的UI。 請(qǐng)注意魄衅,傳遞給數(shù)據(jù)加載操作的閉包也會(huì)更新單元格的UI峭竣。

這就是所有內(nèi)容的關(guān)系,從預(yù)取觸發(fā)操作到正在更新的單元UI晃虫。

構(gòu)建并運(yùn)行應(yīng)用程序并滾動(dòng)表情符號(hào)皆撩。 滾動(dòng)到的Emojis應(yīng)該比以前更快地顯示。

你能發(fā)現(xiàn)一些可以改進(jìn)的東西嗎哲银?如果您滾動(dòng)得非晨竿蹋快,那么您的collection view將開始獲取可能永遠(yuǎn)不會(huì)看到的表情符號(hào)荆责。 請(qǐng)繼續(xù)閱讀滥比。


Canceling a Prefetch - 取消預(yù)取

UICollectionViewDataSourcePrefetching具有可選的委托方法,可讓您知道不再需要數(shù)據(jù)草巡。 這可能發(fā)生守呜,因?yàn)橛脩粢呀?jīng)開始非常快地滾動(dòng)并且可能不會(huì)看到中間單元。 您可以使用委托方法取消任何掛起的數(shù)據(jù)加載操作。

仍然在EmojiViewController.swift中祥得,將以下方法添加到您的UICollectionViewDataSourcePrefetching協(xié)議實(shí)現(xiàn):

func collectionView(_ collectionView: UICollectionView,
  cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
  for indexPath in indexPaths {
    if let dataLoader = loadingOperations[indexPath] {
      dataLoader.cancel()
      loadingOperations.removeValue(forKey: indexPath)
    }
  }
}

代碼循環(huán)遍歷索引路徑并查找附加到它們的任何加載操作。 然后它取消操作并將其從跟蹤操作的字典中刪除玛迄。

構(gòu)建并運(yùn)行應(yīng)用程序。 當(dāng)您快速滾動(dòng)時(shí)棚亩,可能已開始的操作應(yīng)該開始取消蓖议。 在視覺上,事情看起來不會(huì)有太大的不同讥蟆。

需要注意的一點(diǎn)是勒虾,由于cell重用,可能需要重新獲取一些先前可見的cell瘸彤。

后記

本篇主要講述了UICollectionView的數(shù)據(jù)異步預(yù)加載修然,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市质况,隨后出現(xiàn)的幾起案子愕宋,更是在濱河造成了極大的恐慌,老刑警劉巖结榄,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件中贝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡臼朗,警方通過查閱死者的電腦和手機(jī)邻寿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蝎土,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人老厌,你說我怎么就攤上這事瘟则。” “怎么了枝秤?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)慷嗜。 經(jīng)常有香客問我淀弹,道長(zhǎng),這世上最難降的妖魔是什么庆械? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任薇溃,我火速辦了婚禮,結(jié)果婚禮上缭乘,老公的妹妹穿的比我還像新娘沐序。我一直安慰自己,他們只是感情好堕绩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布策幼。 她就那樣靜靜地躺著,像睡著了一般奴紧。 火紅的嫁衣襯著肌膚如雪特姐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天黍氮,我揣著相機(jī)與錄音唐含,去河邊找鬼。 笑死沫浆,一個(gè)胖子當(dāng)著我的面吹牛捷枯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播专执,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼淮捆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了他炊?” 一聲冷哼從身側(cè)響起争剿,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痊末,沒想到半個(gè)月后蚕苇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凿叠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年涩笤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嚼吞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹬碧,死狀恐怖舱禽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恩沽,我是刑警寧澤誊稚,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站罗心,受9級(jí)特大地震影響里伯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渤闷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一疾瓮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧飒箭,春花似錦狼电、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盈匾,卻和暖如春腾务,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背削饵。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工岩瘦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窿撬。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓启昧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親劈伴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子密末,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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