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

问题描述 投票:0回答:4

我正在尝试用 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]
更多的类型安全性。

arrays swift casting closures type-signature
4个回答
2
投票

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

通用的 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 -> _ -> ()'
.

如果我们调用柯里化函数的第一部分,并将其包含在一个新的闭包中(我不知道这是否有名称,我称之为“递归”),如下所示:

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
投票

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

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

缺点是枚举捕获了对方法的强引用。然而(我需要将其从操场上拿出来进行更多测试),这似乎并没有创建一个强大的引用循环。您可以将包含实体设置为

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

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") }
        }

}


0
投票

我用可以有不同签名的

enum
闭包解决了这个问题,但是由于我需要将它们存储在一个数组中,所以我将其包装为
struct
,并用
identifier
代表
Equatable
if case let
进行搜索枚举,例如

class TableViewEvents: NSObject, UITableViewDelegate {
    
    struct Event: Equatable {

        let identifier: String
        let handler: Handler
        
        init(_ handler: Handler) {
            self.identifier = "\(type(of: self)).\(UUID().uuidString)"
            self.handler = handler
        }

        static func == (lhs: TableViewEvents.Event, rhs: TableViewEvents.Event) -> Bool {
            lhs.identifier == rhs.identifier
        }
    }
    
    enum Handler {
        case didSelectRowAtIndexPath((UITableView, IndexPath) -> ())
        case willDisplayCellForRowAtIndexPath((UITableView, UITableViewCell, IndexPath) -> ())
    }
    
    var events: [Event] = []
    
    func add(_ event: Event) -> Self {
        events.append(event)
        return self
    }
    
    func remove(_ event: Event) {
        events.removeAll { e in
            event == e
        }
    }
      
    func didSelectRowAtIndexPath(_ handler: @escaping (UITableView, IndexPath) -> ()) -> Self {
        return add(Event(.didSelectRowAtIndexPath(handler)))
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        events.forEach { event in
            if case .didSelectRowAtIndexPath(let handler) = event.handler {
                handler(tableView, indexPath)
            }
        }
© www.soinside.com 2019 - 2024. All rights reserved.