arrays - 如何在 Swift 的闭包签名中转换参数类型?

标签 arrays swift casting closures type-signature

我正在尝试用 Swift(目前是 Swift 2)编写一个轻型观察器类。这个想法是在实体组件系统中使用它,作为组件相互通信而不耦合在一起的一种方式。

我遇到的问题是所有类型的数据都可以通信,CGVector , 一个 NSTimeInterval等等。这意味着被传递的方法可以有各种类型的签名 (CGVector) -> Void , () -> Void等等

我希望能够将这些不同的签名存储在一个数组中,但仍具有一定的类型安全性。我的想法是数组的类型是 (Any) -> Void或者也许 (Any?) -> Void ,这样我至少可以确保它包含方法。但是我无法通过这种方式传递方法:Cannot convert value of type '(CGVector) -> ()' to expected argument type '(Any) -> ()' .

第一次尝试:

//: Playground - noun: a place where people can play

import Cocoa
import Foundation

enum EventName: String {
    case input, update
}

struct Binding{
    let listener: Component
    let action: (Any) -> ()
}

class EventManager {
    var events = [EventName: [Binding]]()

    func add(name: EventName, event: Binding) {
        if var eventArray = events[name] {
            eventArray.append(event)
        } else {
            events[name] = [event]
        }
    }

    func dispatch(name: EventName, argument: Any) {
        if let eventArray = events[name] {
            for element in eventArray {
                element.action(argument)
            }
        }
    }

    func remove(name: EventName, listener: Component) {
        if var eventArray = events[name] {
            eventArray = eventArray.filter(){ $0.listener.doc  != listener.doc }
        }
    }
}

// Usage test

//Components

protocol Component {
    var doc: String { get }
}

class Input: Component {
    let doc = "InputComponent"
    let eventManager: EventManager

    init(eventManager: EventManager) {
        self.eventManager = eventManager
    }

    func goRight() {
        eventManager.dispatch(.input, argument: CGVector(dx: 10, dy: 0) )
    }
}

class Movement: Component {
    let doc = "MovementComponent"

    func move(vector: CGVector) {
        print("moved \(vector)")
    }

}

class Physics: Component {
    let doc = "PhysicsComponent"

    func update(time: NSTimeInterval){
        print("updated at \(time)")
    }
}


class someClass {
    //events
    let eventManager = EventManager()

    // components
    let inputComponent: Input
    let moveComponent = Movement()

    init() {
        inputComponent = Input(eventManager: eventManager)

        let inputBinding = Binding(listener: moveComponent, action: moveComponent.move) // ! Cannot convert value of type '(CGVector) -> ()' to expected argument type '(Any) -> ()'
        eventManager.add(.input, event: inputBinding)

    }
}

let someInstance = someClass()
someInstance.inputComponent.goRight()

抛出错误 Cannot convert value of type '(CGVector) -> ()' to expected argument type '(Any) -> ()' .

第二次尝试

如果我泛化 Binding结构来识别不同类型的论点我有更多的运气。这个版本基本上可以工作,但是保存方法的数组现在是 [Any] (我不确定是否是试图将 Any 转换回 Binding 结构导致了 Binary operator '!=' cannot be applied to two 'String' operands 下面的稍微奇怪的错误):

struct Binding<Argument>{
    let listener: Component
    let action: (Argument) -> ()
}

class EventManager {
    var events = [EventName: [Any]]()

    func add(name: EventName, event: Any) {
        if var eventArray = events[name] {
            eventArray.append(event)
        } else {
            events[name] = [event]
        }
    }

    func dispatch<Argument>(name: EventName, argument: Argument) {
        if let eventArray = events[name] {
            for element in eventArray {
                (element as! Binding<Argument>).action(argument)
            }
        }
    }

    func remove(name: EventName, listener: Component) {
        if var eventArray = events[name] {
           // eventArray = eventArray.filter(){ ($0 as! Binding).listener.doc  != listener.doc } //Binary operator '!=' cannot be applied to two 'String' operands
        }
    }
}

有没有办法做到这一点,并让数组保存不同类型签名的方法,比如 [(Any?) -> ()]

尝试 3...

四处阅读,例如这里 http://www.klundberg.com/blog/capturing-objects-weakly-in-instance-method-references-in-swift/似乎我上面的方法会导致强引用循环,我需要做的是传递静态方法,例如 Movement.move而不是 moveComponent.move .所以我要存储的类型签名实际上是 (Component) -> (Any?) -> Void而不是 (Any?) -> Void .但我的问题仍然存在,我仍然希望能够以比 [Any] 更类型安全的方式存储这些静态方法的数组。 .

最佳答案

在 Casey Fleser 链接到的 Mike Ash 的博客中建议的一种转换闭包参数的方法是“递归”(?)它。

一个通用的绑定(bind)类:

private class Binding<Argument>{
    weak var listener: AnyObject?
    let action: AnyObject -> Argument -> ()

    init(listener: AnyObject, action: AnyObject -> Argument -> ()) {
        self.listener = listener
        self.action = action
    }

    func invoke(data: Argument) -> () {
        if let this = listener {
            action(this)(data)
        }
    }
}

和事件管理器,没有重复:

class EventManager {

    var events = [EventName: [AnyObject]]()

    func add<T: AnyObject, Argument>(name: EventName, listener: T, action: T -> Argument -> Void) {           
        let binding = Binding(listener: listener, action: action) //error: cannot convert value of type 'T -> Argument -> Void' to expected argument type 'AnyObject -> _ -> ()'

        if var eventArray = events[name] {
            eventArray.append(binding)
        } else {
            events[name] = [binding]
        }
    }

    func dispatch<Argument>(name: EventName, argument: Argument) {
        if let eventArray = events[name] {
            for element in eventArray {
                (element as! Binding<Argument>).invoke(argument)
            }
        }
    }

    func remove(name: EventName, listener: Component) {
        if var eventArray = events[name] {
            eventArray = eventArray.filter(){ $0 !== listener }
        }
    }
}

这仍然会产生同样的错误,无法转换为 AnyObject:

错误:无法将“T -> Argument -> Void”类型的值转换为预期的参数类型“AnyObject -> _ -> ()”

但是如果我们调用 curried 函数的第一部分,并将它包含在一个新的闭包中(我不知道它是否有名字,我称它为“recurrying”),如下所示: action: { action($0 as! T) } 然后一切正常(从 Mike Ash 那里获得的技术)。我想这有点像 hack,因为 Swift 类型安全被规避了。

我也不太理解错误消息:它说它无法将 T 转换为 AnyObject,但随后接受转换为 T?

编辑:到目前为止更新了完整的代码 edit2:更正事件的附加方式 edit3:删除事件现在有效

//: Playground - noun: a place where people can play

import Cocoa
import Foundation

enum EventName: String {
    case input, update
}

private class Binding<Argument>{
    weak var listener: AnyObject?
    let action: AnyObject -> Argument -> ()

    init(listener: AnyObject, action: AnyObject -> Argument -> ()) {
        self.listener = listener
        self.action = action
    }

    func invoke(data: Argument) -> () {
        if let this = listener {
            action(this)(data)
        }
    }

}


class EventManager {
    var events = [EventName: [AnyObject]]()

    func add<T: AnyObject, Argument>(name: EventName, listener: T, action: T -> Argument -> Void) {

        let binding = Binding(listener: listener, action: { action($0 as! T)  }) //

        if events[name]?.append(binding) == nil {
            events[name] = [binding]
        }
    }

    func dispatch<Argument>(name: EventName, argument: Argument) {
        if let eventArray = events[name] {
            for element in eventArray {
                (element as! Binding<Argument>).invoke(argument)
            }
        }
    }

    func remove<T: AnyObject, Argument>(name: EventName, listener: T, action: T -> Argument -> Void) {
        events[name]? = events[name]!.filter(){ ( $0 as! Binding<Argument>).listener !== listener }
    }
}

// Usage test

//Components

class Component {

    weak var events: EventManager?
    let doc: String
    init(doc: String){
        self.doc = doc
    }

}


class Input: Component {

    init() {
        super.init(doc: "InputComponent")
    }

    func goRight() {
        events?.dispatch(.input, argument: CGVector(dx: 10, dy: 0) )
    }

    func goUp() {
        events?.dispatch(.input, argument: CGVector(dx: 0, dy: -5) )
    }
}

class Movement: Component {
    init() {
        super.init(doc: "MovementComponent")
    }
    func move(vector: CGVector) {
        print("moved \(vector)")
    }

}


class Physics: Component {
    init() {
        super.init(doc: "PhysicsComponent")
    }

    func update(time: NSTimeInterval){
        print("updated at \(time)")
    }

    func move(vector: CGVector) {
        print("updated \(vector)")
    }

}

// Entity

class Entity {

    let events = EventManager()

}


class someClass: Entity {

    // components
    let inputComponent: Input
    let moveComponent: Movement
    let physicsComponent: Physics

    override init() {

        inputComponent = Input()
        moveComponent = Movement()
        physicsComponent = Physics()
        super.init()
        inputComponent.events = events

        events.add(.input, listener: moveComponent, action: Movement.move)
        events.add(.input, listener: physicsComponent, action: Physics.move)
    }
}

let someInstance = someClass()

someInstance.inputComponent.goRight()
//moved CGVector(dx: 10.0, dy: 0.0)
//updated CGVector(dx: 10.0, dy: 0.0)

someInstance.events.remove(.input, listener: someInstance.moveComponent, action: Movement.move)
someInstance.inputComponent.goUp()
//updated CGVector(dx: 0.0, dy: -5.0)

someInstance.events.remove(.input, listener: someInstance.physicsComponent, action: Physics.move)
someInstance.inputComponent.goRight()
// nothing

关于arrays - 如何在 Swift 的闭包签名中转换参数类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38073704/

相关文章:

java - 转换为通用原始类。方法运行没有任何异常,如何以及为什么?

javascript - 为什么在 componentDidUpdate 中比较数组不会导致无限循环?

c++ - 将文件读入对象数组C++

c - 输入的值未写入数组

swift - 使用 stackview 使 UITableView 下拉

java - 类型安全 : Unchecked cast from Object to ArrayList<MyVariable>

java - 在 hql 中将字符串列转换为日期

C++ 不允许带有负索引的 vector ?

ios - 如何使用 Swift 在 AVCaptureVideoPreviewLayer 的特定区域捕获二维码数据?

iOS 应用程序在 UIGraphicsEndPDFContext() 崩溃