What’s so special about UIView.init()?

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.

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

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?

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