2

How do you specify a generic property in a protocol where the conforming type specifies the array type?

Info: Lets say we want to create a protocol which defines an array property where the type of the array is unknown. The conforming types specify the type of this array. This is a possible solution:

protocol Wallet {
    associatedtype Value
    var moneyStack: [Value] {get set}
}

struct BitcoinWallet: Wallet {
    var moneyStack: [String]
}

struct EthereumWallet: Wallet {
    var moneyStack: [Int]
}

The problem with this approach is that we can't access the moneyStack property:

let bitcoinWallet = BitcoinWallet(moneyStack: ["A","B"])
let etheureumWallet = EthereumWallet(moneyStack: [1,2])

let wallets: [Wallet] = [bitcoinWallet, etheureumWallet]

for wallet in wallets {
    print(wallet.moneyStack) // not possible
}

We can try another approach where the type of the array is any:

protocol Wallet {
    var moneyStack: [Any] {get set}
}

struct BitcoinWallet: Wallet {
    var moneyStack: [Any]
}

struct EthereumWallet: Wallet {
    var moneyStack: [Any]
}

This is a possible solution but an array which holds [Any] is too generic. I can't specify the type. Example:

let bitcoinWallet = BitcoinWallet(moneyStack: ["A","B"])
let ethereumWallet = EthereumWallet(moneyStack: [1,2])

var wallets: [Wallet] = [bitcoinWallet, ethereumWallet]

for wallet in wallets {
    print(wallet.moneyStack.count) // this works
    print(wallet.moneyStack[0]) // this works
}

let badBitcoinWallet = BitcoinWallet(moneyStack: [12.4, 312312.123]) // BitcoinWallets aren't allowed to hold doubles!

wallets.append(badBitcoinWallet) // This shouldn't be allowed!

The first solution is what I want. The type is specified in every struct but Swift complains I can't use generic constraints. The main problem I want to solve is that I want an array which holds different types of wallets, which all have their own array of a different type.

I thought a protocol would make this easy but it doesn't. Am I doing something wrong with my architecture? Should I even use protocols? Maybe subclassing can solve this?

4
  • I find this vaguely discomfiting, but see Rob Napier's intriguing discussion on this topic: dotconferences.com/2016/01/…. Commented Feb 9, 2018 at 20:10
  • Can I have your opinion on why it's discomfiting? I'd love to hear your feedback @Rob so I can see some other perspective. Thank you for the link btw! Commented Feb 10, 2018 at 16:42
  • In the type erasure pattern, all of those closures for all of the protocol's methods (which you then have to repeat for each conforming type) feels a bit kludgy, a clumsy work-around to some inexplicable limitation in the language (that protocols with typealias can't be used as a types whereas those without can). When you find yourself repeating some mindless code like that, it is code smell for some deeper problem. Rob Napier alludes to these concerns, himself, and it is interesting that his solution to his generic problem solver ended up using a completely different pattern, composition. Commented Feb 10, 2018 at 17:11
  • Thank you so much @Rob! I wish I could upvote comment but the option isn't visible for me. What you said made sense, I have to change the architecture indeed. Commented Feb 10, 2018 at 17:29

1 Answer 1

1

You can't store 2 objects with different types in array (Wallet<String> and Wallet<Int> are different types).

But you can try something like this:

protocol WalletProtocol {
    func printMoneyStack()
}

class Wallet<T> {
    var moneyStack: [T] = []
    init(moneyStack: [T]) {
        self.moneyStack = moneyStack
    }
}

class BitcoinWallet: Wallet<String>, WalletProtocol {
    func printMoneyStack() {
        print(moneyStack)
    }
}

class EthereumWallet: Wallet<Int>, WalletProtocol {
    func printMoneyStack() {
        print(moneyStack)
    }
}


let bitcoinWallet = BitcoinWallet(moneyStack: ["A","B"])
let etheureumWallet = EthereumWallet(moneyStack: [1,2])

let wallets: [WalletProtocol] = [bitcoinWallet, etheureumWallet]

for wallet in wallets {
    wallet.printMoneyStack()
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.