數(shù)據(jù)持久化方案解析(四) —— 基于NSCoding的持久化存儲(chǔ)(二)

版本記錄

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

前言

數(shù)據(jù)的持久化存儲(chǔ)是移動(dòng)端不可避免的一個(gè)問(wèn)題,很多時(shí)候的業(yè)務(wù)邏輯都需要我們進(jìn)行本地化存儲(chǔ)解決和完成啃洋,我們可以采用很多持久化存儲(chǔ)方案,比如說(shuō)plist文件(屬性列表)屎鳍、preference(偏好設(shè)置)宏娄、NSKeyedArchiver(歸檔)、SQLite 3逮壁、CoreData孵坚,這里基本上我們都用過(guò)冈钦。這幾種方案各有優(yōu)缺點(diǎn)环壤,其中酱固,CoreData是蘋(píng)果極力推薦我們使用的一種方式付材,我已經(jīng)將它分離出去一個(gè)專(zhuān)題進(jìn)行說(shuō)明講解绢片。這個(gè)專(zhuān)題主要就是針對(duì)另外幾種數(shù)據(jù)持久化存儲(chǔ)方案而設(shè)立逾滥。
1. 數(shù)據(jù)持久化方案解析(一) —— 一個(gè)簡(jiǎn)單的基于SQLite持久化方案示例(一)
2. 數(shù)據(jù)持久化方案解析(二) —— 一個(gè)簡(jiǎn)單的基于SQLite持久化方案示例(二)
3. 數(shù)據(jù)持久化方案解析(三) —— 基于NSCoding的持久化存儲(chǔ)(一)

源碼

1. Swift

首先看一下代碼組織結(jié)構(gòu)掂榔。

接著看一下sb中的內(nèi)容

下面就是源碼部分了

1. MasterViewController.swift
import UIKit

class MasterViewController: UITableViewController {
  var creatures: [ScaryCreatureDoc] = []
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    navigationItem.leftBarButtonItem = editButtonItem
    
    let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped(_:)))
    navigationItem.rightBarButtonItem = addButton
    
    title = "Scary Creatures"
    
    loadCreatures()
  }
  
  // MARK: - Segues
  
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail" {
      if let indexPath = tableView.indexPathForSelectedRow {
        let object = creatures[indexPath.row]
        let controller = segue.destination as! DetailViewController
        controller.detailItem = object
      }
    }
  }
  
  override func didMove(toParent parent: UIViewController?) {
    tableView.reloadData()
  }
  
  // MARK: - Preloading Data
  func loadCreatures() {
//    let creature1 = ScaryCreatureDoc(title: "Ghost", rating: 5, thumbImage: #imageLiteral(resourceName: "ghostThumb"), fullImage: #imageLiteral(resourceName: "ghost"))
//    let creature2 = ScaryCreatureDoc(title: "Monster", rating: 5, thumbImage: #imageLiteral(resourceName: "monsterThumb"), fullImage: #imageLiteral(resourceName: "monster"))
//    let creature3 = ScaryCreatureDoc(title: "Panda", rating: 1, thumbImage: #imageLiteral(resourceName: "pandaThumb"), fullImage: #imageLiteral(resourceName: "panda"))
//    let creature4 = ScaryCreatureDoc(title: "Red Bug", rating: 3, thumbImage: #imageLiteral(resourceName: "redBugThumb"), fullImage: #imageLiteral(resourceName: "redBug"))
//    let creature5 = ScaryCreatureDoc(title: "Slug", rating: 4, thumbImage: #imageLiteral(resourceName: "slugThumb"), fullImage: #imageLiteral(resourceName: "slug"))
//    let creature6 = ScaryCreatureDoc(title: "Spider", rating: 3, thumbImage: #imageLiteral(resourceName: "spiderThumb"), fullImage: #imageLiteral(resourceName: "spider"))
//    let creature7 = ScaryCreatureDoc(title: "Yeti", rating: 3, thumbImage: #imageLiteral(resourceName: "yetiThumb"), fullImage: #imageLiteral(resourceName: "yeti"))
//
//    creatures = [creature1, creature2, creature3, creature4, creature5, creature6, creature7]
    
    creatures = ScaryCreatureDatabase.loadScaryCreatureDocs()
  }
  
  // MARK: - Table View
  
  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return creatures.count
  }
  
  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MyBasicCell", for: indexPath)
    
    let creature = creatures[indexPath.row]
    cell.textLabel!.text = creature.data?.title
    cell.imageView!.image = creature.thumbImage
    
    return cell
  }
  
  override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
  }
  
  override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
      let creatureToDelete = creatures.remove(at: indexPath.row)
      creatureToDelete.deleteDoc()
      tableView.deleteRows(at: [indexPath], with: .fade)
    }
  }
  
  // MARK: - IBActions
  
  @objc func addTapped(_ sender: Any) {
    let newDoc = ScaryCreatureDoc(title: "New Creature", rating: 0, thumbImage: nil, fullImage: nil)
    creatures.append(newDoc)
    
    let newIndexPath = IndexPath(row: creatures.count - 1, section: 0)
    tableView.insertRows(at: [newIndexPath], with: .automatic)
    tableView.selectRow(at: newIndexPath, animated: true, scrollPosition: .middle)
    performSegue(withIdentifier: "showDetail", sender: self)
  }
}
2. DetailViewController.swift
import UIKit

class DetailViewController: UIViewController {
  @IBOutlet weak var rateView: RateView!
  @IBOutlet weak var detailDescriptionLabel: UILabel!
  @IBOutlet weak var titleField: UITextField!
  @IBOutlet weak var imageView: UIImageView!
  
  private var picker: UIImagePickerController!
  
  var detailItem: ScaryCreatureDoc? {
    didSet {
      if isViewLoaded {
        configureView()
      }
    }
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    picker = UIImagePickerController()
    configurePicker()
    configureView()
  }
  
  func configurePicker() {
    picker.delegate = self
    picker.sourceType = .photoLibrary
    picker.allowsEditing = false
  }
  
  func configureView() {
    rateView.notSelectedImage = #imageLiteral(resourceName: "shockedface2_empty")
    rateView.fullSelectedImage = #imageLiteral(resourceName: "shockedface2_full")
    rateView.editable = true
    rateView.maxRating = 5
    rateView.delegate = self
    
    if let detailItem = detailItem {
      titleField.text = detailItem.data!.title
      rateView.rating = detailItem.data!.rating
      imageView.image = detailItem.fullImage
      detailDescriptionLabel.isHidden = imageView.image != nil
    }
  }
  
  @IBAction func addPictureTapped(_ sender: UIButton) {
    present(picker, animated: true, completion: nil)
  }
  
  @IBAction func titleFieldTextChanged(_ sender: UITextField) {
    detailItem?.data?.title = sender.text!
    detailItem?.saveData()
  }
}

// MARK: - RateViewDelegate

extension DetailViewController: RateViewDelegate {
  func rateViewRatingDidChange(rateView: RateView, newRating: Float) {
    detailItem?.data?.rating = newRating
    detailItem?.saveData()
  }
}

// MARK: - UIImagePickerControllerDelegate

extension DetailViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
  func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    dismiss(animated: true, completion: nil)
  }
  
  func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
    let fullImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
    let concurrentQueue = DispatchQueue(label: "ResizingQueue", attributes: .concurrent)
    
    concurrentQueue.async {
      let thumbImage = fullImage.resized(newSize: CGSize(width: 107, height: 107))
      
      DispatchQueue.main.async {
        self.detailItem?.fullImage = fullImage
        self.detailItem?.thumbImage = thumbImage
        self.imageView.image = fullImage
        self.detailItem?.saveImages()
      }
    }
    dismiss(animated: true, completion: nil)
  }
}

// MARK: - UITextFieldDelegate

extension DetailViewController: UITextFieldDelegate {
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
  }
}
3. Extensions.swift
import UIKit

extension UIImage {
  func resized(newSize: CGSize) -> UIImage {
    let horizontalRatio = newSize.width / size.width
    let verticalRatio = newSize.height / size.height
    
    let ratio = max(horizontalRatio, verticalRatio)
    
    return resized(ratio: ratio)
  }
  
  func resized(ratio: CGFloat) -> UIImage {
    let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
    UIGraphicsBeginImageContextWithOptions(newSize, true, 0)
    draw(in: CGRect(origin: .zero, size: newSize))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage!
  }
}
4. RateView.swift
import UIKit

protocol RateViewDelegate {
  func rateViewRatingDidChange(rateView: RateView, newRating: Float)
}

class RateView: UIView {
  var notSelectedImage: UIImage? {
    didSet {
      refresh()
    }
  }
  
  var fullSelectedImage: UIImage? {
    didSet {
      refresh()
    }
  }
  
  var rating: Float = 0 {
    didSet {
      refresh()
    }
  }
  
  var editable = false
  var imageViews: [UIImageView] = []
  var maxRating = 5 {
    didSet {
      rebindMaxRating()
    }
  }
  
  var midMargin: CGFloat = 5
  var leftMargin: CGFloat = 0
  var minImageSize = CGSize(width: 5, height: 5)
  var delegate: RateViewDelegate!
  
  private func refresh() {
    for (i, imageView) in imageViews.enumerated() {
      if (rating >= Float(i + 1)) {
        imageView.image = fullSelectedImage;
      } else {
        imageView.image = notSelectedImage;
      }
    }
  }
  
  override func layoutSubviews() {
    super.layoutSubviews()
    
    guard notSelectedImage != nil else { return }
    
    let desiredImageWidth = (frame.width - (leftMargin * 2) - (midMargin * CGFloat(imageViews.count))) / CGFloat(imageViews.count)
    let imageWidth = max(minImageSize.width, desiredImageWidth)
    let imageHeight = max(minImageSize.height, frame.height);
    
    for (i, imageView) in imageViews.enumerated() {
      let imageFrame = CGRect(x: leftMargin + (CGFloat(i) * (midMargin + imageWidth)), y: 0, width: imageWidth, height: imageHeight)
      imageView.frame = imageFrame
    }
  }
  
  private func rebindMaxRating() {
    imageViews.forEach { $0.removeFromSuperview() }
    imageViews.removeAll()
    
    for _ in 0..<maxRating {
      let imageView = UIImageView()
      imageView.contentMode = .scaleAspectFill
      imageViews.append(imageView)
      addSubview(imageView)
    }
    
    setNeedsLayout()
    refresh()
  }
  
  func handleTouch(touchLocation: CGPoint) {
    guard editable else { return }
    
    var newRating = 0
    
    for i in stride(from: imageViews.count - 1, to: -1, by: -1) {
      let imageView = imageViews[i]
      if touchLocation.x > imageView.frame.minX {
        newRating = i + 1
        break;
      }
    }
    
    rating = Float(newRating)
  }
  
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
      let touchLocation = touch.location(in: self)
      handleTouch(touchLocation: touchLocation)
    }
  }
  
  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
      let touchLocation = touch.location(in: self)
      handleTouch(touchLocation: touchLocation)
    }
  }
  
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    delegate.rateViewRatingDidChange(rateView: self, newRating: rating)
  }
}
5. ScaryCreatureData.swift
import Foundation

class ScaryCreatureData: NSObject, NSCoding, NSSecureCoding {
  var title = ""
  var rating: Float = 0
  
  init(title: String, rating: Float) {
    super.init()
    self.title = title
    self.rating = rating
  }
  
  // MARK: NSCoding Implementation
  
  enum Keys: String {
    case title = "Title"
    case rating = "Rating"
  }
  
  func encode(with aCoder: NSCoder) {
//    For NSCoding
    aCoder.encode(title, forKey: Keys.title.rawValue)
    aCoder.encode(rating, forKey: Keys.rating.rawValue)
    
//    For NSSecureCoding
//    aCoder.encode(title as NSString, forKey: Keys.title.rawValue)
//    aCoder.encode(NSNumber(value: rating), forKey: Keys.rating.rawValue)
  }
  
  required convenience init?(coder aDecoder: NSCoder) {
//    For NSCoding
//    let title = aDecoder.decodeObject(forKey: Keys.title.rawValue) as! String
//    let rating = aDecoder.decodeFloat(forKey: Keys.rating.rawValue)
    
//    For NSSecureCoding
    let title = aDecoder.decodeObject(of: NSString.self, forKey: Keys.title.rawValue) as String? ?? ""
    let rating = aDecoder.decodeObject(of: NSNumber.self, forKey: Keys.rating.rawValue)
    self.init(title: title, rating: rating?.floatValue ?? 0)
  }
  
  static var supportsSecureCoding: Bool {
    return true
  }
}
6. ScaryCreatureDoc.swift
import UIKit

class ScaryCreatureDoc: NSObject {
  enum Keys: String {
    case dataFile = "Data.plist"
    case thumbImageFile = "thumbImage.png"
    case fullImageFile = "fullImage.png"
  }
  
  private var _data: ScaryCreatureData?
  var data: ScaryCreatureData? {
    get {
      // 1) return the value if already loaded
      if _data != nil { return _data }
      
      // 2) read the saved file as 'Data'
      let dataURL = docPath!.appendingPathComponent(Keys.dataFile.rawValue)
      guard let codedData = try? Data(contentsOf: dataURL) else { return nil }
//      For NSCoding
      // 3) unarchive the object from the Data object
      _data = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(codedData) as? ScaryCreatureData
      
//      For NSSecureCoding
      
//      _data = try! NSKeyedUnarchiver.unarchivedObject(ofClass: ScaryCreatureData.self, from: codedData)
      return _data
    }
    set {
      _data = newValue
    }
  }
  
  private var _thumbImage: UIImage?
  var thumbImage: UIImage? {
    get {
      if _thumbImage != nil { return _thumbImage }
      if docPath == nil { return nil }
      
      let thumbImageURL = docPath!.appendingPathComponent(Keys.thumbImageFile.rawValue)
      guard let imageData = try? Data(contentsOf: thumbImageURL) else { return nil }
      _thumbImage = UIImage(data: imageData)
      return _thumbImage
    }
    set {
      _thumbImage = newValue
    }
  }
  
  private var _fullImage: UIImage?
  var fullImage: UIImage? {
    get {
      if _fullImage != nil { return _fullImage }
      if docPath == nil { return nil }
      
      let fullImageURL = docPath!.appendingPathComponent(Keys.fullImageFile.rawValue)
      guard let imageData = try? Data(contentsOf: fullImageURL) else { return nil }
      _fullImage = UIImage(data: imageData)
      return _fullImage
    }
    set {
      _fullImage = newValue
    }
  }
  
  var docPath: URL?
  
  init(docPath: URL) {
    super.init()
    self.docPath = docPath
  }
  
  init(title: String, rating: Float, thumbImage: UIImage?, fullImage: UIImage?) {
    super.init()
    _data = ScaryCreatureData(title: title, rating: rating)
    self.thumbImage = thumbImage
    self.fullImage = fullImage
    saveData()
    saveImages()
  }
  
  func createDataPath() throws {
    guard docPath == nil else { return }
    
    docPath = ScaryCreatureDatabase.nextScaryCreatureDocPath()
    try FileManager.default.createDirectory(at: docPath!, withIntermediateDirectories: true, attributes: nil)
  }
  
  func saveData() {
    // 1) Do nothing if there is nothing to save
    guard let data = data else { return }
    
    // 2) Create the docPath and the folder on disk
    do {
      try createDataPath()
    }catch {
      print("Couldn't create save folder. " + error.localizedDescription)
      return
    }
    
    // 3) Build the path of the file to write
    let dataURL = docPath!.appendingPathComponent(Keys.dataFile.rawValue)
    
    // 4) Encode the data using NSCoding
    let codedData = try! NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)
    
    // 5) Write the encoded data to the file.
    do {
      try codedData.write(to: dataURL)
    }catch {
      print("Couldn't write to save file: " + error.localizedDescription)
    }
  }
  
  func deleteDoc() {
    if let docPath = docPath {
      do {
        try FileManager.default.removeItem(at: docPath)
      }catch {
        print("Error Deleting Folder. " + error.localizedDescription)
      }
    }
  }
  
  func saveImages() {
    // 1) Make sure that there are images stored
    if _fullImage == nil || _thumbImage == nil { return }
    
    // 2) Create the storage folder if required
    do {
      try createDataPath()
    }catch {
      print("Couldn't create save Folder. " + error.localizedDescription)
      return
    }
    
    // 3) Build the paths for each file
    let thumbImageURL = docPath!.appendingPathComponent(Keys.thumbImageFile.rawValue)
    let fullImageURL = docPath!.appendingPathComponent(Keys.fullImageFile.rawValue)
    
    // 4) Convert the images to Data objects with a PNG representation
    let thumbImageData = _thumbImage!.pngData()
    let fullImageData = _fullImage!.pngData()
    
    // 5) Write the PNG data to disk
    try! thumbImageData!.write(to: thumbImageURL)
    try! fullImageData!.write(to: fullImageURL)
  }
}
7. ScaryCreatureDatabase.swift
import Foundation

class ScaryCreatureDatabase: NSObject {
  static let privateDocsDir: URL = {
    // 1
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    
    // 2
    let documentsDirectoryURL = paths.first!.appendingPathComponent("PrivateDocuments")
    
    // 3
    do {
      try FileManager.default.createDirectory(at: documentsDirectoryURL,
                                              withIntermediateDirectories: true,
                                              attributes: nil)
    } catch {
      print("Couldn't create directory")
    }
    return documentsDirectoryURL
  }()

  class func nextScaryCreatureDocPath() -> URL? {
    // 1) Get all the files and folders within the database folder
    guard let files = try? FileManager.default.contentsOfDirectory(at: privateDocsDir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { return nil }
    var maxNumber = 0
    
    // 2) Get the highest numbered item saved within the database
    files.forEach {
      if $0.pathExtension == "scarycreature" {
        let fileName = $0.deletingPathExtension().lastPathComponent
        maxNumber = max(maxNumber, Int(fileName) ?? 0)
      }
    }
    
    // 3) Return a path with the consecutive number
    return privateDocsDir.appendingPathComponent("\(maxNumber + 1).scarycreature", isDirectory: true)
  }
  
  class func loadScaryCreatureDocs() -> [ScaryCreatureDoc] {
    guard let files = try? FileManager.default.contentsOfDirectory(at: privateDocsDir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { return [] }
    
    return files
      .filter { $0.pathExtension == "scarycreature" }
      .map { ScaryCreatureDoc(docPath: $0) }
  }
}

后記

本篇主要講述了基于NSCoding的持久化存儲(chǔ)窒百,感興趣的給個(gè)贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末词裤,一起剝皮案震驚了整個(gè)濱河市刺洒,隨后出現(xiàn)的幾起案子鳖宾,更是在濱河造成了極大的恐慌,老刑警劉巖逆航,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鼎文,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡因俐,警方通過(guò)查閱死者的電腦和手機(jī)拇惋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)抹剩,“玉大人蚤假,你說(shuō)我怎么就攤上這事“赏茫” “怎么了磷仰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)境蔼。 經(jīng)常有香客問(wèn)我灶平,道長(zhǎng),這世上最難降的妖魔是什么箍土? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任逢享,我火速辦了婚禮,結(jié)果婚禮上吴藻,老公的妹妹穿的比我還像新娘瞒爬。我一直安慰自己,他們只是感情好沟堡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布侧但。 她就那樣靜靜地躺著,像睡著了一般航罗。 火紅的嫁衣襯著肌膚如雪禀横。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天粥血,我揣著相機(jī)與錄音柏锄,去河邊找鬼。 笑死复亏,一個(gè)胖子當(dāng)著我的面吹牛趾娃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缔御,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼抬闷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了刹淌?” 一聲冷哼從身側(cè)響起饶氏,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎有勾,沒(méi)想到半個(gè)月后疹启,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔼卡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年喊崖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雇逞。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荤懂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出塘砸,到底是詐尸還是另有隱情节仿,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布掉蔬,位于F島的核電站廊宪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏女轿。R本人自食惡果不足惜箭启,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛉迹。 院中可真熱鬧傅寡,春花似錦、人聲如沸北救。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)珍策。三九已至淀零,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膛壹,已是汗流浹背驾中。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留模聋,地道東北人肩民。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像链方,于是被迫代替她去往敵國(guó)和親持痰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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