[Pitch] Setup Block


(Andrew Arnopoulos) #1

Hello Swift Community,

To start, I am new to the mail list so please forgive me for any faux pas I may have committed. In addition, please excuse me if this feature has already been proposed.

I have noticed, especially with UIKit classes, that I am consistently overriding initializers in order to call a setup function. In order to keep the class consistent I have to write a bunch of boilerplate code and this becomes more of a problem when the number of initializers increase. The code below is a simple example of some of the boilerplate needed to write a subclass of UIButton.

class MyButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    private func setup() {
        // Setup for custom class
    }
}

What I am proposing is a setup block that is called after every non-convience initializer. The code might look something like the code this:

class MyButton: UIButton {

    convenience init() {
        self.init(frame: CGRect.zero)
    }

    setup {
        // Setup for custom class
    }
}

Every subclass would have a setup block that would be called after ever initializer returns, that includes subsequent calls to super class initializers. So, a subclass would have a setup block that is called after the setup block for the super class is called.

I’m not sure what the best way to implement this would be and I do not have intimate knowledge of swift’s compilation process. However, if I were to hack it together I would create an intermediate representation that would create something like the following:

class MyButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        defer {
            setup()
        }
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        defer {
            setup()
        }
    }

    // This should be named something that does not conflict with function names that are already defined
    private func setup() {
        // Setup for custom class
    }
}

The defer block is not necessary but it does add an addition layer of redundancy and makes the feature more resilient to change. You should also take note that setup is not called within the convenience initializer because setup will be called within the non-convenience initializer.

Thank you everyone for your time, I look forward to hearing everyones feedback.

-Andrew Arnopoulos


(Derrick Ho) #2

You could try awakeFromNib which is called when interface builder creates
the view.

But if you are instantiating it programmatically, the closest thing would
be drawRect. It calls multiple times so you may need to add logic to ensure
that it only happens once.

I think a "didFinishInitialing" method would be a decent addition to the
uikit framework but it doesn't reduce much boilerplate code.

···

On Sat, Nov 19, 2016 at 6:28 PM Andrew Arnopoulos via swift-evolution < swift-evolution@swift.org> wrote:

Hello Swift Community,

To start, I am new to the mail list so please forgive me for any faux pas
I may have committed. In addition, please excuse me if this feature has
already been proposed.

I have noticed, especially with UIKit classes, that I am consistently
overriding initializers in order to call a setup function. In order to keep
the class consistent I have to write a bunch of boilerplate code and this
becomes more of a problem when the number of initializers increase. The
code below is a simple example of some of the boilerplate needed to write a
subclass of UIButton.

class MyButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }

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

    private func setup() {
        // Setup for custom class
    }
}

What I am proposing is a setup block that is called after every
non-convience initializer. The code might look something like the code this:

class MyButton: UIButton {

    convenience init() {
        self.init(frame: CGRect.zero)
    }

    setup {
        // Setup for custom class
    }
}

Every subclass would have a setup block that would be called after ever
initializer returns, that includes subsequent calls to super class
initializers. So, a subclass would have a setup block that is called after
the setup block for the super class is called.

I’m not sure what the best way to implement this would be and I do not
have intimate knowledge of swift’s compilation process. However, if I were
to hack it together I would create an intermediate representation that
would create something like the following:

class MyButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        defer {
            setup()
        }
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        defer {
            setup()
        }
    }

    // This should be named something that does not conflict
with function names that are already defined
    private func setup() {
        // Setup for custom class
    }
}

The defer block is not necessary but it does add an addition layer of
redundancy and makes the feature more resilient to change. You should also
take note that setup is not called within the convenience initializer
because setup will be called within the non-convenience initializer.

Thank you everyone for your time, I look forward to hearing everyones
feedback.

-Andrew Arnopoulos
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tino) #3

Initialization is already quite complex in Swift, so I'm very wary of adding new features and prefer Derricks idea over a new language feature.
I hope Swift is still flexible enough for deep changes in this area — I guess enforcing that there is always a single designated initializer would not only improve on you usecase.


(Jay) #4

Hi Andrew,

There is a similar discussion (in that it solves some aspects of the same
problem) going on at the moment with the subject "Add code to super
methods" - check it out. The parts of this problem it doesn't address is
that you would still have to override multiple init methods to ensure your
common setup was called regardless of which initialiser is used. However,
your idea of a post-init call might solve some of of the other issues being
discussed there and affect that discussion quite a bit. In other words, I
think these two ideas might benefit from being considered together rather
than as separate issues.

···

On Sun, 20 Nov 2016 at 09:58 Tino Heth via swift-evolution < swift-evolution@swift.org> wrote:

Initialization is already quite complex in Swift, so I'm very wary of
adding new features and prefer Derricks idea over a new language feature.
I hope Swift is still flexible enough for deep changes in this area — I
guess enforcing that there is always a single designated initializer would
not only improve on you usecase.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrew Arnopoulos) #5

I agree that this discussion would probably be benefit from being included in the "Add code to super methods” discussion. The one thing that worries me about the pitch is that it might muck up calling semantics. For example:

class Person {
    let firstName: String
    let lastName: String
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    
    func printInformation() {
        print("\(firstName) \(lastName)")
    }
}

class Employee: Person {
    let id: String
    init(firstName: String, lastName: String, id: String) {
        self.id = id
        super.init(firstName: firstName, lastName: lastName)
    }
    
    override(before) func printInformation() {
        print("Employee \(id)")
    }
}

class PartTimeEmployee: Employee {
    let workdays: [Day]
    init(firstName: String, lastName: String, id: String, workdays: Day) {
        self.workdays = workdays
        super.init(firstName: firstName, lastName: lastName, id: id)
    }
    
    override(before) func printInformation() {
        print("Workdays: \(workdays)")
    }
}

If someone instantiates a PartTimeEmployee and calls printInformation how do the calling semantics work? Is PartTimeEmployee called before Employee or is it the other way around? Also, what happens when after or before isn’t used in a superclass that is a subclass of something else? When is before or after called in that situation?

Otherwise, I think the proposals are very similar and would benefit from being together both from a proposal and implementation stand point.

-Andrew

···

On Nov 20, 2016, at 10:01 AM, Jay Abbott via swift-evolution <swift-evolution@swift.org> wrote:

Hi Andrew,

There is a similar discussion (in that it solves some aspects of the same problem) going on at the moment with the subject "Add code to super methods" - check it out. The parts of this problem it doesn't address is that you would still have to override multiple init methods to ensure your common setup was called regardless of which initialiser is used. However, your idea of a post-init call might solve some of of the other issues being discussed there and affect that discussion quite a bit. In other words, I think these two ideas might benefit from being considered together rather than as separate issues.

On Sun, 20 Nov 2016 at 09:58 Tino Heth via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Initialization is already quite complex in Swift, so I'm very wary of adding new features and prefer Derricks idea over a new language feature.
I hope Swift is still flexible enough for deep changes in this area — I guess enforcing that there is always a single designated initializer would not only improve on you usecase.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution