๐Ÿ”ฐtype erasure

โ•ฑ๐Ÿšง under construction

Swift โŸฉ type โŸฉ erasure

๐Ÿšง

๐Ÿ’ˆ็ฏ„ไพ‹

// --------------------------------------------------
//     โญ๏ธ generic protocol (with associated type)
// --------------------------------------------------

protocol Fetcher {
    associatedtype Data
    typealias ResultHandler = (Result<Data, Error>) -> Void
    func fetch(completion: ResultHandler)
}

// conforming type
struct User {
    let id: Int
    let name: String
}

// protocol conformance
struct UserFetcher: Fetcher {
    typealias Data = User
    func fetch(completion: ResultHandler) {
        let user = User(id: 1, name: "Phil")
        completion(.success(user))
    }
}

/*
     โญ๏ธ The Problem
     --------------
     How do we hold a reference to an object that has 
     implemented this protocol?
 */

//struct SomeStruct {
//    let fetcher: Fetcher
//}
// โ›” Protocol โ€˜Fetcherโ€™ can only be used as a generic constraint 
//    because it has Self or associated type requirements
// ๐Ÿ’ฌ The compiler CANNOT determine the associated type `Data`
//    from the declaration.

// ------------------------------------------------
//     Type Erasure: turn PAT into generic type
// ------------------------------------------------

// 1.  define a generic type conforming to protocol
struct AnyFetcher<T>: Fetcher {
    
    // 2. turn associated types into type parameters.
    typealias Data = T
    
    // 3. create members with matching signatures.
    //    (for each member in the protocol)
    private let _fetch: (ResultHandler) -> Void
    
    // 4. erase the type of injected instance (โญ๏ธ)
    //    by storing a reference to all of the methods WITHOUT
    //    storing a reference to the injected instance (โญ๏ธ).
    //               โ•ญโ”€โ”€โ”€ โญ๏ธ โ”€โ”€โ”€โ•ฎ
    init<F: Fetcher>(_ fetcher: F) where F.Data == T {
        // store reference to methods, not the object.
        _fetch = fetcher.fetch
    }
    
    // 5. forward all protocol method calls to stored references
    func fetch(completion: ResultHandler) {
        _fetch(completion)
    }
}

// ----------------
//     use case
// ----------------

// turn `UserFetcher` into `AnyFetcher` (erase `UserFetch` type)
let fetcher = AnyFetcher(UserFetcher())

// let it do fetch
fetcher.fetch { result in
    switch result {
        case .success(let user) : print(user.name)
        case .failure(let error): print(error)
    }
}

Last updated