我正在尝试用 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/