问题
I have a few unit tests in which I'd like to test if a callback is called on the correct dispatch queue.
In Swift 2, I compared the label of the current queue to my test queue. However in Swift 3 the DISPATCH_CURRENT_QUEUE_LABEL
constant no longer exists.
I did find the dispatch_assert_queue
function. Which seems to be what I need, but I'm not sure how to call it.
My Swift 2 code:
let testQueueLabel = "com.example.my-test-queue"
let testQueue = dispatch_queue_create(testQueueLabel, nil)
let currentQueueLabel = String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))!
XCTAssertEqual(currentQueueLabel, testQueueLabel, "callback should be called on specified queue")
Update:
I got confused by the lack of autocomplete, but it is possible to use __dispatch_assert_queue
:
if #available(iOS 10.0, *) {
__dispatch_assert_queue(test1Queue)
}
While this does work for unit tests, it annoyingly stops the whole process with a EXC_BAD_INSTRUCTION
instead of only failing a test.
回答1:
Use dispatchPrecondition(.onQueue(expectedQueue))
, the Swift 3 API replacement for the dispatch_assert_queue()
C API.
This was covered in the WWDC 2016 GCD session (21:00, Slide 128): https://developer.apple.com/videos/play/wwdc2016/720/
回答2:
Answering my own question:
Based on KFDoom's comments, I'm now using setSpecific
and getSpecific
.
This creates a key, sets it on the test queue, and later on, gets it again:
let testQueueLabel = "com.example.my-test-queue"
let testQueue = DispatchQueue(label: testQueueLabel, attributes: [])
let testQueueKey = DispatchSpecificKey<Void>()
testQueue.setSpecific(key: testQueueKey, value: ())
// ... later on, to test:
XCTAssertNotNil(DispatchQueue.getSpecific(key: testQueueKey), "callback should be called on specified queue")
Note that there's no value associated with the key (its type is Void
), I'm only interested in the existence of the specific, not in it's value.
Important!
Make sure to keep a reference to the key, or cleanup after you're done using it. Otherwise a newly created key could use the same memory address, leading to weird behaviour. See: http://tom.lokhorst.eu/2018/02/leaky-abstractions-in-swift-with-dispatchqueue
回答3:
Tests based on KFDoom's answer:
import XCTest
import Dispatch
class TestQueue: XCTestCase {
func testWithSpecificKey() {
let queue = DispatchQueue(label: "label")
let key = DispatchSpecificKey<Void>()
queue.setSpecific(key:key, value:())
let expectation1 = expectation(withDescription: "main")
let expectation2 = expectation(withDescription: "queue")
DispatchQueue.main.async {
if (DispatchQueue.getSpecific(key: key) == nil) {
expectation1.fulfill()
}
}
queue.async {
if (DispatchQueue.getSpecific(key: key) != nil) {
expectation2.fulfill()
}
}
waitForExpectations(withTimeout: 1, handler: nil)
}
func testWithPrecondition() {
let queue = DispatchQueue(label: "label")
let expectation1 = expectation(withDescription: "main")
let expectation2 = expectation(withDescription: "queue")
DispatchQueue.main.async {
dispatchPrecondition(condition: .notOnQueue(queue))
expectation1.fulfill()
}
queue.async {
dispatchPrecondition(condition: .onQueue(queue))
expectation2.fulfill()
}
waitForExpectations(withTimeout: 1, handler: nil)
}
}
回答4:
One option is to set a precondition to test directly for the queue or set "specific" on it and retrieve it later. Further, one could use setSpecific and getSpecific. Alternatively, you can use a precondition check if you're on a queue so that should fulfill the "get current" need. src: https://github.com/duemunk/Async/blob/feature/Swift_3.0/AsyncTest/AsyncTests.swift
and
https://github.com/apple/swift/blob/master/stdlib/public/SDK/Dispatch/Dispatch.swift
回答5:
One related option is to set a Main Queue / UI Queue precondition:
dispatchPrecondition(condition: .onQueue(DispatchQueue.main))
回答6:
/*
Dispatch queue and NSOperations in Swift 3 Xcode 8
*/
protocol Container {
associatedtype ItemType
var count: Int { get }
mutating func pop()
mutating func push(item: ItemType)
mutating func append(item: ItemType)
subscript(i: Int) -> ItemType { get }
}
//Generic Function
struct GenericStack<Element> : Container {
mutating internal func push(item: Element) {
items.append(item)
}
mutating internal func pop() {
items.removeLast()
}
var items = [ItemType]()
internal subscript(i: Int) -> Element {
return items[i]
}
mutating internal func append(item: Element) {
self.push(item: item)
}
internal var count: Int { return items.count }
typealias ItemType = Element
}
var myGenericStack = GenericStack<String>()
myGenericStack.append(item: "Narendra")
myGenericStack.append(item: "Bade")
myGenericStack.count
myGenericStack.pop()
myGenericStack.count
//Some NSOperation
class ExploreOperationAndThread {
func performOperation() {
//Create queue
let queue = OperationQueue()
let operation1 = BlockOperation {
var count = myGenericStack.count
while count > 0 {
myGenericStack.pop()
count -= 1
}
}
operation1.completionBlock = {
print("Operation 1")
}
let operation2 = BlockOperation {
var count = 0
while count == 10 {
myGenericStack.append(item: "ItemAdded")
count += 1
}
}
operation2.completionBlock = {
print("Operation 2")
print(myGenericStack.items)
}
//Suppose operation 3 is related to UI
let operation3 = BlockOperation {
//run on main thread
DispatchQueue.main.async {
print(myGenericStack.items.count)
}
}
operation3.completionBlock = {
print("Operation 3")
print(myGenericStack.items.count)
}
//add operation into queue
queue.addOperation(operation3)
queue.addOperation(operation1)
queue.addOperation(operation2)
//Limit number of concurrent operation in queue
queue.maxConcurrentOperationCount = 1
//add dependancies
operation1.addDependency(operation2)
operation2.addDependency(operation3)
if myGenericStack.items.count == 0 {
//remove dependency
operation1.removeDependency(operation2)
}
}
}
//Other ways of using queues
DispatchQueue.global(qos: .userInitiated).async {
ExploreOperationAndThread().performOperation()
}
DispatchQueue.main.async {
print("I am performing operation on main theread asynchronously")
}
OperationQueue.main.addOperation {
var count = 0
while count == 10 {
myGenericStack.append(item: "Narendra")
count += 1
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5 , execute: {
ExploreOperationAndThread().performOperation()
})
let queue2 = DispatchQueue(label: "queue2") //Default is serial queue
queue2.async {
print("asynchronously")
}
来源:https://stackoverflow.com/questions/37952262/check-if-on-correct-dispatch-queue-in-swift-3