Reputation: 16558
When the following code is run, the self
inside of defaultModuleName
is ReactViewController
when one would expect it to be FooViewController
. Why?
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"
}
}
Upvotes: 1
Views: 81
Reputation: 80781
This is pretty interesting; it appears that the self
available in a property initialiser is merely the type that the property is defined in, rather than the dynamic type of the instance being constructed.
A more minimal example would be:
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
In the property initialiser for bar
, the implicit self
is C.self
, not D.self
; despite the fact that we're constructing a D
instance. So that's what the call to foo
sees as self
.
This also prevents class
member overrides from being called from property initialisers:
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
Therefore I regard this as a bug, and have filed a report here.
Until fixed, a simple solution is to use a lazy property instead, as now self
is the actual instance (upon the property being accessed for the first time), which we get can get the dynamic type of with type(of: self)
.
For example:
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
Applied to your example:
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"
}
}
Upvotes: 2
Reputation: 7351
You just need to pass self
instead of type(of: self)
, and use the String(describing:)
initializer.
class ClassA {
static var className: String {
return String(describing: self)
}
}
class ClassB: ClassA { }
print(ClassB.className) // prints "ClassB"
EDIT: clarification on the var moduleName: String = defaultModuleName
update. Suppose I add this line to the above example (same 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"
This new instanceClassName
is not static, so it is an instance property on ClassA
. It is therefore initialized when ClassA
is initialized (not when ClassB is initialized). Ergo, a property being set within ClassA
, using a reference to className
, will print out ClassA
.
Upvotes: 0