Nicola Prada Nicola Prada - 1 month ago 6
Swift Question

Swift: How to retrieve different type and named primary keys from generic memory stores?

I have two protocols and two objects that implement them. One object uses name:String as its primary key, the other uses code:Int.

protocol AlphaProtocol{
var name:String {get set}
init(name: String)
}

protocol BetaProtocol{
var code: Int {get set}
init(code:Int)
}

class AlphaObject: AlphaProtocol{
var name: String

required init(name: String){
self.name = name
}

}

class BetaObject: BetaProtocol{
var code: Int

required init(code: Int){
self.code = code
}
}


Right now, to store the these objects I use two different memory stores that implement two different protocols, one for each kind of object.

protocol AlphaStoreProtocol{
func addObject(object anObject: AlphaProtocol)
func getObject(name aName:String)->AlphaProtocol?
func removeObject(name aName: String)
}

protocol BetaStoreProtocol{
func addObject(object anObject: BetaProtocol)
func getObject(code aCode:Int)->BetaProtocol?
func removeObject(code aCode: Int)
}

class AlphaStore{

fileprivate var objects = [AlphaProtocol]()

func addObject(object anObject: AlphaProtocol){
if getObject(name: anObject.name) == nil{
objects.append(anObject)
}
}

func getObject(name aName:String)->AlphaProtocol?{
for o in objects{
if o.name == aName{
return o
}
}
return nil
}

func removeObject(name aName: String){
self.objects = self.objects.filter({$0.name != aName})
}
}

class BetaStore: BetaStoreProtocol{

fileprivate var objects = [BetaProtocol]()

func addObject(object anObject: BetaProtocol){
if getObject(code: anObject.code) == nil{
objects.append(anObject)
}
}

func getObject(code aCode:Int)->BetaProtocol?{
for o in objects{
if o.code == aCode{
return o
}
}
return nil
}

func removeObject(code aCode: Int){
self.objects = self.objects.filter({$0.code != aCode})
}
}


Test Code using two tailor made stores.

let alpha = AlphaObject(name: "Alpha")
let beta = BetaObject(code: 12345)

let alphaStore = AlphaStore()
let betaStore = BetaStore()

alphaStore.addObject(object: alpha)
if (alphaStore.getObject(name: alpha.name) != nil){
print("alpha object has been added to alphaStore")
}
alphaStore.removeObject(name: alpha.name)
if (alphaStore.getObject(name: alpha.name) == nil){
print("alpha object has been removed from alphaStore")
}

betaStore.addObject(object: beta)
if (betaStore.getObject(code: beta.code) != nil){
print("beta object has been added to betaStore")
}
betaStore.removeObject(code: beta.code)
if (betaStore.getObject(code: beta.code) == nil){
print("beta object has been removed from betaStore")
}


The goal: using a single generic class for both the stores but I'm stuck because the two objects use two different primary keys (different type and different name) and I can't simply force a generic "Id" as the primary key in the objects. One has to be named "name" and the other "code".

Is there a way to write the getObject and removeObject methods to accept both kind of objects?

protocol GenericStoreProtocol{
associatedtype T
func addObject(object anObject: T)
// func getObject()->T // One object use a name:String, the other code:Int as its primary key!
// func removeObject() // One object use a name:String, the other code:Int as its primary key!
}

class GenericStore<T>: GenericStoreProtocol{

fileprivate var objects = [T]()

func addObject(object anObject: T){
objects.append(anObject)
}

// ...
}

let genericAlphaStore = GenericStore<AlphaProtocol>()
let genericBetaStore = GenericStore<BetaProtocol>()

Answer

I can't simply force a generic "Id" as the primary key in the objects.

Yep, you totally can if you use a single protocol instead of two unrelated ones (AlphaProtocol and BetaProtocol).

protocol KeyedObject {
    associatedtype PrimaryKey : Equatable
    var key: PrimaryKey { get }
}

Just make your objects conform to this protocol; they can declare whatever equatable type you require for the key, they just have to provide some way to access it.

class AlphaObject: KeyedObject {
    typealias PrimaryKey = String
    var name: String

    required init(name: String) {
        self.name = name
    }

    var key: String {
        return self.name
    }
}

Then you can use a straightforward generic class that contains only objects you provided:

class GenericStore<T : KeyedObject> {

    fileprivate var objects = [T]()

    func addObject(object anObject: T){
        objects.append(anObject)
    }

    func getObject(key: T.PrimaryKey) -> T? {
        for o in objects{
            if o.key == key {
                return o
            }
        }
        return nil
    }
    ...
}