Core Data詳細(xì)解析(五) —— 基于多上下文的Core Data簡(jiǎn)單解析示例(一)

版本記錄

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

前言

數(shù)據(jù)是移動(dòng)端的重點(diǎn)關(guān)注對(duì)象,其中有一條就是數(shù)據(jù)存儲(chǔ)。CoreData是蘋果出的數(shù)據(jù)存儲(chǔ)和持久化技術(shù)筝闹,面向?qū)ο筮M(jìn)行數(shù)據(jù)相關(guān)存儲(chǔ)。感興趣的可以看下面幾篇文章划提。
1. iOS CoreData(一)
2. iOS CoreData實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)(二)
3. Core Data詳細(xì)解析(三) —— 一個(gè)簡(jiǎn)單的入門示例(一)
4. Core Data詳細(xì)解析(四) —— 一個(gè)簡(jiǎn)單的入門示例(二)

開始

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

Swift 4.2, iOS 12, Xcode 10

managed object context是用于處理managed objects的內(nèi)存中暫存器。大多數(shù)應(yīng)用只需要一個(gè)托managed object context邢享。大多數(shù)Core Data應(yīng)用程序中的默認(rèn)配置是與主隊(duì)列關(guān)聯(lián)的單個(gè)managed object context鹏往。多個(gè)managed object context使您的應(yīng)用程序更難調(diào)試;在任何情況下骇塘,它都不是你在每個(gè)應(yīng)用程序中使用的東西伊履。

話雖如此,某些情況確實(shí)需要使用多個(gè)managed object context款违。例如唐瀑,長(zhǎng)時(shí)間運(yùn)行的任務(wù)(例如導(dǎo)出數(shù)據(jù)(exporting data))將阻塞僅使用單個(gè)主隊(duì)列managed object context的App的主線程并導(dǎo)致UI遲鈍。

在其他情況下插爹,例如在對(duì)用戶數(shù)據(jù)進(jìn)行編輯時(shí)介褥,將managed object context視為一組更改是有好處的,如果應(yīng)用程序不再需要它們递惋,則可以將其丟棄柔滔。使用子上下文使這成為可能。

在本教程中萍虽,您將通過(guò)為沖浪者提供日記應(yīng)用程序并通過(guò)添加多個(gè)上下文以多種方式改進(jìn)它來(lái)了解多個(gè)managed object context睛廊。

本教程的入門項(xiàng)目是一個(gè)簡(jiǎn)單的日記應(yīng)用程序,適合沖浪者杉编。 在每次沖浪活動(dòng)之后超全,沖浪者可以使用該應(yīng)用程序創(chuàng)建一個(gè)記錄海洋參數(shù)的新日記帳分錄,例如膨脹高度或周期邓馒,并將活動(dòng)評(píng)分為1到5嘶朱。


Introducing SurfJournal - 引入SurfJournal

新建立SurfJournal入門項(xiàng)目。 打開項(xiàng)目光酣,然后構(gòu)建并運(yùn)行應(yīng)用程序疏遏。

啟動(dòng)時(shí),應(yīng)用程序會(huì)列出所有以前的沖浪會(huì)話日記帳分錄救军。 點(diǎn)擊一行可以顯示具有編輯功能的沖浪會(huì)話的詳細(xì)視圖财异。

如您所見,示例應(yīng)用程序可以運(yùn)行并具有數(shù)據(jù)唱遭。 點(diǎn)擊左上角的Export按鈕可將數(shù)據(jù)導(dǎo)出為逗號(hào)分隔值(CSV)文件戳寸。 點(diǎn)擊右上角的加號(hào)(+)按鈕可添加新的日記帳分錄。 點(diǎn)擊列表中的一行將以編輯模式打開條目拷泽,您可以在其中更改或查看沖浪會(huì)話的詳細(xì)信息疫鹊。

盡管示例項(xiàng)目看起來(lái)很簡(jiǎn)單袖瞻,但它實(shí)際上做了很多工作,并且可以作為添加多上下文支持的良好基礎(chǔ)拆吆。 首先虏辫,讓我們確保您對(duì)項(xiàng)目中的各個(gè)類有很好的理解。

打開項(xiàng)目導(dǎo)航器锈拨,查看項(xiàng)目中的完整文件列表:

在進(jìn)入代碼之前砌庄,請(qǐng)花一點(diǎn)時(shí)間來(lái)了解每個(gè)類的內(nèi)容。

  • AppDelegate:首次啟動(dòng)時(shí)奕枢,app委托創(chuàng)建Core Data堆棧并在主視圖控制器JournalListViewController上設(shè)置coreDataStack屬性娄昆。
  • CoreDataStack:此對(duì)象包含稱為stackCore Data對(duì)象的的主要部分。 這次堆棧會(huì)在首次啟動(dòng)時(shí)安裝已包含數(shù)據(jù)的數(shù)據(jù)庫(kù)缝彬。 暫時(shí)不用擔(dān)心萌焰,你會(huì)很快看到它是如何運(yùn)作的。
  • JournalListViewController:示例項(xiàng)目是一個(gè)基于表的單頁(yè)應(yīng)用程序谷浅。 該文件代表該表扒俯。 如果您對(duì)其UI元素感到好奇,請(qǐng)轉(zhuǎn)到Main.storyboard一疯。 有一個(gè)嵌入在導(dǎo)航控制器中的table view控制器和一個(gè)SurfEntryTableViewCell類型的單個(gè)原型單元撼玄。
  • JournalEntryViewController:此類處理創(chuàng)建和編輯沖浪日記條目。 您可以在Main.storyboard中查看其UI墩邀。
  • JournalEntry:此類表示沖浪日記條目掌猛。 它是一個(gè)NSManagedObject子類,具有六個(gè)屬性屬性:date, height, location, period, rating and wind眉睹。 如果您對(duì)此類的實(shí)體定義感到好奇荔茬,請(qǐng)查看SurfJournalModel.xcdatamodel
  • JournalEntry + Helper:這是JournalEntry對(duì)象的擴(kuò)展竹海。 它包括CSV導(dǎo)出方法csv()stringForDate()輔助方法慕蔚。 這些方法在擴(kuò)展中實(shí)現(xiàn),以避免在更改Core Data模型時(shí)被銷毀斋配。

首次啟動(dòng)應(yīng)用時(shí)孔飒,已經(jīng)有大量數(shù)據(jù)。 此示例項(xiàng)目附帶了seeded Core Data數(shù)據(jù)庫(kù)许起。


The Core Data Stack - Core Data堆棧

打開CoreDataStack.swift并在seedCoreDataContainerIfFirstLaunch():中找到以下代碼:

// 1
let previouslyLaunched =
  UserDefaults.standard.bool(forKey: "previouslyLaunched")
if !previouslyLaunched {
  UserDefaults.standard.set(true, forKey: "previouslyLaunched")

  // Default directory where the CoreDataStack will store its files
  let directory = NSPersistentContainer.defaultDirectoryURL()
  let url = directory.appendingPathComponent(
    modelName + ".sqlite")

  // 2: Copying the SQLite file
  let seededDatabaseURL = Bundle.main.url(
    forResource: modelName,
    withExtension: "sqlite")!

  _ = try? FileManager.default.removeItem(at: url)

  do {
    try FileManager.default.copyItem(at: seededDatabaseURL,
                                     to: url)
  } catch let nserror as NSError {
    fatalError("Error: \(nserror.localizedDescription)")
  }  

如您所見十偶,本教程的CoreDataStack.swift版本略有不同:

  • 1) 首先檢查UserDefaults以獲取先前已啟動(dòng)的布爾值。 如果當(dāng)前執(zhí)行確實(shí)是應(yīng)用程序的首次啟動(dòng)园细,則Bool將為false,使if語(yǔ)句為true接校。 在首次啟動(dòng)時(shí)猛频,您要做的第一件事就是將之前的啟動(dòng)設(shè)置為true狮崩,以便seeding操作再也不會(huì)發(fā)生。

  • 2) 然后鹿寻,將應(yīng)用程序包中包含的SQLite種子seed文件SurfJournalModel.sqlite復(fù)制到Core Data提供的方法NSPersistentContainer.defaultDirectoryURL()返回的目錄中睦柴。

現(xiàn)在查看seedCoreDataContainerIfFirstLaunch()的其余部分:

  // 3: Copying the SHM file
  let seededSHMURL = Bundle.main.url(forResource: modelName,
    withExtension: "sqlite-shm")!
  let shmURL = directory.appendingPathComponent(
    modelName + ".sqlite-shm")

  _ = try? FileManager.default.removeItem(at: shmURL)

  do {
    try FileManager.default.copyItem(at: seededSHMURL,
                                     to: shmURL)
  } catch let nserror as NSError {
    fatalError("Error: \(nserror.localizedDescription)")
  }

  // 4: Copying the WAL file
  let seededWALURL = Bundle.main.url(forResource: modelName,
    withExtension: "sqlite-wal")!
  let walURL = directory.appendingPathComponent(
    modelName + ".sqlite-wal")

  _ = try? FileManager.default.removeItem(at: walURL)

  do {
    try FileManager.default.copyItem(at: seededWALURL,
                                     to: walURL)
  } catch let nserror as NSError {
    fatalError("Error: \(nserror.localizedDescription)")
  }

  print("Seeded Core Data")
}
  • 3) 一旦SurfJournalModel.sqlite的副本成功,您就可以復(fù)制支持文件SurfJournalModel.sqlite-shm毡熏。

  • 4) 最后坦敌,復(fù)制剩余的支持文件SurfJournalModel.sqlite-wal

SurfJournalModel.sqlite痢法,SurfJournalModel.sqlite-shmSurfJournalModel.sqlite-wal在首次啟動(dòng)時(shí)無(wú)法復(fù)制的唯一原因是狱窘,如果發(fā)生了一些非常糟糕的事情,例如宇宙輻射造成的磁盤損壞财搁。 在這種情況下蘸炸,設(shè)備(包括任何應(yīng)用程序)可能也會(huì)失敗。 如果文件無(wú)法復(fù)制尖奔,則繼續(xù)沒有意義搭儒,因此catch塊會(huì)調(diào)用fatalError

開發(fā)人員經(jīng)常對(duì)使用abortfatalError感到不滿提茁,因?yàn)樗鼤?huì)導(dǎo)致應(yīng)用程序突然退出并且沒有解釋而使用戶感到困惑淹禾。 這是fatalError可接受的一種情況,因?yàn)閼?yīng)用程序需要Core Data才能工作茴扁。 如果一個(gè)應(yīng)用程序需要Core Data稀拐,但是Core Data不起作用,那么讓應(yīng)用程序繼續(xù)運(yùn)行是沒有意義的丹弱,只會(huì)在以后的某個(gè)時(shí)間以非確定的方式失敗德撬。調(diào)用fatalError至少會(huì)生成堆棧跟蹤,這在嘗試解決問題時(shí)很有用躲胳。 如果您的應(yīng)用程序支持遠(yuǎn)程日志記錄或崩潰報(bào)告蜓洪,則應(yīng)在調(diào)用fatalError之前記錄可能對(duì)調(diào)試有幫助的任何相關(guān)信息。

為了支持并發(fā)讀取和寫入坯苹,此示例應(yīng)用程序中的持久性SQLite存儲(chǔ)使用SHM(共享內(nèi)存文件)和WAL(預(yù)寫日志記錄)文件隆檀。 您不需要知道這些額外文件的工作方式,但您確實(shí)需要知道它們的存在粹湃,并且需要在播種數(shù)據(jù)庫(kù)時(shí)復(fù)制它們恐仑。 如果您無(wú)法復(fù)制這些文件,該應(yīng)用程序?qū)⑵鹱饔梦赡軙?huì)丟失數(shù)據(jù)裳仆。

現(xiàn)在您已經(jīng)了解了從seeded數(shù)據(jù)庫(kù)開始的事情,您將通過(guò)處理臨時(shí)私有上下文來(lái)了解多個(gè)managed object contexts孤钦。


Doing Work in the Background - 在后臺(tái)工作

如果尚未執(zhí)行此操作歧斟,請(qǐng)點(diǎn)擊左上角的Export按鈕纯丸,然后立即嘗試滾動(dòng)會(huì)話日記條目列表。 注意什么静袖? 導(dǎo)出操作需要幾秒鐘觉鼻,并且它會(huì)阻止UI響應(yīng)滾動(dòng)等觸摸事件。

在導(dǎo)出操作期間阻塞了UI队橙,因?yàn)閷?dǎo)出操作和UI都使用主隊(duì)列來(lái)執(zhí)行其工作坠陈。 這是默認(rèn)行為。

解決此問題的傳統(tǒng)方法是使用Grand Central Dispatch在后臺(tái)隊(duì)列上運(yùn)行導(dǎo)出操作捐康。 但是仇矾,Core Data的managed object contexts不是線程安全的。 這意味著您不能只調(diào)度到后臺(tái)隊(duì)列并使用相同的Core Data堆棧吹由。

解決方案很簡(jiǎn)單:使用私有后臺(tái)隊(duì)列而不是主隊(duì)列進(jìn)行導(dǎo)出操作若未。 這將使主隊(duì)列免費(fèi)供UI使用。

但在您進(jìn)入并解決問題之前倾鲫,您需要了解導(dǎo)出操作的工作原理粗合。

1. Exporting Data - 導(dǎo)出數(shù)據(jù)

首先查看應(yīng)用程序如何為JournalEntry實(shí)體創(chuàng)建CSV字符串。 打開JournalEntry + Helper.swift并找到csv()

func csv() -> String {
  let coalescedHeight = height ?? ""
  let coalescedPeriod = period ?? ""
  let coalescedWind = wind ?? ""
  let coalescedLocation = location ?? ""
  let coalescedRating: String
  if let rating = rating?.int32Value {
    coalescedRating = String(rating)
  } else {
    coalescedRating = ""
  }

  return "\(stringForDate()),\(coalescedHeight),\(coalescedPeriod),\(coalescedWind),\(coalescedLocation),\(coalescedRating)\n"
}

如您所見乌昔,JournalEntry返回實(shí)體屬性的逗號(hào)分隔字符串隙疚。 因?yàn)樵试SJournalEntry屬性為nil,所以該函數(shù)使用nil coalescing運(yùn)算符(??)來(lái)導(dǎo)出空字符串磕道,而不是屬性為nil的無(wú)用調(diào)試消息供屉。

注意:nil合并運(yùn)算符(??)如果包含值,則進(jìn)行解包該值溺蕉,否則返回默認(rèn)值伶丐。 例如,以下內(nèi)容:let coalescedHeight = height疯特!= nil哗魂? height! :“”可以使用nil合并運(yùn)算符縮短:let coalescedHeight = height ??“”漓雅。

這就是應(yīng)用程序?yàn)閱蝹€(gè)日記帳分錄創(chuàng)建CSV字符串的方式录别,但應(yīng)用程序如何將CSV文件保存到磁盤? 打開JournalListViewController.swift并在exportCSVFile()中找到以下代碼:

// 1
let context = coreDataStack.mainContext
var results: [JournalEntry] = []
do {
  results = try context.fetch(self.surfJournalFetchRequest())
} catch let error as NSError {
  print("ERROR: \(error.localizedDescription)")
}

// 2
let exportFilePath = NSTemporaryDirectory() + "export.csv"
let exportFileURL = URL(fileURLWithPath: exportFilePath)
FileManager.default.createFile(atPath: exportFilePath,
  contents: Data(), attributes: nil)

下面就逐步看一下CSV導(dǎo)出代碼:

  • 1) 首先邻吞,通過(guò)執(zhí)行fetch請(qǐng)求來(lái)檢索所有JournalEntry實(shí)體组题。

獲取請(qǐng)求與獲取的結(jié)果控制器使用的請(qǐng)求相同。 因此抱冷,您重復(fù)使用surfJournalFetchRequest方法來(lái)創(chuàng)建請(qǐng)求以避免重復(fù)崔列。

  • 2) 接下來(lái),通過(guò)將文件名(“export.csv”)附加到NSTemporaryDirectory方法的輸出徘层,為導(dǎo)出的CSV文件創(chuàng)建URL峻呕。

NSTemporaryDirectory返回的路徑是臨時(shí)文件存儲(chǔ)的唯一目錄利职。 這是一個(gè)很容易再次生成并且不需要由iTunesiCloud備份的文件的好地方趣效。

創(chuàng)建導(dǎo)出URL后瘦癌,調(diào)用createFile(atPath:contents:attributes :)創(chuàng)建一個(gè)空文件,您將在其中存儲(chǔ)導(dǎo)出的數(shù)據(jù)跷敬。 如果文件已存在于指定的文件路徑中讯私,則此方法將首先將其刪除。

一旦應(yīng)用程序具有空文件西傀,它就可以將CSV數(shù)據(jù)寫入磁盤:

// 3
let fileHandle: FileHandle?
do {
  fileHandle = try FileHandle(forWritingTo: exportFileURL)
} catch let error as NSError {
  print("ERROR: \(error.localizedDescription)")
  fileHandle = nil
}

if let fileHandle = fileHandle {
  // 4
  for journalEntry in results {
    fileHandle.seekToEndOfFile()
    guard let csvData = journalEntry
      .csv()
      .data(using: .utf8, allowLossyConversion: false) else {
        continue
    }

    fileHandle.write(csvData)
  }

  // 5
  fileHandle.closeFile()

  print("Export Path: \(exportFilePath)")
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
  self.showExportFinishedAlertView(exportFilePath)

} else {
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
}

以下是文件處理的工作原理:

  • 3) 首先斤寇,應(yīng)用程序需要?jiǎng)?chuàng)建一個(gè)用于寫入的文件處理程序,它只是一個(gè)處理寫入數(shù)據(jù)所需的低級(jí)磁盤操作的對(duì)象拥褂。 要?jiǎng)?chuàng)建用于寫入的文件處理程序娘锁,請(qǐng)使用FileHandle(forWritingTo :)初始化程序。

  • 4) 接下來(lái)饺鹃,迭代所有JournalEntry實(shí)體莫秆。

在每次迭代期間,您嘗試使用JournalEntry上的csv()String上的data(using:allowLossyConversion:)創(chuàng)建UTF8編碼的字符串悔详。

如果成功镊屎,則使用文件處理程序write()方法將UTF8字符串寫入磁盤。

  • 5) 最后茄螃,關(guān)閉導(dǎo)出文件寫入文件處理程序缝驳,因?yàn)椴辉傩枰?/li>

應(yīng)用程序?qū)⑺袛?shù)據(jù)寫入磁盤后,它會(huì)顯示一個(gè)包含導(dǎo)出文件路徑的警告對(duì)話框归苍。

注意:具有導(dǎo)出路徑的此alert控制器可用于學(xué)習(xí)目的用狱,但對(duì)于真實(shí)應(yīng)用程序,您需要為用戶提供檢索導(dǎo)出的CSV文件的方法拼弃,例如使用UIActivityViewController夏伊。

要打開導(dǎo)出的CSV文件,請(qǐng)使用Excel肴敛,Numbers或您喜歡的文本編輯器導(dǎo)航到并打開alert對(duì)話框中指定的文件署海。 如果您在Numbers中打開文件,您將看到以下內(nèi)容:

現(xiàn)在您已經(jīng)了解了應(yīng)用程序當(dāng)前如何導(dǎo)出數(shù)據(jù)医男,現(xiàn)在是時(shí)候進(jìn)行一些改進(jìn)了砸狞。

2. Exporting in the Background - 在后臺(tái)導(dǎo)出

您希望UI在導(dǎo)出過(guò)程中繼續(xù)工作。 要修復(fù)UI問題镀梭,您將在私有后臺(tái)上下文而不是主上下文上執(zhí)行導(dǎo)出操作刀森。

打開JournalListViewController.swift并在exportCSVFile()中找到以下代碼:

// 1
let context = coreDataStack.mainContext
var results: [JournalEntry] = []
do {
  results = try context.fetch(self.surfJournalFetchRequest())
} catch let error as NSError {
  print("ERROR: \(error.localizedDescription)")
}

如前所述,此代碼通過(guò)在managed object context中調(diào)用fetch()來(lái)檢索所有日記條目报账。

接下來(lái)研底,使用以下代碼替換上面的代碼:

// 1
coreDataStack.storeContainer.performBackgroundTask { context in
  var results: [JournalEntry] = []
  do {
    results = try context.fetch(self.surfJournalFetchRequest())
  } catch let error as NSError {
    print("ERROR: \(error.localizedDescription)")
  }

您現(xiàn)在在堆棧的持久存儲(chǔ)容器上調(diào)用performBackgroundTask(_ :)埠偿,而不是使用UI也使用的主managed object context。 這將創(chuàng)建一個(gè)新的managed object context并將其傳遞給閉包榜晦。

performBackgroundTask(_ :)創(chuàng)建的上下文位于私有隊(duì)列上冠蒋,該隊(duì)列不會(huì)阻塞主UI隊(duì)列。 閉包中的代碼在該專用隊(duì)列上運(yùn)行乾胶。 您還可以手動(dòng)創(chuàng)建一個(gè)新的臨時(shí)私有上下文抖剿,其并發(fā)類型為.privateQueueConcurrencyType,而不是使用performBackgroundTask(_ :)识窿。

注意:managed object context可以使用兩種并發(fā)類型:
Private Queue指定將與專用調(diào)度隊(duì)列而不是主隊(duì)列關(guān)聯(lián)的上下文斩郎。 這是您剛剛用于將導(dǎo)出操作移出主隊(duì)列的隊(duì)列類型,因此它不再干擾UI喻频。
Main Queue是默認(rèn)類型缩宜,指定上下文將與主隊(duì)列關(guān)聯(lián)。 此類型是主上下文(coreDataStack.mainContext)使用的類型甥温。 任何UI操作(例如為表視圖創(chuàng)建fetched的結(jié)果控制器)都必須使用此類型的上下文锻煌。
只能從正確的隊(duì)列訪問上下文及其managed objectsNSManagedObjectContext執(zhí)行perform(_:)performAndWait(_ :)以將工作定向到正確的隊(duì)列窿侈。 您可以將啟動(dòng)參數(shù)-com.apple.CoreData.ConcurrencyDebug 1添加到應(yīng)用程序的scheme中炼幔,以捕獲調(diào)試器中的錯(cuò)誤。

接下來(lái)史简,在同一方法中找到以下代碼:

  print("Export Path: \(exportFilePath)")
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
  self.showExportFinishedAlertView(exportFilePath)
} else {
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
}

用以下代碼進(jìn)行替換:

    print("Export Path: \(exportFilePath)")
    // 6
    DispatchQueue.main.async {
      self.navigationItem.leftBarButtonItem =
        self.exportBarButtonItem()
      self.showExportFinishedAlertView(exportFilePath)
    }
  } else {
    DispatchQueue.main.async {
      self.navigationItem.leftBarButtonItem =
        self.exportBarButtonItem()
    }
  }
} // 7 Closing brace for performBackgroundTask

要完成任務(wù):

  • 6) 您應(yīng)始終執(zhí)行與主隊(duì)列上的UI相關(guān)的所有操作乃秀,例如在導(dǎo)出操作完成時(shí)顯示警報(bào)視圖; 否則,可能發(fā)生不可預(yù)測(cè)的事情圆兵。 使用DispatchQueue.main.async在主隊(duì)列上顯示最終的警報(bào)視圖消息跺讯。
  • 7) 最后,添加一個(gè)結(jié)束大括號(hào)殉农,通過(guò)performBackgroundTask(_ :)調(diào)用關(guān)閉您在步驟1中先前打開的塊刀脏。

現(xiàn)在您已將導(dǎo)出操作移動(dòng)到具有專用隊(duì)列的新上下文,構(gòu)建并運(yùn)行以查看它是否有效超凳!

您應(yīng)該看到之前看到的確切內(nèi)容:

點(diǎn)擊左上角的Export按鈕愈污,立即嘗試滾動(dòng)瀏覽會(huì)話日記條目列表。 注意這次有什么不同嗎轮傍? 導(dǎo)出操作仍需要幾秒鐘才能完成暂雹,但table view在此期間繼續(xù)滾動(dòng)。 導(dǎo)出操作不再阻塞UI创夜。

您剛剛目睹了如何在私有后臺(tái)隊(duì)列上工作可以改善用戶的應(yīng)用體驗(yàn)杭跪。 現(xiàn)在,您將通過(guò)檢查子上下文來(lái)擴(kuò)展多個(gè)上下文的使用。

后記

本篇主要講述了基于多上下文的Core Data簡(jiǎn)單解析示例涧尿,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末系奉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子姑廉,更是在濱河造成了極大的恐慌缺亮,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庄蹋,死亡現(xiàn)場(chǎng)離奇詭異瞬内,居然都是意外死亡迷雪,警方通過(guò)查閱死者的電腦和手機(jī)限书,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)章咧,“玉大人倦西,你說(shuō)我怎么就攤上這事×扪希” “怎么了扰柠?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)疼约。 經(jīng)常有香客問我卤档,道長(zhǎng),這世上最難降的妖魔是什么程剥? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任劝枣,我火速辦了婚禮,結(jié)果婚禮上织鲸,老公的妹妹穿的比我還像新娘舔腾。我一直安慰自己,他們只是感情好搂擦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布稳诚。 她就那樣靜靜地躺著,像睡著了一般瀑踢。 火紅的嫁衣襯著肌膚如雪扳还。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天橱夭,我揣著相機(jī)與錄音氨距,去河邊找鬼。 笑死徘钥,一個(gè)胖子當(dāng)著我的面吹牛衔蹲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼舆驶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼橱健!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起沙廉,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拘荡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后撬陵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珊皿,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年巨税,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蟋定。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡草添,死狀恐怖驶兜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情远寸,我是刑警寧澤抄淑,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站驰后,受9級(jí)特大地震影響肆资,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灶芝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一郑原、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧监署,春花似錦颤专、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至晓避,卻和暖如春簇捍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俏拱。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工暑塑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锅必。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓事格,卻偏偏與公主長(zhǎng)得像惕艳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驹愚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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