Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getters and Setters in Swift - Does it make sense to use WillSet and DidSet instead?

I was doing some research about the reasons we should use Get and Set for our properties.

I've noticed 3 main reasons for it

  1. When you want to do/check something before you actually set the property
  2. When you want to have a property that you can only Get from it (maybe for security purposes I guess? ), or give it different access levels.
  3. Hiding the internal representation of the property while exposing a property using an alternative representation. (which for me doesn't make a lot of sense since i can access it on the wrong place using the Set function anyways)

The code below is a example of how you would implement Get and Set for properties in Swift, taking advantage of those 3 points I mentioned:

class Test
{
    private var _testSet:String!
    private var _testGetOnly:String
    var testSet:String{
        get{
            return _testSet
        }
        set{
            _testSet = newValue + "you forgot this string"
        }
    }
    var testGetOnly:String!{
        get{
            return _testGetOnly
        }
    }

    init(testSet:String, testGetOnly:String)
    {
        _testSet = testSet
        _testGetOnly = testGetOnly
    }
}

But this other example below also take advantage of those points mentioned but instead of using another computed property to return the private property value I just use the willSet and didSet observers

class Test
{
    var testGet:String {
        willSet{
            fatalError("Operation not allowed")
        }
    }
    var testWillSet:String!{
        didSet{
            self.testWillSet = self.testWillSet + "you forgot this string"
        }
    }
    init(testGet:String, testWillSet:String)
    {
        self.testGet = testGet
        self.testWillSet = testWillSet 
    }
}

So I'm curious to know what are the ADVANTAGES and DISADVANTAGES of each implementation.

Thanks in advance

like image 386
Henrique da Costa Avatar asked Aug 31 '16 16:08

Henrique da Costa


People also ask

What is difference between willSet and didSet in Swift?

willSet is called before the data is actually changed and it has a default constant newValue which shows the value that is going to be set. didSet is called right after the data is stored and it has a default constant oldValue which shows the previous value that is overwritten.

How do you avoid getters and setters?

Thus: you avoid getters and setters by thinking in terms of behavior, not in terms of state. Getters/setters manipulate state, from the "outside" (by doing avail = purse.

Which choice is an advantage of using getters and setters?

Getters and setters can speed up compilation. Getters and setters provide encapsulation of behavior. Getters and setters provide a debugging point for when a property changes at runtime.

Is didSet called in init?

willSet and didSet observers are not called when a property is first initialized. They are only called when the property's value is set outside of an initialization context.


1 Answers

Your question boils down to compile time vs. run time error. To address your 3 questions:

  1. Yes, willCheck is your only option here
  2. Readonly properties fall into 2 types: (a) those whose value derive from other properties, for example, their sum; and (b) those that you want to be able to change by yourself, but not by the users. The first type truly have no setter; the second type has a public getter and a private setter. The compiler can help you check for that and the program will not compile. If you throw a fatalError in didSet you get a runtime error and your application will crash.
  3. There can be state objects that you don't want the user to freely mess with, and yes, you can completely hide those from the users.

Your code first example was too verbose in defining the backing variables - you don't need to do that. To illustrate these points:

class Test
{
    // 1. Validate the new value
    var mustBeginWithA: String = "A word" {
        willSet {
            if !newValue.hasPrefix("A") {
                fatalError("This property must begin with the letter A")
            }
        }
    }

    // 2. A readonly property
    var x: Int = 1
    var y: Int = 2
    var total: Int {
        get { return x + y }
    }

    private(set) var greeting: String = "Hello world"
    func changeGreeting() {
        self.greeting = "Goodbye world" // Even for private property, you may still
                                        // want to set it, just not allowing the user
                                        // to do so
    }

    // 3. Hide implementation detail
    private var person = ["firstName": "", "lastName": ""]
    var firstName: String {
        get { return person["firstName"]! }
        set { person["firstName"] = newValue }
    }

    var lastName: String {
        get { return person["lastName"]! }
        set { person["lastName"] = newValue }
    }

    var fullName: String {
        get { return self.firstName + " " + self.lastName }
        set {
            let components = newValue.componentsSeparatedByString(" ")
            self.firstName = components[0]
            self.lastName = components[1]
        }
    }
}

Usage:

let t = Test()
t.mustBeginWithA = "Bee"        // runtime error

t.total = 30                    // Won't compile

t.greeting = "Goodbye world"    // Won't compile. The compiler does the check for you
                                // instead of a crash at run time

t.changeGreeting()              // OK, greeting now changed to "Goodbye world"

t.firstName = "John"            // Users have no idea that they are actually changing 
t.lastName = "Smith"            // a key in the dictionary and there's no way for them
                                // to access that dictionary

t.fullName = "Bart Simpsons"    // You do not want the user to change the full name
                                // without making a corresponding change in the
                                // firstName and lastName. With a custome setter, you
                                // can update both firstName and lastName to maintain
                                // consistency

A note about private in Swift 2 vs. Swift 3: if you try this in a Swift 2 playground, you will find t.greeting = "Goodbye world" works just fine. This is because Swift 2 has a strange access level specifier: private means "only accessible within the current file". Separate the class definition and the sample code into different files and Xcode will complain. In Swift 3, that was changed to fileprivate which is both clearer and save the private keyword for something more similar to to Java and .NET

like image 88
Code Different Avatar answered Sep 18 '22 05:09

Code Different