form1.cn
Make a little progress every day

Part 10:iOS的数据持久化(1),文件,归档

25th of March 2017 Swift Swift 2098

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


沙箱目录 

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

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
    }
    
}


此处接 Part 10:iOS的数据持久化(2),Sqlite,CoreData