14

I'm following a tutorial on Swift and I noticed that the author uses var instead of let when declaring an @IBOutlet variable. So I became curious as to why I can't use let instead since an object's properties are still mutable even if the object is constant or is this not the case?

The error Xcode shows when using let is

@IBOutlet attribute requires property to be mutable

but I'm confused because questionLabel is a UILabel object and not necessarily a property of an object. Or is the questionLabel object a property of the current viewController?

import UIKit

class ViewController: UIViewController {

    @IBOutlet let questionLabel: UILabel!

}

Thank you in advance if I'm over analyzing.

luk2302
  • 55,258
  • 23
  • 97
  • 137
Laurence Wingo
  • 3,912
  • 7
  • 33
  • 61
  • 5
    Start by understanding the difference between `let` and `var`: https://stackoverflow.com/questions/24002092/what-is-the-difference-between-let-and-var-in-swift – rmaddy Oct 23 '17 at 15:54
  • 9
    for pretty much the same reason it is an implicitly unwrapped optional. – luk2302 Oct 23 '17 at 16:00
  • 3
    Also related: https://stackoverflow.com/questions/27541993/iboutlets-and-ibactions-require-in-the-end – rmaddy Oct 23 '17 at 16:09

5 Answers5

18

The @IBOulet marked properties are generally properties of a ViewController that are connected using the interface builder. The view you create in the interface builder has to actually connect the interface elements in it to the properties during your actual application runtime.

For that reason it firstly creates a new ViewController using some init without connecting any interface elements. They only get connected at a later stage. For the runtime to be able to hook the properties up to the view elements after the object creation has completed they cannot be constants, they have to be mutable. Because they do not have a value after the initializer has finished they have to be optionals. And to not make using the properties cumbersome afterwards they are implicitly unwrapped optionals, so that you do not have to write label!.property but label.property suffices.

That is why your code crashes as soon as you try to do something with an IBOutlet variable which you failed to connect and that is also the reason why you cannot use / change / manipulate those fields in the initializer.

Regarding your actual var / let confusion. Yes, the object itself that is referenced using let can be changed, e.g. the text of a UILabel BUT the actual object reference cannot be changed. That would mean that if you do not give the constant a specific value in the initializer it would forever remain nil.

luk2302
  • 55,258
  • 23
  • 97
  • 137
7

For the simple reason that it is not assigned during initialization (in the initXXX methods) but later, when the view is being loaded.

The compiler actually cannot be even sure that the variable is ever assigned because the view loading is comletely dynamic.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
5

In swift, all vars and lets can be thought of as properties.

A property is immutable (a constant) if it's declared with let. It's mutable (a variable) if it's declared using the var keyword. That is the defining difference between let and var.

Outlets must be mutable because their value does not get set until after the object is initialized. (The view controller gets initialized and it's outlets don't get loaded right away.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I'm still going to work through the tutorial as I enjoy this conversation with everyone as well as yourself. Before I move on, I must ask just for learning purposes...will I still need to create initializers for my viewController or will it be usable as is by having IBOutlet on the properties and IBAction on the methods? – Laurence Wingo Oct 23 '17 at 16:22
  • 1
    You often don't need a custom initializer for your view controller subclass. Since the IBOutlet properties are declared as optionals they can be left as nil until the view loads. – Duncan C Oct 23 '17 at 20:06
2

You are right questionLabel is an object of type UILabel but used as a property of your class ViewController. That's why you have @IBOutlet attribute requires property to be mutable. If you use var you are saying that a property is mutable. If you use let you are saying that the property is immutable.

Try to create questionLabel without @IBOutletand see what's going on. Probably, you can put let in front.

Arrabidas92
  • 1,113
  • 1
  • 9
  • 20
1

First the ViewController is created, then the view tree is built. That means that when the ViewController finished it's init these views don't exist yet. They will be added just before viewDidLoad by parsing the XML-like data of the storyboard or XIB.

I know that it's Apple's default way of doing things but I would always write my outlets like:

@IBOutlet let questionLabel: UILabel?

For the simple reason it's absolutely not proven this label will really exist at run time. For example when reusing ViewControllers over multiple screens, changing the layout after setting the connections and so on this outlet might not be set. If you would use the questionLabel defined as UILabel! and it would be nil your application will crash. I don't think applications in production should ever crash over something silly like that.

For real application safety the only way to know really for sure it exists is to build your UI's in code. But the ease of use of Storyboards for one off screens is really tempting, I still use them a lot.

Lucas van Dongen
  • 9,328
  • 7
  • 39
  • 60
  • 5
    1. If you are accessing outlets before the outlets have been connected through the storyboard/xib then you have a bug in your code and it should crash. 2. If you are creating the label (or whatever UI component) via code, it shouldn't be marked as an outlet. And even if you do everything in code, it still should be declared with `!` and not `?` because my first point still applies. – rmaddy Oct 23 '17 at 16:14
  • 1
    Interesting, I like the idea of this approach because it seems to be a safer way of doing things. Can I ask, how do you keep track of how the interface should look and function as you do it programmatically? Are you using a sketchbook, zeplin, whiteboard, or what's your process like? – Laurence Wingo Oct 23 '17 at 16:17
  • 2
    Have to downvote because this is exactly the wrong approach. Firstly applications in production should crash if you as the programmer did something wrong. And they should crash as soon as possible: fail-fast / fail early. Treating every outlet as an actual optional will yield completely unreadable and useless code. Interface elements **have** to be present during the run. Using code to generate UI on the fly is even worse imho, but that is arguable, the first argument is not. – luk2302 Oct 23 '17 at 16:49
  • 1
    @rmaddy if you rather like to see things crash as a developer or tester so you know something's wrong early I can certainly understand. That's what assertion errors are for. But your clients are not testers and your AppStore zero stars reviews are not JIRA. If your application crashes in production because something stupid like making the line pink instead of blue crashes in some kind of scenario you didn't test you're not helping your customers nor your product. You are not infallible nor your testers. – Lucas van Dongen Oct 23 '17 at 18:09
  • 1
    @Q.A.Neuro.Tech honestly I usually just write the code, run it a few times to adjust. But I know from experience that certain things need to be 8px or 20px to look good. I think there is software that actually creates the code for you like Paintcode but I never used it. – Lucas van Dongen Oct 23 '17 at 18:16
  • 1
    @DepartamentoB An outlet that is not connected when it should be is an error. Even if your app does not crash, it will not behave correctly. Using `!` is exactly the assertion you are looking for. I have used the same approach for 3 years now and no crash ever got to production or even to testers. The reason being that simply opening the screen (which I do many times in development) would make the app crash. – Sulthan Oct 23 '17 at 18:20
  • 4
    Recently it happened to me that somebody merged changes to a xib and some outlets were no longer connected. With `?` I wouldn't notice easily. – Sulthan Oct 23 '17 at 18:24
  • @Sulthan so to make your life as a developer easier every mistake you do ship leads to a crashing application for your end users? Every time I've seen an error like that ship it was always something trivial that would maybe just a minor annoyance if it wouldn't have taken the whole app down instead. Honestly I think this comes from the Real Programmers syndrome, or maybe writing a `guard` statement with an `assertionFailure` and an proper exception logging seems like a ton of work for every `@IBOutlet ` check you need to do. But shipping crashes has nothing to do with professionalism. – Lucas van Dongen Oct 23 '17 at 18:43
  • I do not ship apps with crashes which is something you are trying to imply here. – Sulthan Oct 23 '17 at 19:47
  • I think there are simply different philosophies no right or wrong, nothing that would warrant a ton of down votes because the answer was leading innocent users into dark woods. But don't forget to think about me as soon as you do ship your Very First Bug! :) – Lucas van Dongen Oct 23 '17 at 20:05