не будет компилироваться. Все, что мне нужно, это понять, почему меня заставляют читать, даже если у протокола есть такое ограничение.

я есть следующий код:

import UIKit

protocol Fooable: class where Self: UIViewController {
    func foo()
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }
}

let vc1: Fooable = SampleViewController()
let vc2: Fooable = SampleViewController()


// vc1.show(vc2, sender: nil) - error: Value of type 'Fooable' has no member 'show'

// (vc1 as! UIViewController).show(vc2, sender: nil) - error: Cannot convert value of type 'Fooable' to expected argument type 'UIViewController'

(vc1 as! UIViewController).show((vc2 as! UIViewController), sender: nil)

закомментированные строки не компилируются.

Почему я вынужден привести объект типа протокола кUIViewController даже еслиFooable Протокол требует, чтобы типы, которые соответствуют ему, наследовали отUIViewController?

Ответы на вопрос(5)

Решение Вопроса

Fooable говорит компилятору, что этот конкретныйUIViewController отвечает наfoo()не меньше не больше

В обратном заключенииFooable делаетне сталиUIViewController обязательно.

ОграничениеSelf: UIViewController это просто еще одна информация для компилятора, чтобы жаловатьсяво время компиляции если затронутый класс неUIViewController

В вашем случае при аннотированииSampleViewController вFooable компилятор знает только то, чтоSampleViewController отвечает наfoo(), Он не знает, что тип на самом деле является подклассомUIViewController.

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

Однако вы можете добавитьshow метод и другие общие свойства / методы к протоколу

protocol Fooable: class where Self: UIViewController {
    func foo()
    func show(_ vc: Fooable, sender: Any?)
}

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

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

let array : [CustomStringConvertible] = ["Foo", 1, false]
array.forEach{ print("\($0)")}

Код печатает три элемента, используяdescription свойство, на которое отвечают все элементы. Компилятор распознает три элемента кактипы, которые имеютdescription свойствоне какString, Int а такжеBool.

Обновить:

В Swift 5 реализована поддержка протоколов, ограниченных суперклассом.

 vadian01 июн. 2018 г., 19:05
@ Хэмиш, я не сделал правила. Ограничение - это ограничение времени компиляции для выдачи ошибки компилятора, если уязвимый тип неUIViewController, Однако в основном соответствие протокола означает, что принимающий тип реагирует на функцию или реализовал свойство независимо от статического типа адаптера.
 Hamish01 июн. 2018 г., 18:54
ОграничениеSelf: UIViewController требует, чтобы соответствующий тип наследовал от (или был)UIViewControllerтак почему бы компилятору "не знать, что тип на самом деле является подклассомUIViewController«? Это кажется вполне разумным, чтобы позволить, так же, как вы можете относиться кP & UIViewController какUIViewController (гдеP это некоторый тип протокола). FWIW, компилятор в настоящее время не полностью поддерживает возможность ограничения классов протоколов (см.мой ответ ниже), что, как я подозреваю, является настоящей проблемой здесь.
 Hamish01 июн. 2018 г., 20:54
Конечно, я просто говорю, что правила в настоящее время здесь нарушены :) Однако я не покупаю аргумент соответствия протокола, являющийся только проверкой требований - по этой логике, протоколы с привязкой к классу (protocol P : class) не сможет иметь ссылочную семантику для значений, напечатанных какP потому что компилятор не будет знать, что принимающие типы являются классами. Но это не так - компилятор знает об ограничении и обрабатывает такие значения как экземпляры классов. Таким же образом компилятор должен знать о: UIViewController ограничивать и обрабатывать такие значения как контроллеры представления

Вам не нужно приводить кFooable введите объект при создании экземпляра вашего контроллера представления. Следующие работы:

import UIKit

protocol Fooable: class where Self: UIViewController {
    func foo()
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }
}

let vc1 = SampleViewController()
let vc2 = SampleViewController()


vc1.show(vc2, sender: nil)

Любой класс может реализовать этот протокол, но толькоUIViewController будет иметьfunc foo() метод доступен.

 zgorawski15 сент. 2017 г., 12:31
«Протоколы Swift не предназначены для использования конкретным классом»? Если бы это было правдой, эта строкаprotocol Fooable: class where Self: UIViewController не будет компилироваться. Все, что мне нужно, это понять, почему меня заставляют читать, даже если у протокола есть такое ограничение.
 jbouaziz15 сент. 2017 г., 12:00
Протоколы Swift не предназначены для использования конкретным классом, поэтому, вероятно, ваш подход не работает. Опять же, если у вас есть больше исходного кода, это поможет решить вашу проблему.
 zgorawski15 сент. 2017 г., 11:46
да, это работает, потому чтоvc1 а такжеvc2 есть сейчасSampleViewController тип. Мой пост явно упрощенный пример, например. считать, что эти объекты возвращаются из функцииfunc buildVC(vcType: VCTypeEnum) -> Fooable
 jbouaziz15 сент. 2017 г., 11:48
@zgorawski Можете ли вы опубликовать больше исходного кода? Без подробностей вам будет очень сложно помочь.
 zgorawski15 сент. 2017 г., 11:49
Также нет, ни один класс не может реализовать этот протокол, проверьте это:ibb.co/ij1Wik

требование класса здесь избыточно, поскольку ваш протокол требует, чтобы любой Fooable расширял UIViewController, который является классом.

Во-вторых, это похоже на какой-то упущение со стороны команды Swift, потому что это работает, хотя все, что doStuff знает о своих аргументах, это то, что они реализуют Fooable, предполагая, что ваш коддолжен просто работать:

class Strawman {
    let name: String
    public func bar(_ x: Strawman) {
        print("\(name) bars \(x.name) from entering.")
    }
    public init(name: String) {
        self.name = name
    }
}

protocol Fooable where Self: Strawman {
    func foo()
}

class StrawFooable: Strawman, Fooable {
    public func foo() { print("Foo!") }
}

let sm1 = StrawFooable(name: "Strawman1")
let sm2 = StrawFooable(name: "Strawman2")

// This will not compile if you define doStuff as
// func doStuff(with x: Fooable, and y: Fooable) {
func doStuff<T: Fooable>(with x: T, and y: T) {
    x.bar(y)
    x.foo()
    y.bar(x)
    y.foo()
}

// This will not compile if you annotate sm1 and sm2 as Fooable.
doStuff(with: sm1, and: sm2)

Моя рекомендация?Подайте отчет об ошибке.

PS. В качестве бонуса WTF, если вы добавите соответствие базовому классу с расширением, компилятор рухнет! Я имею в виду, что в этом нет особого смысла, но на самом деле это не должно вызывать сбой компилятора.

я протоколов, то есть возможность определятьprotocol P where Self : C гдеC это тип класса.

Тот факт, что компилятор не мешает вам делать это до тех пор, пока эта функция на самом деле не реализована, был упущением, как сказал инженер компилятора Swift Слава Пестов:

Слава Пестов добавил комментарий - 31 мая 2018 13:19

[...] «протокол P: Foo, где Self: Class» был обнаружен пользователями случайно, и он не работает полностью. Это был недосмотр, что его не запретили.

Однако это функция, которая предназначена для полной реализации в будущей версии языка как частьSE-0156.

Слава Пестов добавил комментарий - 31 мая 2018 13:19

Оба должны работать, но мы еще не полностью реализовали это предложение.

(редактировать: Слава сейчас реализовал это в# 17611, # 17651, # 17816 & # 17851Таким образом, вы получите их в Swift 5 - вы можете попробоватьглавный снимок или Xcode 10.2 beta тем временем)

После внедрения вы сможете рассматривать такой тип протокола как тип класса, которому требуются соответствующие типы для наследования (например, позволяя обрабатыватьFooable какUIViewController без приведения), так же, как вы можете обрабатывать экзистенциальный класс, такой какFooable & UIViewController какUIViewController.

Не только это, но вы также сможете указать требование суперкласса непосредственно в протоколе, а не вwhere пункт, например:

protocol Fooable : UIViewController {
    func foo()
}

Однако до Swift 5 я бы порекомендовал рулевое управлениехорошо ясно протоколов с ограниченным суперклассом - в настоящее время они имеют некоторые неприятные неровности вокруг них.

Например, это приведет к неправильной компиляции и падению во время выполнения в Swift 4.1:

class C : P {
  func speak() {}
}

protocol P where Self : C {
  func speak()
}

let c: P = C()
c.speak()

и это приведет к падению компилятора в более поздних версиях языка (SR-6816).

В качестве обходного пути вы можете использовать подчеркнутый протокол с экзистенциальными typealias класса, чтобы вместо этого применить ограничение класса. Например:

import UIKit

protocol _Fooable : class {
  func foo()
}

typealias Fooable = _Fooable & UIViewController

class SampleViewController : Fooable /* implicitly : UIViewController */ {
  func foo() {
    print("foo")
  }
}

// ...

let vc1: Fooable = SampleViewController()
let vc2: Fooable = SampleViewController()
vc1.show(vc2, sender: nil)

Обычный шаблон - делать это так:

protocol Fooable {
    func foo()
    var viewController: UIViewController
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }

    var viewController: UIViewController { return self }
}

В Swift 4 вы можете делать переменные типаUIViewController & Fooable, В Swift 3 используйте вышеуказанный трюк.

Ваш ответ на вопрос