DispatchQueue 的正确实现方式

Right way to implement DispatchQueue

我正在为一个应用程序项目编写一个简单的缓存框架,并在闭包中使用 GCD 和 DispatchQueues 来保存和检索框架中的对象。

在编程时,我一次又一次地质疑自己是否正确地完成了它。这是我正在做的一些例子。我正在写 Swift 4.

在获取对象时,我使用了以下代码:

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)
        }
    }
}

我想用我的框架方式,闭包的结果可以直接在应用程序中使用。所以我想在后台异步检索它们之后,我必须在主队列中 "give them back" 。我对这个建议正确吗?我想知道,因为我在 result() 部分生成了许多 DispatchQueue.main.(a)sync({ ... }) 代码块。

我感谢各种提示、评论或更好的解决方案!

有点无关紧要:通常这种异步代码不应该对它调用结果处理程序的位置如此自以为是。调用者可能不想要主队列中的结果。通常 return 队列是 "a private background queue" (HealthKit),可在对象级别配置 (URLSession),可在调用站点配置(大部分 Dispatch),或者以任何方式未承诺(大部分 Foundation)。 UIKit 事物有时会承诺调用主队列上的完成处理程序,但这并不常见。也就是说,如果您非常确定每个调用此方法的人总是希望结果在主队列中,那很好,您可以保证。只是这种事情有时会迫使人们将结果重新发送回其他队列,这是一种浪费。

有了这个,你如何清理它?几种方式。首先,这段代码是不必要的:

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

您可以只使用可选链接在以后的使用中获得相同的结果:

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

"there is no storage handler"和"there is no object in the storage handler,"在这个函数上没有区别,所以你可以让它们成为相同的代码路径。

但更强大的修复方法是提取这样的函数:

// 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)
        }
    }
}

这将 "get and cache a value" 问题与 "call a result handler" 问题分开,因此您不必将两者混为一谈。