14

Quick question: why?


Context:

Am using Swinject dependency injection to add small-s singleton model classes to my views thus:

defaultContainer.registerForStoryboard( MyViewController.self )
{
    responder, viewController in
    viewController.proxy = responder.resolve( MyProxy.self )!
}

I want to guard against the risk of accidentally overwriting this singleton instance by using a computed property thus:

private var _proxy: MyProxy!

var proxy: MyProxy 
{ 
    set { if _proxy == nil { _proxy = newValue } } 
}

But. I can't. Because I must also declare a getter.

Frustrating!

For now I am using the following hack. But really...

var proxy: MyProxy?
{
    get { return nil }
    set { if _proxy == nil && newValue != nil { _proxy = newValue } }
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Joseph Beuys' Mum
  • 2,395
  • 2
  • 24
  • 50

3 Answers3

15

Quick answer for the quick question "Why?":

Once you add a get (or set) to a property, it turns into a Computed Property. Computed properties may be read-only, thus requiring only a getter, but they may not be write-only. So if you add a setter to a property, you turn it into a computer property hence you must add a getter as well.

EDIT: Following comments, Why can Computed Properties be read-only, but not write-only?

I can't find official documentation to back this up, so the following explanation is solely based on my logical point of view:

Computed Properties don't store actual data on their variables; instead they compute data to display.

var a:Int = 5 // Stored property
var timesTen:Int { // Computed Property
    get {
        return a * 10
    }
}

From the example above: a is a Stored property. Therefore, its get method automatically returns a's value. Now think of timesTen: What's its value? Since it doesn't store any value in timesTen variable, you must tell the computed property how and where to read its "value" from. So that's why you can't use a computed property for writing-only purposes.


Workaround / How to avoid it

For simple properties you may use didSet or willSet to achieve the desired purpose:

var proxy: MyProxy?
{
    willSet { if _proxy == nil && newValue != nil { _proxy = newValue } }
}

If you are using Swift 2.x, you may use the guard statement to for a cleaner code:

var proxy: MyProxy?
{
    willSet { 
         guard (_proxy == nil && newValue != nil) else { return }
         _proxy = newValue
    }
}

Another observation

If _proxy is not required to be private you may remove it completely, using only one variable/property instead of two:

var proxy: MyProxy!
{
    willSet { 
         guard (proxy == nil && newValue != nil) else { return }
         // By not preventing `willSet` to continue, `newValue` will automatically assigned to `proxy`
    }
}
mathielo
  • 6,725
  • 7
  • 50
  • 63
  • 1
    "Computed properties ... may _not_ be write-only", but _why_? That's what OP is asking, IMO. – 0x416e746f6e Jan 08 '16 at 13:22
  • Thank you @mathielo. Also: yes Anton Bronnikov I am still interested in the theory behind making computed properties not-write-only, but more importantly I have programming to get on with, and mathielo's answer has practical benefit. Thank you to you both. – Joseph Beuys' Mum Jan 08 '16 at 14:07
  • @TheMotherofJosephBeuys updated the answer adding further details, please check it out. – mathielo Jan 08 '16 at 17:08
  • @AntonBronnikov good point, I've updated the answer adding further explanation. – mathielo Jan 08 '16 at 17:09
  • @TheMotherofJosephBeuys, oh, if I only knew you would accept an answer why you shouldn't do it :) – 0x416e746f6e Jan 08 '16 at 21:53
1

Why cant you just return _proxy in your getter? What does that matter? If you are afraid of exposing your getter you can set your getter to private, thus exposing it only in the containing class.

private(get) var proxy: MyProxy? {
  set { if _proxy == nil && newValue != nil { _proxy = newValue } }
}
tskulbru
  • 2,336
  • 1
  • 20
  • 38
0

At the moment you can only use property observer didSet or willSet

Andrew
  • 885
  • 1
  • 10
  • 15