Класс, соответствующий протоколу как параметр функции в Swift

91

В Objective-C можно указать класс, соответствующий протоколу, в качестве параметра метода. Например, у меня мог бы быть метод, который позволяет только a, UIViewControllerкоторый соответствует UITableViewDataSource:

- (void)foo:(UIViewController<UITableViewDataSource> *)vc;

Я не могу найти способ сделать это в Swift (возможно, пока это невозможно). Вы можете указать несколько протоколов func foo(obj: protocol<P1, P2>), но как вы требуете, чтобы объект также принадлежал к определенному классу?

Мартин Гордон
источник
Вы можете создать собственный класс, например MyViewControllerClass, и убедиться, что этот класс соответствует протоколу, который вам нужен. Затем объявите, что аргумент принимает этот настраиваемый класс. Я понимаю, что это не сработает для каждой ситуации, но это способ ... не ответ на ваш вопрос. Скорее обходной путь.
CommaToast

Ответы:

133

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

Swift 4

func foo<T: UIViewController & UITableViewDataSource>(vc: T) {
    .....
}

Swift 3 (также работает для Swift 4)

func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { 
    ....
}

Swift 2

func foo<T: UIViewController where T: UITableViewDataSource>(vc: T) {
    // access UIViewController property
    let view = vc.view
    // call UITableViewDataSource method
    let sections = vc.numberOfSectionsInTableView?(tableView)
}
Нейт Кук
источник
3
Я считаю, что это немного прискорбно, что это требуется. Надеюсь, что в будущем для этого будет более чистый синтаксис, например protocol<>предоставляет (но protocol<>не может содержать типы, не относящиеся к протоколу).
jtbandes
Это меня оооочень грустно.
DCMaxxx,
Просто из любопытства, не можете ли вы явно развернуть, numberOfSectionsInTableViewпотому что это обязательная функция UITableViewDataSource?
rb612
numberOfSectionsInTableView:необязательно - возможно, вы думаете tableView:numberOfRowsInSection:.
Nate Cook
11
В Swift 3 это, по-видимому, устарело с Xcode 8 beta 6 с предпочтением:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke
29

В Swift 4 этого можно добиться с помощью нового знака &:

let vc: UIViewController & UITableViewDataSource
Йерун Баккер
источник
17

Документация по книге Swift предлагает вам использовать ограничения типа с предложением where:

func someFunction<C1: SomeClass where C1:SomeProtocol>(inParam: C1) {}

Это гарантирует, что inParam имеет тип SomeClass с условием, что он также соответствует SomeProtocol. У вас даже есть возможность указать несколько предложений where, разделенных запятой:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }
Джони Т
источник
1
Ссылку на документацию было бы неплохо увидеть.
Радж
4

С Swift 3 вы можете делать следующее:

func foo(_ dataSource: UITableViewDataSource) {
    self.tableView.dataSource = dataSource
}

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}
Калзем
источник
1
Это относится только к протоколам, а не к протоколу и классу в Swift 3.
Артем Горяев
3

Swift 5:

func foo(vc: UIViewController & UITableViewDataSource) {
    ...
}

Таким образом , по существу , Jeroen ответ «S выше.

Willtherussian
источник
2

А как насчет этого ?:

protocol MyProtocol {
    func getTableViewDataSource() -> UITableViewDataSource
    func getViewController() -> UIViewController
}

class MyVC : UIViewController, UITableViewDataSource, MyProtocol {

    // ...

    func getTableViewDataSource() -> UITableViewDataSource {
        return self
    }

    func getViewController() -> UIViewController {
        return self
    }
}

func foo(_ vc:MyProtocol) {
    vc.getTableViewDataSource() // working with UITableViewDataSource stuff
    vc.getViewController() // working with UIViewController stuff
}
MuHAOS
источник
0

Примечание от сентября 2015 года : это было наблюдение в первые дни Swift.

Это кажется невозможным. У Apple есть это раздражение и в некоторых своих API. Вот один пример из недавно представленного класса в iOS 8 (начиная с бета 5):

UIInputViewController«S textDocumentProxyсобственности:

В Objective-C определено следующим образом:

@property(nonatomic, readonly) NSObject<UITextDocumentProxy> *textDocumentProxy;

и в Swift:

var textDocumentProxy: NSObject! { get }

Ссылка на документацию Apple: https://developer.apple.com/library/prerelease/iOS/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/textDocumentProxy

Клаас
источник
1
Кажется, что это сгенерировано автоматически: протоколы Swift можно передавать как объекты. Теоретически они могли просто набратьvar textDocumentProxy: UITextDocumentProxy! { get }
atlex2
@ atlex2 Вы потеряли тип класса NSObject в пользу типа протокола UITextDocumentProxy.
titaniumdecoy
@titaniumdecoy Нет, ты ошибаешься; у вас все еще есть NSObject, если UITextDocumentProxy объявлен, как и большинство протоколов:@protocol MyAwesomeCallbacks <NSObject>
CommaToast
@CommaToast Не в Swift, вот о чем этот вопрос.
titaniumdecoy
@titaniumdecoy Да, изначально ты был прав. Я был сбит с толку! Извините, что вы ошибались. С другой стороны, у вас все еще есть NSObjectProtocol ... в этом случае ... но я знаю, что это не то же самое.
CommaToast