Mo0812
Mo0812

Reputation: 140

Right way to implement DispatchQueue

I am writing a simple cache framework for an app project and using GCD and DispatchQueues within Closures to save and retrieve objects from the framework.

While programming I question myself again and again about the way to do it right or not. Here is some example of what I am doing. I'm writing in Swift 4.

While getting an object I'm using the following code:

open func get(identifier: String, result:@escaping (NSObject?) -> ()) {
    MKCacheStorageGlobals.dispatchQueue.async {
        //Get object from memory
        if let object = self.storageItems[identifier] {
            DispatchQueue.main.async {
                result(object)
            }
            return
        }

        //Else get object from disk
        guard let storageHandler = self.storageHandler else {
            DispatchQueue.main.async {
                result(nil)
            }
            return
        }
        do {
            if let object = try storageHandler.get(identifier: identifier) {
                self.storageItems[identifier] = object
                DispatchQueue.main.async {
                    result(object)
                }
                return
            }
        } catch {
            print(error.localizedDescription)
        }
        DispatchQueue.main.async {
            result(nil)
        }
    }
}

I want to use my framework in the way, that the results of the closure can used directly in the app. So I thought after retrieving them in the background asynchronous, I have to "give them back" in the main queue. Am I right with this suggestion? I wonder because I produce many DispatchQueue.main.(a)sync({ ... }) code blocks on the result() section.

I appreciate every kind of tip, comment or better solution!

Upvotes: 2

Views: 303

Answers (1)

Rob Napier
Rob Napier

Reputation: 299633

Somewhat irrelevant aside: Typically this kind of async code shouldn't be so opinionated about where it calls the result handler. The caller may not want the result on the main queue. Typically the return queue is either "a private background queue" (HealthKit), configurable at the object level (URLSession), configurable at the callsite (most of Dispatch), or unpromised in any way (much of Foundation). UIKit things sometimes promise to call the completion handler on the main queue, but this is unusual. That said, if you are very certain that every caller to this will always want the result to be on the main queue, that's fine, and you can promise it. It's just the kind of thing that sometimes forces folks to re-dispatch the result back to some other queue, which is wasteful.

With that out of the way, how do you clean this up? Several ways. First, this code is unnecessary:

   guard let storageHandler = self.storageHandler else {
        DispatchQueue.main.async {
            result(nil)
        }
        return
    }

You can just use optional-chaining to get the same result in the later usage:

        if let object = try self.storageHandler?.get(identifier: identifier) {

There's no difference in this function between "there is no storage handler" and "there is no object in the storage handler," so you can make them the same code path.

But the more powerful fix is to extract a function like this:

// Should only be called from MKCacheStorageGlobals.dispatchQueue
private func cacheValue(identifier: String) -> NSObject? {
    do {
        if let object = try self.storageHandler?.get(identifier: identifier) {
            storageItems[identifier] = object
            return object
        }
    } catch {
        print(error.localizedDescription)
    }
    return nil
}

open func get(identifier: String,
              result:@escaping (NSObject?) -> (),
              queue: DispatchQueue = .main) {
    MKCacheStorageGlobals.dispatchQueue.async {
        let value = self.storageItems[identifier] ?? self.cacheValue(identifier: identifier)
        queue.async {
            result(value)
        }
    }
}

This separates the "get and cache a value" concerns from the "call a result handler" concerns, so you don't have to intermix the two.

Upvotes: 3

Related Questions