简体   繁体   中英

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM