Como criar protocolos genéricos em Swift?

85

Eu gostaria de criar um protocolo com um método que leva uma entrada genérica e retorna um valor genérico.

Isso é o que tentei até agora, mas produz o erro de sintaxe.

Uso de identificador não declarado T.

O que estou fazendo errado?

protocol ApiMapperProtocol {
    func MapFromSource(T) -> U
}

class UserMapper: NSObject, ApiMapperProtocol {
    func MapFromSource(data: NSDictionary) -> UserModel {
        var user = UserModel() as UserModel
        var accountsData:NSArray = data["Accounts"] as NSArray     
        return user
    } 
}
Farhad-Taran
fonte
Verifique minha resposta: stackoverflow.com/a/54900296/3564632
denis_lor

Respostas:

141

É um pouco diferente para protocolos. Veja "Tipos associados" na documentação da Apple .

É assim que você usa em seu exemplo

protocol ApiMapperProtocol {
    associatedtype T
    associatedtype U
    func MapFromSource(_:T) -> U
}

class UserMapper: NSObject, ApiMapperProtocol {
    typealias T = NSDictionary
    typealias U = UserModel

    func MapFromSource(_ data:NSDictionary) -> UserModel {
        var user = UserModel()
        var accountsData:NSArray = data["Accounts"] as NSArray
        // For Swift 1.2, you need this line instead
        // var accountsData:NSArray = data["Accounts"] as! NSArray
        return user
    }
}
Lou franco
fonte
5
Observe que o único propósito de ApiMapperProtocol é ser usado para restrição genérica. Não é como se você pudesse escrever let x: ApiMapperProtocol = UserMapper ()
Ben
18
Por que a Apple insiste em tornar tudo tão contra-intuitivo?
deusprogrammer
@Ben, como alguém conseguiria deixar x: ApiMapperProtocol = UserMapper () neste caso?
denis_lor
@denis_lor se xfor local, então você não precisa dizer explicitamente seu tipo let x = UserMapper().
Ben Leggiero
2
@BenLeggiero Acabei de descobrir que você pode fazer coisas como let x: ApiMapperProtocol = UserMapper () se estiver usando um na classe genérica do meio: stackoverflow.com/a/54900296/3564632
denis_lor
21

Para expor um pouco a resposta de Lou Franco , se você quisesse criar um método que usasse um determinado ApiMapperProtocol, faça-o da seguinte maneira:

protocol ApiMapperProtocol {
    associatedtype T
    associatedtype U
    func mapFromSource(T) -> U
}

class UserMapper: NSObject, ApiMapperProtocol {
    // these typealiases aren't required, but I'm including them for clarity
    // Normally, you just allow swift to infer them
    typealias T = NSDictionary 
    typealias U = UserModel

    func mapFromSource(data: NSDictionary) -> UserModel {
        var user = UserModel()
        var accountsData: NSArray = data["Accounts"] as NSArray
        // For Swift 1.2, you need this line instead
        // var accountsData: NSArray = data["Accounts"] as! NSArray
        return user
    }
}

class UsesApiMapperProtocol {
    func usesApiMapperProtocol<
        SourceType,
        MappedType,
        ApiMapperProtocolType: ApiMapperProtocol where
          ApiMapperProtocolType.T == SourceType,
          ApiMapperProtocolType.U == MappedType>(
          apiMapperProtocol: ApiMapperProtocolType, 
          source: SourceType) -> MappedType {
        return apiMapperProtocol.mapFromSource(source)
    }
}

UsesApiMapperProtocolagora tem a garantia de aceitar apenas SourceTypes compatíveis com o fornecido ApiMapperProtocol:

let dictionary: NSDictionary = ...
let uses = UsesApiMapperProtocol()
let userModel: UserModel = uses.usesApiMapperProtocol(UserMapper()
    source: dictionary)
Heath Borders
fonte
Este é um artigo muito bom, votado. Algumas perguntas tolas: por que eles decidiram usar em as!vez de apenas asno Swift 1.2? Segundo: você poderia me dizer por que precisamos definir type aliasnovamente (ou seja, typealias T = NSDictionary typealias U = UserModel) na classe que está em conformidade com o protocolo? Desde já, obrigado.
Unheilig
Não sei por que mudaram de aspara as!. Verifique os devforums.
Heath Borders,
typealias T=NSDictionarye typealias U=UserModelnão são necessários. Eu atualizei o exemplo para refletir isso.
Heath Borders,
2
Como! para indicar que pode falhar. Torna mais claro para o desenvolvedor.
user965972
Está na base da resposta.
Heath Borders
4

Para conseguir ter genéricos e assim declarar let userMapper: ApiMapperProtocol = UserMapper()você tem que ter uma Classe Genérica em conformidade com o protocolo que retorna um elemento genérico.

protocol ApiMapperProtocol {
    associatedtype I
    associatedType O
    func MapFromSource(data: I) -> O
}

class ApiMapper<I, O>: ApiMapperProtocol {
    func MapFromSource(data: I) -> O {
        fatalError() // Should be always overridden by the class
    }
}

class UserMapper: NSObject, ApiMapper<NSDictionary, UserModel> {
    override func MapFromSource(data: NSDictionary) -> UserModel {
        var user = UserModel() as UserModel
        var accountsData:NSArray = data["Accounts"] as NSArray     
        return user
    } 
}

Agora você também pode se referir a userMappercomo um ApiMapperque tem uma implementação específica para UserMapper:

let userMapper: ApiMapper = UserMapper()
let userModel: UserModel = userMapper.MapFromSource(data: ...)
denis_lor
fonte
Qual é o sentido de ter um protocolo neste caso? Não é usado na declaração de userMapper.
alekop de
-1

COMO CRERAR E USAR PROTOCOLO GENÉRICO

protocolo Genérico {

associatedtype T
associatedtype U

func operation(_ t:T)->U

}

// use protocolo genérico

Teste de estrutura: Genérico {

typealias T = UserModel
typealias U = Any

func operation(_ t: UserModel)->Any {
    let dict = ["name":"saurabh"]
    return dict
    
} 

}

Saurabh Sharma
fonte
-3

Você pode usar métodos de modelos com apagamento de tipo ...

protocol HeavyDelegate : class {
  func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R
}  

class Heavy<P, R> {
    typealias Param = P
    typealias Return = R
    weak var delegate : HeavyDelegate?  
    func inject(p : P) -> R? {  
        if delegate != nil {
            return delegate?.heavy(self, shouldReturn: p)
        }  
        return nil  
    }
    func callMe(r : Return) {
    }
}
class Delegate : HeavyDelegate {
    typealias H = Heavy<(Int, String), String>

    func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R {
        let h = heavy as! H
        h.callMe("Hello")
        print("Invoked")
        return "Hello" as! R
    }  
}

let heavy = Heavy<(Int, String), String>()
let delegate = Delegate()
heavy.delegate = delegate
heavy.inject((5, "alive"))
Dsjove
fonte
2
Esta postagem não contém nenhuma explicação. Você também postou como está em stackoverflow.com/questions/28614990/…
user1427799