Classe em conformidade com o protocolo como parâmetro de função em Swift

91

Em Objective-C, é possível especificar uma classe em conformidade com um protocolo como parâmetro de método. Por exemplo, eu poderia ter um método que permite apenas um UIViewControllerque esteja em conformidade com UITableViewDataSource:

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

Não consigo encontrar uma maneira de fazer isso no Swift (talvez ainda não seja possível). Você pode especificar vários protocolos usando func foo(obj: protocol<P1, P2>), mas como você exige que o objeto também seja de uma determinada classe?

Martin Gordon
fonte
Você poderia fazer uma classe personalizada, por exemplo MyViewControllerClass, e certificar-se de que a classe está em conformidade com o protocolo que você deseja. Em seguida, declare que o argumento aceita essa classe personalizada. Sei que não funcionaria para todas as situações, mas é uma maneira ... não uma resposta à sua pergunta. Mais uma solução alternativa.
CommaToast de

Respostas:

132

Você pode definir foocomo uma função genérica e usar restrições de tipo para exigir uma classe e um protocolo.

Swift 4

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

Swift 3 (funciona também para 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)
}
Nate Cook
fonte
3
Acho um pouco lamentável que isso seja necessário. Esperançosamente, no futuro haverá uma sintaxe mais limpa para isso, como protocol<>fornece (mas protocol<>não pode conter tipos que não sejam de protocolo).
jtbandes de
Isso me deixa tãããão triste.
DCMaxxx
Só por curiosidade, você não pode desembrulhar explicitamente numberOfSectionsInTableViewporque é uma função necessária no UITableViewDataSource?
rb612
numberOfSectionsInTableView:é opcional - você pode estar pensando tableView:numberOfRowsInSection:.
Nate Cook
11
No Swift 3, ele parece estar obsoleto a partir do Xcode 8 beta 6, com preferência por:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke
29

No Swift 4, você pode conseguir isso com o novo & sinal:

let vc: UIViewController & UITableViewDataSource
Jeroen Bakker
fonte
17

A documentação do livro Swift sugere que você use restrições de tipo com uma cláusula where:

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

Isso garante que "inParam" seja do tipo "SomeClass" com a condição de que também adere a "SomeProtocol". Você ainda tem o poder de especificar várias cláusulas where delimitadas por uma vírgula:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }
Jon Tsiros
fonte
1
Seria bom ver o link para a documentação.
Raj
4

Com o Swift 3, você pode fazer o seguinte:

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

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}
Kalzem
fonte
1
Isso se aplica apenas a protocolos, não protocolo e classe no swift 3.
Artem Goryaev
2

E desta forma ?:

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
fonte
2

Swift 5:

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

Então, essencialmente , a resposta de Jeroen acima.

Willtherussian
fonte
0

Nota em setembro de 2015 : Esta foi uma observação nos primeiros dias do Swift.

Parece impossível. A Apple também tem esse aborrecimento em algumas de suas APIs. Aqui está um exemplo de uma classe recém-introduzida no iOS 8 (a partir do beta 5):

UIInputViewController's textDocumentProxypropriedade:

Definido em Objective-C da seguinte forma:

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

e em Swift:

var textDocumentProxy: NSObject! { get }

Link para a documentação da Apple: https://developer.apple.com/library/prerelease/iOS/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/textDocumentProxy

Klaas
fonte
1
Isso parece gerado automaticamente: os protocolos Swift podem ser passados ​​como objetos. Teoricamente, eles poderiam apenas digitarvar textDocumentProxy: UITextDocumentProxy! { get }
atlex2
@ atlex2 Você perdeu o tipo de classe NSObject em favor do tipo de protocolo UITextDocumentProxy.
titaniumdecoy
@titaniumdecoy Não, você está errado; você ainda tem NSObject se UITextDocumentProxy for declarado como a maioria dos protocolos são:@protocol MyAwesomeCallbacks <NSObject>
CommaToast
@CommaToast Não em Swift, que é sobre o que trata esta pergunta.
titaniumdecoy
@titaniumdecoy Sim, você estava certo originalmente. Eu estava confuso! Desculpe dizer que você estava errado. Por outro lado, você ainda tem NSObjectProtocol ... neste caso ... mas eu sei que não é a mesma coisa.
CommaToast