form1.cn
Make a little progress every day

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

25th of March 2017 Swift Swift 2000

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


Sqlite方式:NoteModelSQlite.swift

import Foundation

//Sqlite数据库,需要添加 libsqlite3.tbd 的库,然后创建 .h 头文件,并在头文件中引入 #import "sqlite3.h"
//.h 头文件,简单作法:可以先创建一个OC的文件,会提示是否创建头文件,点击确定创建后,头文件会自动创建
class NoteModelSQlite: NSObject {
    
    //定义数据库文件名称的常量
    let DB_FILE = "/NotesList.sqlite3"
    //sqlitedb指针 Swift COpaquePointer
    var db:OpaquePointer?  = nil
    
    let dateFormatter : DateFormatter = DateFormatter()
    
    private static let sharedInstance = NoteModelSQlite() //单例的实例保存这个属性中
    class var sharedFoo: NoteModelSQlite { //swift中的静态计算属性
        
        //初始化
        sharedInstance.plistCopyDocument()
        
        return sharedInstance
    }
    
    
    //修改Note方法
    public func modify(model: Note) {
        
        //得到数据库的文件路径
        let path:NSString = self.getDocumentPath() as NSString
        //将path:NSString转换为C的字符串(char*)
        let cpath = path.cString(using: String.Encoding.utf8.rawValue)
        
        /1、sqlite3_open,打开数据库,如果数据库文件不存在则创建,存在则打开
         @param1 文件路径,
         @param2 db指针类型
         &db是传递db地址*/
        if sqlite3_open(cpath, &db) != SQLITE_OK {
            sqlite3_close(db)//关闭链接
            assert(false, "数据库连接失败")
        } else {
            let sql = "update note set content = ? where cdate = ?"//sql语句
            //sql语句也需要转换成C的字符串
            let cSql = sql.cString(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
            
            var statement : OpaquePointer?  = nil//statement指针对象
            /2、使用sqlite3_prepare_v2函数预处理SQL语句;
             @param1 db指针
             @param2 要执行的sql语句
             @param3 要传递sql语句的大小,传递所有 -1
             @param4 statement指针对象
             @param5 会告诉你那此没有被传递sql语句
             /
            if sqlite3_prepare_v2(db, cSql, -1, &statement, nil) == SQLITE_OK {
                
                //3、使用sqlite3_bind_text函数绑定参数;sql语句中的条件会用 ? 号来代替
                self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                let strDate = self.dateFormatter.string(from: model.Date as Date)//把model.Date转为字符串
                let cDate = strDate.cString(using: String.Encoding.utf8)//转换为C的字符串(char*)
                let cContent = model.Content.cString(using: String.Encoding.utf8.rawValue)//转换为C的字符串(char*)
                / sqlite3_bind_text 绑定参数
                 @param1 statement指针对象
                 @param2 绑定参数的索引 1开始
                 @param3 正真要绑定的参数
                 @param4 要传递的最大字节数
                 @param5 回调函数
                 /
                sqlite3_bind_text(statement, 1, cContent, -1, nil)//绑定content
                sqlite3_bind_text(statement, 2, cDate, -1, nil)//绑定cdate
                
                //4、使用sqlite3_step函数执行SQL语句,遍历结果集;
                if sqlite3_step(statement) != SQLITE_DONE { //如果没有执行完成
                    sqlite3_close(db)//关闭链接
                    assert(false, "信息修改失败")
                }
            }
            //  6、使用sqlite3_finalize和sqlite3_close函数释放资源。
            sqlite3_finalize(statement)//释放sql语句对象
            sqlite3_close(db)
        }
    }
    
    
    //插入Note方法
    public func create(model: Note) {
        //得到数据库的文件路径
        let path:NSString = self.getDocumentPath() as NSString
        //将path:NSString转换为C的字符串(char*)
        let cpath = path.cString(using: String.Encoding.utf8.rawValue)
        
        /1、sqlite3_open,打开数据库,如果数据库文件不存在则创建,存在则打开
         @param1 文件路径,
         @param2 db指针类型
         &db是传递db地址*/
        if sqlite3_open(cpath, &db) != SQLITE_OK {
            sqlite3_close(db)//关闭链接
            assert(false, "数据库连接失败")
        } else {
            let sql = "insert into note (cdate,content) values (?,?)"//sql语句
            //sql语句也需要转换成C的字符串
            let cSql = sql.cString(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
            
            var statement : OpaquePointer?  = nil//statement指针对象
            /2、使用sqlite3_prepare_v2函数预处理SQL语句;
             @param1 db指针
             @param2 要执行的sql语句
             @param3 要传递sql语句的大小,传递所有 -1
             @param4 statement指针对象
             @param5 会告诉你那此没有被传递sql语句
             /
            if sqlite3_prepare_v2(db, cSql, -1, &statement, nil) == SQLITE_OK {
                
                //3、使用sqlite3_bind_text函数绑定参数;sql语句中的条件会用 ? 号来代替
                self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                let strDate = self.dateFormatter.string(from: model.Date as Date)//把model.Date转为字符串
                let cDate = strDate.cString(using: String.Encoding.utf8)//转换为C的字符串(char*)
                let cContent = model.Content.cString(using: String.Encoding.utf8.rawValue)//转换为C的字符串(char*)
                / sqlite3_bind_text 绑定参数
                 @param1 statement指针对象
                 @param2 绑定参数的索引 1开始
                 @param3 正真要绑定的参数
                 @param4 要传递的最大字节数
                 @param5 回调函数
                 /
                sqlite3_bind_text(statement, 1, cDate, -1, nil)//绑定cdate
                sqlite3_bind_text(statement, 2, cContent, -1, nil)//绑定content
                
                //4、使用sqlite3_step函数执行SQL语句,遍历结果集;
                if sqlite3_step(statement) != SQLITE_DONE { //如果没有执行完成
                    sqlite3_close(db)//关闭链接
                    assert(false, "信息添加失败")
                }
            }
            //  6、使用sqlite3_finalize和sqlite3_close函数释放资源。
            sqlite3_finalize(statement)//释放sql语句对象
            sqlite3_close(db)
        }
    }
    
    //删除Note方法
    public func remove(model: Note) {
        //得到数据库的文件路径
        let path:NSString = self.getDocumentPath() as NSString
        //将path:NSString转换为C的字符串(char*)
        let cpath = path.cString(using: String.Encoding.utf8.rawValue)
        
        /1、sqlite3_open,打开数据库,如果数据库文件不存在则创建,存在则打开
         @param1 文件路径,
         @param2 db指针类型
         &db是传递db地址*/
        if sqlite3_open(cpath, &db) != SQLITE_OK {
            sqlite3_close(db)//关闭链接
            assert(false, "数据库连接失败")
        } else {
            let sql = "delete from note where cdate = ?"//sql语句
            //sql语句也需要转换成C的字符串
            let cSql = sql.cString(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
            
            var statement : OpaquePointer?  = nil//statement指针对象
            /2、使用sqlite3_prepare_v2函数预处理SQL语句;
             @param1 db指针
             @param2 要执行的sql语句
             @param3 要传递sql语句的大小,传递所有 -1
             @param4 statement指针对象
             @param5 会告诉你那此没有被传递sql语句
             /
            if sqlite3_prepare_v2(db, cSql, -1, &statement, nil) == SQLITE_OK {
                
                //3、使用sqlite3_bind_text函数绑定参数;sql语句中的条件会用 ? 号来代替
                self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                let strDate = self.dateFormatter.string(from: model.Date as Date)//把model.Date转为字符串
      
                let cDate = strDate.cString(using: String.Encoding.utf8)//转换为C的字符串(char*)
                / sqlite3_bind_text 绑定参数
                 @param1 statement指针对象
                 @param2 绑定参数的索引 1开始
                 @param3 正真要绑定的参数
                 @param4 要传递的最大字节数
                 @param5 回调函数
                 /
                sqlite3_bind_text(statement, 1, cDate, -1, nil)//绑定cdate
                
                //4、使用sqlite3_step函数执行SQL语句,遍历结果集;
                if sqlite3_step(statement) != SQLITE_DONE { //如果没有执行完成
                    sqlite3_close(db)//关闭链接
                    assert(false, "数据库删除失败")
                }
            }
            //  6、使用sqlite3_finalize和sqlite3_close函数释放资源。
            sqlite3_finalize(statement)//释放sql语句对象
            sqlite3_close(db)
        }
    }
    
    //根据主键查询一条数据
    func findByid(model:Note) -> Note? {
        
        //得到数据库的文件路径
        let path:NSString = self.getDocumentPath() as NSString
        //将path:NSString转换为C的字符串(char*)
        let cpath = path.cString(using: String.Encoding.utf8.rawValue)
        
        /1、sqlite3_open,打开数据库,如果数据库文件不存在则创建,存在则打开
         @param1 文件路径,
         @param2 db指针类型
         &db是传递db地址*/
        if sqlite3_open(cpath, &db) != SQLITE_OK {
            sqlite3_close(db)//关闭链接
            assert(false, "数据库连接失败")
        } else {
            let sql = "select cdate,content from note where cdate = ?"//sql语句
            //sql语句也需要转换成C的字符串
            let cSql = sql.cString(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
            
            var statement : OpaquePointer?  = nil//statement指针对象
            /2、使用sqlite3_prepare_v2函数预处理SQL语句;
             @param1 db指针
             @param2 要执行的sql语句
             @param3 要传递sql语句的大小,传递所有 -1
             @param4 statement指针对象
             @param5 会告诉你那此没有被传递sql语句
             /
            if sqlite3_prepare_v2(db, cSql, -1, &statement, nil) == SQLITE_OK {
                
                //3、使用sqlite3_bind_text函数绑定参数;sql语句中的条件会用 ? 号来代替
                let strDate = dateFormatter.string(from: model.Date as Date)//把model.Date转为字符串
                let cDate = strDate.cString(using: String.Encoding.utf8)//转换为C的字符串(char*)
                / sqlite3_bind_text 绑定参数
                 @param1 statement指针对象
                 @param2 绑定参数的索引 1开始
                 @param3 正真要绑定的参数
                 @param4 要传递的最大字节数
                 @param5 回调函数
                 /
                sqlite3_bind_text(statement, 1, cDate, -1, nil)
                
                //4、使用sqlite3_step函数执行SQL语句,遍历结果集;
                if sqlite3_step(statement) == SQLITE_ROW { //有记录返回
                    
                    /5、使用sqlite3_column_text等函数提取字段数据;
                     @param1 statement指针对象
                     @param2 字段索引,比如 cdate==0,content==1
                     @return 返回提char  需要 转为 String
                     /
                    //char* -> String   UnsafePointer相当于 char*
                    let bufDate = sqlite3_column_text(statement, 0)//查询cdate
                    let strDate = String(cString: bufDate!)//转为String
                    self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                    //String->NSDate
                    let date : NSDate = self.dateFormatter.date(from: strDate)! as NSDate
                    
                    let bufContent = sqlite3_column_text(statement, 1)//查询content
                    let strContent = String(cString: bufContent!)//转为String
                    
                    //  6、使用sqlite3_finalize和sqlite3_close函数释放资源。
                    sqlite3_finalize(statement)//释放sql语句对象
                    sqlite3_close(db)
                    
                    let note = Note(date: date, content: strContent as NSString)//合为Note对象
                    
                    return note
                }
            }
        }
        return nil
    }
    
    
    //查询所有数据方法
    func findAll() -> NSMutableArray {
        
        let listData  = NSMutableArray()
        
        //得到数据库的文件路径
        let path:NSString = self.getDocumentPath() as NSString
        //将path:NSString转换为C的字符串(char*)
        let cpath = path.cString(using: String.Encoding.utf8.rawValue)
        
        /1、sqlite3_open,打开数据库,如果数据库文件不存在则创建,存在则打开
         @param1 文件路径,
         @param2 db指针类型
         &db是传递db地址*/
        if sqlite3_open(cpath, &db) != SQLITE_OK {
            sqlite3_close(db)//关闭链接
            assert(false, "数据库连接失败")
        } else {
            let sql = "select cdate,content from note"//sql语句
            //sql语句也需要转换成C的字符串
            let cSql = sql.cString(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
            
            var statement : OpaquePointer?  = nil//statement指针对象
            /2、使用sqlite3_prepare_v2函数预处理SQL语句;
             @param1 db指针
             @param2 要执行的sql语句
             @param3 要传递sql语句的大小,传递所有 -1
             @param4 statement指针对象
             @param5 会告诉你那此没有被传递sql语句
             /
            if sqlite3_prepare_v2(db, cSql, -1, &statement, nil) == SQLITE_OK {
            
                //3、使用sqlite3_bind_text函数绑定参数;sql语句中的条件会用 ? 号来代替
                
                //4、使用sqlite3_step函数执行SQL语句,遍历结果集;
                while sqlite3_step(statement) == SQLITE_ROW { //有记录返回
                    
                    /5、使用sqlite3_column_text等函数提取字段数据;
                     @param1 statement指针对象
                     @param2 字段索引,比如 cdate==0,content==1
                     @return 返回提char  需要 转为 String
                     /
                    let bufDate = sqlite3_column_text(statement, 0)//查询cdate
                    let strDate = String(cString:bufDate!)//cString转为String
                    self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                    //String->NSDate
                    let date : NSDate = self.dateFormatter.date(from: strDate)! as NSDate
                    
                    let bufContent = sqlite3_column_text(statement, 1)//查询content
                    let strContent = String(cString: bufContent!)//转为String
                    
                    let note = Note(date: date, content: strContent as NSString)//合为Note对象
                    
                    listData.add(note)//添加到可变数组中
                }
            }
            //  6、使用sqlite3_finalize和sqlite3_close函数释放资源。
            sqlite3_finalize(statement)//释放sql语句对象
            sqlite3_close(db)
        }
        return listData
    }
    
    //初始化 创建数据表 与 数据库文件
    func plistCopyDocument() {
        //得到数据库的文件路径
        let path:NSString = self.getDocumentPath() as NSString
        print(path)
        //将path:NSString转换为C的字符串(char*)
        let cpath = path.cString(using: String.Encoding.utf8.rawValue)
        
        /sqlite3_open,打开数据库,如果数据库文件不存在则创建,存在则打开
        @param1 文件路径,
        @param2 db指针类型
        &db是传递db地址*/
        if sqlite3_open(cpath, &db) != SQLITE_OK {
            sqlite3_close(db)//关闭链接
            assert(false, "数据库连接失败")
        } else {
            //sql语句:如果Note数据表不存在则创建,cdate 是主键,content为字符类型
            let sql = "create table if not exists Note (cdate text primary key, content text)"
            //sql语句也需要转换成C的字符串
            let cSql = sql.cString(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
            
            /sqlite3_exec执行sql,但sqlite3_exec没有返回结果集
             @param1 指针,
            @param2 sql语句,
            @param3 回调函数,
            @param4 为回调函数传递参数的指针
            @param5 errmsg错误信息*/
            if sqlite3_exec(db, cSql, nil, nil, nil) != SQLITE_OK {
                sqlite3_close(db)//关闭链接
                assert(false, "SQL语句执行失败")
            } else {
                sqlite3_close(db)//执行成功关闭链接
            }
        }
    }
    
    //获取系统沙箱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(DB_FILE) as String
        return wtFile
    }
    
}


CoreData方式:NoteDAO.swift

//如果使用CoreData,下以步骤
//1,创建CoreData文件,在对应文件夹中右击选择CoreData..
//2,创建CoreData模型,进入CoreData文件中,左下方的加号
//3,生成实例文件,注意命名不要和现有类冲突,选中Model.xcdatamodeld->Editor->Create NSManagedObject subclass
//4,拷贝堆栈代码(创建新项目 use CoreData ,AppDelegate.swift中查看)或创建到指定文件位置,注意命名..
//5,要引入 import CoreData

//注:这个是一种convenience方法,即快速实现。所以并不需要新建对应于entity的class,上面第4步可省略

import UIKit
import Foundation
import CoreData

//DAO类,一般按表名命名,相当于PHP框架加的 Model
class NoteDAO: UIViewController {
    
    //保存数据列var,没用数据,数据保存到内存中
    var listData: NSMutableArray! //可变数组类型
    
    //只进行和数据库的交互,不需要保持任何状态,所以可以使用单例模式
    private static let sharedInstance = NoteDAO() //单例的实例保存这个属性中
    class var sharedFoo: NoteDAO { //swift中的静态计算属性
        
        return sharedInstance
    }
    
    //插入Note方法
    func create(model: Note) {
        
        let context = self.getContext()
        // 定义一个entity,这个entity一定要在xcdatamodeld中做好定义
        let entity = NSEntityDescription.entity(forEntityName: "CoreDataNote", in: context)
        
        let person = NSManagedObject(entity: entity!, insertInto: context)
        
        person.setValue(model.Content, forKey: "cContent")
        person.setValue(model.Date, forKey: "cDate")
        
        do {
            try context.save()
            print("saved")
        }catch{
            print(error)
        }
        
    }
    
    //删除Note方法
    func remove(model: Note) {
        //定义查询主体
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreDataNote")
        let entity:NSEntityDescription = NSEntityDescription.entity(forEntityName: "CoreDataNote", in: getContext())!
        //指定要查询的实体
        fetchRequest.entity = entity
        //添加查询条件
        fetchRequest.predicate = NSPredicate(format: "cDate = %@", model.Date)
        
        do {
            //进行查询
            let searchResults = try getContext().fetch(fetchRequest)
            
            if searchResults.count != 0 {
                //删除操作
                self.getContext().delete(searchResults[0] as! NSManagedObject)
                //结果保存
                try getContext().save()
                print("delete success ~ ~")
            }
            
        } catch  {
            print(error)
        }
    }
    
    //修改Note.Content方法
    func modify(model: Note) {
        //定义查询主体
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreDataNote")
        let entity:NSEntityDescription = NSEntityDescription.entity(forEntityName: "CoreDataNote", in: getContext())!
        //指定要查询的实体
        fetchRequest.entity = entity
        //添加查询条件
        fetchRequest.predicate = NSPredicate(format: "cDate = %@", model.Date)
        
        do {
            //进行查询
            let searchResults = try getContext().fetch(fetchRequest)
            
            if searchResults.count != 0 {
                //到得查询出来的对象
                let cNote = searchResults[0] as! CoreDataNote
                //修改对应的字段
                cNote.cContent = model.Content as String
                //结果保存
                try getContext().save()
                print("update success ~ ~")
            }
            
        } catch  {
            print(error)
        }
    }
    
    //查询所有数据方法
    func findAll() -> NSMutableArray {
        //定义可变数组
        let listData = NSMutableArray()
        //定义查询主体
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreDataNote")
        do {
            //进行查询
            let searchResults = try getContext().fetch(fetchRequest)
            //print("numbers of \(searchResults.count)")//获得总数
            
            //遍历结果集
            for p in (searchResults as! [NSManagedObject]){
                //获取对应字段
                let date = p.value(forKey: "cDate") as! NSDate
                let content = p.value(forKey: "cContent") as! NSString
                //创建一个 note 对像
                let note = Note(date: date, content: content)
                //加入可变数组中
                listData.add(note)
            }
        } catch  {
            print(error)
        }
        return listData
        
    }
    
    //查询一条数据方法
    func findById(model: Note) -> String! {//返回类型为可选的,说明Note可以返回nil
        //定义查询主体
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreDataNote")
        let entity:NSEntityDescription = NSEntityDescription.entity(forEntityName: "CoreDataNote", in: getContext())!
        //指定要查询的实体
        fetchRequest.entity = entity
        //添加查询条件
        fetchRequest.predicate = NSPredicate(format: "cDate = %@", model.Date)
        
        do {
            //进行查询
            let searchResults = try getContext().fetch(fetchRequest)
            
            if searchResults.count != 0 {
                //到得查询出来的对象
                let cNote = searchResults[0] as! CoreDataNote
                //查询需要的字段值
                let cContent = cNote.value(forKey: "cContent")!
                //返回
                return cContent as! String
            }
            
        } catch  {
            print(error)
        }
        return nil
    }
    
    // 获取Context上下文
    func getContext () -> NSManagedObjectContext {
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        return appDelegate.persistentContainer.viewContext
    }

}