8
devios1
7y

I can't stand Swift's initializers. No other languages have the problem with constructors/initializers that Swift does. It's a complete failure of a feature and to hell with safety if it comes with this cost.

Just to illustrate how ridiculous it gets, I want to have a class where my initialization logic can be split among reusable parts. That is, the logic that initializes the class with no parameters has logic that I want to reuse in my other initializers. Simple DRY stuff. Well, the only way I can do that in Swift is if I use a convenience initializer that calls another one. But convenience initializers have completely different rules from designated initializers (again, something only Swift does).

For example, you can't access "self" until you call a designated initializer. You can't chain designated initializers, and if you want to chain anything in the same class you have to handcuff yourself by using a "convenience" initializer (there's nothing convenient about them, I might add).

So now I want to subclass my class and initialize myself using one of my superclass initializers. Oh but the one I want to call is a *convenience* initializer so I can't, unless I turn my new initializer into a convenience initializer. Except wait, a convenience initializer must delegate with self.init(), so it can't even call a superclass initializer!

And it just goes round and round and round. I don't know if I should try to convert all of my initializers to convenience initializers or the other way around.

Why all this nonsensical madness? Get rid of the distinction and go back to nice clean powerful initializers like Objective-C. I mean what does it have to take? This is a complete nightmare.

Comments
  • 1
    It's gotten to the point where I am just writing a complete separate set of chaining functions that I call *from* the initializers, because Swift just won't let me do it inside the initializers themselves. It's just sheer madness.
  • 0
    Omg it gets worse. Turns out I *can't* delegate my initialization logic to functions because you aren't allowed to call a function on self from an initializer until after you call super.init, but super.init can't be called until all of the object's properties are initialized. 😒 Yup.
  • 0
    It's like Swift is trying its DAMNEDEST to get me to violate DRY, my most sacred of mantras.
  • 0
    "a convenience initializer must delegate with self.init, so it can't even call a superclass initializer"
    I think this is false. self.init can be referring to a superclass initializer, as long as the parameters line up (like normal) and it's not overridden in the current scope (i.e that class)
  • 0
    A hacky (but DRY) solution: write a single designated initializer (A), have it take some random param (i.e so it's never called externally) then have your empty param initializer be a convenience initializer (B).

    Have (A) contain all your reused logic, i.e the stuff you want to DRY up. Have all your other initializers be convenience, unless you need to not call the logic in (A). Then write a new designated initializer with another random param or set of parameters and use that.
  • 0
    Also, the idea surrounding designated vs convenience initializers is to dry up your code. You only send in the properties you need to set to various initializers and call the designated ones to set the base required properties, and use them, while using convenience ones for edge cases.

    Let me know if I'm wildly off about anything, never written in Swift.
  • 0
    @Nevoic One problem with writing one catch-all designated initializer and a bunch of convenience initializers is that any subclasses you write can only call up to the designated initializers, meaning all those convenience initializers are conveniently inaccessible.

    The message of "a convenience initializer must delegate with self.init()" was pulled right from a compiler error. Those are in the compiler's words.

    Finally, while the intention may have been to allow for dry code, in practice it just means that the language restricts more of what you are allowed to do, forcing you down a single path that may not apply to every situation, and it's just another example of the thing I find most maddening about coding in Swift: that the language knows best and what the developer wants is secondary.

    I prefer languages to be like tools: a base set of capabilities that I can combine in novel ways to achieve new things. With Swift I feel as though I'm always negotiating and compromising.
  • 0
    About the delegation thing, I wasn't arguing that the compiler didn't output that. It's just that, if I understand correctly, you can call self.init and have it refer to a super initializer (if you don't have a matching in class definition). Lmk if I'm wrong, I'm honestly curious.
  • 0
    @Nevoic You can call self.init if you're calling it from a convenience initializer (so they can chain together in the same class) so long as eventually it calls a designated initializer on the same class, but you can't call super.init from a convenience initializer, only a designated initializer, and even then it can only call *designated* initializers on the superclass. It's all a horribly twisted mess and I've yet to write even a simple class whose initializers didn't completely trip me up because of these rules.
  • 0
    What if you do self.init(some_string) (in a convenience initializer) and there's no initializer (any kind) on that class that takes a string, but there is one on the parent class?

    I was wondering if in that specific scenario, the self.init would delegate upwards.
  • 0
    I saw something online that implied it would, that's my point of confusion.
  • 0
    …but a designated initializer can't defer to self.init, only super.init, and only (as I mentioned) a designated initializer on super. So while convenience initializers can chain together, designated initializers can't, but convenience initializers can only be called on the same class, *unless* the subclass re-implements ALL of the designated initializers from its parent.

    And I haven't even mentioned required initializers yet. These are designated initializers that subclasses are *forced* to implement, even if they don't make any sense in the subclass. Swift actually forces you to stub them out with a fatalError call that aborts at runtime if something were to call one.

    Are you starting to see what I'm getting at?
  • 1
    @Nevoic To answer your question, Swift doesn't inherit initializers like every other language on the planet. If you want to initialize a subclass with a string, you HAVE to reimplement the initializer on the subclass, even if all it does is call super.init. (Just pray in that case that the super initializer is a designated initializer and not a convenience initializer otherwise you will be conveniently hosed.)

    Only if a class doesn't define ANY designated initializers will it implicitly inherit all of its parent class's designated initializers. …meaning if you want to add even just one new initializer, you’ll have to reimplement every single designated initializer of the superclass.
Add Comment