创建一个结构体来存储字符串常量了解静态属性和实例属性之间的区别使用NSCoding 协议读写数据
保存和加载Meal对象
在这一步中,您将在Meal 类中实现保存和加载菜肴的行为。使用NSCoding 方法并让Meal 类负责存储和加载其每个属性。它需要通过将每个属性值分配给特定的键来存储其数据。然后,它通过查找与密钥关联的信息来加载数据。
键是一个简单的字符串。您可以根据对您的应用程序最有意义的方式选择自己的密钥。例如,您可以使用键名称来存储名称属性的值。
要知道编码密钥对应哪些数据,请创建一个结构体来存储密钥字符串。这样,当您需要在代码中的多个位置使用该键时,您可以使用常量而不是重复键入字符串(这会增加出错的可能性)。
实现编码键结构Open Meal.swift。在Meal.swift 的//MARK: Properties 部分下,添加以下结构: //MARK: Types
结构属性键{
}在此PropertyKey 结构中,添加以下属性: static let name="name"
静态让照片="照片"
static let rating=" rating" 每个常量对应于膳食的三个属性之一。 static 关键字指示这些常量属于结构本身,而不属于结构的实例。您可以使用结构名称(例如PropertyKey.name)访问这些常量。
您的PropertyKey 结构将如下所示:
结构属性键{
静态让名称="名称"
静态让照片="照片"
静态让评级="评级"
为了能够对自身及其属性进行编码和解码,Meal 类需要符合NSCoding 协议。为了支持NSCoding,Meal 需要子类化NSObject。 NSObject 被认为是定义运行时系统基本接口的基类。
子类化NSObject并遵守NSCoding在Meal.swift 中,找到类行: class Meal {After Meal,添加冒号和NSObject 将NSObject 子类化为类class Meal: NSObject {在NSObject 之后,添加逗号和NSCoding 以采用NSCoding 协议:class Meal: NSObject, NSCoding {现在,Meal 是NSObject 的子类,Meal 类的初始化器必须调用NSObject 类的指定初始化器。因为NSObject 类的唯一初始化器是init(),所以Swift 编译器会自动添加此调用,因此您无需更改代码;但是,如果您愿意,您始终可以添加super.init() 来进行调用。
进一步探索有关Swift 初始化规则的更多信息,请参阅《Swift 编程语言(Swift 4)》中的类继承和初始化以及初始化。
NSCoding 协议声明了两个方法,任何采用它的类都必须实现这些方法,以便这些类的实例可以编码和解码:
编码(使用aCoder: NSCoder)
init?(coder aDecoder: NSCoder)encode(with:) 方法准备用于归档的类信息,并且初始化程序会在创建类时取消归档此数据。您需要实现encode(with:)方法和初始化程序才能正确存储和加载数据。
实现encodeWithCoder NSCoding方法在Meal.swift 中,在右大括号之前,添加以下注释://MARK: NSCoding。在此注释下方,添加以下方法: func编码(with aCoder: NSCoder) {
}在encode(with:)方法中,添加以下代码:aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(照片, forKey: PropertyKey.photo)
aCoder.encode( rating, forKey: PropertyKey. rating) 这个NSCoder类定义了几个encode(_:forKey:)方法,每个方法的第一个参数有不同的类型。每种方法都对给定类型的数据进行编码。在上面所示的代码中,前两行传递的是String 参数,而第三行传递的是Int 参数。这些行对每个Meal 类的属性值进行编码,并使用相应的键来存储它们。
编码(with:)方法应如下所示:
func 编码(使用aCoder: NSCoder){
aCoder.encode(名称, forKey: PropertyKey.name)
aCoder.encode(照片, forKey: PropertyKey.photo)
aCoder.encode(评级, forKey: PropertyKey. rating)
}使用编码方法编写,实现初始化方法来解码这些编码数据。
实现初始化方法类加载菜品在文件顶部,导入Unified Logging System,就在导入UIKit 的下方。 import os.log 在encodeWithCoder(_:)方法下面,添加以下初始化方法: required opportunity init?(coder aDecoder: NSCoder) {
}required 修饰符意味着如果子类定义了自己的初始值设定项,则必须在每个子类中实现该初始值设定项。
便利修饰符意味着这是一个辅助初始化程序,并且它必须从同一类调用指定的初始化程序。
问号表明这是一个可失败的初始化器,它可以返回nil。
在初始化程序中添加以下代码: //名称是必需的。如果我们无法解码名称字符串,则初始化程序应该失败。
Guard let name=aDecoder.decodeObject(forKey: PropertyKey.name) as?字符串其他{
os_log("无法解码Meal 对象的名称。", log: OSLog.default, type:debug)
返回零
}decodeObject(forKey:) 方法对编码信息进行解码。 decodeObjectForKey(_:)的返回值为Any?可选类型。此保护语句解开可选类型并将其降级为String 类型,然后再将其分配给名称常量。如果这些操作失败,整个初始化就会失败。
在前面的代码之后,添加以下代码: //由于photo 是Meal 的可选属性,因此只需使用条件转换即可。
让photo=aDecoder.decodeObjectForKey(PropertyKey.photo) 为? UIImage 您将decodeObject(forKey:)的返回值降级为UIImage并将其分配给照片常量。如果降级失败,它会将nil 分配给照片属性。这里不需要使用guard语句,因为照片属性本身是可选类型。
然后添加代码: let rating=aDecoder.decodeIntegerForKey(PropertyKey. rating)decodeIntegerForKey(_:) 方法取消归档一个整数。因为decodeIntegerForKey的返回值是Int,所以不需要对解码后的值进行降级,也没有可选的类型来解包。
在此实现方法的末尾添加以下代码: //必须调用指定的初始值设定项。
self.init(name: name, photo: photo, rating: rating) 用作方便的初始化程序。此初始化程序必须在完成之前调用其类指定的初始化方法。作为此初始值设定项的参数,您需要向其传递一个常量值。该常量是在您归档数据时创建的。
新的init?(coder:) 初始化方法如下所示:
需要方便初始化吗?(coder aDecoder: NSCoder) {
//名称为必填项。如果我们无法解码名称字符串,则初始化程序应该失败。
Guard let name=aDecoder.decodeObject(forKey: PropertyKey.name) as?字符串其他{
os_log("无法解码Meal 对象的名称。", log: OSLog.default, type:debug)
返回零
}
//因为photo 是Meal 的可选属性,所以只需使用条件转换。
让photo=aDecoder.decodeObject(forKey: PropertyKey.photo) 为?用户界面图像
让rating=aDecoder.decodeInteger(forKey: PropertyKey. rating)
//必须调用指定的初始值设定项。
self.init(name: 姓名, photo: 照片, rating: 评级)
}
需要方便初始化吗?(coder aDecoder: NSCoder) {
//名称为必填项。如果我们无法解码名称字符串,则初始化程序应该失败。
Guard let name=aDecoder.decodeObject(forKey: PropertyKey.name) as?字符串其他{
os_log("无法解码Meal 对象的名称。", log: OSLog.default, type:debug)
返回零
}
//因为photo 是Meal 的可选属性,所以只需使用条件转换。
让photo=aDecoder.decodeObject(forKey: PropertyKey.photo) 为?用户界面图像
让rating=aDecoder.decodeInteger(forKey: PropertyKey. rating)
//必须调用指定的初始值设定项。
self.init(name: 姓名, photo: 照片, rating: 评级)
接下来,您需要文件系统中将读取和写入数据的持久路径,因此您需要知道在哪里查找。
创建数据的文件路径在Meal.swift 中的//MARK: Properties 部分下,添加以下代码:
//MARK:归档路径
static let DocumentsDirectory=FileManager().urls(for:documentDirectory, in:userDomainMask).first!
static let ArchiveURL=DocumentsDirectory.appendingPathComponent("meals") 您使用static 关键字标记这些常量,这意味着它们属于此类并且不是此类的实例。在Meal 类之外,您可以使用语法Meal.ArchiveURL.path 访问此路径。不用担心创建了多少个Meal 类的实例,这些属性只会有一份副本。
DocumentsDirectory 常量使用文件管理器的urls(for:in:) 方法来查找应用程序文件目录的URL。这是应用程序可以存储用户数据的目录。此方法返回一个URL 数组,其中第一项(第一个!)返回一个包含数组中第一个URL 的可选值;但是,只要枚举正确,返回的数组就必须包含匹配项。因此,强制解包可选类型是安全的。确定文档目录的URL 后,您可以使用此URL 创建应用程序存储数据的URL。在这里,您通过将膳食添加到文档URL 的末尾来创建文件URL。进一步探索有关与iOS 文件系统交互的更多信息,请参阅文件系统编程指南。
检查点:使用Command-B 构建应用程序。它应该可以正常工作,没有任何错误。
保存和加载菜品列表
为了能够保存和加载单个菜肴,您需要在用户添加、编辑或删除菜肴时保存和加载菜肴列表。
实现保存菜品列表的方法打开MealTableViewController.swift。在//MARK: 私有方法部分中,在右大括号之前,添加以下方法: private func saveMeals() {
}在saveMeals() 方法中,添加以下代码: let isSuccessfulSave=NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path) 此方法尝试将Meals 数组存档到指定位置,并在操作成功时返回true。该方法使用您在Meal 类中定义的常量Meal.ArchiveURL 来指定保存消息的位置。
但是,如何快速测试数据是否保存成功呢?将消息打印到控制台以指示结果。
添加以下if 语句if isSuccessfulSave {
os_log("餐食成功保存。", log: OSLog.default, type:debug)
} 别的{
os_log("保存餐食失败.", log: OSLog.default, type:error)
}如果保存成功,会在控制台输出调试信息。如果保存失败,会在控制台输出错误信息。
您的saveMeals() 方法应如下所示:
私有函数saveMeals() {
让isSuccessfulSave=NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path)
如果保存成功{
os_log("膳食成功保存。", log: OSLog.default, type:debug)
} 别的{
os_log("保存餐食失败.", log: OSLog.default, type:error)
}
现在实现加载菜肴的方法。
实现加载菜品列表的方法在MealTableViewController.swift 中,在右大括号之前,添加以下方法: private func loadMeals() -[Meal]? {
}此方法返回一个可选类型数组,其元素是Meal 对象。这意味着它可以返回Meal 对象的数组,或者返回nil。
在loadMeals() 方法中,添加以下代码: return NSKeyedUnarchiver.unarchiveObject(withFile: Meal.ArchiveURL.path) as? [Meal] 此方法取消归档存储在Meal.ArchiveURL.path 路径中的对象,并将其缩减为Meal 对象数组。这段代码使用了as?运算符,因此如果降级失败它可以返回nil。通常出现此故障是因为阵列尚未保存。在这种情况下,unarchiveObject(withFile:) 方法返回nil。尝试将nil 降级为[Meal] 也会失败,并返回nil 本身。
您的loadMeals() 方法如下所示:
private func loadMeals() -[膳食]? {
返回NSKeyedUnarchiver.unarchiveObject(withFile: Meal.ArchiveURL.path) 作为? [一顿饭]
}要使用这些方法,您需要添加代码以在用户添加、删除或编辑菜肴时保存和加载菜单列表。
当用户添加、删除、或者编辑菜品的时候保存菜品列表在MealTableViewController.swift 中,找到unwindToMealList(sender:) 方法: @IBAction func unwindToMealList(sender: UIStoryboardSegue) {
如果让sourceViewController=sender.source as? MealViewController, 让meal=sourceViewController.meal {
如果让selectedIndexPath=tableView.indexPathForSelectedRow {
//更新现有餐食。
膳食[selectedIndexPath.row]=膳食
tableView.reloadRows(at: [selectedIndexPath], with:none)
}
别的{
//添加新餐。
让newIndexPath=IndexPath(row: 餐食.count,section: 0)
膳食.追加(膳食)
tableView.insertRows(at: [newIndexPath], with:automatic)
}
}
}在else 子句下面,添加以下代码: //保存餐点。
saveMeals() 添加新菜肴或更新现有菜肴后,此代码将保存餐食数组。确保此代码位于外部if 语句内。
在MealTableViewController.swift 中,找到tableView(_:commit:forRowAt:) 方法: //重写以支持编辑表视图。
覆盖func tableView(_ tableView: UITableView, 提交EditingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
如果编辑风格==.删除{
//从数据源中删除该行
餐食.删除(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with:fade)
} 否则如果editingStyle==.insert {
//创建适当类的新实例,将其插入数组,并向表视图添加新行
}
}在Meals.removeAtIndex(indexPath.row) 之后,添加以下代码: saveMeals() 此代码将在删除菜品时保存Meals 数组。
您的unwindToMealList(_:) 方法如下所示:
@IBAction func unwindToMealList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? MealViewController, let meal = sourceViewController.meal { if let selectedIndexPath = tableView.indexPathForSelectedRow { // Update an existing meal. meals[selectedIndexPath.row] = meal tableView.reloadRows(at: [selectedIndexPath], with: .none) } else { // Add a new meal. let newIndexPath = IndexPath(row: meals.count, section: 0) meals.append(meal) tableView.insertRows(at: [newIndexPath], with: .automatic) } // Save the meals. saveMeals() } }你的 tableView(_:commit:forRowAt:)方法看上去是这样的: // Override to support editing the table view. override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { // Delete the row from the data source meals.remove(at: indexPath.row) saveMeals() tableView.deleteRows(at: [indexPath], with: .fade) } else if editingStyle == .insert { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } }现在,菜品列表会在合适的时间保存,接下来确保菜品会在合适的时候被加载。加载存储的数据的合适位置是在这个table view的viewDidLoad方法中。在合适的时机加载菜品列表在MealTableViewController.swift中,找到viewDidLoad()方法:override func viewDidLoad() { super.viewDidLoad() // Use the edit button item provided by the table view controller. navigationItem.leftBarButtonItem = editButtonItem // Load the sample data. loadSampleMeals() }在设置完编辑按钮(navigationItem.leftBarButtonItem = editButtonItem)之后,添加如下if语句:// Load any saved meals, otherwise load sample data. if let savedMeals = loadMeals() { meals += savedMeals }如果loadMeals()成功,就会返回一个Meal对象的数组,这个条件就是true,if语句就会执行。如果loadMeals()返回nil,就不会有菜品被加载,if语句也不会被执行。这段代码会添加任何成功加载的菜品到meals数组。 在if语句之后,添加else分句,并把loadSampleMeals()放到分句中:else { // Load the sample data. loadSampleMeals() }现在viewDidLoad()方法看上去是这样的: override func viewDidLoad() { super.viewDidLoad() // Use the edit button item provided by the table view controller. navigationItem.leftBarButtonItem = editButtonItem // Load any saved meals, otherwise load sample data. if let savedMeals = loadMeals() { meals += savedMeals } else { // Load the sample data. loadSampleMeals() } }检查点:运行应用。如果你添加一些新菜品并退出应用,这些你添加的菜品会在下一次你打开应用的时候显示。小结
在本课中,你添加了保存和加载应用数据的功能。这让数据持续存在于多次运行中。无论何时应用启动,它就会加载已存在的数据。当数据被修改时,应用就会保存它。这个应用能够安全的终止而不会丢失任何数据。 到此本应用完成。恭喜!你现在已经有了一个功能齐全的应用。关于Master iOS App Development with Swift: Your Comprehensive Guide到此分享完毕,希望能帮助到您。
【Master iOS App Development with Swift: Your Comprehensive Guide】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
我一直想学习制作iOS app!这篇文章看起来很棒。
有13位网友表示赞同!
Swift 语言一直都是非常酷炫的选择,期待了解更多。
有10位网友表示赞同!
终于可以开始我的 iOS 开发之旅了!感谢分享。
有10位网友表示赞同!
最近开始对苹果系统开发很有兴趣,这篇文章太适合我啦。
有11位网友表示赞同!
学习制作手机应用真是个很酷的技能。
有17位网友表示赞同!
不知道这个文章会讲哪些方面的iOS app开发?
有19位网友表示赞同!
我的朋友一直在做iOS 开发,我可以分享这篇给他!
有9位网友表示赞同!
之前看过一些Swift 的基础教程,我想来深入学习一下。
有13位网友表示赞同!
感觉苹果设备的手机系统越来越强大,能自己开发应用太厉害了!
有9位网友表示赞同!
想用 Swift 语言打造一个属于自己的iOS app!
有20位网友表示赞同!
从入门到精通 iOS 开发,需要多久时间呢?
有8位网友表示赞同!
学习这篇文章后我想试试用 Swift 写一个小程序。
有20位网友表示赞同!
这个领域是不是很热门?有没有很多工作机会?
有14位网友表示赞同!
看评论说这篇文章很棒,我一定要去看看!
有16位网友表示赞同!
学习 iOS 开发是一件很有挑战的事情吗?
有14位网友表示赞同!
之前试过一些编程语言,现在想尝试 Swift。
有12位网友表示赞同!
这个文章适合什么类型的开发者推荐呢?
有17位网友表示赞同!
希望这篇文章能够讲得很详细,方便新手理解!
有9位网友表示赞同!
学习 iOS 开发需要哪些资源和工具?
有20位网友表示赞同!
看完这篇文章之后可以有什么样的应用案例吗?
有9位网友表示赞同!