2016-08-18 3 views
2

Таким образом, я делаю SpriteKit игру, и я хочу, чтобы направить update, mouseDown, keyDown и т.д. ... события, которые вызываются на GameScene, к тому, что сцены дети, при условии, что они являются обычным подклассом SKNode, у которого есть соответствующие респонденты.Общий механизм отправки с помощью выполнения заданных испытаний Протокол соответствия & селектор

Я сделал несколько протоколов в зависимости от того, какие функции я хочу вызвать на каждом узле, так как у меня есть KeyboardDelegate для узлов, которые должны получить keyDown, keyUp и т.д ... UpdateDelegate для update, didSimulatePhysics и т.д ... и что-то вроде этого для каждого типа вызовов в целом у меня есть UpdateDelegate, KeyboardDelegate, MouseDelegate и CollisionDelegate каждый для соответствующего типа вызовов.

И так в GameScene у меня есть такие функции, как:

func someEvent(someParameter: ArgumentType) { 
    for child in children where child is SomeProtocol { 
     //onSomeEvent should be required by SomeProtocol 
     (child as! SomeProtocol).onSomeEvent(someParameter) 
    } 
} 

Проблема заключается в том, что существует около 20 таких событий, которые я хочу вперед и у меня есть тот же самый код в каждой функции, единственная часть которые варьируются: SomeProtocol и onSomeEvent.

Я попытался сделать функцию, которая обрабатывает все, что так мне не нужно писать этот код для каждого события, я хочу передать так:

extension SKNode { 
    func forward<T>(method: Selector, onChildrenOfType: T.Type, withArgument argument: AnyObject) { 
     for child in children where child is T { 
      child.performSelector(method, withObject: first) 
     } 
    } 
} 

Расширения было на SKNode, так что я бы способные переадресовывать вызовы на каждого ребенка каждого отдельного узла, принимающего вызов, независимо от того, какой тип SKNode они были.

Сама функция не казалась совершенно прав, и это не потому, что он не работает, я не мог пройти протокол напрямую, без использования SomeProtocol.self и всякий раз, когда я назвал это так:

override func someEvent(someParameter: ArgumentType) { 
    self.recurse(#selector(onSomeEvent(_:)), onChildrenOfType: SomeProtocol.self, withArgument: someParameter) 
} 

Я бы получил use of unresolved identifier 'onSomeEvent', я предполагаю, потому что компилятор не знает, где искать функцию onSomeEvent.

Итак ... есть ли способ, чтобы сделать эту работу (к тому же, возможно, используя старый синтаксис селекторов), предпочтительно, если я могу передать более, что 2 параметра (так как performSelector только позволяет до 2, и objc_msgSend и NSInvocation недоступны), или я должен просто придерживаться копирования кода в каждом событии, которое я хочу переслать.

ответ

2

Так что это было интересно & довольно сложно. Ключ должен протестировать runtime -protocol тип, переданный как параметр, - это скорее мета, и это невозможно в Swift, который использует проверку соответствия времени компиляции для эффективности безопасности &. Поэтому в первую очередь вам необходимо заземлить все в среде выполнения Obj-C, а ключом является метод conformsToProtocol. Если вам нужна такая универсальная отправка, я бы не пробовал передавать более двух аргументов: вместо этого передайте какой-то контейнер (например, NSNotificationuserInfo: NSDictionary) со значениями, которые вам нужны. Ниже можно легко адаптироваться к функции, которая принимает 1 параметр, как я уверен, вы знаете, используя вариант withObject:performSelector:. Обратите внимание, что функция селектора должна вернуть объект, иначе ваш код сильно разбился, поэтому я просто выбрал self здесь для удобства (и цепочки!)

import Foundation 

// performSelector methods must return a reference to something 
@objc protocol P1 { 
    func doP1Action() -> P1 
} 
@objc protocol P2 { 
    func doP2Action() -> P2 
} 
class C1a: NSObject, P1 { 
    func doP1Action() -> P1 { print("I'm doing C1a's impl of a P1 Action"); return self } 
} 
class C1b: NSObject, P1 { 
    func doP1Action() -> P1 { print("I'm doing C1b's impl of a P1 Action"); return self } 
} 
class C2a: NSObject, P2 { 
    func doP2Action() -> P2 { print("I'm doing C2a's impl of a P2 Action"); return self } 
} 
class C2b: NSObject, P2 { 
    func doP2Action() -> P2 { print("I'm doing C2b's impl of a P2 Action"); return self } 
} 

var children = [ C1a(), C2b(), C1b(), C1a(), C2a() ] 

func performAction(action: Selector, forObjectsConformingTo conform: Protocol, inArray array: [AnyObject]) -> [AnyObject] { 
    return array.flatMap { $0.conformsToProtocol(conform) ? $0.performSelector(action).takeRetainedValue() : nil } 
} 


// And now the fully generic reusable logic: 
print("Performing actions on P1's:") 
performAction(#selector(P1.doP1Action), forObjectsConformingTo: P1.self, inArray: children) 

print("Performing actions on P2's:") 
performAction(#selector(P2.doP2Action), forObjectsConformingTo: P2.self, inArray: children) 

//Performing actions on P1's: 
//I'm doing C1a's impl of a P1 Action 
//I'm doing C1b's impl of a P1 Action 
//I'm doing C1a's impl of a P1 Action 
//Performing actions on P2's: 
//I'm doing C2b's impl of a P2 Action 
//I'm doing C2a's impl of a P2 Action 

Насколько рекурсия, это самая легкая часть, и ничего общего с навигацией типа исчисления или API Foundation. Вы просто поместите рекурсивный звонок в тело forEach на детей self.

+0

Спасибо, поэтому решение, по-видимому, использовало SomeProtocol.onSomeEvent при создании селектора, теперь оно работает. Хотя вы только что вспомнили, что существует NSNotificationCenter, поэтому я посмотрю, может ли это быть более полезным. +1 за предложение использовать метод flatMap (хотя фильтра достаточно) – lsauceda

+0

Вы правы, использование 'flatMap' было неэффективным. Изменен как полностью функциональный стиль, если методы что-то вычислили. – BaseZen

+0

Хм, я больше думал о (array.filter {$ 0.conformsToProtocol (соответствовать)}). ForEach {$ 0.performSelector (action)} вместо обновленного кода, потому что мне не нужен фильтрованный массив после выполнения селекторы. – lsauceda

 Смежные вопросы

  • Нет связанных вопросов^_^