Too many empty "required" UIView.init(coder:) methods

Hi,


I have a lot of UIViews where the compiler forces me to add an init(coder:) initializer, like this:


class FooView : UIView /* or a UIView subclass */ {
   ...
   required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
   ...
}


It claims it's required but my program runs fine without it. I do not create the views from an archive.


This makes me wonder if something is wrong here with the design of the library, or the concept of a 'required' initializer. What do people think? Does it make sense or is this a wart? If so, can it be fixed?


Rob

If you are absolutely certain you'll never load the view from Interface Builder, can you use NS_UNAVAILABLE (w/e the Swift translation is for the macro)?


I still use ObjC. What I consider to be somewhat of a *wart* is when a convenience initializer does work that the designated initializers don't do, and you'd like to inherit the functionality of the convenience initializer rather than reimplement it in your subclasses own designated initializer but for whatever reason they choose not to mark that initializer as a designated initializer (NSWindowController initWithWindowNibName: comes to mind in the cases when you want to enforce that a window controller, which won't be subclassed, uses a particular nib).

The problem is that Swift guarantees that initialization is done safely, but Obj-C does not. In this case, both initWithFrame and initWithCoder are designated initializers, so implementing one in your subclass requires you to implement the other. (If you don't re-define initWithFrame in your subclass, you inherit both initializers, so the problem doesn't arise.)


You have to understand that initialization in Obj-C has always been unsafe, but that we used standard patterns to ward off the crashes. Unfortunately, that is truly patterns, not a pattern. The rules for what to do in a subclass varied from class to class, and you just had to somehow know what worked and what didn't. This was not merely a theoretical problem.


With Swift, there are safety guarantees, and following the rules is like stopping at stop signs on roads with no traffic — you're perfectly safe to ignore them, until you're dead.


Unfortunately, there are a few "old school" cases where initialization APIs designed for Obj-C were convenient enough without the rules, but don't work really well in Swift. NSCoding is one, NSWindowController is another, and NSDocument is a third that's even more annoying.


The easiest solution is to live with the pain. Alternatively, it's pretty straightforward to write Obj-C intermediate subclasses that reorganize the initializers, and inherit from these subclasses for all your real subclasses. For example, you can make a UIView subclass that turns initWithCoder into a convenience initializer. I'm not sure that it's worth it, since the Swift compiler will insert the missing initializer for you with a fix-it, but you can.

> The problem is that Swift guarantees that initialization is done safely, but Obj-C does not. In this case, both initWithFrame and initWithCoder are designated initializers, so implementing one in your subclass requires you to implement the other. (If you don't re-define initWithFrame in your subclass, you inherit both initializers, so the problem doesn't arise.)

This isn't exactly what's happening though maybe this isn't important to your point. If I define `init()` in my subclass of UIView, then I also have to define `init(coder:)`, but it doesn't ask me to define `init(frame:)`.


I can do this below, as you suggested - put `convenience` on the `init(coder:)` and you don't have to redefine it a subclass. I don't really see why that it is the case.


class MyView1 : UIView {
    init() {
        super.init(frame: .zero)
    }   
    required convenience init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }   
}
class MyView2 : MyView1 {
    override init() {
        super.init()
    }
}


But add another designated init to either class, eg:


class MyView2 : MyView1 {
    init(str: String) {
        super.init()
    }
}


And again it will demand you add init(coder:) to MyView2. Weird.

In the last example, that happens because you haven't overridden all of the designated inits — just "init()" in this case — and therefore you do not inherit the superclass convenience initializers — that is "init(coder:)". The earlier MyView2 is fine because it overrides all designated inits — again just "init()" — and so it does inherit convenience inits. All of this is a consequence of the init inheritance rules documented in the Swift language guide.


In the MyView1 case, I think problem is that your re-definition of "init" prevents automatic inheritance, so you don't need "init(frame:)" but you still need "init(coder:)" to satisfy the "required" keyword in the NSCoding protocol.


Like I said, it would probably be easier to provide the intermediate class in Obj-C, so that you could break the rules without being reprimanded by the compiler, but the easiest approach is to forget the intermediate classes and just provide a "init(coder:)" when the compiler insists.

try @available(*, unavailable)

Too many empty "required" UIView.init(coder:) methods
 
 
Q