2017-01-08 11 views
0

При использовании NSCoding или NSKeyedArchiver для отношения родитель-потомок; Я не могу установить ссылку на ребенка родительскому, так как он закончит сбой на Swift Playground.NSCoding - сохранение родительского атрибута вызывает сбой

Я хочу иметь ссылку в своем классе Child классу Parent.

Но это закончит сбой на игровой площадке, когда дело доходит до загрузки данных обратно в память.

class Parent: NSObject, NSCoding { 
    var children:[Child] = [Child]() 

    init(children:[Child]?) { 
     if let childrenList = children { 
      self.children = childrenList 
     } 
    } 

    public convenience required init?(coder aDecoder: NSCoder) { 
     let children = aDecoder.decodeObject(forKey: "children") as! [Child] 

     self.init(children: children) 
    } 

    func encode(with aCoder: NSCoder) { 
     aCoder.encode(children, forKey:"children") 
    } 

} 

class Child: NSObject, NSCoding { 
    var parent: Parent 

    init(parent:Parent) { 
     self.parent = parent 
    } 

    public convenience required init?(coder aDecoder: NSCoder) { 
     let parent = aDecoder.decodeObject(forKey: "parent") as! Parent 

     self.init(parent: parent) 
    } 

    func encode(with aCoder: NSCoder) { 
     aCoder.encode(parent, forKey:"parent") 
    } 
} 

var parent1 = Parent.init(children: nil) 
var parent2 = Parent.init(children: nil) 
var child1 = Child.init(parent: parent1) 
var child2 = Child.init(parent: parent2) 
parent1.children.append(child1) 
parent2.children.append(child2) 

let parents = [parent1, parent2] 


let manager = FileManager.default 
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as URL 
let writeFile: URL = url.appendingPathComponent("sample.data") 

print ("Attempting to write to: \(writeFile.path)") 


NSKeyedArchiver.archiveRootObject(parents, toFile: writeFile.path) 

// Crash occurs here. 
if let parentData = NSKeyedUnarchiver.unarchiveObject(withFile: writeFile.path) as? [Parent] { 
    for p in parentData { 
     print ("\(p.children.count)") 
    } 
} 

Child В классе я хочу ссылку на родителя; так что в будущем я могу выполнять тесты или фильтрацию на дочерние объекты с определенными родителями.

Однако, я всегда получаю эту аварию на детской площадке:

Playground execution aborted: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x10). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

Кажется врезаться на ссылку на родительский объект.

Как обеспечить, чтобы NSCoding сохранил родительский атрибут в дочернем объекте?

Большое спасибо


Edit: Refactored код

Этот рефакторинга кода, кажется, работает, хотя я не знаю, если я сделал это правильно.

class Parent: NSObject, NSCoding { 
    private (set) var children:[Child] = [Child]() 

    override init() { 
     super.init() 
    } 

    public convenience required init?(coder aDecoder: NSCoder) { 
     let children = aDecoder.decodeObject(forKey: "children") as! [Child] 

     self.init() 
     self.createChildren(children: children) 
    } 

    func encode(with aCoder: NSCoder) { 
     aCoder.encode(children, forKey:"children") 
    } 

    func addChild(child:Child) { 
     child.parent = self 
     self.children.append(child) 
    } 

    private func createChildren(children:[Child]) { 
     for child:Child in children { 
      self.addChild(child: child) 
     } 
    } 
} 

class Child: NSObject, NSCoding { 
    weak var parent: Parent? 

    override init() { 
     super.init() 
    } 

    public convenience required init?(coder aDecoder: NSCoder) { 
     self.init() 
    } 

    func encode(with aCoder: NSCoder) { 
    } 
} 

var parent1 = Parent.init() 
var parent2 = Parent.init() 
var child1 = Child.init() 
var child2 = Child.init() 
parent1.addChild(child: child1) 
parent2.addChild(child: child2) 
let parents = [parent1, parent2] 


let manager = FileManager.default 
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as URL 
let writeFile: URL = url.appendingPathComponent("sample.data") 

print ("Attempting to write to: \(writeFile.path)") 


NSKeyedArchiver.archiveRootObject(parents, toFile: writeFile.path) 


if let parentData = NSKeyedUnarchiver.unarchiveObject(withFile: writeFile.path) as? [Parent] { 
    for p in parentData { 
     print ("\(p.children.count)") 
    } 
} 

ответ

1

Подумайте, что происходит в вашем коде. Вы архивируете родителя. Родители архивируют своих детей. Затем каждый из этих детей пытается архивировать своего родителя. Затем каждый из этих родителей пытается архивировать своих детей. Этот цикл продолжается и продолжается до тех пор, пока он не «бум».

У вас есть несколько вопросов:

  1. В вашем Child классе, parent свойство должно быть weak. В противном случае у вас есть ссылочный цикл и много проблем с памятью.
  2. Ваш класс Child не должен пытаться кодировать/декодировать его родительский.
  3. Класс Parent должен быть установлен как родительский элемент для каждого дочернего элемента при декодировании родителя.

Ваша авария вызвана нарушение вопроса 2.

В качестве примечания к выдаче 3, я бы реорганизовать код. Не переносите родителя при создании ребенка. И не выставляйте непосредственно массив children в свой класс Parent. Я бы добавил методы в класс Parent для добавления и получения дочерних элементов. Метод добавления детей должен установить свойство parent каждого добавленного к нему дочернего элемента.

+0

Хорошо, я постараюсь реорганизовать мой код. Рекомендация сделать ссылки на родителя слабой ссылкой является хорошей. Я не думал об этом. Я попытаюсь реорганизовать свой код и посмотреть, где я нахожусь после этого – zardon

+0

. Я обновил свой код.(1) Создал 'children' частный (set) var, (2) дочерние объекты не кодируют/декодируют родителя (3) функция для добавления дочерних элементов, которая устанавливает родительское свойство (4) родительское свойство является' слабым ' Код больше не падает, хотя я не знаю, сделал ли я все правильно. – zardon