I'm trying to write a light observer class in Swift (currently Swift 2). The idea is to use it within an Entity Component system, as a means for the components to communicate with one-another without being coupled together.
The problem I'm having is that all types of data could be communicated, a CGVector
, an NSTimeInterval
and so on. This means that the method being passed could have all kinds of type signatures (CGVector) -> Void
, () -> Void
etc.
I'd like to be able to store these varying signatures in an array, but still have some type safety. My thinking is that the type for the array would be (Any) -> Void
or perhaps (Any?) -> Void
, so that I can at least ensure that it contains methods. But I'm having trouble passing methods in this way: Cannot convert value of type '(CGVector) -> ()' to expected argument type '(Any) -> ()'
.
First attempt:
//: 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()
Throws the error Cannot convert value of type '(CGVector) -> ()' to expected argument type '(Any) -> ()'
.
Second attempt
If I genericize the Binding
struct to recognise different types of arguments I have a bit more luck. This version basically works, but the array holding the methods is now [Any]
( I'm not sure whether it's the attempt to cast Any
back to the Binding
struct that is causing the slightly odd error below 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
}
}
}
Is there a way to do this and have the array hold methods of varying type signatures, something like [(Any?) -> ()]
?
Attempt 3...
Reading around, eg here http://www.klundberg.com/blog/capturing-objects-weakly-in-instance-method-references-in-swift/ it seems that my approach above will lead to strong reference cycles, and that what I need to do is pass the static method eg Movement.move
rather than moveComponent.move
. So the type signature I would be storing would actually be (Component) -> (Any?) -> Void
rather than (Any?) -> Void
. But my question still stands, I still would like to be able to store an array of these static methods with a bit more type-safety than just [Any]
.
(CGVector) -> ()
to an(Any) -> ()
. You're saying that a given function that takes aCGVector
input can now take any input, which isn't correct at all (functions are contravariant in this regard). I suspect you'll either have to factor out the closure storage logic and let whoever calls thedispatch
method provide the closure to call, much like this github project – or as you say, you'll have to store the closures asAny
and then typecast them back when you need to call them. – Grazf: { f($0 as T) }
– DealerI suspect you'll either have to factor out the closure storage logic and let whoever calls the dispatch method provide the closure to call
the problem there is that this decoupling pattern depends on the dispatcher being completely agnostic about who may or may not be receiving its dispatches. – Dealer