Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/106.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 在Swift中使用NSCoding在分层类中实现数据持久化的最佳实践是什么?_Ios_Swift_Hierarchical Data_Nscoding_Data Persistence - Fatal编程技术网

Ios 在Swift中使用NSCoding在分层类中实现数据持久化的最佳实践是什么?

Ios 在Swift中使用NSCoding在分层类中实现数据持久化的最佳实践是什么?,ios,swift,hierarchical-data,nscoding,data-persistence,Ios,Swift,Hierarchical Data,Nscoding,Data Persistence,问题:我已经在iOS应用程序中使用NSKeyedArchiver实现了数据持久化,目前它的功能是保存分层数据,但只保存顶级类的数据。如何从任何级别保存整个层次结构 背景:我正在开发一个iOS应用程序,它使用分层类的数据结构,例如学校、教室、学生。基本上,学校类包含教室数组(以及其他属性,如学区、姓名、电话号码等)。教室类包含学生数组(以及其他属性,如教师、房间号等),学生类具有每个学生的属性(例如姓名、年级、课程等) 该应用程序有三个视图控制器,层次结构的每一层各有一个,允许在每一层更改数据:D

问题:我已经在iOS应用程序中使用NSKeyedArchiver实现了数据持久化,目前它的功能是保存分层数据,但只保存顶级类的数据。如何从任何级别保存整个层次结构

背景:我正在开发一个iOS应用程序,它使用分层类的数据结构,例如学校、教室、学生。基本上,学校类包含教室数组(以及其他属性,如学区、姓名、电话号码等)。教室类包含学生数组(以及其他属性,如教师、房间号等),学生类具有每个学生的属性(例如姓名、年级、课程等)

该应用程序有三个视图控制器,层次结构的每一层各有一个,允许在每一层更改数据:DistrictTableViewController有一个学校对象数组,可以添加/删除数组元素;SchoolTableViewController有一个教室对象数组,可以从教室对象数组中添加/删除元素,ClassroomViewController允许用户添加/删除/编辑学生

我已经使用NSCoding在所有三个类中实现了数据持久化,目前它的功能是在层次结构中保存数据,但我只能从顶级DistrictTableVC(应用程序入口点)保存数据。DistrictTableVC有一个saveSchools()方法。相反,我希望能够保存来自三个ViewController中任何一个的更改,例如,对学生属性的更改将立即保存学生对象,以及教室中的学生数组和学校中的教室数组

当前的配置是DistrictTableVC将单个学校对象传递给SchoolTableVC,SchoolTableVC将单个教室对象传递给ClassroomVC。我想我应该做的是:

  • 创建一个名为District的新顶级班级,该班级拥有一系列学校,并使用NSCoding
  • 在三个VCs之间传递一个区域对象,而不是单一的低级对象
  • 将saveSchools()方法从DistrictTableVC移动到新的District类,允许我从三个ViewController中的任意一个调用它 由于我不是专业人士,我想了解:

  • 我走对了吗?或
  • 也许有人知道更好的方法
  • 感谢您的阅读

    class DistrictTableViewController: UITableViewController {
    
        private let reuseIdentifier = "schoolCell"
    
        var schoolsArray = [School]()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.navBarTitle.title = "Schools"
    
            // Load saved Schools if they exist, otherwise load sample data
            if let savedSchools = loadSchools() {
                schoolsArray += savedSchools
                print("Loading saved schools")
    
                // Update all School stats
                updateSchoolListStats()
    
            } else {
                // Load the sample data
                loadSampleSchools()
                print("Failed to load saved data. Loading sample data...")
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        //MARK: TableView datasource
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return schoolsArray.count
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! SchoolTableViewCell
    
            // Configure the cell...
            let school = schoolsArray[indexPath.row]
            school.calcSchoolStats()
    
            return cell
        }
    
        // Override to support conditional editing of the table view.
        override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
            // Return false if you do not want the specified item to be editable.
            return true
        }
    
        // Override to support editing the table view.
        override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
            if editingStyle == .delete {
    
                // Delete the row from the data source
                schoolsArray.remove(at: indexPath.row)
                saveSchools()
    
                tableView.deleteRows(at: [indexPath], with: .fade)
    
            } 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
            }    
        }
    
        // MARK: - Navigation
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
            super.prepare(for: segue, sender: sender)
    
            // Deselect any selected cells
            for (_, cell) in tableView.visibleCells.enumerated() {
                cell.isSelected = false
            }
    
            // SchoolTableViewCell pressed: pass the selected school to SchoolsTableViewController
            if (segue.identifier ?? "") == "showSchoolDetail" {
                //guard let schoolsTableViewController = segue.destination as? SchoolsTableViewController else {
                fatalError("Unexpected destination: \(segue.destination)")
                }
                guard let selectedSchoolCell = sender as? SchoolTableViewCell else {
                    fatalError("Unexpected sender: \(String(describing: sender))")
                }
                guard let indexPath = tableView.indexPath(for: selectedSchoolCell) else {
                    fatalError("The selected SchoolTableViewCell is not being displayed by the table")
                }
    
                schoolTableViewController.school = schoolsArray[indexPath.row]
            }
    
            // Add button pressed: show SchoolAttributesViewController
            if addBarButtonItem == sender as? UIBarButtonItem {
                guard segue.destination is SchoolAttributesViewController else {
                    fatalError("Unexpected destination: \(segue.destination)")
                }
            }
        }
    
        @IBAction func unwindToSessionsTableViewController(sender: UIStoryboardSegue) {
    
            if let sourceViewController = sender.source as? SchoolsTableViewController, let school = sourceViewController.school {
    
                if let selectedIndexPath = tableView.indexPathForSelectedRow {
                    // Update an existing session
                    schoolsArray.array[selectedIndexPath.row] = school
                    tableView.reloadRows(at: [selectedIndexPath], with: .none)
                } else {
                    // Add a new school to the Table View
                    schoolsArray.insert(session, at: 0) // Update date source; add new school to the top of the table
    
                    let newIndexPath = IndexPath(row: 0, section: 0)
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                    tableView.cellForRow(at: newIndexPath)?.isSelected = true
    
                    tableView.cellForRow(at: newIndexPath)?.selectedBackgroundView = bgColorView
                }
                //updateSessionListStats()
                //sessionsTableView.reloadData()
    
                saveSchools()
            }
        }
    
        //MARK: Actions
        private func saveSchools() {
            let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(schoolsArray, toFile: School.ArchiveURL.path)
    
            if isSuccessfulSave {
                os_log("Schools successfully saved", log: OSLog.default, type: .debug)
            } else {
                os_log("Failed to save schools...", log: OSLog.default, type: .error)
            }
        }
        //MARK: Private Methods
        private func updateSchoolListStats() {
            for (_, school) in schoolsArray.array.enumerated() {
                for (_, classroom) in school.classroomArray.enumerated() {
                    classroom.calcStats()
                }
                school.calcSchoolStats()
            }
        }
        private func loadSchools() -> [School]? {
            return NSKeyedUnarchiver.unarchiveObject(withFile: School.ArchiveURL.path) as? [School]
        }
    
    class School: NSObject, NSCoding {
    
        //MARK: Properties
        var name: String
        var district: String
        var phoneNumber: Int
        var classroomArray = [Classroom]()
    
        //MARK: Archiving Paths
        static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
        static let ArchiveURL = DocumentsDirectory.appendingPathComponent("schoolsArray")
    
        init (name: String = "Default", district: String = "", phoneNumber: Int = -1, classroomArray = [Classroom]()) {
            self.name = name
            self.district = district
            self.phoneNumber = phoneNumber
            self.classroomArray = classroomArray
        }
    
        func calcSchoolStats() {
        }
    
        //MARK: NSCoding Protocol
        func encode(with aCoder: NSCoder) {
    
            aCoder.encode(name, forKey: "name")
            aCoder.encode(district, forKey: "district")
            aCoder.encode(phoneNumber, forKey: "phoneNumber")
            aCoder.encode(classroomArray, forKey: "classroomArray")
        }
        required convenience init?(coder aDecoder: NSCoder) {
            // The name is required. If we cannot decode a name string, the initializer should fail.
            guard let name = aDecoder.decodeObject(forKey: "name") as? String else {
                os_log("Unable to decode the name for a School object.", log: OSLog.default, type: .debug)
                return nil
            }
            let district = aDecoder.decodeObject(forKey: "district") as! String
            let phoneNumber = aDecoder.decodeInteger(forKey: "phoneNumber")
            let classroomArray = aDecoder.decodeObject(forKey: "classroomArray") as! [Classroom]
    
            // Must call designated initializer.
            self.init(name: name, district: district, phoneNumber: phoneNumber, classroomArray: classroomArray)
        }
    }
    
    class Classroom: NSObject, NSCoding {
    
        //MARK: Properties
        var teacher: String
        var roomNumber: Int
        var studentArray = [Student]()
    
        //MARK: Archiving Paths
        static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
        static let ArchiveURL = DocumentsDirectory.appendingPathComponent("classroomsArray")
    
        init (teacher: String = "", building: Int = -1, studentArray = [Student]()) {
            self.teacher = teacher
            self.roomNumber = roomNumber
            self.studentArray = studentArray
        }
    
        func calcStats() {
        }
    
        //MARK: NSCoding Protocol
        func encode(with aCoder: NSCoder) {
    
            aCoder.encode(teacher, forKey: "teacher")
            aCoder.encode(roomNumber, forKey: "roomNumber")
            aCoder.encode(studentArray, forKey: "studentArray")
        }
        required convenience init?(coder aDecoder: NSCoder) {
            // The teacher is required. If we cannot decode a teacher string, the initializer should fail.
            guard let teacher = aDecoder.decodeObject(forKey: "teacher") as? String else {
                os_log("Unable to decode the teacher for a Classroom object.", log: OSLog.default, type: .debug)
                return nil
            }
            let roomNumber = aDecoder.decodeInteger(forKey: "roomNumber")
            let studentArray = aDecoder.decodeObject(forKey: "studentArray") as! [Student]
    
            // Must call designated initializer.
            self.init(teacher: teacher, roomNumber: roomNumber, studentArray: studentArray)
        }
    }
    
    class Student: NSObject, NSCoding {
    
        //MARK: Properties
        var first: String
        var last: String
        var grade: Int
        var courses: [String]
    
        //MARK: Archiving Paths
        static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
        static let ArchiveURL = DocumentsDirectory.appendingPathComponent("students")
    
        init (first: String = "", last: String = "", grade: Int = -1, courses = [String]()) {
            self.first = first
            self.last = last
            self.grade = grade
            self.courses = courses
        }
    
        //MARK: NSCoding Protocol
        func encode(with aCoder: NSCoder) {
    
            aCoder.encode(first, forKey: "first")
            aCoder.encode(last, forKey: "last")
            aCoder.encode(grade, forKey: "grade")
            aCoder.encode(courses, forKey: "courses")
        }
        required convenience init?(coder aDecoder: NSCoder) {
            // The first name is required. If we cannot decode a first name string, the initializer should fail.
            guard let first = aDecoder.decodeObject(forKey: "first") as? String else {
                os_log("Unable to decode the first name for a Student object.", log: OSLog.default, type: .debug)
                return nil
            }
            let last = aDecoder.decodeObject(forKey: "last") as! String
            let grade = aDecoder.decodeInteger(forKey: "grade")
            let courses = aDecoder.decodeObject(forKey: "courses") as! [String]
    
            // Must call designated initializer.
            self.init(first: first, last: last, grade: grade, courses: courses)
        }
    }
    

    开始工作了!现在,每个视图控制器都有一个district对象,可以在修改数据模型时调用district.saveDistrict()

    类别地区:NSObject,NSCoding{

    //MARK: Properties
    var array: [School]
    
    //MARK: Archiving Paths
    static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("District")
    
    init (array: [School] = [School]()) {
        self.array = array
    }
    
    //MARK: Actions
    func saveDistrict() {
        let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(array, toFile: District.ArchiveURL.path)
    
        if isSuccessfulSave {
            os_log("Schools array successfully saved", log: OSLog.default, type: .debug)
        } else {
            os_log("Failed to save schools array...", log: OSLog.default, type: .error)
        }
    }
    func loadSavedDistrict() -> District? {        
        var savedDistrict = District()
        if let districtConst = NSKeyedUnarchiver.unarchiveObject(withFile: District.ArchiveURL.path) as? [School] {
            savedDistrict = District(array: districtConst)
        }
        return savedDistrict
    }
    
    //MARK: NSCoding Protocol
    func encode(with aCoder: NSCoder) {
        aCoder.encode(array, forKey: "array")
    }
    required convenience init?(coder aDecoder: NSCoder) {
        // The array is required. If we cannot decode the array, the initializer should fail.
        guard let array = aDecoder.decodeObject(forKey: "array") as? [School] else {
            os_log("Unable to decode the Schools array object.", log: OSLog.default, type: .debug)
            return nil
        }
    
        // Must call designated initializer.
        self.init(array: array)
    }
    

    }

    让它工作起来了!现在,每个视图控制器都有一个district对象,可以在修改数据模型时调用district.saveDistrict()

    类别地区:NSObject,NSCoding{

    //MARK: Properties
    var array: [School]
    
    //MARK: Archiving Paths
    static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("District")
    
    init (array: [School] = [School]()) {
        self.array = array
    }
    
    //MARK: Actions
    func saveDistrict() {
        let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(array, toFile: District.ArchiveURL.path)
    
        if isSuccessfulSave {
            os_log("Schools array successfully saved", log: OSLog.default, type: .debug)
        } else {
            os_log("Failed to save schools array...", log: OSLog.default, type: .error)
        }
    }
    func loadSavedDistrict() -> District? {        
        var savedDistrict = District()
        if let districtConst = NSKeyedUnarchiver.unarchiveObject(withFile: District.ArchiveURL.path) as? [School] {
            savedDistrict = District(array: districtConst)
        }
        return savedDistrict
    }
    
    //MARK: NSCoding Protocol
    func encode(with aCoder: NSCoder) {
        aCoder.encode(array, forKey: "array")
    }
    required convenience init?(coder aDecoder: NSCoder) {
        // The array is required. If we cannot decode the array, the initializer should fail.
        guard let array = aDecoder.decodeObject(forKey: "array") as? [School] else {
            os_log("Unable to decode the Schools array object.", log: OSLog.default, type: .debug)
            return nil
        }
    
        // Must call designated initializer.
        self.init(array: array)
    }
    

    }/P>如果你想在本地设备上坚持,你可以考虑使用CordaDATA来保存你的对象。您可以采取的另一种策略是利用Firebase远程存储数据。如果您序列化(编码)顶级对象(区域数组),则无法单独加载/保存嵌套的学校/教室/学生数组。始终必须加载/保存完整的顶级阵列。对于您的问题,另一种方法可能更可取@Adrian提到了CoreData或Firebase。问问自己:应用程序的一个用户(地区/学校/教室/学生)总共有多少个实体(不同类型)。如果总数超过几百个,您肯定应该使用CoreData或第三方数据库解决方案。感谢您的回复!我可能应该在我的原始帖子中提到,我正在努力避免使用CoreData。再说一次,我不是专业人士,但从我在研究()中看到的情况来看,CoreData需要进行更多的设置工作。不过我会调查Firebase。。。我想远程数据存储在未来的版本中会很好。为了回答卢茨的问题,它非常小,可能不到100。再次感谢!如果你想坚持本地设备,你可以考虑使用CordaDATA来保存你的对象。您可以采取的另一种策略是利用Firebase远程存储数据。如果您序列化(编码)顶级对象(区域数组),则无法单独加载/保存嵌套的学校/教室/学生数组。始终必须加载/保存完整的顶级阵列。对于您的问题,另一种方法可能更可取@Adrian提到了CoreData或Firebase。问问自己:应用程序的一个用户(地区/学校/教室/学生)总共有多少个实体(不同类型)。如果总数超过几百个,您肯定应该使用CoreData或第三方数据库解决方案。感谢您的回复!我可能应该在我的原始帖子中提到,我正在努力避免使用CoreData。再说一次,我不是专业人士,但从我在研究()中看到的情况来看,CoreData需要进行更多的设置工作。不过我会调查Firebase。。。我想远程数据存储在未来的版本中会很好。为了回答卢茨的问题,它非常小,可能不到100。再次感谢!