使用NSCoding保存包含自定义对象的数组

时间:2017-03-12 18:13:27

标签: swift3 ios10

我正在创建一个待办事项列表应用程序来学习输入和输出,它有一个简单的列表项目结构。我使用3个类来管理它们:

  • TodoManager:是一个单例,用于集中管理视图控制器中这些列表中的列表和项目。它包含一系列TodoLists和一系列函数来添加列表,将它们标记为已完成并返回列表。
  • TodoList:有一个字符串var(name),bool var(已完成)和一个TodoItems数组
  • TodoItem:有一个字符串var(name)和一个bool var(已完成)。

我想存储我的自定义对象数组[TodoList],以便稍后加载它,我正在寻找世界上最简单的方法来实现它。 UserDefaults不允许自定义对象(因为它不应该因为它是设置)所以我需要使用NSCoding持久保存数据,为此我需要让我的TodoList类继承自NSObjects。

class TodoManager: NSObject, NSCoding {
    // the singleton
    static let shared = TodoManager()

    // filePath var
    private var filePath : String {
        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return url.appendingPathComponent("objectsArray").path
    }

    // init
    override private init() {}

    // array to store all lists
    private var lists = [TodoList]()

    func newTodoList(title: String) {
        lists.append(TodoList(instanceTitle: title))
    }

    // coding
    func encode(with coder: NSCoder) {
        coder.encode(lists, forKey: "lists")
    }

    required convenience init(coder decoder: NSCoder) {
        self.init()
        lists = decoder.decodeObject(forKey: "lists") as! [TodoList]
    }

    // saving and loading
    func saveAll() {
        let data = lists
        NSKeyedArchiver.archiveRootObject(data, toFile: filePath)
    }

    func loadAll() {
        if let dataArray = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [TodoList] {
            lists = dataArray
        }
    }
}

class TodoList: NSObject, NSCoding {
    // array to store items in this list instance
    private var items = [TodoItem]()

    // vars to store title and completion status
    private var title = String()
    private var completed = Bool()

    // init
    init(instanceTitle: String) {
        title = instanceTitle
        completed = false
    }

    // coding
    func encode(with coder: NSCoder) {
        coder.encode(items, forKey: "lists")
        coder.encode(title, forKey: "title")
        coder.encode(completed, forKey: "completed")
    }

    required convenience init(coder decoder: NSCoder) {
        self.items = decoder.decodeObject(forKey: "items") as! [TodoItem]
        self.title = decoder.decodeObject(forKey: "title") as! String
        self.completed = decoder.decodeBool(forKey: "completed")
        self.init() // <----- critical line
    }

    // item-related
    func addItem(title: String) {
        items.append(TodoItem(instanceTitle: title))
    }
}

class TodoItem: NSObject, NSCoding {
    private var title = String()
    private var completed = Bool()


    // inits
    init(instanceTitle: String) {
        title = instanceTitle
        completed = false
    }

    func encode(with coder: NSCoder) {
        coder.encode(title, forKey: "title")
        coder.encode(completed, forKey: "completed")
    }

    required convenience init(coder decoder: NSCoder) {
        self.init() // <----- similar critical line
        title = decoder.decodeObject(forKey: "title") as! String
        completed = decoder.decodeBool(forKey: "completed")
    }
}

我遇到的问题是,在easy init中,instanceTitle是未声明的,因此我无法将其传递给self.init()。我无法将声明添加到所需的便利init,因为它将错误该类不符合所需的协议。我尝试了很多变化,但经过几个小时的盯着这个并使用我的谷歌foo我无法弄明白。我在NSCoding的兔子洞里做错了什么?

1 个答案:

答案 0 :(得分:0)

当调用便捷initializer()时,由请求新类实例的函数最初传递给指定init()的实际参数不需要传递通过便利init 。使用占位符调用指定的初始化程序,该占位符在执行时填写。对于我的情况,它看起来像这样:

init(instanceTitle: String) {
    title = instanceTitle
    completed = false
}

required convenience init(coder decoder: NSCoder) {
    // call designated init
    self.init(instanceTitle: "[placeholder]")
    items = decoder.decodeObject(forKey: "items") as! [TodoItem]
    title = decoder.decodeObject(forKey: "title") as! String
    completed = decoder.decodeBool(forKey: "completed")
}