Tyrus Rechs
Tyrus Rechs

Reputation: 73

How to get generics with completion and resultType working?

I'm currently writing and database access class for two database APIs (realm and Firestore). With the intention to slim down the code i try to solve the whole thing a little sleaker with generics (#1). Unfortunately, it's not working. Where do I miss the point?

I tried with defining associatedtypes (#2) and setting them within the RealmAccessStragy class. But on this point the compiler returns error if try to access the protocol via the PersistenceController.

I am grateful for any help!

APPROACH #1

enum DataResult<T> {
    case success(T)
    case failure(Error)
}

protocol DataApiAccess: AnyObject {
func read<U, T>(primaryKey: U, completion: @escaping ((DataResult<T>) -> Void))
}

class RealmAccessStrategy {
    ...
    func read<U, T>(primaryKey: U, completion: @escaping ((DataResult<T>) -> Void)) {
        guard let realmObject = realmInstance.object(ofType: realmObjectType, forPrimaryKey: primaryKey) else {
            completion(.failure(RealmAccessError.noObject))
            return
        }

        completion(.success(realmObject)) // ERROR: Member 'success' in 'DataResult<_>' produces result of type 'DataResult<T>', but context expects 'DataResult<_>'
    }
}


// Later implementation
class PersistenceController {
    private let strategy: DataApiAccess

    init(use: DataApiAccess) {
        self.strategy = use
    }

    func load<U, T>(primaryKey: U, completion: @escaping ( (DataResult<T>) -> Void ) ) {
        strategy.read(primaryKey: primaryKey, completion: completion)
    }
}

🆘 ERROR: Member 'success' in 'DataResult<>' produces result of type 'DataResult', but context expects 'DataResult<>'

APPROACH #2

enum DataResult<T> {
    case success(T)
    case failure(Error)
}

protocol DataApiAccess {
    associatedtype ReturnType

    func read(primaryKey: PrimaryKeyType, completion: @escaping DataApiHandler<ReturnType>)
}

class RealmAccessStrategy: DataApiAccess {

    ...

    // Typealias
    internal typealias ReturnType = Object

    func read(primaryKey: Any, completion: @escaping ((DataResult<Object>) -> Void)) {
        guard let realmObject = realmInstance.object(ofType: realmObjectType, forPrimaryKey: primaryKey) else {
            completion(.failure(RealmAccessError.noObject))
            return
        }

        completion(.success(realmObject))
    }
}


class PersistenceController {
    private let strategy: DataApiAccess // ERROR: Protocol 'DataApiAccess' can only be used as a generic constraint because it has Self or associated type requirements

    init(use: DataApiAccess) {
        self.strategy = use
    }

    ...

    }
}

🆘 ERROR: Protocol 'DataApiAccess' can only be used as a generic constraint because it has Self or associated type requirements

Upvotes: 0

Views: 186

Answers (1)

Vicaren
Vicaren

Reputation: 660

You cannot set variable generic protocols but you can set methods

Example code below

  • Create enum for base result:

    enum DataResult<T> {
        case success(T)
        case failure(Error)
    }
    

    ///Set a protocol generic methods:
    protocol DataApiAccess {
        func read<T: Codable>(primaryKey: PrimaryKeyType, completion: @escaping (DataResult<T>) -> Void)
    }

    class RealmAccessStrategy: DataApiAccess {

        func read<T: Codable>(primaryKey: PrimaryKeyType, completion: @escaping (DataResult<T>) -> Void) {
            // Read data from database
        }
    }

    class NetworkAccessStrategy: DataApiAccess {


        func read<T: Codable>(primaryKey: PrimaryKeyType, completion: @escaping (DataResult<T>) -> Void) {
             // Get data from request
        }
    }

    class PersistenceController {

        private let strategy: DataApiAccess

        init(use: DataApiAccess) {
            // Set dependency inversion for offline or online state
            self.strategy = use
        }

        func foo() {
             // TODO
            //strategy.read(primaryKey: <#T##PrimaryKeyType#>, completion: <#T##(DataResult<Decodable & Encodable>) -> Void#>)
        }


    }

Enjoy!

Upvotes: 1

Related Questions