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