Swift自定义类的存储

在Swift开发中,许多时候会涉及到存储自定义的类,不管是存储到本地文件还是远程服务器,都会涉及到编码和解码的问题。下面就来介绍一下在Swift中怎么存储自定义的类。
在Swift中存储自定义的类有两种方法,一种是Swift 3.0版本的NSCoding,还有一种是Swift 4.0版本的Codable。我们就两种方法来谈谈其中的区别吧。

Swift 3.0: NSCoding

在NSCoding这个协议在OC中就存在,所以自然而然的,Swift也能使用它。使用NSCoding协议,需要这个类继承自NSObject并遵循NSCoding协议,而且还要实现NSCoding协议中的两个方法,才能编码和解码自定义的类。例子如下:

class Person: NSObject, NSCoding {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    //编码
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
        aCoder.encode(age, forKey: "age")
    }

    //解码
    required init?(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as! String
        self.age = aDecoder.decodeInteger(forKey: "age")
    }
}

其中编码和解码两个方法就是NSCoding协议中要求实现的两个方法,只有实现了这两个方法,才能将自定义的类序列化和反序列化。接下来,我们就要创建一个Person类的实例,并保存到本地文件中,和从本地文件中读取内容。

let person = Person(name: "Tom", age: 18)

//拿到一个本地文件的URL
let manager = FileManager.default
var url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
url?.appendPathComponent("test.txt")

//序列化Person实例
let dataWrite = NSKeyedArchiver.archivedData(withRootObject: person)

do {
    //将数据写入文件
    try dataWrite.write(to: url!)
} catch {
    print("保存到本地文件失败")
}

//从本地文件读取数据
if let dataRead = try? Data(contentsOf: url!) {
    //反序列化数据,并将结果强转为Person类的实例
    let person = NSKeyedUnarchiver.unarchiveObject(with: dataRead) as! Person
    //输出内容
    print("name: \(person.name)")  //输出: name: Tom
    print("age: \(person.age)")  //输出: age: 18
} else {
    print("文件不存在,读取本地文件失败")
}

可以看出,当第一步完成之后,保存自定义类就变得非常简单了,只需要使用系统定义好的NSKeyedArchiver类和NSKeyedUnarchiver类就可以了。

Swift 4.0: Codable

接下来再看看Swift 4.0的Codable协议怎么保存自定义类。
在Swift 4.0版本,Apple推出了一个新的协议叫Codable。只要类或者结构体遵循了这个协议,就能够很方便地将类或者结构体的实例转换为JSON数据,并且不需要在类或者结构体中实现编码和解码方法,可以看出,Codable协议比NSCoding协议更加方便、简洁。废话不多说,来看看具体的实现:

class Person: Codable {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let person = Person(name: "Tom", age: 18)

//拿到一个本地文件的URL
let manager = FileManager.default
var url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
url?.appendPathComponent("test.txt")

//将Person实例转换为JSON数据
let dataWrite = try JSONEncoder().encode(person)
do {
    try dataWrite.write(to: url!)
} catch {
    print("保存到本地文件失败")
}

//从本地文件读取数据
if let dataRead = try? Data(contentsOf: url!) {
    //将数据转换为Person实例
    let person = try JSONDecoder().decode(Person.self, from: dataRead)
    //输出内容
    print("name: \(person.name)")  //输出: name: Tom
    print("age: \(person.age)")  //输出: age: 18
} else {
    print("文件不存在,读取本地文件失败")
}

可以看出,Codable协议比NSCoding协议方便很多,而且代码看起来也简洁了很多。

Codable的坑

那可能就有人想问了,既然有了Codable协议,而且还这么简洁,那么NSCoding协议的存在还有什么意义呢?这个问题博主当初也想了很久,后来在一次偶然的机会,发现了Codable协议的不足。先卖个关子,我们来看一个需求:
要求:现有一个AnimalProtocol协议,该协议有一个eat()方法,然后实现两个类Cat和Dog,这两个类都有一个类型为String的name属性,并且需要遵循AnimalProtocol协议,实现eat()方法。创建一个数组,数组中保存若干个Cat实例和Dog实例,将此数组保存到本地文件中,并从文件中读取到相应的内容。

先来看看NSCoding协议是怎么实现这个需求的:

//定义协议
protocol AnimalProtocol {
    func eat()
}

//定义Cat类
class Cat: NSObject, NSCoding, AnimalProtocol {
    var name: String

    init(name: String) {
        self.name = name
    }

    func eat() {
        print("cat \(self.name) eat")
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
    }

    required init?(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as! String
    }
}

//定义Dog类
class Dog: NSObject, NSCoding, AnimalProtocol {
    var name: String

    init(name: String) {
        self.name = name
    }

    func eat() {
        print("dog \(self.name) eat")
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
    }

    required init?(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as! String
    }
}

//创建一个空数组(因为这个数组要同时保存两个类的实例,所以需要以它们共同的一个类型来创建数组)
var animalArray = [AnimalProtocol]()

//然后用循环创建多个Cat和Dog实例,并加入数组中
for i in 1...3 {
    let cat = Cat(name: "cat\(i)")
    let dog = Dog(name: "dog\(i)")

    animalArray.append(cat)
    animalArray.append(dog)
}

let manager = FileManager.default
var url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
url?.appendPathComponent("test.txt")

let dataWrite = NSKeyedArchiver.archivedData(withRootObject: animalArray)
do {
    try dataWrite.write(to: url!)
} catch {
    print("保存到本地文件失败")
}

if let dataRead = try? Data(contentsOf: url!) {
    let newArray = NSKeyedUnarchiver.unarchiveObject(with: dataRead) as! [AnimalProtocol]

    for item in newArray {
        item.eat()
    }
}
//输出如下
/*
cat cat1 eat
dog dog1 eat
cat cat2 eat
dog dog2 eat
cat cat3 eat
dog dog3 eat
*/

可以看到,虽然步骤有一点点麻烦,但是还是能很方便地实现我们的需求的,接下来,我们再看看Codable协议是怎么做的吧。
先上代码:

//定义协议
protocol AnimalProtocol: Codable {
    func eat()
}

//定义Cat类
class Cat: AnimalProtocol {
    var name: String

    init(name: String) {
        self.name = name
    }

    func eat() {
        print("cat \(self.name) eat")
    }
}

//定义Dog类
class Dog: AnimalProtocol {
    var name: String

    init(name: String) {
        self.name = name
    }

    func eat() {
        print("dog \(self.name) eat")
    }
}

var animalArray = [AnimalProtocol]()

for i in 1...3 {
    let cat = Cat(name: "cat\(i)")
    let dog = Dog(name: "dog\(i)")

    animalArray.append(cat)
    animalArray.append(dog)
}

let manager = FileManager.default
var url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
url?.appendPathComponent("test.txt")

let dataWrite = try JSONEncoder().encode(animalArray)

do {
    try dataWrite.write(to: url!)
} catch {
    print("保存到本地文件失败")
}

if let dataRead = try? Data(contentsOf: url!) {
    let newArray = try JSONDecoder().decode([AnimalProtocol].self, from: dataRead)

    for item in newArray {
        item.eat()
    }
} else {
    print("文件不存在,读取本地文件失败")
}

看代码没错,而且能通过编译,但是一运行,直接crash。还报了一个莫名其妙的错误,错误是这样写的:fatal error: Array does not conform to Encodable because AnimalProtocol does not conform to Encodable.: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.65/src/swift/stdlib/public/core/Codable.swift, line 3962
2017-10-14 19:43:37.565213+0800 study[5383:411423] fatal error: Array does not conform to Encodable because AnimalProtocol does not conform to Encodable.: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.65/src/swift/stdlib/public/core/Codable.swift, line 3962
我去!这就郁闷了,明明我的类遵循了Codable协议,你却说没有遵循,这是什么鬼,而且就算用扩展让Array遵循协议也不可以

extension Array: Codable {
}

难道是遇到鬼了?不会吧!
后来博主请教导师,才知道原来这是Codable的一个不足点。遵循了Codable协议的类的数组,其中的元素必须为同一个类型。虽然这里是用协议类型去创建的数组,但其元素却是遵循了该协议类型的类的实例。所以本质上不尽相同。

如果在这里将AnimalProtocol协议改成一个类的话,虽然在运行时不会崩溃,但却无法将其向下转换为其子类了。代码如下:

//定义父类
class Animal: Codable {
    func eat() {
        print("Animal")
    }

}

//定义Cat类
class Cat: Animal {
    var name: String

    init(name: String) {
        self.name = name
        super.init()
    }

    required init(from decoder: Decoder) throws {
        fatalError("init(from:) has not been implemented")
    }

    override func eat() {
        print("cat \(self.name) eat")
    }
}

//定义Dog类
class Dog: Animal {
    var name: String

    init(name: String) {
        self.name = name
        super.init()
    }

    required init(from decoder: Decoder) throws {
        fatalError("init(from:) has not been implemented")
    }

    override func eat() {
        print("dog \(self.name) eat")
    }
}

var animalArray = [Animal]()

for i in 1...3 {
    let cat = Cat(name: "cat\(i)")
    let dog = Dog(name: "dog\(i)")

    animalArray.append(cat)
    animalArray.append(dog)
}

let manager = FileManager.default
var url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
url?.appendPathComponent("test.txt")

let dataWrite = try JSONEncoder().encode(animalArray)

do {
    try dataWrite.write(to: url!)
} catch {
    print("保存到本地文件失败")
}

if let dataRead = try? Data(contentsOf: url!) {
    let newArray = try JSONDecoder().decode([Animal].self, from: dataRead)

    for item in newArray {
        if item is Cat {
            (item as! Cat).eat()
        } else if item is Dog {
            (item as! Dog).eat()
        } else {
            item.eat()
        }
    }
} else {
    print("文件不存在,读取本地文件失败")
}
/*
输出结果为:
Animal
Animal
Animal
Animal
Animal
Animal
*/

以上便是博主个人的一些看法,如有错误,还请各位看客纠正。

本文转自:https://blog.csdn.net/average17/article/details/78236589

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,947评论 4 60
  • 近段时间感觉来自各方的压力非常巨大,工作中的种种不可控状况频出,要出售的房子迟迟卖不掉,要装修的新房因资金不到...
    你今天怎么样阅读 245评论 0 0
  • 9月4日消息,据国外媒体报道,来自市场研究公司comScore的最新数字显示,美国7月份智能手机的人口使用率已经达...
    f11015f29d83阅读 273评论 0 1