Общий механизм диспетчеризации, использующий тест и селектор на соответствие протоколу во время выполнения.

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

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

И так в GameSceneI есть такие функции как:

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 недоступны), или должен Я просто копирую код в каждом событии, которое хочу переслать.


person lsauceda    schedule 18.08.2016    source источник


Ответы (1)


Так что это было интересно и довольно сложно. Ключевым моментом является проверка типа протокола runtime, переданного в качестве параметра, — это скорее метаданные, что невозможно в Swift, который использует проверку соответствия во время компиляции для эффективности и безопасности. Итак, прежде всего вам нужно заземлить все в среде выполнения Obj-C, и ключом здесь является метод conformsToProtocol. Если вам нужна такая очень общая отправка, я бы не стал передавать более 2 аргументов: вместо этого передайте какой-то контейнер (например, NSNotification userInfo: 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.

person BaseZen    schedule 19.08.2016
comment
Спасибо, значит, решение, по-видимому, использовало SomeProtocol.onSomeEvent при создании селектора, теперь оно работает. Хотя вы только что вспомнили, что NSNotificationCenter существует, так что я посмотрю, может ли это быть более полезным. +1 за предложение использовать метод flatMap (хотя фильтра достаточно) - person lsauceda; 20.08.2016
comment
Вы правы, использование flatMap было неэффективным. Изменено на полностью функциональный стиль на случай, если методы что-то вычислят. - person BaseZen; 20.08.2016
comment
Хм, я больше думал о (array.filter { $0.conformsToProtocol(conform) }).forEach { $0.performSelector(action) } вместо кода, который вы обновили, потому что мне не нужен отфильтрованный массив после выполнения селекторов . - person lsauceda; 20.08.2016