Cuando se ejecuta el siguiente código, el self
dentro de defaultModuleName
es ReactViewController
cuando uno esperaría que fuera FooViewController
. ¿Por qué?
class ReactViewController: UIViewController {
var moduleName: String = defaultModuleName
static var defaultModuleName: String {
let t = String(reflecting: self) // Also tried NSStringFromClass
guard let s = t.split(separator: ".").last else { return "" }
guard let r = s.range(of: "ViewController") else { return "" }
return String(s.prefix(upTo: r.lowerBound))
}
}
class FooViewController: ReactViewController {
override func viewDidLoad() {
super.viewDidLoad();
print(moduleName); // Prints "React"
}
}
Respuestas
2 para la respuesta № 1Esto es bastante interesante; parece que el self
disponible en un inicializador de propiedad es simplemente el tipo en el que se define la propiedad, en lugar del tipo dinámico de la instancia que se está construyendo.
Un ejemplo más mínimo sería:
class C {
static var foo: String { return "(self)" }
let bar = foo // the implicit "self" in the call to "foo" is always C.
}
class D : C {}
print(D().bar) // C
En el inicializador de propiedades para bar
, lo implícito self
es C.self
no D.self
; A pesar del hecho de que estamos "construyendo una D
ejemplo. Así que eso es lo que la llamada foo
ve como self
.
Esto también evita class
el miembro anula la llamada desde los inicializadores de propiedades:
class C {
class var foo: String { return "C" }
let bar = foo
}
class D : C {
override class var foo: String { return "D" }
}
print(D().bar) // C
Por lo tanto considero esto como un error, y tengo presentó un informe aquí.
Hasta que se arregle, una solución simple es usar un perezoso propiedad en cambio, como ahora self
es la instancia real (al acceder a la propiedad por primera vez), la cual obtenemos puede obtener el tipo dinámico con type(of: self)
.
Por ejemplo:
class C {
static var foo: String { return "(self)" }
// private(set) as the property was a "let" in the previous example.
lazy private(set) var bar = type(of: self).foo
}
class D : C {}
print(D().bar) // D
Aplicado a tu ejemplo:
class ReactViewController : UIViewController {
lazy var moduleName = type(of: self).defaultModuleName
static var defaultModuleName: String {
let t = String(reflecting: self) // Also tried NSStringFromClass
guard let s = t.split(separator: ".").last else { return "" }
guard let r = s.range(of: "ViewController") else { return "" }
return String(s.prefix(upTo: r.lowerBound))
}
}
class FooViewController : ReactViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(moduleName) // Prints "Foo"
}
}
0 para la respuesta № 2
Solo necesitas pasar self
en lugar de type(of: self)
, y usar el String(describing:)
inicializador
class ClassA {
static var className: String {
return String(describing: self)
}
}
class ClassB: ClassA { }
print(ClassB.className) // prints "ClassB"
EDIT: aclaración sobre el var moduleName: String = defaultModuleName
actualizar. Supongamos que agrego esta línea al ejemplo anterior (la misma idea):
class ClassA {
// This is a property of ClassA -> it gets implicitly initialized
// when ClassA does -> it uses ClassA.className for its value
var instanceClassName = className
static var className: String {
return String(describing: self)
}
}
class ClassB: ClassA { }
print(ClassB().instanceClassName) // prints "ClassA"
Esta nueva instanceClassName
no es estático, por lo que es una propiedad de instancia en ClassA
. Por lo tanto, se inicializa cuando ClassA
se inicializa (no cuando se inicializa ClassB). Ergo, una propiedad que se establece dentro de ClassA
, usando una referencia a className
se imprimirá ClassA
.