swift 数据持久化之CoreData(模型嵌套,一对一,一对多)

iOS数据持久化方案有很多,比如FMDBRealmCoreData等,FMDB需要自己写SQL语句,很传统很经典的数据库操作,关于FMDB我在两年前写过一篇数据库操作之FMDB,那时候还是使用的swift2,感兴趣的可以参考参考。CoreData是Apple提供的数据持久化框架,使用起来步骤稍微繁杂,数据模型可图形化。realm是一个开源框架,用于取代SQLite和CoreData,我比较习惯也比较喜欢的一个框架。以后有时间我会介绍realm的集成与使用,不过今天主角是CoreData,关于CoreData,很久以前我写过数据持久化存储—CoreData使用,朋友说语言版本较老,也较复杂,于是就有了这篇新作,作为iOS开发者,CoreData的运用是必备技能,虽然使用的场景不多(若没有要求,我不会采用),但是需要掌握,这也是很多面试官都喜欢问的点,因此,本次用更简单的文字和例子阐述其使用方法,让每个开发者都能看个明白,本次采用swift4,包含了模型嵌套存储,一对一,一对多关系。

项目中CoreData的嵌入

嵌入CoreData很简单,如果是还未创建工程,那么在创建工程时勾选上Use Core Data,工程就会自动生成一个与工程名字相同的.xcdatamodeld文件,以及在AppDelegate文件中自动生成相应代码。

如果已经创建了工程也不要紧,command + n找到Data Model并创建,创建的名字最好与工程名相同,否则可能会出现未知错误,不过作为例子我这里就随便起一个了。

创建完成后在文件目录中会多出一个.xcdatamodeld文件,现在先不忙管它,进入AppDelegate文件,import CoreData并添加相关代码,NSPersistentContainer(name:)中的参数必须与工程名一致,完事后如下:

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    // MARK: - Core Data stack
    
    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentContainer(name: "TestCoreData2")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                
                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    // MARK: - Core Data Saving support
    
    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }


}

到此,CoreData算是导入到工程中了。

CoreData的使用

CoreData为我们提供基本数据类型存储,可供选择的选项有14个,我这里就不一一列举,下文会有一张图能看到支持的类型。虽然只有14个选项,但是在我看来,CoreData是没有什么数据不能存储的,因为有个选项是Transformable,从字面意思就知道这个是可以转换的类型,从代码中用option可以查到它显示的类型是NSObject,这就很好办了,iOS中的所有对象都继承自NSObject,那我就可以将任意类型的数据转为NSObject再存储了,不过在转之前要注意:数据类型必须遵守NSCoding协议,这也是CoreData最大的诟病,需要自己实现协议中的encode和decode方法,如果模型有很多属性,就需要多写很多代码。

创建模型

找到之前的.xcdatamodeld文件并打开,选择Add Entity创建一个模型并取名,我这里取作TBModel1,右侧第一栏Attributes就是模型的属性了,可以选择模型属性类型。

简单的创建几个属性,因为CoreData不存在主键一说,所以自己设置一个id属性作为主键,这个id的唯一性由开发者自己保证。


有时候一个模型会嵌套多个模型怎么处理?比如TBModel1需要拥有另一个模型,那就再创建一个GFModel(GirlFriendModel ?? true)。

点击下方Editor Style可以切换视图,可以看到此时TBModel1GFModel并无任何关联(不由自主的想到了歌词:十年之前,我不认识你,你不属于我~~)

此时需要牵线搭桥给他们建立关系,切换回表格视图在Relationships中点击+号做如下操作


再次切换视图就发现他们有了联系

好了,可视化模型操作就结束了,接下来是coding环节。

在需要使用CoreData的文件中import CoreData
增删改查前需要先获取CoreData的上下文,为了方便,这里先作为文件内部全局变量

fileprivate let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

然后存储一个带girlfriend的TBModel1(一对一)

import UIKit
import CoreData

class ViewController: UIViewController {
    
    fileprivate let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    override func viewDidLoad() {
        super.viewDidLoad()
        
        insert()
    }
    
    func insert(){
        
        let data = NSEntityDescription.insertNewObject(forEntityName: "TBModel1", into: context) as! TBModel1
        let girlfriend = NSEntityDescription.insertNewObject(forEntityName: "GFModel", into: context) as! GFModel
        girlfriend.name = "翠花"
        girlfriend.age = 18
        data.desc = "测试CoreData"
        data.girlfriend = girlfriend
        data.id = 0
        //保存
        do {
            try context.save()
            print("保存成功!")
        } catch {
            fatalError("不能保存:\(error)")
        }
    }
}

运行程序会看到控制台输出保存成功

如果TBModel1有多个朋友怎么办(一对多),那就先定义一个朋友模型,遵循NSCoding协议并实现协议方法

class TBFriendModel: NSObject, NSCoding{
    
    var name = ""
    var age = 18
    
    func encode(with aCoder: NSCoder) {
        
        aCoder.encode(name, forKey: "TBCore_name");
        aCoder.encode(age, forKey: "TBCore_age");
    }
    
    required init?(coder aDecoder: NSCoder) {
        
        name = (aDecoder.decodeObject(forKey: "TBCore_name") as? String) ?? "无名氏"
        age = (aDecoder.decodeObject(forKey: "TBCore_age") as? Int) ?? 18
    }
    
    override init() {
        super.init()
    }
    
}

修改一下刚才的insert方法并运行程序,控制台输出保存成功

func insert(){
        
        let data = NSEntityDescription.insertNewObject(forEntityName: "TBModel1", into: context) as! TBModel1
        let girlfriend = NSEntityDescription.insertNewObject(forEntityName: "GFModel", into: context) as! GFModel
        girlfriend.name = "翠花"
        girlfriend.age = 18
        data.desc = "测试CoreData"
        data.girlfriend = girlfriend
        data.id = 0
        
        let friend1 = TBFriendModel()
        friend1.name = "小明"
        friend1.age = 20
        
        let friend2 = TBFriendModel()
        friend2.name = "小黄"
        friend2.age = 22
        
        data.anyObject = [friend1, friend2] as NSObject
        
        
        //保存
        do {
            try context.save()
            print("保存成功!")
        } catch {
            fatalError("不能保存:\(error)")
        }
    }

现在来查询一下刚才保存的模型(如果运行过最初的insert方法,需要先卸载APP重新运行修改后的insert方法,因为运行两次后会有两个id为0的模型,其中一个没有anyObject)

func query(){
        
        //声明数据的请求
        let fetchRequest = NSFetchRequest<TBModel1>(entityName:"TBModel1")
        //控制查询结果的数量(分页)
        fetchRequest.fetchLimit = 3
        //查询的偏移量
        fetchRequest.fetchOffset = 0
        //设置查询条件(可以采用正则)
        let predicate = NSPredicate(format: "id= '0' ", "")
        fetchRequest.predicate = predicate
        //查询
        do {
            let fetchedObjects = try context.fetch(fetchRequest)
            //遍历查询的结果
            for model in fetchedObjects{
                print(model.desc)
                print(model.girlfriend?.name)
                print((model.anyObject as! [TBFriendModel])[0].name)
            }
        }
        catch {
            fatalError("不能查询:\(error)")
        }
    }

运行程序控制台输出如下:


简单修改一下数据,其实修改数据就是先查询,更新数据后重新保存

func update(){
        
        let fetchRequest = NSFetchRequest<TBModel1>(entityName:"TBModel1")
        
        let predicate = NSPredicate(format: "id= '0' ", "")
        fetchRequest.predicate = predicate
        
        do {
            let fetchedObjects = try context.fetch(fetchRequest)
            for model in fetchedObjects{
                model.girlfriend?.name = "翠翠翠花"
                (model.anyObject as! [TBFriendModel])[0].name = "小小小明"
                //重新保存
                try context.save()
            }
        }
        catch {
            fatalError("不能更新:\(error)")
        }
    }

更新后再查询结果如下


删除和更新机制一样,先查询后再将模型删除

func delete(){
        
        let fetchRequest = NSFetchRequest<TBModel1>(entityName:"TBModel1")
        let predicate = NSPredicate(format: "id= '0' ", "")
        fetchRequest.predicate = predicate
        
        do {
            let fetchedObjects = try context.fetch(fetchRequest)
            for model in fetchedObjects{
                //删除对象
                context.delete(model)
            }
        }
        catch {
            fatalError("不能删除:\(error)")
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容