99

I'd like to create a protocol with a method that takes a generic input and returns a generic value.

This is what I've tried so far, but it produces the syntax error.

Use of undeclared identifier T.

What am I doing wrong?

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
    } 
}
idmean
  • 14,540
  • 9
  • 54
  • 83
Farhad-Taran
  • 6,282
  • 15
  • 67
  • 121

5 Answers5

163

It's a little different for protocols. Look at "Associated Types" in Apple's documentation.

This is how you use it in your example

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
    }
}
Saikiran Komirishetty
  • 6,525
  • 1
  • 29
  • 36
Lou Franco
  • 87,846
  • 14
  • 132
  • 192
  • 5
    Note that the sole purpose of ApiMapperProtocol is to be used for generic constraint. It's not like you can write let x: ApiMapperProtocol = UserMapper() – Ben Jun 17 '15 at 04:20
  • 31
    Why does Apple insist on making everything so counter intuitive? – deusprogrammer Apr 21 '16 at 18:49
  • @Ben how would one achieve let x: ApiMapperProtocol = UserMapper() in this case? – denis_lor Feb 25 '19 at 14:52
  • @denis_lor if `x` is local, then you don't need to explicitly say its type, so `let x = UserMapper()`. – Ky - Feb 25 '19 at 21:16
  • What about DI and making it possible to create a mock out of it so it can be tested as well? I actually think associated types cant help here. I think we need to play with Type Wrapping instead – denis_lor Feb 25 '19 at 21:28
  • 2
    @BenLeggiero I just found out you can do things like let x: ApiMapperProtocol = UserMapper() if using a in the middle generic class: https://stackoverflow.com/a/54900296/3564632 – denis_lor Feb 27 '19 at 07:41
  • Why can't we use protocol ApiMapperProtocol for this? Since this is more intuitive and other generics in the language follow this syntax. – Sourav Kannantha B Feb 07 '21 at 12:50
  • Here's an answer why such syntax: https://stackoverflow.com/questions/26554987/why-dont-associated-types-for-protocols-use-generic-type-syntax-in-swift – Lubiluk May 27 '21 at 11:30
  • With Swift 5.6 we are finally able to use it with `any` keyword: `let x: any ApiMapperProtocol = UserMapper()` – Ruslan Mansurov Feb 06 '23 at 03:24
27

To expound on Lou Franco's answer a bit, If you wanted to create a method that used a particular ApiMapperProtocol, you do so thusly:

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

UsesApiMapperProtocol is now guaranteed to only accept SourceTypes compatible with the given ApiMapperProtocol:

let dictionary: NSDictionary = ...
let uses = UsesApiMapperProtocol()
let userModel: UserModel = uses.usesApiMapperProtocol(UserMapper()
    source: dictionary)
Heath Borders
  • 30,998
  • 16
  • 147
  • 256
  • This is a very nice write-up, upvoted. A couple of foolish questions: why did they decide on using `as!` instead of just `as` in Swift 1.2? Second: could you tell me why we need to define `type alias` again (i.e., `typealias T = NSDictionary typealias U = UserModel` ) in the class that conforms to the protocol? Thanks in advance. – Unheilig Apr 17 '15 at 08:25
  • I don't know why they switched from `as` to `as!`. Check the devforums. – Heath Borders Apr 17 '15 at 15:44
  • `typealias T=NSDictionary` and `typealias U=UserModel` aren't required. I updated the example to reflect that. – Heath Borders Apr 17 '15 at 15:45
  • 3
    as! to indicate it might fail. Makes it clearer to the developer. – user965972 Apr 30 '15 at 09:49
  • It's at the bottom of the answer. – Heath Borders Jun 09 '15 at 20:43
5

In order to achieve having generics and as well having it declare like this let userMapper: ApiMapperProtocol = UserMapper() you have to have a Generic Class conforming to the protocol which returns a generic element.

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

Now you can also refer to userMapper as an ApiMapper which have a specific implementation towards UserMapper:

let userMapper: ApiMapper = UserMapper()
let userModel: UserModel = userMapper.MapFromSource(data: ...)
denis_lor
  • 6,212
  • 4
  • 31
  • 55
  • 5
    What's the point of having a protocol in this case? It's not used in the declaration of `userMapper`. – alekop Jun 07 '19 at 19:26
0

How to create and use generic Protocol:

protocol Generic {
    
    associatedtype T
    associatedtype U

    func operation(_ t: T) -> U
}


// Use Generic Protocol

struct Test: Generic {

    typealias T = UserModel
    typealias U = Any
    
    func operation(_ t: UserModel) -> Any {
        let dict = ["name":"saurabh"]
        return dict
    }
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Saurabh Sharma
  • 187
  • 2
  • 8
-3

You can use templates methods with type-erasure...

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
  • 37
  • 1
  • 2
    This post contains no explanation. You've also posted it as is on http://stackoverflow.com/questions/28614990/swift-delegate-for-a-generic-class – user1427799 Nov 13 '16 at 17:32