Перегибы свойств Свифта не вызывают в каскаде

Допустим, у меня есть простая древовидная структура, подобная этой:

class Tree {
    weak var ancestor: Tree?
    var children = [Tree]()
}

Каждый узел дерева хранит ссылку на своих потомков, а слабый (чтобы избежать циклов ссылок) - на самого старого предка.

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

let grandParent = Tree()
let parent      = Tree()
let child       = Tree()
let grandChild  = Tree()

child.children.append(grandChild)
parent.children.append(child)
grandParent.children.append(parent)

// Should set ancestor in the whole hierarchy.
grandParent.ancestor = grandParent

Я пытался добиться этого с помощью наблюдателей свойств, перебирая потомки узла всякий раз, когда его предок установлен так, что значение распространяется, но не заметил ни didSet ни willSet вызывается прошло первый ребенок. Вот код, с которым я столкнулся:

weak var ancestor: Tree? = nil {
    didSet {
        for child in self.children {
            child.ancestor = self.ancestor
        }
    }
}

Как и ожидалось, предок grandParent правильно установлен, как и у parent, Тем не менее, те его потомков (child а также grandChild) не. Обратите внимание, что я наблюдаю тот же результат с willSet (конечно, корректируя приведенный выше код, чтобы принять во внимание, что self.ancestor не изменился бы еще).

Может ли кто-нибудь указать мне, что я здесь скучаю?


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

var ancestor: Tree? {
    get {
        return self._ancestor
    }

    set(newAncestor) {
        self._ancestor = newAncestor
        for child in self.children {
            child.ancestor = newAncestor
        }
    }
}

private weak var _ancestor: Tree? = nil

1 ответ

Решение

Я видел это упомянутое ранее, и похоже, что это ошибка Swift, которая срабатывает, если вы пытаетесь установить свойство внутри willSet / didSet для другого экземпляра того же типа.

Как ни странно, синтаксис имеет значение и, кажется, работает над ошибкой. Например, я могу заставить ваш код работать, изменив didSet к этому:

weak var ancestor: Tree? = nil {
    didSet {
        children.forEach { $0.ancestor = ancestor }
    }
}

Он делает то же самое, но не вызывает ошибку компилятора.


ОБНОВИТЬ

Ранее это было зарегистрировано как ошибка на swift.org и все еще открыто: https://bugs.swift.org/browse/SR-419

И есть пара дополнительных комментариев, разъясняющих причину и какие обходные пути могут избежать этого здесь: https://twitter.com/uint_min/status/804795245728698368

Другие вопросы по тегам