1

In my last question, I asked how to write setter for a subscript of a computed property in Swift. I think my question was not substantial enough to be understood. The answer given was either incorrect or complicated for a small task. After a long thought, I still think maybe a brighter man can offer a more inspiring answer.

To subside confusion, my question is if there is a shorthand setter declaration for a subscript of an array in Swift. Because there is shorthand setter declaration for an array in swift, but not its subscript.

A shorthand getter/setter declaration is

var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set {
        origin.x = newValue.x - (size.width / 2)
        origin.y = newValue.y - (size.height / 2)
    }
}

Basically, in a scenario where a set action for action[i] will cause actionButton[i] to be updated. There are basically two ways to do this quickly.

First solution

func setActionAtIndex(index:Int, forValue newValue:SomeClass){
    action[index] = newValue
    self.updateActionButtonAtIndex(index)
}

This solution above is easy to understand, however, it requires a function, which takes two lines of code, in one class. Not quite "Swift".

Second solution

var action: [SomeClass] {
    subscript(index:Int){
        set(index:Int,newValue:SomeClass){
           action[index] = newValue
           //extra action to be performed 
           updateActionButtonAtIndex(index)

        }
        get{
           return action[index]
        }
    }
}

Needless to say, this is absolutely wrong and this solution is nonexistent.

Why it is wrong?

Expected 'get', 'set', 'willSet', or 'didSet' keyword to start an accessor definition

Hence, is there a solution that is similar to second solution but works?

Community
  • 1
  • 1
donkey
  • 4,285
  • 7
  • 42
  • 68

2 Answers2

4

What you're not understanding is that an ordinary setter is the shorthand you are looking for. If an object has an array property and someone sets into that array, the setter is called.

For example:

class Thing {
    var arr : [Int] = [1, 2, 3] {
        didSet {
            println("I saw that")
        }
    }
}

var t = Thing()
t.arr[1] = 100 // "I saw that"

So all you have to do is use didSet to compare the new value to the old value and respond however you like to the change.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Oh yeah! Is that it? I was thinking about it in a much too complicated way. Last person gave me a whole new object class to handle it. I knew it could be done simpler. Thank you! – donkey Jan 18 '15 at 01:34
  • Nice answer! +1. This is a consequence of swift's value semantics for arrays. – Alex Brown Jan 18 '15 at 01:36
  • "Last person" here - is there a nice way to find out which value(s) have changed in this situation? I understood the original question to be about knowing the specific index. – jrturton Jan 18 '15 at 10:15
  • 1
    If you want to know the exact index of change, you compare `oldValue` to `arr`(the name of your array) to find out of which index is the value changed, it normally just takes O(n) time – donkey Jan 18 '15 at 11:29
  • @jrturton See now my other answer - much faster, but less satisfactory, in my view. – matt Jan 18 '15 at 16:28
1

I did think of another way, but I don't like it much. Use an extension to add another subscript to setter to Array - a setter that allows you to supply a callback. The problem with this approach is that you have remember to supply the callback! This puts the onus on whoever sets into the index, which is probably not what you had in mind. Still, it has the advantage, once configured, of brevity - and it takes O(0) time:

extension Array {
    subscript(ix:Int, f:(T,T,Int)->Void) -> T {
        set {
            let old = self[ix]
            self[ix] = newValue
            f(old,newValue,ix)
        }
        get {
            return self[ix]
        }
    }
}

class Thing {
    var arr : [Int] = [1, 2, 3]
    func f (oldv:Int, newv:Int, ix:Int) {
        println("changed \(oldv) to \(newv) at \(ix)")
    }
}

var t = Thing()
t.arr[1,t.f] = 100 // "changed 2 to 100 at 1"
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • This is kind of the approach I had, but I used a wrapper so you only set the callback once, and could set using subscripting as normal. Agree it's messier. – jrturton Jan 18 '15 at 16:37
  • @jrturton Yes, I thought of the wrapper too, of course. But that seems like too much work. :) The real problem here is that (1) Array is a struct, not a class, so you can't subclass it, and (2) an extension can't declare a stored property (if it could, I would just store the last-changed index and have done). – matt Jan 18 '15 at 16:48