artemchen
artemchen

Reputation: 169

Swift class with no initializers but with property observer

I've just started learning swift and have a question about class initializers. Here is my sample code:

struct Resolution {
  var width = 0
  var height = 0
}

let someResolution = Resolution()

class TV {
  var res: Resolution! {
    didSet {
      res.width = 1
      res.height = 1
    }
  }
}

var television = TV()
var resolution = Resolution()
television.res = resolution

I'm trying to use property observer. When I implicitly unwrap property observer type which is struct, everything works. Otherwise, it says "class has no initializers. I'm confused why should I implicitly unwrap struct. Thanks in advance!

Upvotes: 1

Views: 1887

Answers (2)

Paul Cantrell
Paul Cantrell

Reputation: 9324

It’s not entirely clear from your question, but I assume you are asking why you can’t replace this:

var res: Resolution! {

…with this:

var res: Resolution {

Your clue comes from the full compiler error message, which you can see by looking in the compiler logs (or the playground console if you’re in a playground):

error: class 'TV' has no initializers
class TV {
      ^
note: stored property 'res' without initial value prevents synthesized initializers
  var res: Resolution {
      ^
error: 'TV' cannot be constructed because it has no accessible initializers
var television = TV()

When the type of the property is Resolution!, you’re saying, “it can be nil, but if anybody tries to read it while it’s nil, then that’s a terrible calamity and my whole app should crash.” So Swift initializes TV with res set to nil, and expects you to set it to something else right away.

However, when the type of the property is Resolution, you’re saying, “it cannot ever be nil, not ever, not even when nobody is looking.” That means that res must have a value from the moment that TV is instantiated. But what should that value be? There’s no default to set it to. You have two options if the type is Resolution:

  1. Set it to some default value using a property initializer (which is what it looks like you’re trying to do):

     class TV {
       var res: Resolution = Resolution(width: 1, height: 1)
     }
    

    …or with an initializer, you can let Swift infer the type:

     class TV {
       var res = Resolution(width: 1, height: 1)
     }
    
  2. Ask whoever’s creating the TV to specify the initial value when instantiating the class:

    class TV {
      init(res: Resolution) {
        self.res = res
      }
    
      var res: Resolution {
        didSet {
          res.width = 1
          res.height = 1
        }
      }
    }
    
    var resolution = Resolution()
    var television = TV(res: resolution)
    

Aside: all of this makes absolutely no sense, because you’re changing width & height to 1 immediately after the resolution is set (what?!), but it does compile.

So that “synthesized initializers” message from the compiler? Swift will automatically make an empty init() for your classes that does nothing, but not if there are any non-optional properties to be initialized. If there are, you have to write your own initializer.

(Note that if TV were a struct, then Swift would generate that init(res:) for you. It does with structs, but not with classes.)

A third option is to make the type Resolution?:

class TV {
  var res: Resolution? {
    didSet {
      res?.width = 1
      res?.height = 1
    }
  }
}

That’s a better choice than Resolution!, which is just a crash waiting to happen. Implicitly unwrapped optionals should only be a last resort, something to use in very specific, carefully considered circumstances.

Upvotes: 2

matt
matt

Reputation: 535304

Every instance variable must have a value by the end of initialization time.

  • If you don't use an Optional, then you must supply an actual Resolution instance here as the value of res — and you are not doing that.

  • If you do use an Optional, the compiler supplies nil as the value, and so the need for a value is satisfied.

One possibility is to write your class like this:

class TV { var res = Resolution() { didSet { res.width = 1 res.height = 1 } } }

Now res has an initial value: namely, an actual Resolution instance.

But what you are doing is not wrong. It is quite common to use an Optional when you know that cannot provide a value for an instance variable at initialization time, but you intend to provide one soon after.

Upvotes: 0

Related Questions