本章中主要学习数据持久化与各种持久化方式的实现方法
沙箱目录
沙箱目录设计的原理就是只能允许自己的应用访问目录,而不允许其他的应用访问。
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 } }