简体   繁体   English

Swift:捕获逃脱被调用函数的闭包中的inout参数

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

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. 然后我尝试通过(inout)引用传递有问题的参数,然后从中生成getter / setter对。 This did not work. 这没用。 The Swift docs does say (paraphrasing) that Swift figures out when to copy, and when to not. Swift文档确实说(释义)Swift确定何时复制,何时不复制。 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! 谢谢!

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 . 在Objective-C中,这是用__block声明的。 In Swift, this __block behavior is implicit. 在Swift中,这个__block行为是隐含的。

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. 但是,为了使块能够在函数调用者的作用域中看到修改inout变量(可能在稍后的时间),这意味着调用者作用域中传递的变量也需要以这样的方式存储:可以比堆栈帧寿命更长。 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 ; 根据被调用函数的类型,它所知道的是它的一个参数是inout ; it doesn't know that the function plans to capture that inout variable in a block. 它不知道该函数计划捕获块中的inout变量。 So it doesn't know to prepare this __block storage for this passed variable. 所以它不知道为这个传递的变量准备这个__block存储。

It can be done as follows. 它可以如下完成。 )Notice that the closure and the inout param have the same lifespan.) )请注意,闭包和inout参数具有相同的寿命。)

/// A class providing access to a resource with an inout parameter in an escaping closure.   
     class ProtectedResource<ValueType> {
            private var protectedResourceArray = [ValueType]()
            private var protectedResourceArrayLock = NSRecursiveLock()
            private let opq = OperationQueue()

            func performWithResource(block: @escaping (inout [ValueType]) -> ()) {
                opq.addOperation { [weak self] in
                    guard let strongSelf = self else {
                        return
                    }
                    strongSelf.protectedResourceArrayLock.lock()
                    block(&strongSelf.protectedResourceArray)
                    strongSelf.protectedResourceArrayLock.unlock()
                }
            }
        }

/// Some other class using that in out parameter. 

        func run() {

            func updateArray(array: inout [String]) {
                print("Performing on \(array)")
                array.append("test")
            }

            protectedResource.performWithResource(block: updateArray)

            protectedResource.performWithResource {
                print("Performing on \($0)")
            }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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