How can I use Type Erasure with a protocol using associated type - ios

I am working on a project that has a network client that basically follows the below pattern.
protocol EndpointType {
var baseURL: String { get }
}
enum ProfilesAPI {
case fetchProfileForUser(id: String)
}
extension ProfilesAPI: EndpointType {
var baseURL: String {
return "https://foo.bar"
}
}
protocol ClientType: class {
associatedtype T: EndpointType
func request(_ request: T) -> Void
}
class Client<T: EndpointType>: ClientType {
func request(_ request: T) -> Void {
print(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
client.request(.fetchProfileForUser(id: "123"))
As part of tidying up this project and writing tests I have found the it is not possible to inject a client when conforming to the ClientType protocol.
let client: ClientType = Client<ProfilesAPI>() produces an error:
error: member 'request' cannot be used on value of protocol type
'ClientType'; use a generic constraint instead
I would like to maintain the current pattern ... = Client<ProfilesAPI>()
Is it possible to achieve this using type erasure? I have been reading but am not sure how to make this work.

To your actual question, the type eraser is straight-forward:
final class AnyClient<T: EndpointType>: ClientType {
let _request: (T) -> Void
func request(_ request: T) { _request(request) }
init<Client: ClientType>(_ client: Client) where Client.T == T {
_request = client.request
}
}
You'll need one of these _func/func pairs for each requirement in the protocol. You can use it this way:
let client = AnyClient(Client<ProfilesAPI>())
And then you can create a testing harness like:
class RecordingClient<T: EndpointType>: ClientType {
var requests: [T] = []
func request(_ request: T) -> Void {
requests.append(request)
print("recording: \(request.baseURL)")
}
}
And use that one instead:
let client = AnyClient(RecordingClient<ProfilesAPI>())
But I don't really recommend this approach if you can avoid it. Type erasers are a headache. Instead, I would look inside of Client, and extract the non-generic part into a ClientEngine protocol that doesn't require T. Then make that swappable when you construct the Client. Then you don't need type erasers, and you don't have to expose an extra protocol to the callers (just EndpointType).
For example, the engine part:
protocol ClientEngine: class {
func request(_ request: String) -> Void
}
class StandardClientEngine: ClientEngine {
func request(_ request: String) -> Void {
print(request)
}
}
The client that holds an engine. Notice how it uses a default parameter so that callers don't have to change anything.
class Client<T: EndpointType> {
let engine: ClientEngine
init(engine: ClientEngine = StandardClientEngine()) { self.engine = engine }
func request(_ request: T) -> Void {
engine.request(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
And again, a recording version:
class RecordingClientEngine: ClientEngine {
var requests: [String] = []
func request(_ request: String) -> Void {
requests.append(request)
print("recording: \(request)")
}
}
let client = Client<ProfilesAPI>(engine: RecordingClientEngine())

Related

Protocol in property, error Protocol can only be used as a generic constraint

I'm trying to make an inheritance generic case and i've been done in Dart and it worked like this:
// DART
class Car<T> implements Vehicle<T> {
EngineInterface? engine;
StorageInterface<T> storage;
Car({
required this.engine,
required this.storage,
});
}
but when i try to make in Swift, it a little bit different:
The error is
Protocol 'StorageProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Here is my other code:
protocol Vehicle {
associatedtype T
func accelerate() -> String
func brake() -> String
func refill(source: T) -> String
}
protocol StorageProtocol {
associatedtype T
func fill(source: T) -> String
func getSource() -> T
}
I tried to refactor the code like this:
class Car<T>: Vehicle where T : StorageProtocol {
var engine: EngineProtocol
var storage: T
init(engine: EngineProtocol, storage: T) {
self.engine = engine
}
func accelerate() -> String {
return ""
}
func brake() -> String {
return ""
}
func refill(source: T.T) -> String {
storage.fill(source: source)
}
}
but, i have to change the Car's generic
let petrolEngine = PetrolEngine()
let tank = Tank()
let car = Car<Tank>(
engine: petrolEngine, storage: tank
)
car.refill(source: Oil())
instead of use Oil inside Car generic
let car = Car<Oil>(
engine: petrolEngine, storage: tank
)
I found the solution,
that we have to make alias and add where clause in generic context
class Car<TypeAlias, EngineAlias: EngineProtocol, StorageAlias: StorageProtocol> : Vehicle
where StorageAlias.T == TypeAlias, EngineAlias.T == TypeAlias {
typealias T = TypeAlias
var engine: EngineAlias
var storage: StorageAlias
init(engine: EngineAlias, storage: StorageAlias) {
self.engine = engine
self.storage = storage
}
func accelerate() -> String {
return "▶️ \(engine.move()) -> Engine Move"
}
func brake() -> String {
return "⏹ Braked"
}
func refill(source: T) -> String {
let fillTheStorage = storage.fill(source: source)
return "💧 \(fillTheStorage)"
}
}
reference: Generic Where Clause

Store a closure with generic type

I am writing tests for a structure that has a func with a closure whom in turn has a child parameter of Result<ModelProtocol>.
However, when I am writing the mock for my struct it refuses to store the closure thinking that <T> != <ModelProtocol>. In turn this is correct, since this is a generic type.
The error I am getting now is:
Playground execution failed:
error: Test.playground:51:49: error: cannot assign value of type '(Result<T>) -> Void' to type '((Result<ModelProtocol>) -> Void)?'
self.doSomethingCompletionHandler = completionHandler
^~~~~~~~~~~~~~~~~
Which is the problem because <T> actually is of type T: ModelProtocol.
How can I store the closure (completionHandler) so I can call it at a later point to run the closure manually (by the test).
This is an example of what my problem in a playground:
public enum Result<Value> {
case success(Value)
case failure(Error)
public var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
public var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}
protocol ModelProtocol {
init(aString: String)
}
protocol AnImportantProtocol {
func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: #escaping((Result<T>)->Void))
}
enum StructureError : Error {
case defaultError
}
struct StructureOne : AnImportantProtocol {
func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: #escaping ((Result<T>) -> Void)) {
debugPrint("Doing something")
if let model = ExampleModel(aString: "Test") as? T {
completionHandler(.success(model))
} else {
completionHandler(.failure(StructureError.defaultError))
}
}
}
class StructureOneMock : AnImportantProtocol {
var doSomethingInvokeCount: Int = 0
var doSomethingCompletionHandler: ((Result<ModelProtocol>)->Void)? = nil
func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: #escaping ((Result<T>) -> Void)) {
self.doSomethingInvokeCount += 1
self.doSomethingCompletionHandler = completionHandler
}
func callCompletionHandler(result: Result<ModelProtocol>) {
if let doSomethingCompletionHandler = self.doSomethingCompletionHandler {
doSomethingCompletionHandler(result)
}
}
}
struct ExampleModel {
let someString: String
}
extension ExampleModel : ModelProtocol {
init(aString: String) {
self.someString = aString
}
}
I believe you have a contravariance error that isn't obvious because the variance is happening in the generic parameter.
Consider the following code:
class Cat {}
class Kitten: Cat {}
class Cougar: Cat {}
protocol CatDayCareProtocol {
func setRaiseFunction(raiseFunction: #escaping (Cat) -> Cougar)
}
class CatDayCare: CatDayCareProtocol {
func setRaiseFunction(raiseFunction: #escaping (Cat) -> Cougar) {
self.raiseFunction = raiseFunction
}
private var raiseFunction: ((Cat) -> Cougar)? = nil
func callCougerRaiseFunction() -> Cougar? {
let cougar = Cougar()
return raiseFunction?(cougar)
}
}
let catDayCare = CatDayCare()
catDayCare.setRaiseFunction(raiseFunction: { kitty: Kitten in
return Cougar()
})
In this example, swift throws the following error:
error: Contravariance.playground:23:51: error: expected expression
catDayCare.setRaiseFunction(raiseFunction: { kitty: Kitten in
This seems unintuitive as a kitten is a Cat, so wouldn't this be fine? Let's consider what happens if you tried to do the callCougerRaiseFunction(). It instantiates a cougar, which is a cat, and calls its raise function, which expects a cat so this is legal. However, if you pass a function expecting a kitten as a parameter, suddenly you are passing a cougar to a function that wants a kitten and it is sad.
Now for your example, you have
func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: #escaping ((Result<T>) -> Void)) {
self.doSomethingInvokeCount += 1
self.doSomethingCompletionHandler = completionHandler
}
In this example, T is strictly just as or more specific than a ModelProtocol (as it could be any protocol that inherits from ModelProtocol), which I believe makes Result<T> as a function parameter contravariant with the data type Result<ModelProtocol>. It is just that the compiler isn't quite smart enough to know it is contravariance, but it does know the conversion isn't legal.
As for actually solving the problem, is it really necessary to use a generic? Why can't you just use:
protocol AnImportantProtocol {
func doSomething(firstParameter: String, completionHandler: #escaping((Result<ModelProtocol>)->Void))
}
You are probably better off using an associatedType constraint in this case, for example:
protocol AnImportantProtocol {
associatedtype MyType: ModelProtocol
func doSomething(firstParameter: String, completionHandler ((Result<MyType>)->Void)?)
}
Then you typealias in the implementations:
class StructureOneMock<T: ModelProtocol> : AnImportantProtocol {
typealias MyType = T
var doSomethingInvokeCount: Int = 0
var doSomethingCompletionHandler: ((Result<MyType>)->Void)? = nil
func doSomething(firstParameter: String, completionHandler: ((Result<MyType>) -> Void)?) {
self.doSomethingInvokeCount += 1
self.doSomethingCompletionHandler = completionHandler
}
func callCompletionHandler(result: Result<MyType>) {
if let doSomethingCompletionHandler = self.doSomethingCompletionHandler {
doSomethingCompletionHandler(result)
}
}
}
You could skip the generic T in the implementations and specify a concrete type there.

Static member cannot be used on protocol meta type

What I am trying to accomplish is to make Proxy protocol that will route my class to appropriate service.
I have 3 types of service per 1 proxy:OnlineService,OfflineService,DemoService each for one of modes (online, offline,demo).
I created protocol :
protocol Proxy {
associatedtype ServiceProtocol
associatedtype OfflineServiceType: OfflineService
associatedtype OnlineServiceType: WebService
associatedtype DemoServiceType: DemoService
}
extension Proxy {
static var service: ServiceProtocol.Type {
if isOnlineMode() {
return OfflineServiceType.self as! ServiceProtocol.Type
} else if isDemoMode(){
return DemoServiceType.self as! ServiceProtocol.Type
}else{
return OnlineServiceType.self as! ServiceProtocol.Type
}
}
}
and then on Customer proxy class
class CustomersServiceProxy: Proxy, CustomersService {
typealias ServiceProtocol = CustomersService
typealias OfflineServiceType = CustomersOfflineService
typealias OnlineServiceType = CustomerWebService
public static func customerDetails(for customer: Customer, completion: #escaping (CustomerDetails) -> Void) {
service.customerDetails(for: customer, completion: completion)
}
}
But I got error:
Static member 'customerDetails' cannot be used on protocol metataype 'CustomerServiceProxy.ServiceProtocol.Protocol' (aka 'CustomerService.Protocol').
I suggest that this happens because Proxy service variable is returning CustomerService.Type instead of Type that is conforming to CustomerService. Is there any workaround to this?
well, u r missing a step, for example:
protocol Proxcy {}
extention Proxcy {
static func a() { print("A")
}
struct A: Proxcy {}
struct B: Proxcy {
A.a()
}

How to determine the generic type from protocol implementation

I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
extension Test {
func returnSomething() -> T {
let valueToReturn = readValueFromPLISTthatCanReturnAStringOrDictionary() as T
return valueToReturn
}
}
So finally I have 2 clases, TestString and TestDictionary that both implements Test protocol and I want to indicate the T parameter and I want to use the default implementation. How I do this?
class TestString: Test {}
class TestDictionary: Test { }
class TestString: Test where Test.T = String or similar?
I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
No problem. Let's write that down.
enum StringOrDictionary {
case string(String)
case dictionary([String: String])
}
protocol Test {
func returnSomething() -> StringOrDictionary
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
Sounds good. I'll assume that readValueFromPLISTthatCanReturnAStringOrDictionary() actually returns Any, since that's what is returned by propertyList(from:).
extension Test {
func returnSomething() -> StringOrDictionary {
let value = readValueFromPLISTthatCanReturnAStringOrDictionary()
switch value {
case let string as String: return .string(string)
case let dictionary as [String: String]: return .dictionary(dictionary)
default: fatalError() // Or perhaps you'd like to do something else
}
}
}
It'd probably be nice to name your type something more meaningful than StringOrDictionary, but other than that, it should be pretty straightforward. Just make a type that means what you say. You want a type that means "OR" and that is an enum. (If you want a type that means "AND" that's a struct BTW.)
Regarding your answer, this isn't legal:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
The way to define your T is to implement the required method.
class RandomClass: Test {
func returnSomething() -> String {
return ""
}
}
If you wanted to share some common code, then you can attach that as an extension rather than a default implementation. You could write a returnString() method and call it from the RandomClass.returnSomething(). This is all very useful in some cases, but I definitely wouldn't use it in this case. You don't mean "returns any possible type (T)." You mean "returns one of two possible types" and that's an enum, not a generic.
Update: Apparently they've added a new feature that they've talked about but I thought wasn't in yet. You could now implement RandomClass this way:
class RandomClass: Test {
typealias T = String
}
(Which is a very nice new feature, even if it's not a good answer for this problem.)
Here's a solution to your immediate problem:
Create 2 subtypes of your protocol, each with a different definition of the associated type, and a different default implementation. You select which default implementation you'd like your classes to use by picking between the 2 sub types.
The next issue here is that [String: String] isn't Hashable. This is due to a lack of support for conditional conformances (e.g. the ability to express that a Dictionary is Hashable iff the keys and values are both Hashable), one of Swift's largest downfalls, IMO. You'll probably want to use the type erasing wrapper AnyHashable.
protocol ResultProvider {
associatedtype Result: Hashable
func getResult() -> Result
}
protocol StringResultProvider: ResultProvider {
typealias Result = String
}
extension StringResultProvider {
func getResult() -> String {
return "A string result"
}
}
protocol IntResultProvider: ResultProvider {
typealias Result = Int
}
extension IntResultProvider {
func getResult() -> Int {
return 123
}
}
class TestIntResult: IntResultProvider {}
class TestString: StringResultProvider {}
print(TestString().getResult())
print(TestIntResult().getResult())
// protocol DictionaryResultProvider: ResultProvider {
// typealias Result = [String: String]
// }
// extension DictionaryResultProvider {
// func getResult() -> [String: String] {
// return ["A dictionary": "result"]
// }
// }
// class TestDictionaryProvider: DictionaryResultProvider {}
You need to specify the typealias when you extend the class, like so:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
extension String: Test {
typealias T = Int
}
func def() -> Int {
return 6
}
extension Test {
func returnSomething() -> T {
return def() as! Self.T
}
}
"".returnSomething()
6
However, I couldn't find a way to do it without force casting.
The only working solution is made the generic in the function and specify the variable type when calling the function. I was wondering if i could specify the T type when i implement the protocol in the class, similar like this:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
But previous example just don't work, so an alternative could be this:
protocol Test {
func doSomething<T>() -> T
}
extension Test {
func doSomething<T>(key: String) -> T {
return returnDictOrStringFromPLIST(key: key) as! T
}
}
class TestString: Test {
func getValue() {
let bah: String = doSomething()
}
}
class TestDict: Test {
func getValue() {
let bah: [String: String] = doSomething()
}
}

Factory of generic types in Swift (Protocols and generics)

I'm trying to create a factory of generic types that implement a protocol. The problem is that in the make method of the adapter factory I get the following error: Protocol 'Adapter' can only be used as a generic constraint because it has Self or associated type requirements.
Here is an example of what I'm doing now:
protocol Adapter {
typealias T
static func method1(parameter: T)
}
final class AdapterFactory<T>: NSObject {
static func make(name: String = "") -> Adapter.Type {
switch name {
case "Adapter1":
return ConcreteAdapter1<T>.self
default:
return ConcreteAdapter2<T>.self
}
}
}
final class ConcreteAdapter1<T>: NSObject, Adapter {
static func method1(parameter: T) {
// bla, bla, bla
}
}
You can use a pattern used in swift standard library(see Sequence and AnySequence) i.e using something like AnyAdapter that implements the Adapter protocol by delegating every method call to the underlying implementation(a concrete implementation of Adapter protocol like ConcreteAdapter1) or by using closures.
Your factory would then return AnyAdapter instead of Adapter. It may seems unnatural at first but using AnyAdapter as the type gives the same advantage as using the protocol (obviously it is a workaround) since AnyAdapter is not a concrete implementation by itself instead it delegates the implementation to a concrete implementation.
Here's the code
protocol Adapter {
typealias Element
func method1(parameter: Element)
func method2(parameter : Element)
}
struct AnyAdapter<Element> : Adapter {
private let _method1 : (Element) -> ()
private let _method2 : (Element) -> ()
init<A:Adapter where A.Element == Element>(_ base:A) {
_method1 = { base.method1($0) }
_method2 = { base.method2($0) }
}
func method1(parameter: Element) {
_method1(parameter)
}
func method2(parameter: Element) {
_method2(parameter)
}
}
final class ConcreteAdapter1<T>: NSObject, Adapter {
func method1(parameter: T) {
print("Concrete Adapter 1 method 1")
}
func method2(parameter: T) {
print("Concrete Adapter 1 method 2")
}
}
final class ConcreteAdapter2<T> : Adapter {
func method1(parameter: T) {
print("Concrete adapter 2 method 1")
}
func method2(parameter: T) {
print("Concrete Adapter 2 method 2")
}
}
final class AdapterFactory<T>: NSObject {
static func make(name: String = "") -> AnyAdapter<String> {
switch name {
case "Adapter1":
let concreteAdapter1 = ConcreteAdapter1<String>()
return AnyAdapter(concreteAdapter1)
default:
let concreteAdapter2 = ConcreteAdapter2<String>()
return AnyAdapter(concreteAdapter2)
}
}
}
I am not using a static method in protocol to make things simpler since statics don't operate well with generic types.
To be honest this is a shortcoming in the language and i would like it work like this thing to be simplified like in java or C#.
Hope this helps.

Resources