如何在DispatchQueue.main

时间:2017-03-18 19:30:04

标签: ios swift unit-testing grand-central-dispatch

警告 - 我读过关于测试线程的几个问题,但可能错过了答案,所以如果答案在那里并且我错过了,请指出我正确的方向。

我想测试在主队列上执行对reloadData的tableView调用。

这应该导致代码通过测试:

var cats = [Cat]() {
    didSet {
        DispatchQueue.main.async { [weak self] in
            tableView.reloadData()
        }
    }
}

此代码应导致测试失败:

var cats = [Cat]() {
    didSet {
        tableView.reloadData()
    }
}

测试应该是什么样的?

对测试人员的注意事项:我知道在运行应用程序时这是一件很容易捕获的事情,但当您重构和添加抽象层和多个网络时,这也很容易被忽略调用并希望用一些数据而不是其他数据等来更新UI ...所以请不要回答"对UI的更新进入主线程"我已经知道了。谢谢!

2 个答案:

答案 0 :(得分:1)

使用 dispatch_queue_set_specific 功能,以便将键值对与main queue

相关联

然后使用 dispatch_queue_get_specific 检查是否存在密钥&值:

fileprivate let mainQueueKey = UnsafeMutablePointer<Void>.alloc(1)
fileprivate let mainQueueValue = UnsafeMutablePointer<Void>.alloc(1)

/* Associate a key-value pair with the Main Queue */
dispatch_queue_set_specific(
    dispatch_get_main_queue(), 
    mainQueueKey, 
    mainQueueValue, 
    nil
)

func isMainQueue() -> Bool {
    /* Checking for presence of key-value on current queue */
    return (dispatch_get_specific(mainQueueKey) == mainQueueValue)
}

答案 1 :(得分:0)

我采取了更复杂的方法,将相关的Bool值添加到UITableView,然后调用UITableView来重定向reloadData()

fileprivate let reloadDataCalledOnMainThreadString = NSUUID().uuidString.cString(using: .utf8)!
fileprivate let reloadDataCalledOnMainThreadKey = UnsafeRawPointer(reloadDataCalledOnMainThreadString)

extension UITableView {
    var reloadDataCalledOnMainThread: Bool? {
        get {
            let storedValue = objc_getAssociatedObject(self, reloadDataCalledOnMainThreadKey)
            return storedValue as? Bool
        }
        set {
            objc_setAssociatedObject(self, reloadDataCalledOnMainThreadKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    dynamic func _spyReloadData() {
        reloadDataCalledOnMainThread = Thread.isMainThread

        _spyReloadData()
    }

    //Then swizzle that with reloadData()
}

然后在测试中我更新了后台线程上的猫,所以我可以检查它们是否在主线程上重新加载。

func testReloadDataIsCalledWhenCatsAreUpdated() {
    // Checks for presence of another associated property that's set in the swizzled reloadData method
    let reloadedPredicate = NSPredicate { [controller] _,_ in
        controller.tableView.reloadDataWasCalled
    }
    expectation(for: reloadedPredicate, evaluatedWith: [:], handler: nil)

    // Appends on the background queue to simulate an asynchronous call
    DispatchQueue.global(qos: .background).async { [weak controller] in
        let cat = Cat(name: "Test", identifier: 1)
        controller?.cats.append(cat)
    }

    // 2 seconds seems excessive but NSPredicates only evaluate once per second
    waitForExpectations(timeout: 2, handler: nil)

    XCTAssert(controller.tableView.reloadDataCalledOnMainThread!,
              "Reload data should be called on the main thread when cats are updated on a background thread")
}