Wie erstelle ich generische Protokolle in Swift?

85

Ich möchte ein Protokoll mit einer Methode erstellen, die eine generische Eingabe verwendet und einen generischen Wert zurückgibt.

Dies ist, was ich bisher versucht habe, aber es erzeugt den Syntaxfehler.

Verwendung der nicht deklarierten Kennung T.

Was mache ich falsch?

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
quelle
Bitte überprüfen Sie meine Antwort: stackoverflow.com/a/54900296/3564632
denis_lor

Antworten:

139

Bei Protokollen ist das etwas anders. Schauen Sie sich "Zugehörige Typen" in der Apple-Dokumentation an .

So verwenden Sie es in Ihrem Beispiel

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
quelle
5
Beachten Sie, dass der einzige Zweck von ApiMapperProtocol darin besteht, generische Einschränkungen zu verwenden. Es ist nicht so, dass Sie let x schreiben können: ApiMapperProtocol = UserMapper ()
Ben
17
Warum besteht Apple darauf, alles so kontraintuitiv zu machen?
Deusprogrammer
@Ben wie würde man in diesem Fall x: ApiMapperProtocol = UserMapper () erreichen lassen?
denis_lor
@denis_lor Wenn xes lokal ist, müssen Sie seinen Typ nicht explizit angeben let x = UserMapper().
Ben Leggiero
2
@ BenLeggiero Ich habe gerade herausgefunden, dass Sie Dinge wie let x tun können: ApiMapperProtocol = UserMapper (), wenn Sie eine in der mittleren generischen Klasse verwenden: stackoverflow.com/a/54900296/3564632
denis_lor
21

Um die Antwort von Lou Franco ein wenig zu ApiMapperProtocolerläutern: Wenn Sie eine Methode erstellen möchten , die eine bestimmte Methode verwendet , tun Sie dies folgendermaßen:

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

UsesApiMapperProtocolwird jetzt garantiert nur SourceTypes akzeptiert, die mit den folgenden kompatibel sind ApiMapperProtocol:

let dictionary: NSDictionary = ...
let uses = UsesApiMapperProtocol()
let userModel: UserModel = uses.usesApiMapperProtocol(UserMapper()
    source: dictionary)
Heidegrenzen
quelle
Dies ist eine sehr schöne Zusammenfassung, positiv bewertet. Ein paar dumme Fragen: Warum haben sie sich entschieden, as!statt nur asin Swift 1.2 zu verwenden? Zweitens: Können Sie mir sagen, warum wir in der Klasse, die dem Protokoll entspricht , type aliaserneut definieren müssen (dh typealias T = NSDictionary typealias U = UserModel)? Danke im Voraus.
Unheilig
Ich weiß nicht, warum sie von aszu gewechselt sind as!. Überprüfen Sie die Devforen.
Heath Borders
typealias T=NSDictionaryund typealias U=UserModelsind nicht erforderlich. Ich habe das Beispiel aktualisiert, um dies widerzuspiegeln.
Heath Borders
2
wie! um anzuzeigen, dass es fehlschlagen könnte. Macht es dem Entwickler klarer.
user965972
Es ist am Ende der Antwort.
Heath Borders
4

Um zu erreichen, dass Generika vorhanden sind und auch so deklariert let userMapper: ApiMapperProtocol = UserMapper()werden, muss eine generische Klasse vorhanden sein, die dem Protokoll entspricht, das ein generisches Element zurückgibt.

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

Jetzt können Sie auch userMapperals solche bezeichnen ApiMapper, die eine spezifische Implementierung haben in Richtung UserMapper:

let userMapper: ApiMapper = UserMapper()
let userModel: UserModel = userMapper.MapFromSource(data: ...)
denis_lor
quelle
Was bringt es in diesem Fall, ein Protokoll zu haben? Es wird nicht in der Deklaration von verwendet userMapper.
Alekop
-3

Sie können Vorlagenmethoden mit Typlöschung verwenden ...

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
quelle
2
Dieser Beitrag enthält keine Erklärung. Sie haben es auch so gepostet, wie es auf stackoverflow.com/questions/28614990/…
user1427799