2

It is a typical design pattern to use a Singleton to represent an "API Manager" object like this:-

class APIManager {
    let ENDPOINT_URL = "http://remote-server-fqdn.com"
    var storage : Storage!
    var currentlyLoggedIn = false
    class var sharedInstance: APIManager {
        struct Static {
        //---to contain the one and only instance of this class---
        static var instance: APIManager!
        //---a token to ensure that the class is only instantiated once---
        static var token: dispatch_once_t = 0
    }
    //---executes a block object once and only once for the lifetime of an application---
    dispatch_once(&Static.token) {
    //---creates an instance of this class---
        Static.instance = APIManager()
        Static.instance.storage = Storage.sharedInstance
    }
   //---returns the instance of this class---
    return Static.instance
}

This ensures that we have an APIManager object, with a consistent token, that we can use at different parts of our ViewController or Model methods. Not much of a problem and easily understood.

THE QUESTION

However, the question is --- is it appropriate to also include actual API server calls (or any kind of asynchronous execution) which are already async by nature if I am using libraries such as Alamofire or AFNetworking as methods into my APIManager class?

Will the fact that these (async) methods are nested inside my Singleton class cause any unintended performance issue or unintended side effects?

I understand that the async server API calls or a dispatch_async method will make use of GCD and concurrency is still achieved.

As an additional question: is this concurrency achieved by the Singleton's async method calls concurrent parallel OR concurrent but not parallel?

For instance, like this:-

class APIManager {
    // .... same block of code as above for declaring dispatch_once above.


    // additional code which are API server calls (and async by nature) here
    func loginRequest(username : String, password : String, completion: (loginSuccess : Bool) -> Void) {
        // Alamofire.request ...
    }

    func getRequest(...) {
        // Alamofire.request ...
    }

    func postRequest(...) {
        // Alamofire.request ...
    }

    func testAsync() {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
            println("just do something asynchronously")
        }
    }
}
Calvin Cheng
  • 35,640
  • 39
  • 116
  • 167
  • 1
    I've been under the impression that an API manager class's purpose was also to conform to design-pattern principles, e.g. MVC. So I do believe it's appropriate to add such calls to the manager. As for concurrent v. parallel, as I understand,GCD works by use of dispatch queues which handle their own thread management. Tasks are performed in a FIFO manner, with dispatch queues assigning tasks to threads as needed. So by default, I don't think tasks will necessarily occur in parallel but they will be concurrent (unless you explicitly use a serial queue). – Louis Tur Jan 15 '15 at 03:30
  • Thanks @LouisTur - can I ask if this is a code organization pattern that you use in your projects? i.e. nesting your async API calls in your "API Manager Singleton class"? – Calvin Cheng Jan 15 '15 at 04:09
  • Yup, I've been taught to move API calls to a separate singleton class to manage such tasks across a project. Though recently I've also run into opponents to such a model since it has the potential to introduce race conditions -- though my API calls are simple enough that I'm not concerned about that scenario yet. – Louis Tur Jan 15 '15 at 04:52
  • So the method calls made by the Singleton isn't thread safe? I understand that 'dispatch_one' makes the code inside the dispatch_one block thread safe; but the rest of the Singleton's Async methods aren't thread safe? – Calvin Cheng Jan 15 '15 at 05:20
  • i think the singleton guarantees that you're accessing a shared pool of resources for the API calls, but thread safety will depend on how you write its methods (atomic properties alone wont do it). But this is reaching the limits of what I know on the subject :) Though, searching did turn up this really really interesting article that Im going to read when Im slightly less sleepy: [Thread-Safe Class Design](http://www.objc.io/issue-2/thread-safe-class-design.html) – Louis Tur Jan 15 '15 at 06:10
  • Thanks for trying anyhow :-) Though I am not sure how a Singleton can make any guarantee that its async methods will access a shared pool of resources. If I write those exact same async methods in a normal class which is not a singleton, they should behave exactly like they are doing being encapsulated by the Singleton class. – Calvin Cheng Jan 15 '15 at 07:31

1 Answers1

1

You have a couple of effectively orthogonal issues here. The first issue is safe one-time initialization. You seem to have that under control (dispatch_once). The next issue is using a singleton to abstract out an external dependency. That's great -- it can help with testing, later migration, refactoring, etc. It also looks like you have that under control, and unless there's some part that I'm missing, you should probably leave that part as simple and "thin" as possible.

Then you seem to be asking if you should then build up more complex operations in the same API-abstraction class, and I would say the answer is probably, "no." Make other class(es) for that. It sounds like there may also be some non-trivial concurrency issues there (i.e. shared state), but as always, there's no silver bullet -- you just have to work through those yourself.

One common pattern for dealing with shared state is to have a private dispatch queue, and to have all the operations that read or write the shared state do so with dispatch_sync and dispatch_barrier_sync respectively. But that's just a simple, boilerplate pattern for managing shared state in a concurrent programming environment (i.e. keeping different threads from stomping on each other's reads/writes); it does nothing for API/App-specific interdependencies (for instance, if you wanted to delay all other API calls while an initial login/registration call is still pending reply.) The latter is something that you would have to write yourself.

ipmcc
  • 29,581
  • 5
  • 84
  • 147