Type Erasure

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Type Erasure

Protocol With Associated Type

A protocol with associated type (PAT) is like a generic protocol, but the generic type is one of the protocol’s requirements. It gives you a placeholder name to use in the rest of the protocol definition.

protocol Request {
  associatedtype Response
  associatedtype Error: Swift.Error
  func perform(then handler: @escaping (Result<Response, Error>) -> Void)
}
struct NetworkRequest: Request {
  typealias Response = HTTPURLResponse
  typealias Error = URLError
  func perform(then handler: @escaping (Result<Response, Error>) -> Void) { }
}
struct NetworkRequest: Request {
  func perform(then handler: @escaping (Result<HTTPURLResponse, URLError>) -> Void) {
    // ...
  }
}

Type Erasure

When you want to use a PAT (or any generic type) as a concrete type, you must employ type erasure: Convert a PAT into a concrete type by removing type information. Type erasure hides a generic type in a non-generic type that’s easier to use in your code. You’ll often want to use a PAT as a type, like this:

func fetchAll(_ requests: [Request]) { ... }
Use of protocol 'Request' as a type must be written 'any Request'
Replace 'Request' with 'any Request'
Protocol 'Request' can only be used as a generic constraint because it has Self or associated type requirements.
func fetchAll<R: Request>(_ requests: [R]) { ... }
struct AnyRequest<Response, Error: Swift.Error> {
  typealias Handler = (Result<Response, Error>) -> Void
  let perform: (@escaping Handler) -> Void
  let handler: Handler
}
protocol APIRequest {
  var url: URL { get }
  var method: HTTPMethod { get }
  associatedtype Output
  func decode(_ data: Data) throws -> Output
}

class APIRequestCache<Value> {
  private var store: [APIRequest: Value] = [:]
}
struct AnyAPIRequest: Hashable {
  let url: URL
  let method: HTTPMethod
}
class APIRequestCache<Value> {
  private var store: [AnyAPIRequest: Value] = [:]

  func response<R: APIRequest>(for request: R) -> Value? where R.Output == Value {
    let erasedAPIRequest = AnyAPIRequest(url: request.url, method: request.method)
    return store[erasedAPIRequest]
  }

  func saveResponse<R: APIRequest>(_ response: Value, for request: R) where R.Output == Value {
    let erasedAPIRequest = AnyAPIRequest(url: request.url, method: request.method)
    store[erasedAPIRequest] = response
  }
}

Primary Associated Types

Here’s another feature introduced in Swift 5.7: You can declare one or more of your protocol’s associated types as its primary associated types by including them in angle brackets:

protocol Request2<Response> {
    associatedtype Response
    associatedtype Error: Swift.Error
    func perform(then handler: @escaping (Result<Response, Error>) -> Void)
}
extension Request2 where Response == HTTPURLResponse { ... }
extension Request2<HTTPURLResponse> { ... }
let networkRequests: [any Request2<HTTPURLResponse>]
See forum comments
Download course materials from Github
Previous: Combining Protocols Next: Types