5

I have the following code (EDIT: Updated the code so everyone can compile it and see):

import UIKit

struct Action
{
    let text: String
    let handler: (() -> Void)?
}

class AlertView : UIView
{
    init(actions: [Action]) {
        super.init(frame: .zero)

        for action in actions {
//            let actionButton = ActionButton(type: .custom)
//            actionButton.title = action.title
//            actionButton.handler = action.handler
//            addSubview(actionButton)
        }
    }

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

class TextAlertView : AlertView
{
    init() {
        super.init(actions: [
            Action(text: "No", handler: nil),
            Action(text: "Yes", handler: { [weak self] in
                //use self in here..
            })
        ])
    }

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

class MyViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let alert = TextAlertView()
        view.addSubview(alert)
        self.view = view
    }
}

Everytime I instantiate TextAlertView, it crashes on super.init with bad access. However, if I change:

Action(title: "Yes", { [weak self] in
    //use self in here..
})

to:

Action(title: "Yes", {
    //Blank.. doesn't reference `self` in any way (weak, unowned, etc)
})

it works!

Is there a way to reference self be it weak or not inside the action block during a super initialization (in the above I do it in a parameter to super.init?

The code compiles.. it just crashes at runtime at random.

Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • Could it be because Action is a struct? – Mike Taverne Feb 26 '18 at 20:29
  • @MikeTaverne; I just tried making it a `class` instead of `struct`.. Same issue. I updated the code so we can compile it via Playground or regular app and see. – Brandon Feb 26 '18 at 20:35
  • 3
    That's a pretty horrible bug, you shouldn't be able to capture `self` before calling `super.init` – it is fixed in the latest Swift 4.1 snapshot though, you'll get the expected "'self' used before 'super.init' call" error. – Hamish Feb 26 '18 at 21:36

2 Answers2

8

Short answer:

You cannot capture and use self as a value before super.init returns. In your case, you are trying to "pass" self to super.init as an argument.

As per why the second part works, simply because without using self in it, it does not capture self, thus it does not use self as a value.

If you don't want to use self in the closure, then you don't need to worry about strong/weak reference there, because there is no reference to self there at all (since it was not captured). No danger of retain cycle.


Short sidenote about "using self as a value" - you can use self on the left-hand side of an assignment to refer to properties of the self when initializing them:

let myProperty: String

init(with myProperty: String) {
    // this usage of self is allowed
    self.myProperty = myProperty
    super.init(nibName: nil, bundle: nil)
}

Longer answer with references and stuff:

As per documentation:

Safety check 4

An initializer cannot call any instance methods, read the values of any instance properties, or refer to self as a value until after the first phase of initialization is complete.

First phase of initialization is ended by calling super.init, when the

From the same documentation:

Phase 1

A designated or convenience initializer is called on a class.

Memory for a new instance of that class is allocated. The memory is not yet initialized.

A designated initializer for that class confirms that all stored properties introduced by that class have a value. The memory for these stored properties is now initialized.

The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.

This continues up the class inheritance chain until the top of the chain is reached.

Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance’s memory is considered to be fully initialized, and phase 1 is complete.

So only after calling super.init you are allowed to use self as value:

Phase 2

Working back down from the top of the chain, each designated initializer in the chain has the option to customize the instance further. Initializers are now able to access self and can modify its properties, call its instance methods, and so on.

Finally, any convenience initializers in the chain have the option to customize the instance and to work with self.

Now I am not surprised at all that when you try to use self as a value in a capture list of the closure, that it crashes. I am more surprised that the compiler does allow you to do it - now I guess it's an edge case for which error handling wasn't implemented.

In the second case:

Action(title: "Yes", {
    //Blank.. doesn't reference `self` in any way (weak, unowned, etc)
})

You don't really capture self, that's why it is allowed and it works. But you don't have access to self there. Try to add there some code that uses self and the compiler will complain:

enter image description here

So in the end, if you want to use self in the closure, you will have to find a way how to first call super.init and only after that add self capturing closures to the properties.

Community
  • 1
  • 1
Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • I realize why the second case worked (as per the comment in the code). What I did not realize, is that the capturing inside the first case's block was not allowed or why the compiler allowed it to even compile. – Brandon Feb 26 '18 at 20:46
  • @Brandon well.. then it was really my assumption that was wrong :D.. anyway, now you know – Milan Nosáľ Feb 26 '18 at 20:47
  • @Milan I was referring to your comment: “Try to add there some code that uses self and the compiler will complain.” I assume when the compiler complains, it means the code won’t compile. – Mike Taverne Feb 26 '18 at 20:50
  • @MikeTaverne oh.. note that OP did not use `self` there, that's why it was compiling for him.. I added a screenshot showing that the xcode indeed complains – Milan Nosáľ Feb 26 '18 at 20:52
  • @MikeTaverne sorry for misunderstanding – Milan Nosáľ Feb 26 '18 at 20:53
  • @Brandon if you did not want to use `self` in the closure, then you do not have to worry about retain cycle, as I mentioned, in the case that compiles and works the `self` is not captured, thus there is no reference from closure to `self` – Milan Nosáľ Feb 26 '18 at 21:04
  • @MilanNosáľ; I wanted to use self in the closure so I had put `weak self` instead and saw that it compiled and got hit with the nasty surprise later on lol. – Brandon Feb 26 '18 at 21:24
2

You can work around this by using the following construct:

class TextAlertView : AlertView
{
    init() {
        weak var weakSelf: TextAlertView?
        super.init(actions: [
            Action(text: "No", handler: nil),
            Action(text: "Yes", handler: {
                guard let self = weakSelf else { return }
                // Now you can just use self as normal
            })
        ])
        weakSelf = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}        
Werner Altewischer
  • 10,080
  • 4
  • 53
  • 60