Introducing Post Init

Note: I'm not sure if this issue affects all swift developers. This issue occurs for me very often when creating subclasses of UIKit components.

I'm missing the possibility to execute code directly after init was called. A lot of my custom UIKit look like this:

class CustomCell: UITableViewCell {
    public convenience init() {
        self.init(frame: .zero)
    }

    public override init(frame: CGRect) {
        super.init(frame: frame)
        setUpUI()
    }

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

    private func setUpUI() {
        // some post init setup code
        // Adding layout constraints could be done here,
        // since it requires the members to be initialized
        // and we want to avoid redundant code
    }
}

If there was a post init block there would be no need to to override the inits at all. In cases where some code is needed inside the init it would still provide a pattern to do so. So it would be consistent.

With post initialization bocks the code above would look like this:

class CustomCell: UITableViewCell {
    postInit { // similar to deinit
        // some post init setup code
        // Adding layout constraints could be done here,
        // since it requires the members to be initialized
        // and we want to avoid redundant code
    }
}

Since convenience inits always call another init they do not need to call the post init code. Designated inits will always call the post init code. The compiler could just copy the post init code to every every designated init or put a function call there.

In the Java enterprise environment the usage of PostConstruct annotations is common. Probably there're more use cases for server side swift. What do you think? Is this to specific or has to few use cases to add it to swift?

2 Likes

-1
Imagine debugging headaches this would cause.

class Foo: UIView {
  init(backgroundColor: UIColor) {
    super.init()
   self.backgroundColor = backgroundColor
}

class Bar: Foo {
  postInit {
    self.backgroundColor = .blue
  }
}

Then someone else tries to call Bar(backgroundColor: .red) and gets a view with a wrong color. There is a bug, so he tries to debug it by looking up implementation of init(backgroundColor:), but it looks fine. He sets up a breakpoint in the init, prints the background color with the debugger and it still looks fine. He runs his program, and it is broken!

2 Likes

Imho the root of the problem is that there are several independent inits... I guess this is part of the Objective-C heritage, and if you can avoid it, I always do.

3 Likes

This can be easily achieved otherwise:

class MyView: UIView {
	
	private lazy var _innerInit: Void = {
		self.backgroundColor = .red
	}()
	
	override func willMove(toSuperview newSuperview: UIView?) {
		super.willMove(toSuperview: newSuperview)
		
		_innerInit
	}
	
}

The lazy var will guarantee that the _innerInit is executed just once.

That might be true, but the issue there is that the implementation of Bar is wrong. So I don't know if that really applies since you have done the implementation by yourself. So I guess you would have a look at the class you are using. You can already do much worse stuff with the willSet and didSet, if you want to. You could compare this behavior to UIViewControllers in UIKit and I think you would definitely have a look at the viewWillAppear etc of the viewControllers. Since you know the lifecycle.

It would be straight forward:
UIView.init() -> UIView.postInit -> Foo.init(backgroundColor:) -> Foo.postInit -> Bar.init(backgroundColor) -> Bar.postInit

The language would guarantee a specific execution, so it is probably easier to debug. However I understand the argument that knowledge is required to deal with this kind of bugs.

This guarantees that it will be executed just once. But not when. Or if it will be executed at all.

This is why it gets called in willMove(toSuperview:) which is always called before the view is added or removed from the view hierarchy. This is often what one needs...

Bugs happen. To prevent bugs like these you would have to:

  1. Every time you write postInit you check every init of every superclass
  2. Every time you add/modify init in Foo you check every subclass of Foo in your project.

You can already do much worse stuff with the willSet and didSet, if you want to

Not really. If I see that something weird is happening in the line spam.eggs = 123 I can cmd+click to the definition of var eggs and clearly see that there is didSet in there

you would definitely have a look at the viewWillAppear etc

We shouldn't try to make debugging init worse, just because debugging UIKit lifecycle is just as bad.

This is just specific to UIViewSubclasses. I probably should have chosen a non UIKit specific example.

I definitely agree. Bugs happen all the time. But keep in mind that cmd+click is an IDE feature. The same could be implemented for postInits. cmd+click could show all classes inits and postInits in the list.

I get your point and I agree to some degree, but this is about swift and not IDEs.

I might've as well said "go to the definition of var eggs" :)
My point is that I like behavior of X is in the definition of X, and there is no spooky action at a distance. I think the only example of spooky action at a distance right now is defer, which is written pretty close it's execution.

@edit
and deinit, but that one is necessary

1 Like

-1
This is a UIKit problem, and we shouldn't be tailoring Swift to address legacy design decisions that were implemented with another programming language in mind.

5 Likes
Terms of Service

Privacy Policy

Cookie Policy