Swiftformat: Rule Suggestion: Add @unavailable to compiler-suggested trivial init(coder:) implementation

Created on 1 May 2019  路  10Comments  路  Source: nicklockwood/SwiftFormat

Hello! When you subclass something that's NSCoding and add an initializer, the compiler mentions that an init(coder:) implementation is required and suggests a trivial conformance that simply calls fatalError(). It looks like this:

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

I'm probably not the only one who has these sprinkled all over the place. They become slightly less annoying when Xcode's autocompletion stops suggesting this useless initializer, which you can accomplish with an @available attribute:

@available(*, unavailable)
required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

I would love it if SwiftFormat could add that @available attribute wherever it notices the canned fatalError() implementation. (I also tend to rename the parameter because I can't stand the name aDecoder, but that's not part of this rule suggestion.)

The rule implementation could probably be pretty picky about when it triggers, as the code suggested by the fix-it hasn't changed in years. No need to ask SwiftFormat to trace the function execution and figure out whether it always ends in a fatalError() or anything fancy like that.

I'm not sure whether a markTrivialInitCoderAsUnavailable rule is in SwiftFormat's purview, but I know I'd use it!

enhancement

All 10 comments

I can tackle this one if no one is working on it.

I am thinking of starting with something simpler like initCoderUnavailable. I would make it optional though. In my experience, whenever I started to add @available(*, unavailable) I was either only using programmatic views and/or migrating legacy xibs.

Let me know what you think.

@facumenzella That sounds awesome! Thanks for picking this up.

Re-reading this issue (which I had totally forgotten about :), I can't think of a situation when I would call init(coder:) directly, so your simpler approach makes sense to me.

@facumenzella sounds good - let me know if you need any pointers.

@nolanw implemented in 0.46.0.

Hi, this rule broke our iOS builds via Fastlane:

Components/AvatarMember.swift:15:14: error: cannot override 'init' which has been marked unavailable required init?(coder _: NSCoder) { ^ Components/AvatarView.swift:115:14: note: 'init(coder:)' has been explicitly marked unavailable here required init?(coder _: NSCoder) { ^ Components/AvatarSelf.swift:18:14: error: cannot override 'init' which has been marked unavailable required init?(coder _: NSCoder) { ^ Components/AvatarView.swift:115:14: note: 'init(coder:)' has been explicitly marked unavailable here required init?(coder _: NSCoder) { ^

We had a subclass of a UIView class that didn鈥檛 implement init(coder:), which was overridden in the subclass. It compiled previously before the unavailable directive was added, but broke because both this class and the superclass were marked as unavailable.

@ydnar so the issue arises when one class subclasses another that has overridden init?(coder _: NSCoder) with a stub implementation?

I'll look into adding a fix for that. In the meantime, adding:

// swiftformat:disable:next initCoderUnavailable

Just before the init?(coder _: NSCoder) should prevent the attribute being applied. Otherwise, you can disable the rule globally in your .swiftformat file or command-line args with --disable initCoderUnavailable

Thanks! We ended up removing the override from the subclass. The problem turned out to be ours, and swiftformat revealed it.

This rule was really special, since the availability is part of the interface and this rule wanted to change it.

I think formatter should never try to change the interface, this rule would be more appropriate to be a lint rule. We should remove it from this project.

I have an implementation of a base class like this:

public class Superclass: NSObject, NSSecureCoding {

    public class var supportsSecureCoding: Bool {
        true
    }

    override init() {}

    public func encode(with coder: NSCoder) {}

    // empty implementation, there is no fatalError() here
    public required init?(coder: NSCoder) {}
}

And I have also subclasses like this:

public final class Subclass: Superclass {

    override public static var supportsSecureCoding: Bool {
        true
    }

    override public init() {
        super.init()
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    override public func encode(with coder: NSCoder) {
        super.encode(with: coder)
    }
}

This rule breaks the build by adding @available(*, unavailable) to the Superclass' public required init?(coder: NSCoder).

@realf since you aren't actually using the required init in the superclass, you can remove the override in the subclass to fix the breakage. Ideally SwiftFormat would do that for you, but it's not currently possible.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shawnkoh picture shawnkoh  路  3Comments

MaxDesiatov picture MaxDesiatov  路  3Comments

PompeiaPaetenari picture PompeiaPaetenari  路  4Comments

Cyberbeni picture Cyberbeni  路  4Comments

DavinAhn picture DavinAhn  路  4Comments