Search code examples
swiftgenericstype-erasureswift-protocolsassociated-types

Convert Abstract Type to Concrete Type in Swift


I am trying to make Data Model for my app. here is the scenario:

my app has Customer Model which contains customer's info, and also contain his/her Payment Source. the API gives me two kind of payment sources: card and bank account which they have completely different fields.

So, here is my problem, I want to have abstract type which is PaymentSource then within each PaymentSource have a function to return object casted to it's type. some how I am type erasure.

I needed to put my abstract type in a box and use it as Concrete type (AnyPaymentSource).

So, I've done as following:

protocol PaymentSource {
    associatedtype Kind
    func cast() -> Kind
}

struct AnyPaymentSource<PS: PaymentSource> {
    private var paymentSource: PS
    init(paymentSource: PS) {
        self.paymentSource = paymentSource
    }
    func cast() -> PS.Kind {
        return paymentSource.cast()
    }
}

struct Card: PaymentSource {
    func cast() -> Card {
        return self
    }
}

struct BankAccount: PaymentSource {
    func cast() -> BankAccount {
        return self
    }
}

struct Customer { 
    var firstName: String
    var lastName: String
    var email: String
    var paymentSource : AnyPaymentSource<PaymentSource> 
}

but Customer gives me error with following description:

Using 'PaymentSource' as a concrete type conforming to protocol 'PaymentSource' is not supported

where am I doing wrong?


Solution

  • Swift is statically typed language. That means the type of a variable must be known at compile time.

    When i was faced with this problem, i solved it something like this

    protocol PaymentSource {
        associatedtype Kind
        func cast() -> Kind
    }
    
    struct AnyPaymentSource<PS: PaymentSource> {
        private var paymentSource: PS
        init(paymentSource: PS) {
            self.paymentSource = paymentSource
        }
        func cast() -> PS.Kind {
            return paymentSource.cast()
        }
    }
    
    struct Card: PaymentSource {
        func cast() -> Card {
            return self
        }
    }
    
    struct BankAccount: PaymentSource {
        func cast() -> BankAccount {
            return self
        }
    }
    
    struct Customer<T:PaymentSource> {
        var firstName: String
        var lastName: String
        var email: String
        var paymentSource : AnyPaymentSource<T>
    }
    func test(){
        let customerWithCard = Customer<Card>(
            firstName: "",
            lastName: "",
            email: "",
            paymentSource: AnyPaymentSource(paymentSource: Card())
        )
        let customerWithBankAccount = Customer<BankAccount>(
            firstName: "",
            lastName: "",
            email: "",
            paymentSource: AnyPaymentSource(paymentSource: BankAccount())
        )
        print(customerWithCard.paymentSource.cast())
        print(customerWithBankAccount.paymentSource.cast())
        return
    }