Функция, которая принимает в качестве параметров протокол и соответствующий экземпляр класса (!)

Я пытаюсь понять, как определить функцию, которая принимает следующие два параметра:

  1. Протокол.
  2. Экземпляр класса (ссылочный тип), соответствующий этому протоколу.

Например, учитывая

protocol P { }
class C : P { } // Class, conforming to P
class D { }     // Class, not conforming to P
struct E: P { } // Struct, conforming to P

это должно скомпилироваться:

register(proto: P.self, obj: C()) // (1)

но они не должны компилироваться:

register(proto: P.self, obj: D()) // (2)  D does not conform to P
register(proto: P.self, obj: E()) // (3)  E is not a class

Это легко, если мы отбросим условие, что второй параметр является экземпляром класса:

func register<T>(proto: T.Type, obj: T) {
    // ...
}

но это также примет структуру (тип значения) в (3). Это выглядело многообещающим и компилируется

func register<T: AnyObject>(proto: T.Type, obj: T) {
    // ...
}

но тогда ни один из (1), (2), (3) больше не компилируется, например

register(proto: P.self, obj: C()) // (1)
// error: cannot invoke 'register' with an argument list of type '(P.Protocol, obj: C)'

Я предполагаю, что причина ошибки компилятора та же, что и в протоколе не соответствует себе?.

Еще одна неудачная попытка

func register<T>(proto: T.Type, obj: protocol<T, AnyObject>) { }
// error: non-protocol type 'T' cannot be used within 'protocol<...>'

Жизнеспособной альтернативой была бы функция, которая принимает в качестве параметров

  1. Протокол класса.
  2. Экземпляр типа, соответствующего этому протоколу.

Здесь проблема в том, как ограничить первый параметр так, чтобы принимались только протоколы классов.

История вопроса. Недавно я наткнулся на проект SwiftNotificationCenter, который реализует ориентированный на протокол , введите безопасный механизм уведомления. У него есть метод register, который выглядит следующим образом:

public class NotificationCenter {

    public static func register<T>(protocolType: T.Type, observer: T) {
        guard let object = observer as? AnyObject else {
            fatalError("expecting reference type but found value type: \(observer)")
        }

        // ...
    }

    // ...
}

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

Я упустил что-то простое / очевидное?


person Martin R    schedule 08.06.2016    source источник
comment
Не идеально (или именно ваш вопрос), но сделает ли это определение protocol X: class {} более безопасным?   -  person sschale    schedule 08.06.2016
comment
Я замечал такое поведение раньше - как только вы ограничиваете общий тип, похоже, Swift больше не позволяет ему принимать абстрактный тип. В этом случае T будет P, что не является конкретным типом. Я также заметил подобное поведение с типами, связанными с протоколом (как только вы ограничите их, они могут принимать только конкретные типы) - на самом деле существует сообщить об этом.   -  person Hamish    schedule 08.06.2016
comment
@sschale: Это помогло бы, если бы вы могли ограничить функцию, чтобы она принимала только протоколы классов (что мне тоже не удалось).   -  person Martin R    schedule 08.06.2016
comment
Это случай GMTA, я тоже хочу придумать какое-то расширение, которое позволяет вам зарегистрироваться в качестве слушателя протокола.   -  person Fattie    schedule 09.06.2016


Ответы (1)


Вы не можете делать то, что пытаетесь сделать напрямую. Это не имеет ничего общего со ссылочными типами, потому что любые ограничения делают T экзистенциальными, поэтому невозможно удовлетворить их на сайте вызова, когда вы ссылаетесь на метатип протокола P.self: P.Protocol и адепта C. Есть особый случай, когда T не ограничен, что позволяет ему работать в первую очередь.

Гораздо более распространенным случаем является ограничение T: P и требование P: class, потому что единственное, что вы можете сделать с метатипом произвольного протокола, - это преобразовать имя в строку. Это может быть полезно в этом узком случае, но это все; подпись с таким же успехом могла бы быть register<T>(proto: Any.Type, obj: T) для всего хорошего, на что она способна.

Теоретически Swift может поддерживать ограничение метатипов, ala register<T: AnyObject, U: AnyProtocol where T.Type: U>(proto: U, obj: T), но я сомневаюсь, что это будет полезно во многих сценариях.

person russbishop    schedule 09.06.2016