How to create generic protocols in Swift?

Swift

Swift Problem Overview


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

Swift Solutions


Solution 1 - Swift

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

Solution 2 - Swift

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)

Solution 3 - Swift

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: ...)

Solution 4 - Swift

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

Solution 5 - Swift

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFarhad-TaranView Question on Stackoverflow
Solution 1 - SwiftLou FrancoView Answer on Stackoverflow
Solution 2 - SwiftHeath BordersView Answer on Stackoverflow
Solution 3 - Swiftdenis_lorView Answer on Stackoverflow
Solution 4 - SwiftSaurabh SharmaView Answer on Stackoverflow
Solution 5 - SwiftDsjoveView Answer on Stackoverflow