Почему свойство lazy и didSet в конечном итоге вызовут рекурсию?

#ios #swift

Вопрос:

Приведенный ниже код работает просто отлично

 class A {

    var s: MyStruct! {
        didSet {
            print("didSet")
            print(n)
        }
    }

    lazy var n: Int = s.x   1

    func viewDidLoad() {
        s = MyStruct()
    }
}

struct MyStruct {
    var x = 1
    init() { print("MyStruct init") }
}

let a = A()
a.viewDidLoad()
 

с выходом :

 MyStruct init
didSet
2
 

Однако, если у нас есть ленивые свойства, как показано ниже

 class A {

    var s: MyStruct! {
        didSet {
            print("didSet")
            print(n)
        }
    }

    lazy var n: Int = s.x   1

    func viewDidLoad() {
        s = MyStruct()
    }
}

struct MyStruct {
    lazy var x = 1
    init() { print("MyStruct init") }
}

let a = A()
a.viewDidLoad()
 

Это закончится бесконечным рекурсивным вызовом

 MyStruct init
didSet
didSet
didSet
...
 

Почему свойство lazy и didSet в конечном итоге вызовут рекурсию?

Комментарии:

1. ленивые вары на конструкциях следует использовать очень осторожно или вообще не использовать.

2. Доступ x к MyStruct-это операция изменения ( mutating get ), поэтому это запускает свойство didSet for s . Таким образом, бесконечный цикл print(n) -> получить > s.x -> позвонить > s.didSet -> > print(n) -> …

Ответ №1:

Вы декларируете lazy stored собственность. Когда структура инициализируется с MyStruct() помощью , в ней не сохраняется значение MyStruct.x .

Он будет заполнен только при первом доступе к нему. Когда свойство изменяется, тип значения, например struct MyStruct , также считается измененным — поэтому оно didSet вызывается (снова) при первом доступе x .

Вот как это становится бесконечным циклом.

  1. viewDidLoad() > A.s.setter > A.s.didset [Ожидается]
  2. Первый доступ A.n.getter к этой print(n) части.
  3. s.x хранится лениво, и при первом заполнении значения (обновлении) оно срабатывает — A.s.modify > A.s.didset и мы снова приземляемся A.n.getter .
  4. Он бесконечно повторяется между 2 amp; 3 после этого.

Смотрите скриншот — введите описание изображения здесь

Комментарии:

1. Я все еще не понимаю. Когда мы приземлимся в третий раз n.getter , зачем мы достигнем s.modify ? В этот момент значение n должно быть уже известно ( s.x к тому времени должно быть инициализировано до 1). Разве он не может просто вернуть это, не получив s.x ?

2. На скриншоте Frame 11 не завершено выполнение — значение n НЕ было получено print(n) частично, и каким-то образом нам удалось снова приземлиться в том же месте. Мы можем легко разорвать эту петлю, отложив вызов print(n) 0.1 seconds примерно на некоторое время. Пока он синхронен, он будет вести себя таким образом.