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?


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)
}

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

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 }

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
}

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
}

2

Swift 5:

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

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


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.