I came across a very strange behavior in Swift lately.
Normally, when using a meta type, I can only reference its required initializer.
class A {
init(_ Val: Int)
}
let t = A.self
t.init(3) // error
This is expected.
However, if my type is a subclass of UIView, and I don’t add/override any designated initializer, the compiler will allow me to reference .init(), which is not marked with “required”.
class A: UIView {}
let t = A.self
t.init() // valid, but unexpected.
Even though this could leave my program in an unsafe state:
class B: A {
init(_ val: Int) { super.init(frame: .zero) }
required init?(_ coder: NSCoder) { fatalError() }
}
let t: A.Type = B.self
t.init() // crash.
So what is special about UIView.init(), that makes the compiler treat it differently, even somewhat dangerously?
1 Like
The only thing that I can think of is that UIView is an Objective-C class that ultimately inherits from NSObject as part of Cocoa Touch. Inheriting from UIView tags your class an @objc class, not a normal Swift class. NSObject has a designated initializer init(), which gets called if no intervening derived class overrides it.
Avi
3
I tested this theory, and it appears to be false.
class C: NSObject {
// required init(x: Int) {
// print(x)
// }
}
class D: C {
// required init(x: Int) {
// super.init(x: x)
// }
}
let t = D.self
t.init() // Constructing an object of class type 'D' with a metatype value must use a 'required' initializer
mayoff
(Rob Mayoff)
4
I think this is not a property of UIView but a property of any class imported from Objective-C. This does not compile:
class A { }
_ = { $0.init() }(A.self)
// ^ Constructing an object of class type 'A' with a metatype value must use a 'required' initializer
But these compile:
import Foundation
_ = { $0.init() }(NSObject.self)
_ = { $0.init() }(NSString.self)
_ = { $0.init() }(NSArray.self)
_ = { $0.init() }(NSDictionary.self)
_ = { $0.init() }(NSDate.self)
Note that it's not sufficient to declare your class @objc. This still doesn't compile:
@objc class A: NSObject { }
_ = { $0.init() }(A.self)
// ^ same error as before
The class has to be defined in Objective-C and imported.
2 Likes
You are right.
I still wonder if there're any specific reasons why NSObject.init is special? Maybe because the compiler "assumes" every normal @objc type "should" support init?
jayton
(Jens Ayton)
6
Every Objective-C subclass of NSObject does have an init method, whether you want it to or not.
However, you can mark it with __attribute__((unavailable)), in which case the Swift importer won’t let you call it:
@interface SomeClass: NSObject
- (instancetype)init __attribute__((unavailable));
@end
_ = SomeClass() // error: 'init()' is unavailable
_ = { $0.init() }(SomeClass.self) // error: 'init()' is unavailable
2 Likes