Я пытаюсь написать класс легкого наблюдателя в Swift (в настоящее время Swift 2). Идея состоит в том, чтобы использовать его в системе Entity Component как средство взаимодействия компонентов друг с другом, не будучи связанными друг с другом.
Проблема, с которой я сталкиваюсь, заключается в том, что могут быть переданы все типы данных, 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]
.
(CGVector) -> ()
в(Any) -> ()
. Вы говорите, что заданная функция, которая принимает входные данныеCGVector
, теперь может принимать любые входные данные, что совершенно неверно (функции в этом отношении контравариантны). Я подозреваю, что вам придется либо исключить логику хранения замыканий, либо позволить тому, кто вызывает методdispatch
, предоставить замыкание для вызова, примерно так: проект github - или, как вы говорите, вам придется хранить замыкания какAny
, а затем приводить их обратно, когда вам нужно их вызывать. - person Hamish   schedule 28.06.2016f: { f($0 as T) }
- person OliverD   schedule 28.06.2016I suspect you'll either have to factor out the closure storage logic and let whoever calls the dispatch method provide the closure to call
проблема заключается в том, что этот шаблон разделения зависит от того, что диспетчер совершенно не зависит от того, кто может или не может получать его отправки. - person OliverD   schedule 29.06.2016