Por lo que sé y como se menciona en este hilo, si configuro un valor de propiedad en su observador didSet , no debería volver a activar el observador. Bien, entonces escribí un código como este:
class B { var i = 0 { didSet { print("didSet called") self.i += 1 } } } var y = B() yi = 2 print(yi) Este código imprime "didSet called" y 3 como salida como se esperaba. Pero hice un pequeño cambio en este código de la siguiente manera:
class B { var i = 0 { didSet { print("didSet called") doit(val: self) } } func doit(val: B) { val.i += 1 } } var y = B() yi = 2 print(yi) Pero ahora cae en un bucle infinito que imprime "didSet called" . ¿Por qué si establezco un valor para la variable dentro didSet pasándolo a través de un argumento de función, didSet nuevamente? Dado que el objeto pasado debe referirse al mismo objeto, no sé por qué sucede esto. Probé y si lo configuro a través de un cierre en didSet en lugar de una función normal, vuelve a un bucle infinito.
Actualización: es divertido que incluso esto desencadene un bucle infinito:
class B { var i = 0 { didSet { print("called") doit() } } func doit() { self.i += 1 } }Después de consultar con Swift github y hacer preguntas sobre este problema, descubrí que este problema es más complejo de lo que parece. Pero hay una regla específica sobre este problema:
El observador
didSetno se activará solo si el acceso a la propiedad dentro de su propio observadordidSetse puede realizar a través del acceso directo a la memoria.
El problema es que es un poco ambiguo cuándo el acceso a la propiedad será directo (a menos que probablemente sea un desarrollador de Swift). Una característica importante que tiene un efecto en mi pregunta es esta:
El método de instancia de clase nunca accede directamente a las propiedades de clase.
Esta cita muestra un problema con mi código, aunque puedo argumentar que cuando un miembro de la instancia debería poder acceder a la propiedad directamente siempre que lo llame en didSet observe. Cuando tengo un código como este:
class B { var i = 0 { didSet { print("called") doit() } } func doit() { self.i += 1 } } La función doit() no puede acceder a i directamente, lo que activa didSet nuevamente y causa un bucle infinito.
Ahora, ¿cuál es la solución?
Puede usar inout para pasar propiedades de su propio didSet a una función de instancia sin activar didSet . Algo como esto:
class B { var i = 0 { didSet { print("called") doit(&i) } } func doit(_ i: inout Int) { i += 1 } } Y una última cosa. A partir de Swift 5 , las condiciones para seleccionar el acceso directo a la memoria para las propiedades dentro de su propio didSet serán más restringidas. Según github, las únicas condiciones que utilizarán el acceso directo a la memoria son las siguientes:
Within a variable's own didSet/willSet specifier, access its storage directly if either: 1) It's a 'plain variable' (ie a variable that's not a member). 2) It's an access to the member on the implicit 'self' declaration. If it's a member access on some other base, we want to call the setter as we might be accessing the member on a *different* instance.
Esto significa que los códigos como los siguientes activarán un bucle infinito mientras que ahora no lo hace:
class B { var i = 0 { didSet { print("called") var s = self si += 1 } } }