arrays - How do I cast the argument type within a closure signature in Swift? -
i'm trying write light observer class in swift (currently swift 2). idea use within entity component system, means components communicate one-another without being coupled together.
the problem i'm having types of data communicated, cgvector
, nstimeinterval
, on. means method being passed have kinds of type signatures (cgvector) -> void
, () -> void
etc.
i'd able store these varying signatures in array, still have type safety. thinking type array (any) -> void
or perhaps (any?) -> void
, can @ least ensure contains methods. i'm having trouble passing methods in way: cannot convert value of type '(cgvector) -> ()' expected argument type '(any) -> ()'
.
first attempt:
//: playground - noun: place 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] { 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 { } } 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 @ \(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) -> ()' expected argument type '(any) -> ()' eventmanager.add(.input, event: inputbinding) } } let someinstance = someclass() someinstance.inputcomponent.goright()
throws error cannot convert value of type '(cgvector) -> ()' expected argument type '(any) -> ()'
.
second attempt
if genericize binding
struct recognise different types of arguments have bit more luck. version works, array holding methods [any]
( i'm not sure whether it's attempt cast any
binding
struct causing odd error below binary operator '!=' cannot applied 2 '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] { 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 applied 2 'string' operands } } }
is there way , have array hold methods of varying type signatures, [(any?) -> ()]
?
attempt 3...
reading around, eg here http://www.klundberg.com/blog/capturing-objects-weakly-in-instance-method-references-in-swift/ seems approach above lead strong reference cycles, , need pass static method eg movement.move
rather movecomponent.move
. type signature storing (component) -> (any?) -> void
rather (any?) -> void
. question still stands, still able store array of these static methods bit more type-safety [any]
.
one approach casting parameters of closure, suggested in mike ash's blog casey fleser linked to, "recurry"(?) it.
a genericised binding class:
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 = listener { action(this)(data) } } }
and event manager, without recurrying:
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' 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] { 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 } } } }
this still produces same error, of not being able cast anyobject
:
error: cannot convert value of type 't -> argument -> void' expected argument type 'anyobject -> _ -> ()'
.
bu if call first part of curried function, , enclose within new closure (i don't know if has name, i'm calling "recurrying"), this: action: { action($0 as! t) }
works (technique taken mike ash). guess bit of hack, in swift type safety being circumvented.
i don't understand error message: it's saying can't convert t
anyobject
, accepts casting t
?
edit: updated complete code far edit2: corrected how events appended edit3: removing events works
//: playground - noun: place 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 = 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] { 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 @ \(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
Comments
Post a Comment