Clase conforme al protocolo como parámetro de función en Swift

91

En Objective-C, es posible especificar una clase conforme a un protocolo como parámetro de método. Por ejemplo, podría tener un método que solo permita un UIViewControllerque se ajuste a UITableViewDataSource:

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

No puedo encontrar una manera de hacer esto en Swift (quizás aún no sea posible). Puede especificar varios protocolos usando func foo(obj: protocol<P1, P2>), pero ¿cómo se requiere que el objeto también sea de una clase en particular?

Martin Gordon
fuente
Puede crear una clase personalizada, por ejemplo, MyViewControllerClass, y asegurarse de que la clase se ajuste al protocolo que le interesa. Luego declare que el argumento acepta esa clase personalizada. Me doy cuenta de que no funcionaría en todas las situaciones, pero es una forma ... aunque no una respuesta a tu pregunta. Más una solución.
CommaToast

Respuestas:

132

Puede definir foocomo una función genérica y usar restricciones de tipo para requerir tanto una clase como un protocolo.

Rápido 4

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

Swift 3 (también funciona 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
fuente
3
Creo que es un poco desafortunado que esto sea necesario. Con suerte, en el futuro habrá una sintaxis más limpia para esto, como protocol<>proporciona (pero protocol<>no puede contener tipos que no sean de protocolo).
jtbandes
Esto me pone taaaan triste.
DCMaxxx
Solo por curiosidad, ¿no puede desenvolver explícitamente numberOfSectionsInTableViewporque es una función requerida en UITableViewDataSource?
rb612
numberOfSectionsInTableView:es opcional, es posible que esté pensando en tableView:numberOfRowsInSection:.
Nate Cook
11
En Swift 3, esto parece estar obsoleto a partir de Xcode 8 beta 6 con preferencia por:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke
29

En Swift 4 puedes lograr esto con el nuevo signo &:

let vc: UIViewController & UITableViewDataSource
Jeroen Bakker
fuente
17

La documentación del libro Swift sugiere que use restricciones de tipo con una cláusula where:

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

Esto garantiza que "inParam" es de tipo "SomeClass" con la condición de que también se adhiera a "SomeProtocol". Incluso tiene el poder de especificar múltiples cláusulas where delimitadas por una coma:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }
Jon Tsiros
fuente
1
Hubiera sido bueno ver el enlace a la documentación.
Raj
4

Con Swift 3, puede hacer lo siguiente:

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

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}
Kalzem
fuente
1
Esto solo se aplica a los protocolos, no al protocolo y la clase en
Swift
2

¿Y de esta manera ?:

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

Rápido 5:

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

Entonces, esencialmente , la respuesta de Jeroen anterior.

willtherussian
fuente
0

Nota en septiembre de 2015 : esta fue una observación en los primeros días de Swift.

Parece imposible. Apple también tiene esta molestia en algunas de sus API. Aquí hay un ejemplo de una clase recién introducida en iOS 8 (a partir de la versión beta 5):

UIInputViewController's textDocumentProxypropiedad:

Definido en Objective-C como sigue:

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

y en Swift:

var textDocumentProxy: NSObject! { get }

Enlace a la documentación de Apple: https://developer.apple.com/library/prerelease/iOS/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/textDocumentProxy

Klaas
fuente
1
Esto parece generado automáticamente: los protocolos Swift se pueden pasar como objetos. En teoría, solo podrían escribirvar textDocumentProxy: UITextDocumentProxy! { get }
atlex2
@ atlex2 Ha perdido el tipo de clase NSObject a favor del tipo de protocolo UITextDocumentProxy.
titaniumdecoy
@titaniumdecoy No, estás equivocado; todavía tiene NSObject si se declara UITextDocumentProxy como la mayoría de los protocolos:@protocol MyAwesomeCallbacks <NSObject>
CommaToast
@CommaToast No en Swift, que es de lo que trata esta pregunta.
titaniumdecoy
@titaniumdecoy Sí, originalmente tenías razón. ¡Estaba confundido! Siento decirle que estaba equivocado. Por el lado positivo, todavía tiene NSObjectProtocol ... en este caso ... pero sé que no es lo mismo.
CommaToast