本章中主要学习数据持久化与各种持久化方式的实现方法

沙箱目录 

沙箱目录设计的原理就是只能允许自己的应用访问目录,而不允许其他的应用访问。

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

    }

     

}