本章中主要学习数据持久化与各种持久化方式的实现方法
沙箱目录
沙箱目录设计的原理就是只能允许自己的应用访问目录,而不允许其他的应用访问。
1. Documents 目录 大量的数据,经常变化,最重要的是这个目录中数据,iCLoud 和 iTunes 备份。
2. Library 目录
使用偏好(系统设置)、缓存数据,不进行 iCLoud 和 iTunes 备份。
3. tmp 目录
临时数据,不进行 iCLoud 和 iTunes 备份。经常会被清除的。
沙箱目录与资源目录的区别: 资源目录中的内容是只 读的,沙箱目录是放置那些可以读写的数据的。
持久化方式
1.属性列表文件,集合对象可以读写到属性列表文件中。
2.对象归档,一个对象状态和被保持到归档文件中。
3.SQLite 数据库,SQLite 是个开源嵌入式关系型数据库。
4.CoreData,本质上也是通过 SQLite 存储的,它是一种对象关系映射技术(ORM)。
以下为本章中实现的Demo代码
展示层:View/MarterViewController.swift、DetailViewController.swift、AddViewController.swift
MarterViewController.swift
import UIKit
//表视频主页面
class MarterViewController: UITableViewController {
var objects = NSMutableArray()
let Controller:NoteController = NoteController()
override func viewDidLoad() {
super.viewDidLoad()
//代码实现左侧按钮
self.navigationItem.leftBarButtonItem = self.editButtonItem
self.objects = self.Controller.findAll()
//(接收通知)监听通知事件RegisterCompletionNotification,交给registerCompletion函数处理
NotificationCenter.default.addObserver(self, selector: #selector(CreateNoteList(notification:)), name: NSNotification.Name.init(rawValue: "CreateNoteList"), object: nil)// . object: nil 可以发送通知的视图接收过来
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//TableViewDataSource 协议方法实现
//返回表视图节的个数
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
//返回表视图每个节中的行数
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.objects.count
}
//返回每一个 自定义cell 的内容
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//请求可重用单元格,需要一个标识,CustomCellTableViewCell为自定义单元格类
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
let object = self.objects[indexPath.row] as! Note//查询数组中的Note出来
cell.textLabel!.text = object.Content as String//取Note的Content的属性
return cell
}
//点击Cell会触发视图控制器的Segue方法
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//判断是跳转的哪个segue,showtow为segue在故事板中定义的identifier
if segue.identifier == "toDetail" {
//得到二级(Detail控制器)
let datailVC = segue.destination as! DetailViewController
//当前表视图被选择单元格
let indexPath = self.tableView.indexPathForSelectedRow
//当前单元格所对应的Note对象
let object = self.objects[(indexPath?.row)!] as! Note
//将信息传给二级的listData
datailVC.listData = object.Content as String
//设置二级表视图名称
datailVC.title = "Detail"
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// 如果不希望指定项可编辑,则返回false
return true
}
//进行操作,当前主要处理delete操作
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let removeNote = self.objects[indexPath.row] as? Note
self.objects = self.Controller.remove(model: removeNote!)
tableView.deleteRows(at: [indexPath], with: .left)
} 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.
}
}
//实现通知监听方法
func CreateNoteList(notification : NSNotification) {
let allData = notification.object as! NSMutableArray//取到投送过来对像
self.objects = allData//拿到的数据给当前视图的变量
self.tableView.reloadData()//重新加载当前表视图
}
}DetailViewController.swift
import UIKit
//点击表视频图后进入的详细页面
class DetailViewController: UIViewController {
@IBOutlet weak var DetailLebel: UILabel!
var listData:String!
override func viewDidLoad() {
super.viewDidLoad()
self.DetailLebel.text = self.listData
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}AddViewController.swift
import UIKit
//添加备忘录
class AddViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
let Controller:NoteController = NoteController()
@IBAction func onDone(_ sender: UIBarButtonItem) {
self.textField.resignFirstResponder()//username放弃第一响应者
self.dismiss(animated: true, completion: nil)//关闭模态
}
@IBAction func onSave(_ sender: UIBarButtonItem) {
//实例化一个新的Note
let note = Note(date: NSDate(), content: self.textField.text! as NSString)//NSDate(),为当前日期
//调用业务逻辑层的createNote方法,会返回添加后的所有数据
let objs = Controller.createNote(model: note)
//注册通知,传输数据 . object: objs 把返回的所有数据投送出去
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "CreateNoteList"), object: objs, userInfo: nil)
self.dismiss(animated: true, completion: nil)//关闭模态
self.textField.resignFirstResponder()//username放弃第一响应者
}
override func viewDidLoad() {
super.viewDidLoad()
//注册点击事件
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//点击空白处关闭键盘方法
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .ended {
self.textField.resignFirstResponder()//username放弃第一响应者
}
sender.cancelsTouchesInView = false
}
}业务逻辑层:Controller/NoteController.swift
NoteController.swift,等等四种持久化方式都用一个业务逻辑类
import Foundation
//业务逻辑层,Note为plist,NoteCoding为归档,,等等四种持久化方式都用一个业务逻辑类
class NoteController {
//查询所用数据方法
func findAll() -> NSMutableArray {
let dao:NoteModelSQlite = NoteModelSQlite.sharedFoo
return dao.findAll()
}
//插入Note方法
func createNote(model: Note) -> NSMutableArray {
let dao:NoteModelSQlite = NoteModelSQlite.sharedFoo
dao.create(model: model) //会有警告,因为create方法有返回值但在这里并没有使用
return dao.findAll()
}
//删除Note方法
func remove(model: Note) -> NSMutableArray {
let dao:NoteModelSQlite = NoteModelSQlite.sharedFoo
dao.remove(model: model)
return dao.findAll()
}
}数据持久层:Model/Note.swift、NoteModel.swift、NoteCoding.swift、NoteModelCoding.swift、NoteModelSQlite.swift、DemoApp-10-Bridging-Header.h(OC的头文件)
业务领域对像类:Note.swift,等等四种持久化方式都用一个业务领域对象类
import Foundation
//业务领域对象类
class Note {
let Date: NSDate
var Content: NSString
init(date:NSDate,content:NSString) {
self.Date = date
self.Content = content
}
}plist文件方式:NoteModel.swift
import Foundation
//plist文件方式
class NoteModel {
let dateFormatter : DateFormatter = DateFormatter()
private static let sharedInstance = NoteModel() //单例的实例保存这个属性中
class var sharedFoo: NoteModel { //swift中的静态计算属性
//初始化 拷贝文件到沙箱
sharedInstance.plistCopyDocument()
return sharedInstance
}
//修改Note方法
public func modify(model: Note) -> Int {
let path = self.getDocumentPath()
let array = NSMutableArray(contentsOfFile: path)
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in array! {
let dict = item as! NSDictionary
let strDate = dict["date"] as! String
let date = dateFormatter.date(from: strDate)!
//比较日期主键是否相等
if date == model.Date as Date {
//修改字典中的某个对应content key的值
dict.setValue(model.Content, forKey: "content")
//在次写入文件
array?.write(toFile: path, atomically: true)
}
}
return 0
}
//插入Note方法
public func create(model: Note) {
let path = self.getDocumentPath()
let array = NSMutableArray(contentsOfFile: path)
//将日期转为字符串
let strDate = dateFormatter.string(from: model.Date as Date)
//组成一个字典元素
let dict = NSDictionary(objects: [model.Content,strDate], forKeys: ["content" as NSCopying,"date" as NSCopying])
//把字典元素添加到数组中
array?.add(dict)
//插入到文件
array?.write(toFile: path, atomically: true)
}
//删除Note方法
public func remove(model: Note) {
let path = self.getDocumentPath()
let array = NSMutableArray(contentsOfFile: path)
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in array! {
let dict = item as! NSDictionary
let strDate = dict["date"] as! String
let date = dateFormatter.date(from: strDate)!
//比较日期主键是否相等
if date == model.Date as Date {
array?.remove(dict)//删除数组元素中的对应字典
//写入文件
array?.write(toFile: path, atomically: true)
}
}
}
//根据主键查询一条数据
func findByid(model:Note) -> Note? {
//实例时间对象于格式化
self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let SXplist = self.getDocumentPath()
let array = NSArray(contentsOfFile: SXplist)
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in array! {
let dict = item as! NSDictionary//取到数组中的每一个字典
let strDate = dict["date"] as! String//将字典中key的date取出
let date = dateFormatter.date(from: strDate)!
if date == model.Date as Date {
let content = dict["content"] as! String//将字典中的content取出
let note = Note(date: date as NSDate, content: content as NSString)
return note
}
}
return nil
}
//查询所有数据方法
func findAll() -> NSMutableArray {
//实例时间对象于格式化
self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
//得到已拷贝到沙箱目录的plist文件
let SXplist = self.getDocumentPath()
//将文件读到变量中,array中是数组套字典的格式
let array = NSArray(contentsOfFile: SXplist)
//字义可变数组对象
let listData = NSMutableArray()
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in array! {
let dict = item as! NSDictionary//取到数组中的每一个字典
let strDate = dict["date"] as! String//将字典中key的date取出
let date = dateFormatter.date(from: strDate)!
let content = dict["content"] as! String//将字典中的content取出
let note = Note(date: date as NSDate, content: content as NSString)//实例化note对象,返回一个note对象
listData.add(note)//添加到可变数组中
}
return listData
}
//将plist文件拷贝到沙箱Document目录
func plistCopyDocument() {
//获得FileManager的单例
let fileManager = FileManager.default
//获得资源目录
let defaultDBPath = Bundle.main.resourcePath as String!
//在defaultDBPath字符串的基础上累加另一个字符串NotesList.plist,目录+文件路径
let dbFile = defaultDBPath?.appending("/NotesList.plist")
//目录+文件路径
let writablepaht = self.getDocumentPath()
//判断文件是否存在,如果存在则不重复拷贝
let dbexits = fileManager.fileExists(atPath: writablepaht)
if dbexits != true {
do {//当代码提示后有throws时, 需要 try一下 有抛出异常的操作
//拷贝资源目录的文件 to 沙箱目录中
try fileManager.copyItem(atPath: dbFile!, toPath: writablepaht)
}catch {
//使用断言方法,如果bool为false,会打印message字符串
//assert(bool, message)
print("出现异常")
}
}
//print(writablepaht)
}
//获取系统沙箱Document目录方法
func getDocumentPath() -> String {
let documentDirectory: NSArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let myDocPath = documentDirectory[0] as! NSString
//在myDocPath字符串的基础上累加另一个字符串NotesList.plist,目录+文件路径
let wtFile = myDocPath.appending("/NotesList.plist") as String
return wtFile
}
}归档方式:NoteCoding.swift,NoteModelCoding.swift
import Foundation
//归档与反归档,被归档条件,1必须是NSObject对象,2必须实现NSCoding这个协议
class NoteCoding: NSObject, NSCoding {
let Date: NSDate
var Content: NSString
init(date:NSDate,content:NSString) {
self.Date = date
self.Content = content
}
//NSCoding要求实现的 “解码” 在反归档时候调用的,必须要求实现的,“应该” 内部调用
public required init?(coder aDecoder: NSCoder) {
self.Date = aDecoder.decodeObject(forKey: "Date") as! NSDate
self.Content = aDecoder.decodeObject(forKey: "Content") as! NSString
}
//NSCoding要求实现的 “编码” 在归档时候调用的,必须要求实现的,“应该” 内部调用
public func encode(with aCoder: NSCoder) {
aCoder.encode(self.Date, forKey: "Date")
aCoder.encode(self.Content, forKey: "Content")
}
}
=============================
import Foundation
//归档与反归档
class NoteModelCoding: NSObject {
//保存数据列表
var listData: NSMutableArray!
//归档与返归档用到的KEY
let ARCH_KEY = "ackksy"
let dateFormatter : DateFormatter = DateFormatter()
private static let sharedInstance = NoteModelCoding() //单例的实例保存这个属性中
class var sharedFoo: NoteModelCoding { //swift中的静态计算属性
//初始化 创建归档文件
sharedInstance.plistCopyDocument()
return sharedInstance
}
//修改Note方法
public func modify(model: NoteCoding) -> Int {
let path = self.getDocumentPath()
//获得所有数据
let array = self.findAll()
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in array {
let note = item as! NoteCoding
//比较日期主键是否相等
if note.Date == model.Date {
note.Content = model.Content//修改数据,待测试...
//array进行归档
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(array, forKey: ARCH_KEY)
archiver.finishEncoding()
data.write(toFile: path, atomically: true)
break
}
}
return 0
}
//插入Note方法
public func create(model: NoteCoding) {
let path = self.getDocumentPath()
let array = self.findAll()
array.add(model)
//array进行归档
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(array, forKey: ARCH_KEY)
archiver.finishEncoding()
data.write(toFile: path, atomically: true)
}
//删除Note方法
public func remove(model: NoteCoding) {
let path = self.getDocumentPath()
//获得所有数据
let array = self.findAll()
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in array {
let note = item as! NoteCoding
//比较日期主键是否相等
if note.Date == model.Date {
array.remove(note)//删除当前匹配出的Note
//array进行归档
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(array, forKey: ARCH_KEY)
archiver.finishEncoding()
data.write(toFile: path, atomically: true)
break
}
}
}
//根据主键查询一条数据
func findByid(model:NoteCoding) -> NoteCoding? {
//得到已拷贝到沙箱目录的plist文件
let SXplist = self.getDocumentPath()
//将归档文件读取到Data中
let data = NSData(contentsOfFile: SXplist)!
if data.length > 0 {//判断文件是否读取成功
//开始反归档
//定义反归档对象,与data关联
let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
//解码ARCH_KEY的归档文件 并 转为可变数组赋值给listData
self.listData = unarchiver.decodeObject(forKey: ARCH_KEY) as! NSMutableArray
//结束反归档
unarchiver.finishDecoding()
//业务逻辑层的数据的数组中套Note。因此我们需要转换。
for item in self.listData {
let note = item as! NoteCoding
//比较日期主键是否相等
if note.Date == model.Date {
return note
}
}
}
return nil
}
//查询所有数据方法
func findAll() -> NSMutableArray {
//得到已拷贝到沙箱目录的plist文件
let SXplist = self.getDocumentPath()
//将归档文件读取到Data中
let data = NSData(contentsOfFile: SXplist)!
if data.length > 0 {//判断文件是否读取成功
//开始反归档
//定义反归档对象,与data关联
let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
//解码ARCH_KEY的归档文件 并 转为可变数组赋值给listData
self.listData = unarchiver.decodeObject(forKey: ARCH_KEY) as! NSMutableArray
//结束反归档
unarchiver.finishDecoding()
}
return self.listData
}
//将plist文件拷贝到沙箱Document目录
func plistCopyDocument() {
//获得FileManager的单例
let fileManager = FileManager.default
//目录+文件路径
let writablepaht = self.getDocumentPath()
//判断文件是否存在,如果存在则不重复拷贝
let dbexits = fileManager.fileExists(atPath: writablepaht)
if dbexits != true {
//添加一些测试数据
self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date1: NSDate = self.dateFormatter.date(from: "2015-01-01 16:01:03")! as NSDate
let note1: NoteCoding = NoteCoding(date:date1, content: "Welcome to MyNote.")
let date2: NSDate = self.dateFormatter.date(from: "2015-01-02 8:01:03")! as NSDate
let note2: NoteCoding = NoteCoding(date:date2, content: "欢迎使用MyNote。")
//定义一个listData:NSMutableArray常量后,需要在实例化NSMutableArray()
self.listData = NSMutableArray()
self.listData.add(note1)
self.listData.add(note2)
//进行归档
let data = NSMutableData()//归档需要可变的Data实例
let archiver = NSKeyedArchiver(forWritingWith: data)//定义归档对象,与data关联
archiver.encode(self.listData, forKey: self.ARCH_KEY)//编码listdata数组到ARCH_KEY中
archiver.finishEncoding()//完成编码
data.write(toFile: writablepaht, atomically: true)//写入归档文件
}
//print(writablepaht)
}
//获取系统沙箱Document目录方法
func getDocumentPath() -> String {
let documentDirectory: NSArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let myDocPath = documentDirectory[0] as! NSString
//在myDocPath字符串的基础上累加另一个字符串NotesList.plist,目录+文件路径
let wtFile = myDocPath.appending("/NotesList.fo") as String
return wtFile
}
}
















