美文网首页
Core Data 初识Demo

Core Data 初识Demo

作者: BBH_Life | 来源:发表于2017-10-10 15:12 被阅读119次

    1、前言

    最近打算将以往不太深入研究的技术研究研究,其中之一就是Core Data。购买了一本objec.io | ObjC 中国出版的Core Data来一步一步实现其demo并理解Core Data的奥义。
    Core Data是Apple针对数据管理和数据库相关方面给出的解决方案。它并不是单纯的数据库,而是一套对象图管理系统。它默认使用SQLite作为底层存储,通过由低向高地将相关的管理组建构造为一个栈,来提供缓存和对象管理机制。
    本文章将比较详细的描述如何使用Core Data来实现一个学校师生管理APP。
    初步需求:管理学生列表。
    学生属性:id, name, gender, classNum
    实现结果:通过TableView来展示学生列表

    Github demo在这里

    2、新建项目和配置CoreData


    一、配置Core Data文件
    1、新建一个Xcode项目,右键文件->NewFile->Core Data Model
    2、打开新建的.xcdatamodeld文件,展示效果如下:

    Student.xcdatamodeld
    3、点击Add Entity,添加实体Student,这里ENTITIES处多了一个Student
    4、点击Student,在Attributes中添加属性,属性选择类型,右边的配置栏中有可选和Index选项。

    二、配置好.xcdatamodeld之后,我们需要新建一个对应的子类来使用CoreData
    新建Swift文件,来实现对应的CoreData子类:

    import UIKit
    import CoreData
    
    public final class Student: NSManagedObject {
        @NSManaged public private(set) var student_id: Int16
        @NSManaged public private(set) var name: String
        @NSManaged public private(set) var gender: Bool
        @NSManaged public private(set) var class_num: Int16
    }
    

    其中NSManagedObject是一个空的类,表明类由Core Data管理。
    @NSManaged关键字表明属性由Core Data管理。
    在此,我们已经新建了CoreData图管理文件,和对应的一个Student子类,我们需要将两者建立关系。
    如下图,打开.xcdatamodeld,右侧配置栏中,选择Class 和Module为正确的内容:

    为数据模型配置子类

    三、设置Core Data栈
    我们已经有数据模型和其子类了,接下来需要设置一个Core Data 栈来使用它们。
    我们会在整个APP都使用下面这一个上下文:为了不出现混乱,建议一个APP使用一个Core Data上下文。所以以下的代码我们写在AppDelegate.swift中。

    1、确定存储地址

    private let StoreURL = URL.init(string: NSHomeDirectory() + "/Documents")?.appendingPathExtension("Student.student")
    

    2、创建APP使用的Core Data 上下文

    public func createStudentMainContext() -> NSManagedObjectContext {
            let bundles = [Bundle(for: Student.self)] // 获取数据对象模型所在的Bundle,这样就算代码在其他Bundle中,也可以工作
            // 搜索所有的Bundle,并将其合并成一个托管对象
            guard let model = NSManagedObjectModel.mergedModel(from: bundles) else {
                fatalError("model not found")
            }
            // 创建持久化协调器,用Model初始化,用SQLite存储,存储路径是之前确定的Document路径
            let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
            try! psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: StoreURL, options: nil)
            
            // 新建上下文,使用mainQueue来配置,表示在UI线程中我们可以安全的访问它
            let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            context.persistentStoreCoordinator = psc // 将持久化协调器配置为之前创建的协调器psc
            return context
        }
    

    3、在AppDelegate中创建一个Context:

    创建Context

    在此我们已经有了数据模型,有了数据模型的子类,有了Core Data上下文,可以开始使用这个上下文做一些相关的数据操作了,此处书中是按上述方法创建的Context,但是传递过程有一些繁琐,我将其修改为了单例模式,在单例中获取APP的Core Data 上下文:详情见下列代码

    import UIKit
    import CoreData
    
    class CoreDataManager: NSObject {
        static let manager = CoreDataManager()
        private let StoreURL = URL.init(string: NSHomeDirectory() + "/Documents")?.appendingPathExtension("Student.student")
        var managedObjectContext: NSManagedObjectContext?
        
        override init() {
            super.init()
            managedObjectContext = createStudentMainContext()
        }
        
        public func createStudentMainContext() -> NSManagedObjectContext {
            let bundles = [Bundle(for: Student.self)] // 获取数据对象模型所在的Bundle,这样就算代码在其他Bundle中,也可以工作
            // 搜索所有的Bundle,并将其合并成一个托管对象
            guard let model = NSManagedObjectModel.mergedModel(from: bundles) else {
                fatalError("model not found")
            }
            // 创建持久化协调器,用Model初始化,用SQLite存储,存储路径是之前确定的Document路径
            let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
            try! psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: StoreURL, options: nil)
            
            // 新建上下文,使用mainQueue来配置,表示在UI线程中我们可以安全的访问它
            let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            context.persistentStoreCoordinator = psc // 将持久化协调器配置为之前创建的协调器psc
            return context
        }
    }
    

    四、获取请求 Fetch
    一般的,我们使用下列代码来实现Fetch:

            let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
            let sortDescriptor = NSSortDescriptor(key: "student_id", ascending: false)
            request.sortDescriptors = [sortDescriptor]
            request.fetchBatchSize = 20
    
            let result = try! CoreDataManager.manager.managedObjectContext?.execute(request)
            // 这里的CoreDataManager是上文中的单例
    

    但是在APP越来越庞大之后,我们可以做一些优化,让代码更简洁更易懂:
    优化过程:
    1)编写一个协议,让协议作为中间者,使得代码解耦:

    /// Core Data Entity 需要遵循的协议,面向协议编程
    public protocol ManagedObjectType {
        static var entityName: String { get } // 返回Entity的名字
        static var defaultSortDescriptors: [NSSortDescriptor] { get } // 返回排序的要求
    }
    
    extension ManagedObjectType {
        public static var defaultSortDescriptors: [NSSortDescriptor] {
            return []
        }
        
        public static var sortedFetchRequest: NSFetchRequest<NSFetchRequestResult> {
            let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
            request.sortDescriptors = defaultSortDescriptors
            return request
        } // 返回配置好的Request
    }
    

    2)然后使得数据模型对应的类Student遵循这个协议:

    extension Student: ManagedObjectType {
        public static var entityName: String {
            return "Student"
        }
        
        public static var defaultSortDescriptors: [NSSortDescriptor] {
            return [NSSortDescriptor.init(key: "student_id", ascending: false)]
        }
    }
    

    3)通过以上的优化,之前的Fetch代码就变成了以下形式:

    let request = Student.sortedFetchRequest
    request.fetchBatchSize = 20
    

    4)然后使用上下文来执行这个Request就行:

    let result = try! CoreDataManager.manager.managedObjectContext?.execute(request)
    // 这里的CoreDataManager是上文中的单例
    

    知道如何查询之后,我们再给Core Data增加一些实际数据,这样来完成实际的功能。


    五、操作数据
    1、增加数据
    普通的增加数据代码如下:

            guard let student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: CoreDataManager.manager.managedObjectContext!) as? Student else {
                fatalError("student not found")
            }
            student.student_id = 12
            student.name = "bbh"
            student.gender = true
            student.class_num = 1
            try! CoreDataManager.manager.managedObjectContext?.save()
    

    这样的操作是可行的,但是在这个项目中是不能够编译的,因为我们将student的属性设置成了只读,所以如果没有在Student类中插入,那么久无法实现set属性。而且插入的地方有很多,如果每个插入都写这么多代码,会很繁琐,接下来我们来优化一下其中的代码:
    1)首先扩展一下NSManagedObjectContext

    // 这里使用了Swift 4的新特性,可以用 & 符号连接类和协议
    extension NSManagedObjectContext {
        public func insertObject<A: NSManagedObject & ManagedObjectType>() -> A {
            guard let obj = NSEntityDescription.insertNewObject(forEntityName: A.entityName, into: self) as? A else {
                fatalError("Wrong object type")
            }
            return obj
        }
    
        public func saveOrRollBack() -> Bool {
            do {
                try save()
                return true
            } catch {
                rollback()
                return false
            }
        }
        
        public func performChanges(block: @escaping ()->()) {
            perform {
                block()
                if self.saveOrRollBack() {
                    print("保存成功")
                } else {
                    print("保存失败")
                }
            }
        }
    }
    

    2)在给Student类扩展,让其可以一个方法就添加数据

    extension Student {
        public static func insertIntoContext(moc: NSManagedObjectContext, contentDic: [String:Any]) -> Student {
            let student: Student = CoreDataManager.manager.managedObjectContext!.insertObject()
            student.student_id = contentDic["student_id"] as? Int16 ?? 0
            student.name = contentDic["name"] as? String ?? "没有数据"
            student.gender = contentDic["gender"] as? Bool ?? false
            student.class_num = contentDic["class_num"] as? Int16 ?? 0
            return student
        }
    }
    

    3)然后再使用时候就是这个样子的了:

    CoreDataManager.manager.managedObjectContext?.performChanges {
                Student.insertIntoContext(moc: CoreDataManager.manager.managedObjectContext!, contentDic: ["student_id":1, "name":"bbh", "gender":true, "class_num":7])
            }
    

    个人觉得这里需要一定时间和经验来理解,我的Github demo在这里 ,大家可以去看看高亮的代码,下载下来跑跑。

    2、删除数据
    删除数据本身来说非常简单:

    // 这里使用了之前的单例来获取ManagedObjectContext
    var s = Student()
    CoreDataManager.manager.managedObjectContext?.performChanges {
                s = Student.insertIntoContext(moc: CoreDataManager.manager.managedObjectContext!, contentDic: ["student_id":1, "name":"bbh", "gender":true, "class_num":7])
            } // 这里是添加了一个数据
    CoreDataManager.manager.managedObjectContext?.performChanges {
                CoreDataManager.manager.managedObjectContext?.delete(s)
            }// 这里删除了相应的数据
    

    在实际使用过程中,建议大家使用监听数据的形式,不然则需要手动管理数据与UI之间的关系。
    从这里开始,我们基本完成了CoreData的初始化,配置相应的类,知道了如何增删改查,下面我们将Demo完善就好


    六、APP主要代码
    这里是搭建了一个tableView来展示CoreData中保存的数据,查看Demo 的全部代码请点击这里
    ViewController代码:

    class ViewController: UIViewController {
        @IBOutlet weak var tableView: UITableView!
        @IBOutlet weak var name: UITextField!
        @IBOutlet weak var gender: UISegmentedControl!
        @IBOutlet weak var student_id: UITextField!
        @IBOutlet weak var class_num: UITextField!
        var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
        @IBAction func addInfo(_ sender: Any) {
            // 添加同学信息
            CoreDataManager.manager.managedObjectContext?.performChanges {
                let student_id: String = self.student_id.text!
                let name: String = self.name.text!
                let gender: Bool = self.gender.selectedSegmentIndex == 0 ? true : false
                let class_num: String = self.class_num.text!
                Student.insertIntoContext(moc: CoreDataManager.manager.managedObjectContext!, contentDic: ["student_id":Int16(student_id) ?? -1, "name":name, "gender":gender, "class_num":Int16(class_num) ?? -1 as Int16])
            }
        }
        @IBAction func tapBackView(_ sender: Any) {
            self.view.endEditing(true)
        }
    }
    

    ViewController扩展:

    // MARK: - Life Circle
    extension ViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            initFetchedResultsController()
        }
    }
    
    
    // MARK: - Actions
    extension ViewController {
        func initFetchedResultsController() {
            let request = Student.sortedFetchRequest
            request.fetchBatchSize = 20
            fetchedResultsController = NSFetchedResultsController<NSFetchRequestResult>(fetchRequest: request, managedObjectContext: CoreDataManager.manager.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "cacheName")
            fetchedResultsController?.delegate = self
            try! fetchedResultsController?.performFetch()
        }
    }
    

    ViewController tableView协议:

    extension ViewController: UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            if fetchedResultsController == nil {
                return 0
            }
            return (fetchedResultsController?.sections![section].numberOfObjects)!
        }
        func numberOfSections(in tableView: UITableView) -> Int {
            if fetchedResultsController == nil {
                return 0
            }
            return (fetchedResultsController?.sections?.count)!
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! StudentTableViewCell
            guard let s = fetchedResultsController?.sections?[indexPath.section].objects?[indexPath.row] as? Student else { return cell }
            cell.name.text = s.name
            cell.gender.text = s.gender ? "男" : "女"
            cell.class_num.text = "\(s.class_num)"
            cell.student_id.text = "\(s.student_id)"
            return cell
        }
        
    }
    

    下面是NSFetchedResultsControllerDelegate:

    extension ViewController: NSFetchedResultsControllerDelegate {
        func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            tableView.beginUpdates()
        }
        
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
            switch type {
            case .insert:
                guard let indexPath = newIndexPath else { fatalError("Index path should be not nil") }
                tableView.insertRows(at: [indexPath], with: .fade)
            case .update:
                break
                /*
                guard let indexPath = indexPath else { fatalError("Index path should be not nil") }
                let object = objectAtIndexPath(indexPath)
                guard let cell = tableView.cellForRow(at: indexPath) as? Cell else { break }
                delegate.configure(cell, for: object)
     */
            case .move:
                guard let indexPath = indexPath else { fatalError("Index path should be not nil") }
                guard let newIndexPath = newIndexPath else { fatalError("New index path should be not nil") }
                tableView.deleteRows(at: [indexPath], with: .fade)
                tableView.insertRows(at: [newIndexPath], with: .fade)
            case .delete:
                guard let indexPath = indexPath else { fatalError("Index path should be not nil") }
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
        }
        
        func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            tableView.endUpdates()
        }
    }
    

    3、尾巴

    在本文中主要涵盖的要点有:
    CoreData模型的建立,CoreData新建模型对应子类,CoreData的操作(使用上下文封装来实现增删改查),以及使用NSFetchedResultsController来实现数据和UI的合成。
    希望对有需求的同学有所帮助,谢谢阅读。

    相关文章

      网友评论

          本文标题:Core Data 初识Demo

          本文链接:https://www.haomeiwen.com/subject/pchvyxtx.html