Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Capture inout parameter in closures that escape the called function

Tags:

macos

ios

swift

I tried to write an "editor" class that could retain a reference to a property on a different object for later mutation. I first wrote the editor class to receive a closure for reading, and a closure for writing. This worked. I then tried to pass the parameter in question by (inout) reference, and then generate the getter / setter pair from that. This did not work. The Swift docs does say (paraphrasing) that Swift figures out when to copy, and when to not. I think I am up against unpredictability of that limitation, but thought I'd pose the question just the same.

Alternatively, is it possible to get a curried function for the individual getter and setter?

My code is:

class SomeModel : Printable {

    var a:String

    init(a:String) {
        self.a = a
    }

    var description:String {
        return "\(self.a)"
    }
}


class Editor {

    var getter:()-> String
    var setter:(String)->()

    init(getter:()-> String, setter:(String)->()) {
        self.getter = getter
        self.setter = setter
    }

    convenience init(inout bindTo:String) {
        self.init(
            getter:{ return bindTo },
            setter: { v in bindTo = v })
    }

    func read() -> String {
        return self.getter()
    }

    func write(value:String) {
        self.setter(value)
    }
}


func testBindTo() {
    var readModel =  SomeModel(a:"Did not capture by reference");
    var bindForReading = Editor(bindTo: &readModel.a)
    readModel.a = "captured by reference!"
    println(bindForReading.read())

    var writeModel =  SomeModel(a:"Did not capture by reference");
    var bindForWriting = Editor(bindTo: &writeModel.a)
    bindForWriting.write("captured by reference")
    println(writeModel)
}

testBindTo()


func testExplicitGetterSetter() {

    var readModel =  SomeModel(a:"Did not capture by reference");
    var bindForReading = Editor(
        getter: { readModel.a },
        setter: { v in readModel.a = v })
    readModel.a = "captured by reference!"
    println(bindForReading.read())

    var writeModel =  SomeModel(a:"Did not capture by reference");
    var bindForWriting = Editor(
        getter: { writeModel.a },
        setter: { v in writeModel.a = v })
    bindForWriting.write("captured by reference")
    println(writeModel)     
}

testExplicitGetterSetter()

The results are:

Did not capture by reference
Did not capture by reference
captured by reference!
captured by reference

Thanks!

like image 560
Chris Conover Avatar asked May 03 '15 22:05

Chris Conover


1 Answers

I don't think this is possible. And it shouldn't be possible, if you think about it, because it would be super unsafe.

Because closures can outlive the scope they were created in, captured variables must be stored with the block. But in order to be able to assign to the captured variable and share the state of that variable between the (one or more) block(s) that captured it and the original scope, the blocks cannot just capture the value of the variable (which would create independent copies of the variable), but capture a kind of "reference" to a shared copy. This means that assignable variables that are captured by blocks must be stored specially. In Objective-C, this is declared with __block. In Swift, this __block behavior is implicit.

However, in order for the block to modify an inout variable (potentially at a later time) as it is seen in the function caller's scope, that would mean that the passed variable in the caller's scope would also need to be stored in a way that can outlive the stack frame. But the caller function doesn't know this. All it knows from the type of the called function is that one of its parameters is inout; it doesn't know that the function plans to capture that inout variable in a block. So it doesn't know to prepare this __block storage for this passed variable.

like image 118
newacct Avatar answered Sep 28 '22 00:09

newacct