Come creare protocolli generici in Swift?


85

Vorrei creare un protocollo con un metodo che accetta un input generico e restituisce un valore generico.

Questo è quello che ho provato finora, ma produce l'errore di sintassi.

Uso dell'identificatore non dichiarato T.

Che cosa sto facendo di sbagliato?

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

Si prega di verificare la mia risposta: stackoverflow.com/a/54900296/3564632
denis_lor

Risposte:


143

È un po 'diverso per i protocolli. Guarda "Tipi associati" nella documentazione di Apple .

Ecco come lo usi nel tuo esempio

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

5
Si noti che l'unico scopo di ApiMapperProtocol deve essere utilizzato per vincoli generici. Non è come se si potesse scrivere let x: ApiMapperProtocol = UserMapper ()
Ben

19
Perché Apple insiste nel rendere tutto così controintuitivo?
deusprogrammer

@ Ben come si potrebbe ottenere let x: ApiMapperProtocol = UserMapper () in questo caso?
denis_lor

@denis_lor se xè locale, non è necessario dire esplicitamente il suo tipo, quindi let x = UserMapper().
Ben Leggiero

2
@BenLeggiero Ho appena scoperto si possono fare cose come let x: = ApiMapperProtocol UserMapper () se si utilizza una nella classe generica centrale: stackoverflow.com/a/54900296/3564632
denis_lor

21

Per esporre un po ' la risposta di Lou Franco , se si voleva creare un metodo che usasse un particolare ApiMapperProtocol, lo si fa così:

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

UsesApiMapperProtocolora è garantito che accetti solo messaggi SourceTypecompatibili con il dato ApiMapperProtocol:

let dictionary: NSDictionary = ...
let uses = UsesApiMapperProtocol()
let userModel: UserModel = uses.usesApiMapperProtocol(UserMapper()
    source: dictionary)

Questo è un articolo molto carino, votato positivamente. Un paio di domande sciocche: perché hanno deciso di utilizzare as!invece che solo asin Swift 1.2? Secondo: potresti dirmi perché dobbiamo definire di type aliasnuovo (cioè typealias T = NSDictionary typealias U = UserModel) nella classe conforme al protocollo? Grazie in anticipo.
Unheilig

Non so perché siano passati da asa as!. Controlla i devforum.
Heath Borders

typealias T=NSDictionarye typealias U=UserModelnon sono obbligatori. Ho aggiornato l'esempio per riflettere questo.
Heath Borders

2
come! per indicare che potrebbe non riuscire. Rende tutto più chiaro allo sviluppatore.
user965972

È in fondo alla risposta.
Heath Borders

4

Per ottenere generici e anche dichiararli in questo modo let userMapper: ApiMapperProtocol = UserMapper()è necessario disporre di una classe generica conforme al protocollo che restituisce un elemento generico.

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

Ora puoi anche fare riferimento a userMapperun ApiMapperche ha un'implementazione specifica verso UserMapper:

let userMapper: ApiMapper = UserMapper()
let userModel: UserModel = userMapper.MapFromSource(data: ...)

1
Qual è il punto di avere un protocollo in questo caso? Non è utilizzato nella dichiarazione di userMapper.
alekop

-1

COME CREARE E UTILIZZARE IL PROTOCOLLO GENERICO

protocollo Generic {

associatedtype T
associatedtype U

func operation(_ t:T)->U

}

// usa il protocollo generico

struct Test: Generic {

typealias T = UserModel
typealias U = Any

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

}


-3

Puoi usare i metodi dei modelli con la cancellazione del 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"))

2
Questo post non contiene spiegazioni. Hai anche pubblicato come è su stackoverflow.com/questions/28614990/...
user1427799
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.