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

时间:2016-06-28 10:29:28

标签: arrays swift casting closures type-signature

我正在尝试在Swift中编写一个轻型观察者类(目前是Swift 2)。我们的想法是在实体组件系统中使用它,作为组件彼此通信而不耦合在一起的手段。

我遇到的问题是可以传达所有类型的数据,CGVectorNSTimeInterval等等。这意味着传递的方法可以包含各种类型的签名(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而不是{{1} }。因此,我将存储的类型签名实际上是moveComponent.move而不是(Component) -> (Any?) -> Void。但我的问题仍然存在,我仍然希望能够存储一组这些静态方法,其类型安全性比(Any?) -> Void更多。

3 个答案:

答案 0 :(得分:2)

在迈克·阿什的博客中提到的一种关闭关闭参数的方法是,凯西·弗莱尔(Casey Fleser)与之相关,是为了重新开始#(?)它。

泛化的Binding类:

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

error: cannot convert value of type 'T -> Argument -> Void' to expected argument type 'AnyObject -> _ -> ()'

如果我们调用curried函数的第一部分,并将其封闭在一个新的闭包中(我不知道这是否有一个名字,我称之为&#34;重新播放&#34) ;),像这样:action: { action($0 as! T) }然后一切正常(技术取自Mike Ash)。我想这有点像黑客,因为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

答案 1 :(得分:0)

如何存储不同类型签名集合的不同方法。不使用泛型,强制转换或类型擦除,而是使用带有相关类型的枚举,表示您要使用的每种签名类型,例如

enum Signature{
    case cgvector(CGVector -> Void)
    case nstimeinterval(NSTimeInterval -> Void)

缺点是enum捕获了对该方法的强引用。但是(我需要将它从游乐场中取出来进行更多测试),这似乎并没有创建一个强大的参考周期。您可以将包含实体设置为nil,并且其所有组件似乎都已取消初始化。我不太确定那里发生了什么。对我来说,这种枚举方法似乎比将AnyObject数组中的通用包装器放在更清晰,并且必须不断地进行转换和类型擦除。

欢迎评论和批评。

/*:
 ## Entity - Component framework with a notification system for decoupled communications between components

 ### Limitations:
 1. Closure class stores a strong reference to the components. But, a strong reference cycle is not created. 
 2. A given class instance (component) can only subscribe to a given event with one method.
 */

import Cocoa
import Foundation

enum EventName: String {
    case onInput
}

//A type-safe wrapper that stores closures of varying signatures, and allows them to be identified by the hashValue of its owner.
class Closure {

    enum Signature {
        case cgvector(CGVector -> Void)
        case nstimeinterval(NSTimeInterval -> Void)

        func invoke(argument: Any){
            switch self {
            case let .cgvector(closure):        closure(argument as! CGVector)
            case let .nstimeinterval(closure):  closure(argument as! NSTimeInterval)
            }
        }
    }

    var method: Signature
    weak var owner: Component?

    init(owner: Component, action: Closure.Signature) {
        method = action
        self.owner = owner
    }

}

// Entity

class Entity {

    var components = Set<Component>()
    private var events = [EventName: [Closure]]()

    deinit {
        print("Entity deinit")
    }

    // MARK: component methods

    func add(component: Component){
        components.insert(component)
        component.parent = self
    }

    func remove(component: Component){
        unsubscribeFromAll(component)
        components.remove(component)
    }

    func remove<T: Component>(type: T.Type){
        guard let component = retrieve(type) else {return}
        remove(component)
    }

    func retrieve<T: Component>(type: T.Type) -> T? {
        for item in components {
            if item is T { return item as? T}
        }
        return nil
    }

    // MARK: event methods

    func subscribe(listener: Component, method: Closure.Signature, to event: EventName ){
        let closure = Closure(owner: listener, action: method)
        // if event array does not yet exist, create it with the closure.
        if events[event] == nil {
            events[event] = [closure]
            return
        }
        // check to make sure this listener has not subscribed to this event already
        if ((events[event]!.contains({ $0.owner! == listener })) == false) {
            events[event]!.append(closure)
        }
    }

    func dispatch(argument: Any, to event: EventName ) {
        events[event]?.forEach(){ $0.method.invoke(argument) }
    }

    func unsubscribe(listener: Component, from name: EventName){
        //events[name]? = events[name]!.filter(){ $0.hashValue != listener.hashValue }
        if let index = events[name]?.indexOf({ $0.owner! == listener }) {
            events[name]!.removeAtIndex(index)
        }
    }

    func unsubscribeFromAll(listener: Component){
        for (event, _) in events {
            unsubscribe(listener, from: event)
        }
    }

}


//Components

class Component: Hashable {
    weak var parent: Entity?
    var doc: String { return "Component" }
    var hashValue: Int { return unsafeAddressOf(self).hashValue }

    deinit {
        print("deinit \(doc)")
    }

}

func == <T: Component>(lhs: T, rhs: T) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

//: #### Usage test

class Input: Component {
    override var doc: String { return "Input" }


    func goRight() {
        parent?.dispatch(CGVector(dx: 10, dy: 0), to: .onInput )
    }

    func goUp() {
        parent?.dispatch(CGVector(dx: 0, dy: -10), to: .onInput )
    }
}

class Movement: Component {
    override var doc: String { return "Movement" }

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


class Physics: Component {
    override var doc: String { return "Physics" }

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

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

// an example factory
var entity: Entity? = Entity()
if let instance = entity {

    // a couple of ways of adding components
    var inputComponent = Input()

    instance.add(inputComponent)
    instance.add(Movement())
    instance.add(Physics())

    var m = instance.retrieve(Movement.self)
    instance.subscribe(m!, method: .cgvector(m!.move), to: .onInput)

    let p = instance.retrieve(Physics.self)!
    instance.subscribe(p, method: .cgvector(p.move), to: .onInput)
    inputComponent.goRight()
    inputComponent.goUp()
    instance.retrieve(Input.self)?.goRight()

    instance.remove(Movement.self)
    m = nil

    inputComponent.goRight()
}
entity = nil //not a strong ref cycle

答案 2 :(得分:0)

我陷入那种境地,但是我找到了一个不错的解决方案 使用匿名内联函数,就像映射 这是一个例子


var cellConfigurator: ((UITableViewCell, _ index: IndexPath) -> Void)?

func setup<CellType: UITableViewCell>(cellConfig: ((CellType, _ index: IndexPath) -> ())?)
{
        // this mini function maps the closure
        cellConfigurator = { (cell: UITableViewCell, _ index: IndexPath) in
            if let cellConfig = cellConfig, let cell = cell as? CellType {
                cellConfig(cell, index)
            }
            else
            { print("-- error: couldn't cast cell") }
        }

}