3

Why in earlier when we invoke self in a computed property like this example we would need to write lazy var but now we don't have to. why?

   let(lazy var in earlier times) pauseButton: UIButton = {
    let button = UIButton(type: .system)
    let image = UIImage(named: "pause")
    button.setImage(image, for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.tintColor = .white
    button.addTarget(self, action: #selector(handlePause), for: .touchUpInside)

    return button
    }()
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
Ninja
  • 309
  • 7
  • 26
  • You still do, I just pasted your code into Xcode 9 wrapped around a class and it doesn't compile because it can't find `self`. – Bruno Rocha Oct 17 '17 at 18:02
  • Just to make sure that I got it right, you mean `self.handlePause` is it correct? – Ahmad F Oct 17 '17 at 18:04
  • @BrunoRocha I justed pasted this code snippet to Xcode 9, it works fine for me. – Ahmad F Oct 17 '17 at 18:05
  • Where is this code located? Unless this code is being called inside an instance method/property, referencing `self` like this is impossible because this property will be created before the class itself. If this is at the class's scope, are you sure it isn't just Xcode screwing up? I saw it happen a few times with this case... – Bruno Rocha Oct 17 '17 at 18:17
  • @BrunoRocha I assume that what are you saying is right, this is what mentioned in Swift documentation: "If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods."; Actually this kind of confusing to me now :) – Ahmad F Oct 17 '17 at 18:21
  • see [Are lazy vars in Swift computed more than once?](https://stackoverflow.com/q/26454812/5175709) – mfaani Oct 17 '17 at 19:35
  • @AhmadF I was able to reproduce it now! I was getting the error because I had a pure class. By conforming it to NSObject (UIViewController) in your case I was able to make it compile. I have no idea why though... – Bruno Rocha Oct 17 '17 at 19:49

2 Answers2

6

I think there is a misunderstanding, which is what you mentioned in the code snippet is not a computed property! it is just a stored property which has been initialized by a closure; As mentioned in the Swift Initialization - Setting a Default Property Value with a Closure or Function:

If a stored property’s default value requires some customization or setup, you can use a closure or global function to provide a customized default value for that property. Whenever a new instance of the type that the property belongs to is initialized, the closure or function is called, and its return value is assigned as the property’s default value.

You could check: Difference between computed property and property set with closure.

Note that the closure of pauseButton will be executed without even using it, if you tried to check it (add a breakpoint in it), you will notice that. I assume this is not what are your expecting -and not what are you aiming to-, so you should declare it as lazy var instead of let.

However,

Referring to the same Swift documentation:

If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.

Implying that:

class MyViewController: UIViewController {
    let btnTitle = "pause"

    let pauseButton: UIButton = {
        let button = UIButton(type: .system)
        let image = UIImage(named: btnTitle)
        button.setImage(image, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.tintColor = .white
        button.addTarget(self, action: #selector(handlePause), for: .touchUpInside)

        return button
    }()

    func handlePause() { }
}

Will gives an error on the let image = UIImage(named: btnTitle):

enter image description here

That should also be applicable for any other instance member, for instance, if you would try to add view.addSubview(button) into the closure, you will get the same error for view instance member.

But for a reason (I have no idea why), working with selectors seems to be a special case, because button.addTarget(self, action: #selector(handlePause), for: .touchUpInside) worked fine for me (Xcode 9.0), nevertheless if you tried to add self to it, as:

button.addTarget(self, action: #selector(self.handlePause), for: .touchUpInside)

you would get the following error:

enter image description here

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • You are amazing) but it still opaque, but more clear 'opaque') well maybe we can't use self.handlePause selector because (of course) the rest instance has not been initialized (even functions somehow do not maybe created or wrote to machine code or smith else) , nonetheless we can mention self in our addTarget method inside closure of stored property. I think(maybe it is wrong) class don't have to be initialized while it is used by addTarget method as target. Class can be aware that it is target without initialize its properties) maybe that?) – Ninja Oct 17 '17 at 22:04
0

If you use button.addTarget in a regular stored property, you won't get a compile-time error. But I'm pretty sure it is a bug. I experimentally realize that selectors in regular stored properties causes unpredictable results in iOS versions 14.2 and higher. The selector may be released or any other selector that is given in the stored property closure may be associated with the button. As result, tapping on a button may trigger an action that is intended to be triggered by another button.

To do not tussle with such issues, I stick to the old way and use button.addTarget only in lazy stored properties.

Dorukhan Arslan
  • 2,676
  • 2
  • 24
  • 42