0

I have a Singleton class:

class Session {
  static let sharedInstance = Session()
  private init() {}
  // app properties
  var token: String!
  var promotions: JSON!
}

In a networking class I have multiple async calls that I fire off using a map function for each url:

let urls = [ "promotions": "http:..." ]

let results = urls.map { (k, url) in
  self.getFromApi(url, params: ["token" : token]) { response in
    guard response as? JSON != nil else { return }

    Session.sharedInstance[k] = response
    // the above should compile to Session.sharedInstance["promotions"] = response.
  }

The issue lies in trying to set the response on the sharedInstance by subscript. I've attempted to implement a subscript on the Singleton to no avail:

subscript(property: Any) -> Any {
  get {
    return Session.sharedInstance[property]
  }
  set(newValue) {
    Session.sharedInstance[property] = newValue
  }
}

I've also attempted to forego subscripting via conformance to NSObject and using KVC however this also didn't work & furthermore I lose all type safety.

Any help or advice is very much appreciated. Thanks in advance.

Kyle G
  • 4,347
  • 4
  • 26
  • 39
  • why do you need subscript? I'm guessing because you have a lot more properties like `promotions`? – R Menke Dec 04 '15 at 16:29
  • also there is no need to do: `Session.sharedInstance[property] = newValue` just use : `self.[property] = newValue` Then it also becomes clear that you are creating a recursive subscript. – R Menke Dec 04 '15 at 16:39

2 Answers2

1

You can make your Singleton a bag class. You have a dictionary that holds your JSONs or properties. Everything else is just computed values.

class Session {
    static let sharedInstance = Session()
    private init() {}
    // app properties
    var token: String!

    private var fetchedJSON : [String:Any] = [:]

    var keys : [String] {
        return Array(fetchedJSON.keys)
    }

    // handy to do downcasts in here
    var promotions : JSON? {
        get {
            if let promotions = fetchedJSON["promotions"] as? JSON {
                return promotions
            } else {
                return nil
            }
        }
        set(value) {
            fetchedJSON["prmotions"] = value
        }
    }

    subscript(property: String) -> Any? {
        get {
            return fetchedJSON[property]

        }
        set(newValue) {
            fetchedJSON[property] = newValue
        }
    }
}

Or create a limited KVC (this can also be done through reflection as stated in the other answer by Oliver Borchert):

class Singleton {

    var alpha : Any?
    var beta : Any?
    var delta : Any?

    subscript(property: String) -> Any? {
        get {
            switch property {
            case "alpha" : return self.alpha
            case "beta" : return self.beta
            case "delta" : return self.delta
            default : return nil
            }
        }
        set(newValue) {
            switch property {
            case "alpha" : self.alpha = newValue
            case "beta" : self.beta = newValue
            case "delta" : self.delta = newValue
            default : return
            }
        }
    }   
}
R Menke
  • 8,183
  • 4
  • 35
  • 63
1

You might make use of reflection. I don't use it that often so if you have any further questions, just google 'Swift reflection'. But be beware: reflection might be looking good but it is slow and only works with NSObject subclasses (I think, but feel free to try it out). It works like the following:

subscript(property: String) -> Any {
    get {
        for i in 0..<reflect(self).count { // loop through all properties
            if reflect(self)[i].0 == property { // find property with specified name
                return reflect(self)[i].1.summary // return property's value
            }
        }
        fatalError("no such property found")
    } set(newValue) {
        for i in 0..<reflect(self).count { // loop through all properties
            if reflect(self)[i].0 == property { // find property with specified name
                self.setValue(newValue, forKey: property) // not possible to do otherwise in Swift
                return
            }
        }
    }
}
borchero
  • 5,562
  • 8
  • 46
  • 72