我正在尝试用 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?) -> ()]
?
四处阅读,例如这里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 博客中建议的一种强制转换闭包参数的方法是“重复”(?)它。
通用的 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
存储不同类型签名集合的不同方法。不要使用泛型、转换或类型擦除,而是使用带有关联类型的枚举来表示您要使用的每种签名类型,例如
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
我遇到了这种情况,但我找到了一个很酷的解决方案 使用匿名内联函数,就像映射 这是一个例子
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") }
}
}
我用可以有不同签名的
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)
}
}