6

There are a cases where I might want to model data where it makes sense for a value to be restricted to a given range.

For example, if I want to represent a "mammal", I might want to restrict a legs property to 0–4.

My first attempt is shown below:

class Mammal {
    var _numLegs:Int?

    var numLegs:Int {
    get {
        return _numLegs!
    }
    set {
        if 0...4 ~= newValue {
            self._numLegs = newValue
        }
        else {
            self._numLegs = nil
        }
    }
    }
}

However, this seems unsatisfactory since all properties are "public" there is nothing stopping the customer of the class from setting Mammal._numLegs to some arbitrary value.

Any better ways to do it?

dertkw
  • 7,798
  • 5
  • 37
  • 45
j b
  • 5,147
  • 5
  • 41
  • 60
  • I'm not sure in swift, but in objective-C, doing Mammal.numLegs = 5 called the setter (if you have a property with a custom setter). – Lord Zsolt Jun 13 '14 at 18:19
  • 7
    rather than underscore you should use something much harder to type like: – Grady Player Jun 13 '14 at 18:23
  • 3
    In all seriousness, you can use a `didSet` property observer and check if its out of bounds there – Jack Jun 13 '14 at 18:24
  • 3
    As [this question](http://stackoverflow.com/questions/24003918/does-swift-have-access-modifiers) states, there *will be* access modifiers in Swift eventually. It's just a beta now. So I think that ignoring the fact that someone can access fields that should be private is OK for now. – FreeNickname Jun 13 '14 at 18:29
  • 1
    I agree with @FreeNickname, waiting until access modifiers are available is probably the best solution. Maybe just add a `FIXME` or similar that reminds you to revisit it in your code later. – Craig Otis Jun 13 '14 at 18:33
  • @JackWu `didSet` looks spot on as it avoids the need for an intermediate property. Do you want to make the comment into an answer so I can accept? – j b Jun 13 '14 at 18:56
  • Another option is to use a protocol, like so: http://devblog.reverb.com/post/88673812266/private-methods-and-properties-in-swift – Aaron Brager Jun 13 '14 at 19:19
  • "However, this seems unsatisfactory since all properties are public there is nothing stopping the customer of the class from setting Mammal._numLegs to some arbitrary value." It's not your job to stop people from using your code. Perhaps 20 years down the line someone will need to fiddle with Mammal._numlegs. You just document the official public API and anyone who does anything else does it at their own risk. – alcalde Jun 13 '14 at 20:07

2 Answers2

8

Just for fun I decided to write a snippet with @jackWu's suggestion (+1) in order to try that didSet thing:

class Mammal {
    var numLegs:UInt? {
        didSet { if numLegs? > 4 { numLegs = nil } }
    }

    init() {
        numLegs = nil
    }
}

It works perfectly. As soon as you try to set numLegs to 5 or greater, boom, it gets nilled automatically. Please note that I used Uint to avoid negative leg quantities :)

I really like the elegance of didSet.

Jean Le Moignan
  • 22,158
  • 3
  • 31
  • 37
  • @jackWu you posted while I was writing. Let me know if you want me to delete my answer. – Jean Le Moignan Jun 13 '14 at 19:13
  • 1
    @JackWu I've accepted @JeanLeMoignan's answer because the use of `UInt` and the optional seems a bit more elegant and readable. Thanks for pointing out `didSet` – j b Jun 13 '14 at 20:11
  • 1
    No problem, I do like this answer. I didn't use the optional because your original code had it as non-optional. Karma is not as important as a good answer :] – Jack Jun 13 '14 at 21:28
  • Since you guys seem to like `didSet` so much, I'd just like to throw in here that `willSet` is available too! Also, inside `didSet` you can access the previous value with `oldValue`. And you can define `willSet(newValue) { }` and access `newValue` in there as well! Refer to :https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Properties.html – Jack Jun 13 '14 at 21:31
  • Hmmm, good point @jackWu. Could be useful in cases we must react _before_ any value gets written. – Jean Le Moignan Jun 13 '14 at 21:56
  • 2
    What exactly do you have against those with a negative number of legs? Are you a negative gigot bigot? – devguydavid Aug 07 '14 at 22:00
  • It's worth noting that 'The Swift Programming Language' [here](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID320) discourages the use of `UInt` just to restrict the range of an integer, citing code interoperability and avoided type conversions as the reason – Jared Khan Apr 07 '17 at 14:17
5

In this specific case, you want a property observer, you could implement it like this:

class Mammal {
    init () {
        numLegs = 0
        super.init()
    }
    var numLegs:Int {
        didSet: {
            if !(numLegs ~= 0...4) {
               numLegs = max(0,min(numLegs,4)) // Not sure if this is what you want though
            }   
        }
    }
}

Looking at this though, I'm not sure if this would recurse and call didSet again...I guess it wouldn't be too bad because it would pass the check the second time

Jack
  • 16,677
  • 8
  • 47
  • 51