persianas compatible apple ios swift uiviewcontroller properties didset

ios - compatible - ¿Qué pasa si quiero asignar una propiedad a sí mismo?



homekit compatible (4)

Si intento ejecutar el siguiente código:

photographer = photographer

Me sale el error

Asignar una propiedad a sí mismo.

Quiero didSet la propiedad para forzar al photographer didSet block para que se ejecute.

Este es un ejemplo de la vida real: en la clase "16. Segues and Text Fields" del curso de invierno de Stanford para iOS 2013 (13:20), el profesor recomienda escribir un código similar al siguiente:

@IBOutlet weak var photographerLabel: UILabel! var photographer: Photographer? { didSet { self.title = photographer.name if isViewLoaded() { reload() } } } override func viewDidLoad() { super.viewDidLoad() reload() } func reload() { photographerLabel.text = photographer.name }

Nota : Hice los siguientes cambios: (1) el código se cambió de Objective-C a Swift; (2) porque está en Swift, uso el bloque didSet de la propiedad en lugar del método setPhotographer: (3) en lugar de self.view.window Estoy usando isViewLoaded porque el primero obliga erróneamente a que se cargue la vista al acceder a la propiedad view ; (4) el método reload() (solo) actualiza una etiqueta por motivos de simplicidad, y porque se parece más a mi código; (5) la etiqueta IBOutlet del fotógrafo se agregó para admitir este código más simple; (6) ya que estoy usando Swift, la isViewLoaded() ya no existe simplemente por razones de rendimiento, ahora es necesario para evitar un fallo, ya que el IBOutlet se define como UILabel! ¿Y no UILabel? por lo tanto, intentar acceder a él antes de que se cargue la vista bloqueará la aplicación; esto no era obligatorio en Objective-C ya que usa el patrón de objeto nulo.

La razón por la que llamamos a recargar dos veces es porque no sabemos si la propiedad se establecerá antes o después de crear la vista. Por ejemplo, el usuario puede primero establecer la propiedad, luego presentar el controlador de vista, o puede presentar el controlador de vista y luego actualizar la propiedad.

Me gusta que esta propiedad sea independiente en cuanto a cuándo se carga la vista (es mejor no hacer suposiciones sobre el tiempo de carga de la vista), por lo que quiero usar este mismo patrón (solo ligeramente modificado) en mi propio código:

@IBOutlet weak var photographerLabel: UILabel? var photographer: Photographer? { didSet { photographerLabel?.text = photographer.name } } override func viewDidLoad() { super.viewDidLoad() photographer = photographer }

Aquí, en lugar de crear un nuevo método para llamar desde dos lugares, solo quiero el código en el bloque didSet . Quiero que viewDidLoad didSet a didSet a llamarse, así que asigno la propiedad a sí mismo. Sin embargo, Swift no me permite hacer eso. ¿Cómo puedo forzar la didSet a didSet ?


¿Desea hacer una función a la que didSet llama y luego llamar a esa función cuando desea actualizar algo? Parece que esto protegería contra los desarrolladores que van a WTF? en el futuro


Antes de Swift 3.1 , podría asignarse el name la propiedad con:

name = (name)

pero esto ahora da el mismo error: "asignar una propiedad a sí mismo" .

Hay muchas otras formas de solucionar esto, incluida la introducción de una variable temporal:

let temp = name name = temp

Esto es demasiado divertido para no ser compartido. Estoy seguro de que la comunidad puede encontrar muchas más formas de hacer esto, cuanto más loco, mejor

class Test: NSObject { var name: String? { didSet { print("It was set") } } func testit() { // name = (name) // No longer works with Swift 3.1 (bug SR-4464) // (name) = name // No longer works with Swift 3.1 // (name) = (name) // No longer works with Swift 3.1 (name = name) name = [name][0] name = [name].last! name = [name].first! name = [1:name][1]! name = name ?? nil name = nil ?? name name = name ?? name name = {name}() name = Optional(name)! name = ImplicitlyUnwrappedOptional(name) name = true ? name : name name = false ? name : name let temp = name; name = temp name = name as Any as? String name = (name,0).0 name = (0,name).1 setValue(name, forKey: "name") // requires class derive from NSObject name = Unmanaged.passUnretained(self).takeUnretainedValue().name name = unsafeBitCast(name, to: type(of: name)) name = unsafeDowncast(self, to: type(of: self)).name perform(#selector(setter:name), with: name) // requires class derive from NSObject name = (self as Test).name unsafeBitCast(dlsym(dlopen("/usr/lib/libobjc.A.dylib",RTLD_NOW),"objc_msgSend"),to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject unsafeBitCast(class_getMethodImplementation(type(of: self), #selector(setter:name)), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject unsafeBitCast(method(for: #selector(setter:name)),to:(@convention(c)(Any?,Selector,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject _ = UnsafeMutablePointer(&name) _ = UnsafeMutableRawPointer(&name) _ = UnsafeMutableBufferPointer(start: &name, count: 1) withUnsafePointer(to: &name) { name = $0.pointee } //Using NSInvocation, requires class derive from NSObject let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name)) var localVarName = name withUnsafePointer(to: &localVarName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) } invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self) } } let test = Test() test.testit()


Hay algunas soluciones alternativas, pero no tiene mucho sentido hacerlo. Si un programador (futuro mantenedor del código) ve un código como este:

a = a

Ellos lo eliminarán.

Dicha declaración (o una solución alternativa) nunca debe aparecer en su código.

Si su propiedad se ve así:

var a: Int { didSet { // code } }

entonces no es una buena idea invocar el controlador didSet a = a .

¿Qué pasa si un futuro mantenedor agrega una mejora de rendimiento al conjunto de didSet como este?

var a: Int { didSet { guard a != oldValue else { return } // code } }

La verdadera solución es refactorizar:

var a: Int { didSet { self.updateA() } } fileprivate func updateA() { // the original code }

Y en lugar de a = a llamar directamente a updateA() .

Si estamos hablando de puntos de venta, una solución adecuada es forzar la carga de vistas antes de asignar por primera vez:

@IBOutlet weak var photographerLabel: UILabel? var photographer: Photographer? { didSet { _ = self.view // or self.loadViewIfNeeded() on iOS >= 9 photographerLabel?.text = photographer.name // we can use ! here, it makes no difference } }

Eso hará que el código en viewDidLoad innecesario.

Ahora es posible que se pregunte "¿por qué debería cargar la vista si todavía no la necesito? Solo quiero almacenar mis variables aquí para uso futuro" . Si eso es lo que está preguntando, significa que está usando un controlador de vista como clase de modelo, solo para almacenar datos. Eso es un problema de arquitectura por sí mismo. Si no quieres usar un controlador, ni siquiera lo instales. Utilice una clase modelo para almacenar sus datos.


@vacawama hizo un gran trabajo con todas esas opciones. Sin embargo, en iOS 10.3 , Apple prohibió algunas de estas formas y lo más probable es que vuelva a hacerlo en el futuro.

Nota: Para evitar el riesgo y futuros errores, usaré una variable temporal.

Podemos crear una función simple para eso:

func callSet<T>(_ object: inout T) { let temporaryObject = object object = temporaryObject }

Sería usado como: callSet(&foo)

O incluso un operador unario, si hay uno adecuado ...

prefix operator += prefix func +=<T>(_ object: inout T) { let temporaryObject = object object = temporaryObject }

Sería usado como: +=foo