iOS Swift 原生 字典数组转模型 JSONDecoder 对象存储 NSKeyedArchiver

前言:

最近在写关于网络请求相关的代码。简单来说,我要做的事情是:

1、创建一个 Swift Model 类
2、通过网络请求 JSON 转 Model
3、把这个 Model 存到沙盒里
4、把存起来的 Model 拿出来接着用

以上4步都是使用iOS原生的代码做的事情,不使用三方框架。
在写代码的时候,也遇到了一些问题。所以,做个总结。
我再也不想写这玩意了

Swift
代码地址:https://github.com/gityuency/Autolayout
示例代码类名 【SwiftCodingViewController】

运行截图

运行截图1.png
运行截图2.png

第一阶段 认识你的JSON

我们的JSON数据长这个样子:
第一个,最外层是个字典,里面包含了 字符串类型(String),整型(Int),浮点型(Float),布尔(Bool),并且包含了子对象,一个数组(Array),一个字典(Dictionary),这个JSON应该具有些许的代表性了:

{
    "name": "农夫果园",
    "location": "上海市 浦东新区 申迪北路 753号 上海迪士尼度假区",
    "number": 10001,
    "money": 998.12,
    "open": true,
    "fruits": [
        {
            "name": "火龙果",
            "count": 2000,
            "price": 56.23,
            "onsale": true
        },
        {
            "name": "山竹",
            "count": 555,
            "price": 17.22,
            "onsale": false
        }
    ],
    "owner": {
        "name": "姬友大人",
        "age": 30
    }
}

第二个,最外层是个数组,数组里面也包含了 字符串类型(String),整型(Int),浮点型(Float),布尔(Bool):

[
    {
        "name": "苹果🍎",
        "count": 8855,
        "price": 6.23,
        "onsale": false
    },
    {
        "name": "菠萝🍍",
        "count": 555,
        "price": 55.22,
        "onsale": false
    },
    {
        "name": "樱桃🍒",
        "count": 2567,
        "price": 100.5,
        "onsale": true
    }
]

这两种类型的JSON串你都得解出来,所以,在下面的的代码里面,都有对应的解法。

第二阶段 写你的Model

在上面的JSON串中,有很多不同的数据类型,所以,在写Model的时候,也要注意类型,还有其他细节。关于这个Model,要注意的事情我都写在了代码注释里面。
存对象,NSKeyedArchiver,需要继承 NSCoding 协议
编码解码,JSONDecoder,需要继承 Codable 协议

示例的 Model:

import Foundation

class HomePageModel: NSObject, NSCoding, Codable {
    
    // 1号坑
    // 如果这个字段 "name_wrong_example" 在后端返回过来的 json 串里没有, 而这里定义类型 是 "String" 不是 "String?" 将会导致在 JSONDecoder 的时候字典转模型失败
    // 同样的道理, 不管定义的属性类型是什么, 只要是在 json 串里没有的, 不使用可选型,都会导致解析失败,所以,为了安全起见,把这些属性都定义为可选型吧
    //var name_wrong_example: String = "初始值,"  //错误
    //var name_wrong_example: String? = "初始值"  //正确
    //var name_wrong_example: String?            //正确
    
    var name: String?
    
    var number: Int?
    
    // 2号坑
    // 如果这个字段 "money" 在后端返回过来的 json 串里是浮点类型的, 有小数点, 那么需要定义为 Float, 如果定义为 Int, 将会导致 JSONDecoder 的时候字典转模型失败
    // 需要注意的问题是, 在json转模型的时候, 这个字段的数值精度会丢失.
    //var money: Int? = 998      //错误,定义的类型和返回的json串里的类型不一致
    //var money: Float? = 22.33  //正确 可以赋初始值
    //var money: Float?          //正确
    
    var money: Float?
    
    
    var open: Bool?

    ///"address"这个字段在 json 串里是没有的, json 串里的 "location" 字段在这个模型里面也没有定义, 这么做, 是为了查看 缺少字段, 写错字段,会不会引起崩溃
    ///如果这里的 address 不使用 可选型, 写成这样: [ var address: String = "" ]  就炸了
    var address: String?
    
    // 对象里面包含了一个数组类型的值
    var fruits: [FruitsInfo]?
    
    
    // 对象里面还包含了一个对象
    var owner: OwnerInfo?
    
    
    override init() {
        
    }
    
    // NSCoding 协议里面的方法
    func encode(with aCoder: NSCoder) {
        
        aCoder.encode(name, forKey: "name")
        aCoder.encode(number, forKey: "number")
        aCoder.encode(money, forKey: "money")
        aCoder.encode(open, forKey: "open")

        aCoder.encode(fruits, forKey: "fruits")
        aCoder.encode(owner, forKey: "owner")
    }
    
    // NSCoding 协议里面的方法
    required init?(coder aDecoder: NSCoder) {
        super.init()
        
        name = (aDecoder.decodeObject(forKey: "name") as? String) ?? ""
        number = aDecoder.decodeObject(forKey: "number") as? Int
        money = aDecoder.decodeObject(forKey: "money") as? Float
        
        // 3号坑
        // 在这个方法里面, 如果解码的方法调用不对,也是会造成失败, 无法顺利取出对象, 所有的属性(Bool, String, Int ...), 在解码的时候都要调用 decodeObject, 然后该转类型的转类型
        //open = aDecoder.decodeBool(forKey: "open") //错误, 不能因为我知道它是bool类型就使用 "decodeBool", 因为这里定义的属性都是可选型, 同样, 也不能使用 "decodeInteger" 这样明确解码类型的方法去解码其他可选型的属性.
        open = aDecoder.decodeObject(forKey: "open") as? Bool //正确
        
        
        fruits = aDecoder.decodeObject(forKey: "fruits") as? [FruitsInfo]
        owner = aDecoder.decodeObject(forKey: "owner") as? OwnerInfo
    }
}

/// 二级模型 水果摊
class FruitsInfo: NSObject, NSCoding, Codable {
    
    var name: String?
    var count: Int?
    var price: Float?
    var onsale: Bool?
    
    override init() {
        
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.name, forKey: "name")
        aCoder.encode(self.count, forKey: "count")
        aCoder.encode(self.price, forKey: "price")
        aCoder.encode(self.onsale, forKey: "onsale")
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init()
        name = (aDecoder.decodeObject(forKey: "name") as? String) ?? ""
        count = aDecoder.decodeObject(forKey: "count") as? Int
        price = aDecoder.decodeObject(forKey: "price") as? Float
        onsale = aDecoder.decodeObject(forKey: "onsale") as? Bool
    }
}

/// 二级模型 商店老板
class OwnerInfo: NSObject, NSCoding, Codable {
    
    var name: String?
    var age: Int?
    
    override init() {
        
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
        aCoder.encode(age, forKey: "age")
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init()
        name = (aDecoder.decodeObject(forKey: "name") as? String) ?? ""
        age = aDecoder.decodeObject(forKey: "age") as? Int
    }
}



extension HomePageModel {
    override var description: String {
        return  """
        \(String(describing: name))
        \(String(describing: number))
        \(String(describing: money))
        \(String(describing: open))
        \(String(describing: address))
        \(String(describing: fruits))
        \(String(describing: owner))
        """
    }
}


extension FruitsInfo {
    override var description: String {
        return  """
        \(String(describing: name))
        \(String(describing: count))
        \(String(describing: price))
        \(String(describing: onsale))
        """
    }
}

extension OwnerInfo {
    override var description: String {
        return  """
        \(String(describing: name))
        \(String(describing: age))
        """
    }
}

第三阶段 JSON 转 Model

这个地方就要使用JSON转Model的类了,写来写去就那么几句话,但是吧,不经常写,还是容易尴尬。我把这些代码写到了一个类里面。
代码如下:

import Foundation

/// 字典转模型工具类,  重复代码不抽取
struct YXTransferToModel {
    
    /// 字典转模型
    public static func toModelObject<T>(_ dictionary: Any?, to type: T.Type) -> T? where T: Decodable {
        
        guard let dictionary = dictionary else {
            print("❌ 传入的数据解包失败!")
            return nil
        }
        
        if !JSONSerialization.isValidJSONObject(dictionary) {
            print("❌ 不是合法的json对象!")
            return nil
        }
        
        guard let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) else {
            print("❌ JSONSerialization序列化失败!")
            return nil
        }
        
        guard let model = try? JSONDecoder().decode(type, from: data) else {
            print("❌ JSONDecoder字典转模型失败!")
            return nil
        }
        
        return model
    }
    
    /// 数组转模型
    public static func toModelArray<T>(_ array: Any?, to type: T.Type) -> [T]? where T: Decodable {
        
        guard let array = array else {
            print("❌ 传入的数据解包失败!")
            return nil
        }
        
        if !JSONSerialization.isValidJSONObject(array) {
            print("❌ 不是合法的json对象!")
            return nil
        }
        
        guard let data = try? JSONSerialization.data(withJSONObject: array, options: []) else {
            print("❌ JSONSerialization序列化失败!")
            return nil
        }
        
        guard let arrayModel = try? JSONDecoder().decode([T].self, from: data) else {
            print("❌ JSONDecoder数组转模型失败!")
            return nil
        }
        
        return arrayModel
    }
}

第三阶段 (插曲) Model 转 JSON,String

这都是经常干的事情了,JSON 和 Model 互转,所以,也写到一起。
代码如下:

import Foundation

/*
 来自网上的解释
 
 NSJSONReadingMutableContainers:返回可变容器,NSMutableDictionary或NSMutableArray。
 
 NSJSONReadingMutableLeaves:返回的JSON对象中字符串的值为NSMutableString,目前在iOS 7上测试不好用,应该是个bug,参见:
 http://stackoverflow.com/questions/19345864/nsjsonreadingmutableleaves-option-is-not-working
 
 NSJSONReadingAllowFragments:允许JSON字符串最外层既不是NSArray也不是NSDictionary,但必须是有效的JSON Fragment。例如使用这个选项可以解析 @“123” 这样的字符串。参见:
 http://stackoverflow.com/questions/16961025/nsjsonserialization-nsjsonreadingallowfragments-reading
 */


/// 字典转模型工具类,  重复代码不抽取
struct YXTransferToJson {
    
    /// 模型转字符串
    public static func model<T>(toString model: T) -> String? where T: Encodable {
        
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        
        guard let data = try? jsonEncoder.encode(model) else {
            print("❌ jsonEncoder解码失败!")
            return nil
        }
        
        guard let jsonString = String(data: data, encoding: .utf8) else {
            print("❌ data到字符串失败!")
            return nil
        }
        return jsonString
    }
    
    /// 模型转字典
    public static func model<T>(toDictionary model: T) -> [String: Any]? where T: Encodable {
        
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        
        guard let data = try? jsonEncoder.encode(model) else {
            print("❌ jsonEncoder解码失败!")
            return nil
        }
        
        guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {
            print("❌ data到字典失败!")
            return nil
        }
        return dictionary
    }
    
    /// 模型转数组
    public static func model<T>(toArray model: T) -> [Any]? where T: Encodable {
        
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        
        guard let data = try? jsonEncoder.encode(model) else {
            print("❌ jsonEncoder解码失败!")
            return nil
        }
        
        guard let array = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [Any] else {
            print("❌ data到数组失败!")
            return nil
        }
        return array
    }
}

第四阶段 把模型存到沙盒里

通过上面的 JSON 转 Model, 已经拿到了 Model, 我们在断网的情况下,也需要在页面上显示数据,这就需要把 Model 存起来,先存个沙盒,使用 NSKeyedArchiver。
代码如下:

import Foundation

/// 把 模型对象 或者 模型数组 存到 沙盒 里面, 重复代码不抽取
struct YXSaverForSandBox {
    
    static let KeyCacheModelName = "取一个好听的名字"
    
    static let KeyCacheArrayName = "你叫姬友最好听"
    
    private static let YXModelCache = "YXModelCache" //真机下面好像不能直接使用 Document 文件夹, 会引起崩溃, 所以自己创建一个文件夹
    
    private static let dateFormatter = DateFormatter()
    
    /// 把 模型对象 存到 沙盒 里面
    static func saveToSandBox(key: String, with modelObject: NSCoding) {
        
        guard let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
            print("❌ 获取沙盒目录失败!")
            return
        }
        
        let userDirPath = URL(fileURLWithPath: docPath).appendingPathComponent(YXModelCache)
        
        guard (try? FileManager.default.createDirectory(at: userDirPath, withIntermediateDirectories: true, attributes: [:])) != nil else {
            print("❌ 创建沙盒文件目录失败!")
            return
        }
        
        let dataFullPath = "\(docPath)/\(YXModelCache)/\(key)"
        
        NSKeyedArchiver.archiveRootObject(modelObject, toFile: dataFullPath)
    }
    
    /// 把 模型对象 从 沙盒 里取出来
    static func fetchFromSandBox<T>(key: String, asObject type: T.Type) -> T? where T: NSCoding {
        
        guard let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
            print("❌ 获取沙盒目录失败!")
            return nil
        }
        
        let dataFullPath = "\(docPath)/\(YXModelCache)/\(key)"
        
        guard let data = NSKeyedUnarchiver.unarchiveObject(withFile: dataFullPath) as? T else {
            print("❌ unarchiveObject 失败!")
            return nil
        }
        return data
    }
    
    /// 把 模型数组 存到 沙盒 里面
    static func saveToSandBox(key: String, with modelArray: [NSCoding]) {
        
        guard let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
            print("❌ 获取沙盒目录失败!")
            return
        }
        
        let userDirPath = URL(fileURLWithPath: docPath).appendingPathComponent(YXModelCache)
        
        guard (try? FileManager.default.createDirectory(at: userDirPath, withIntermediateDirectories: true, attributes: [:])) != nil else {
            print("❌ 创建沙盒文件目录失败!")
            return
        }
        
        let dataFullPath = "\(docPath)/\(YXModelCache)/\(key)"
        
        NSKeyedArchiver.archiveRootObject(modelArray, toFile: dataFullPath)
    }
    
    /// 把 模型数组 从 沙盒 里取出来
    static func fetchFromSandBox<T>(key: String, asArray type: T.Type) -> [T]? where T: NSCoding {
        
        guard let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
            print("❌ 获取沙盒目录失败!")
            return nil
        }
        
        let dataFullPath = "\(docPath)/\(YXModelCache)/\(key)"
        
        guard let data = NSKeyedUnarchiver.unarchiveObject(withFile: dataFullPath) as? [T] else {
            print("❌ unarchiveObject 失败!")
            return nil
        }
        return data
    }
    
    /// 把模型存到沙盒中,使用日期区分,这样会存很多的文件
    static func save(model: NSCoding) {
        
        let date = Date()
        dateFormatter.dateFormat = "yyyy年 MM月 dd日 HH时 mm分 ss秒 SSS毫秒"
        let strDate = dateFormatter.string(from: date)
        
        guard let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
            print("❌ 获取沙盒目录失败!")
            return
        }
        
        let userDirPath = URL(fileURLWithPath: docPath).appendingPathComponent(YXModelCache)
        
        guard (try? FileManager.default.createDirectory(at: userDirPath, withIntermediateDirectories: true, attributes: [:])) != nil else {
            print("❌ 创建沙盒文件目录失败!")
            return
        }
        
        let dataFullPath = "\(docPath)/\(YXModelCache)/\(strDate)"
        
        NSKeyedArchiver.archiveRootObject(model, toFile: dataFullPath)
    }
    
    /// 删掉沙盒里的文件
    static func removeAll() {
        
        guard let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
            print("❌ 获取沙盒目录失败!")
            return
        }
        
        let dirPath = "\(docPath)/\(YXModelCache)"
        
        guard ((try? FileManager.default.removeItem(atPath: dirPath)) != nil) else {
            print("❌ 删除文件夹失败!")
            return
        }
    }
}

第四阶段(插曲) 把模型存到 UserDefaults 里

有时候吧,为了图省事,存到 UserDefaults 里这种事情也是干得出来的,所以,把这样的方法也写到一起。
代码如下:

import Foundation

/// 把 模型对象 或者 模型数组 存到 UserDefaults 里面, 重复代码不抽取
struct YXSaverForUserDefaults {
    
    //MARK: - Key
    static let KeyHomePageModel = "KeyHomePageModel"
    
    static let KeyFruitsArray = "KeyFruitsArray"
    
    //MARK: - Model
    /// 把 模型对象 存到 UserDefaults 里面
    static func saveToUserDefaults(key: String, with modelObject: NSCoding) {
        let data = NSKeyedArchiver.archivedData(withRootObject: modelObject)
        UserDefaults.standard.set(data, forKey: key)
    }
    
    /// 把 模型对象 从 UserDefaults 里取出来
    static func fetchFromUserDefaults<T>(key: String, asObject type: T.Type) -> T? where T: NSCoding {
        
        guard let data = UserDefaults.standard.value(forKey: key) as? Data else {
            print("❌ 从UserDefault里解析data失败!")
            return nil
        }
        
        guard let model = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T else {
            print("❌ data转模型失败!")
            return nil
        }
        return model
    }
    
    //MARK: - Array
    /// 把 模型数组 存到 UserDefaults 里面
    static func saveToUserDefaults(key: String, with modelArray: [NSCoding]) {
        let data = NSKeyedArchiver.archivedData(withRootObject: modelArray)
        UserDefaults.standard.set(data, forKey: key)
    }
    
    /// 把 模型数组 从 UserDefaults 里取出来
    static func fetchFromUserDefaults<T>(key: String, asArray type: T.Type) -> [T]? where T: NSCoding {
        
        guard let data = UserDefaults.standard.value(forKey: key) as? Data else {
            print("❌ 从UserDefault里解析data失败!")
            return nil
        }
        
        guard let array = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [T] else {
            print("❌ data转模型失败!")
            return nil
        }
        return array
    }
    
    /// 清理数据
    static func clearCacheModel(key: String) {
        UserDefaults.standard.removeObject(forKey: key)
    }
}

第五阶段 测试代码

到这里代码都写得差不多了,现在创建一个 ViewController,来测试并使用那些代码。
这里我使用了 https://github.com/Alamofire/Alamofire 来发网络请求,
还用了花瓶 https://www.charlesproxy.com/
的 “Map Local” 功能来Mock数据。只是为了,看起来,就像真的一样。

ViewController 代码如下:

import UIKit
import Alamofire

class SwiftCodingViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    /// 字典转模型
    @IBAction func actionDicToModel(_ sender: UIButton) {
        
        let urlString = "http://www.yuency.com/yuencyDictionary.json"
        
        Alamofire.request(urlString).responseJSON { (json) in
            
            switch json.result {
                
            case .success:
                
                // 字典转模型
                let modelObject = YXTransferToModel.toModelObject(json.result.value, to: HomePageModel.self)
                
                //模型转 json / 字典
                print("--->\n \(YXTransferToJson.model(toString: modelObject) ?? "失败")")
                print("--->\n \(YXTransferToJson.model(toDictionary: modelObject) ?? ["失败":"失败"])")
                
                // 把模型 存到 userdefault 里
                YXSaverForUserDefaults.saveToUserDefaults(key: YXSaverForUserDefaults.KeyHomePageModel, with: modelObject!)
                
                // 把模型存到沙盒里
                YXSaverForSandBox.saveToSandBox(key: YXSaverForSandBox.KeyCacheModelName, with: modelObject!)
                
                // 按照日期把数据存到沙盒(会存很多这样的文件)
                YXSaverForSandBox.save(model: modelObject!)
                
                
            case .failure(let error):
                print("网络请求失败 \(error)")
            }
        }
    }
    
    /// 数组转模型
    @IBAction func actionArrToModel(_ sender: UIButton) {
        
        let urlString = "http://www.yuency.com/yuencyArray.json"
        
        Alamofire.request(urlString).responseJSON { (json) in
            
            switch json.result {
                
            case .success:
                
                // 数组转模型
                let modelArray = YXTransferToModel.toModelArray(json.result.value, to: FruitsInfo.self)
                
                //模型数组转 json / Array
                print("--->\n \(YXTransferToJson.model(toString: modelArray) ?? "失败")")
                print("--->\n \(YXTransferToJson.model(toArray: modelArray) ?? ["失败"])")
                
                // 把模型数组 存到 userdefault 里
                YXSaverForUserDefaults.saveToUserDefaults(key: YXSaverForUserDefaults.KeyFruitsArray, with: modelArray!)
                
                // 把模型数组 存到沙盒里
                YXSaverForSandBox.saveToSandBox(key: YXSaverForSandBox.KeyCacheArrayName, with: modelArray!)
                
            case .failure(let error):
                print("网络请求失败 \(error)")
            }
        }
    }
    
    @IBAction func actionFetchDataFromUserDefault(_ sender: UIButton) {
        // 从UserDefaults取出模型
        let modelObject = YXSaverForUserDefaults.fetchFromUserDefaults(key: YXSaverForUserDefaults.KeyHomePageModel, asObject: HomePageModel.self)
        print(modelObject ?? "没有解出来")
        
        // 从UserDefaults取出模型
        let modelArray = YXSaverForUserDefaults.fetchFromUserDefaults(key: YXSaverForUserDefaults.KeyFruitsArray, asArray: FruitsInfo.self)
        print(modelArray ?? "没有解出来")
    }
    
    
    @IBAction func actionFetchDataFromSandBox(_ sender: UIButton) {
        //从沙盒里取出模型
        let modelObjectFromSanBox = YXSaverForSandBox.fetchFromSandBox(key: YXSaverForSandBox.KeyCacheModelName, asObject: HomePageModel.self)
        print(modelObjectFromSanBox ?? "没有解出来")
        
        //从沙盒里取出模型数组
        let modelArrayFromSanBox = YXSaverForSandBox.fetchFromSandBox(key: YXSaverForSandBox.KeyCacheArrayName, asArray: FruitsInfo.self)
        print(modelArrayFromSanBox ?? "没有解出来")
    }
    
    
    @IBAction func actionClearAll(_ sender: UIButton) {
        
        YXSaverForUserDefaults.clearCacheModel(key: YXSaverForUserDefaults.KeyHomePageModel)
        YXSaverForUserDefaults.clearCacheModel(key: YXSaverForUserDefaults.KeyFruitsArray)
        
        YXSaverForSandBox.removeAll()
    }
}

结语:

现在应该说是前年了,好像是8月17号的样子。天还很热,写代码的时候,靠着窗子,对着大屏幕,听着这首鬼畜 https://www.bilibili.com/video/av3816897?from=search&seid=3916296380692081353 感觉还挺亢奋。还有一首他的电音之王,貌似搜不到了。有些东西啊,再听的时候,就觉得,年代久远了。但愿从没听过

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容