DispatchQueue freezed main queue - swift

I need to do some methods one by one without interrupting, in a separate thread, that way I have in my class
fileprivate var queue = DispatchQueue(label: "ProgressHUD", qos: .userInteractive)
and when I need show/hide I do some like this
queue.sync {
__hide()
}
or
queue.sync {
__show()
}
in this __%%() methods, I need to do some actions with UI in the main thread, and I need to do all action from --show/--hide one by one without interrupting. So in inner, I use
private func __show()
{
let g = DispatchGroup()
g.enter()
DispatchQueue.main.async {
print("go?")
self.doMyAction1()
self.doMyAction2()
self.doMyAction3()
g.leave()
}
g.wait()
}
but the app always freezes on line g.wait() and doesn't call main.async and doesn't print go?. What I do wrong!?

func dispatchGroup() -> String {
var message = ""
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 5) {
message = "Hiii Friends"
group.leave()
}
group.wait()
return message
}

Related

Swift MapKit annotations not loading until map tapped

I'm adding a bunch of annotations to a map and as the user moves and pans around to different countries I remove the annotations and add in some more. The problem I'm facing is the new annotations don't show until I've interacted with the map, either tap, pinch, pan or zoom.
I've tried placing the map.addAnnotations() into a DispatchQueue but that didn't work and I'm also offsetting the built method loadNewCountry(country: String) into a dispatchGroup. None of these are working!
Note: I've got several thousand annotations of varying types so loading them all in memory won't work for older devices :)
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
checkIfLoadNewCountry()
}
func checkIfLoadNewCountry() {
let visible = map.centerCoordinate
geocode(latitude: visible.latitude, longitude: visible.longitude) { placemark, error in
if let error = error {
print("\(error)")
return
} else if let placemark = placemark?.first {
if let isoCountry = placemark.isoCountryCode?.lowercased() {
self.loadNewCountry(with: isoCountry)
}
}
}
}
func loadNewCountry(with country: String) {
let annotationsArray = [
self.viewModel1.array,
self.viewModel2.array,
self.viewModel3.array
] as [[MKAnnotation]]
let annotations = map.annotations
autoreleasepool {
annotations.forEach {
if !($0 is CustomAnnotationOne), !($0 is CustomAnnotationTwo) {
self.map.removeAnnotation($0)
}
}
}
let group = DispatchGroup()
let queue = DispatchQueue(label: "reload-annotations", attributes: .concurrent)
group.enter()
queue.async {
self.viewModel1.load(country: country)
group.leave()
}
group.enter()
queue.async {
self.viewModel2.load(country: country)
group.leave()
}
group.wait()
DispatchQueue.main.async {
for annoArray in annotationsArray {
self.map.addAnnotations(annoArray)
}
}
}
The key issue is that the code is initializing the [[MKAnnotation]] with the current view model results, then starting the load of the view models models for a new country, and then adding the old view model annotations to the map view.
Instead, grab the [[MKAnnotation]] after the reloading is done:
func loadNewCountry(with country: String) {
let annotations = map.annotations
annotations
.filter { !($0 is CustomAnnotationOne || $0 is CustomAnnotationTwo || $0 is MKUserLocation) }
.forEach { map.removeAnnotation($0) }
let group = DispatchGroup()
let queue = DispatchQueue(label: "reload-annotations", attributes: .concurrent)
queue.async(group: group) {
self.viewModel1.load(country: country)
}
queue.async(group: group) {
self.viewModel2.load(country: country)
}
group.notify(queue: .main) {
let annotationsArrays: [[MKAnnotation]] = [
self.viewModel1.array,
self.viewModel2.array,
self.viewModel3.array
]
for annotations in annotationsArrays {
self.map.addAnnotations(annotations)
}
}
}
Unrelated to the problem at hand, I have also:
simplified the DispatchGroup group syntax;
eliminated the wait as you should never block the main thread;
eliminated the unnecessary autoreleasepool;
added MKUserLocation to the types of annotations to exclude (even if you're not showing the user location right now, you might at some future date) ... you never want to manually remove MKUserLocation or else you can get weird UX;
renamed annotationArrays to make it clear that you’re dealing with an array of arrays.
As an aside, the above raises thread-safety concerns. You appear to be updating your view models on a background queue. If you are interacting with these view models elsewhere, make sure to synchronize your access. And, besides, the motivating idea of “view models” (as opposed to a “presenter” pattern, for example) is that you hook them up so that they inform the view of changes themselves.
So, you might consider:
Give the view models asynchronous startLoad methods;
Give the view models some mechanism to inform the view (on the main queue) of changes when a load is done (whether observers, delegate protocol, closures, etc.).
Make sure the view models synchronize interaction with their properties (e.g., array).
E.g., let us imagine that the view model is updating the view via closures:
typealias AnnotationBlock = ([MKAnnotation]) -> Void
protocol CountryLoader {
var didAdd: AnnotationBlock? { get set }
var didRemove: AnnotationBlock? { get set }
}
class ViewModel1: CountryLoader {
var array: [CustomAnnotationX] = []
var didAdd: AnnotationBlock?
var didRemove: AnnotationBlock?
func startLoad(country: String, completion: (() -> Void)? = nil) {
DispatchQueue.global().async {
let newArray: [CustomAnnotationX] = ... // computationally expensive load process here (on background queue)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.didRemove?(self.array) // tell view what was removed
self.array = newArray // update model on main queue
self.didAdd?(newArray) // tell view what was added
completion?() // tell caller that we're done
}
}
}
}
That is a thread-safe implementation that abstracts the view and view controller from any of the complicated asynchronous processes. Then the view controller needs to configure the view model:
class ViewController: UIViewController {
#IBOutlet weak var map: MKMapView!
let viewModel1 = ViewModel1()
let viewModel2 = ViewModel2()
let viewModel3 = ViewModel3()
override func viewDidLoad() {
super.viewDidLoad()
configureViewModels()
}
func configureViewModels() {
viewModel1.didRemove = { [weak self] annotations in
self?.map?.removeAnnotations(annotations)
}
viewModel1.didAdd = { [weak self] annotations in
self?.map?.addAnnotations(annotations)
}
...
}
}
Then, the “reload for country” becomes:
func loadNewCountry(with country: String) {
viewModel1.startLoad(country: country)
viewModel2.startLoad(country: country)
viewModel3.startLoad(country: country)
}
Or
func loadNewCountry(with country: String) {
showLoadingIndicator()
let group = DispatchGroup()
group.enter()
viewModel1.startLoad(country: country) {
group.leave()
}
group.enter()
viewModel2.startLoad(country: country) {
group.leave()
}
group.enter()
viewModel3.startLoad(country: country) {
group.leave()
}
group.notify(queue: .main) { [weak self] in
self?.hideLoadingIndicator()
}
}
Now that’s just one pattern. The implementation details could vary wildly, based upon how you have implemented your view model. But the idea is that you should:
make sure the view model is thread-safe;
abstract the complicated threading logic out of the view and keep it in the view model; and
have some process whereby the view model informs the view of the relevant changes.

How can I trigger a process after a returned publisher would be subscribed?

I have a function that returns a publisher. This publisher gives the results of a background process. I only want to trigger the background process when the publisher would be subscribed, so that no results are lost. The background process can update its results many times, so the variant with Future is not suitable.
private let passthroughSubject = PassthroughSubject<Data, Error>()
// This function will be used outside.
func fetchResults() -> AnyPublisher<Data, Error> {
return passthroughSubject
.eraseToAnyPublisher()
.somehowTriggerTheBackgroundProcess()
}
extension MyModule: MyDelegate {
func didUpdateResult(newResult: Data) {
self.passthroughSubject.send(newResult)
}
}
What have I tried?
Future:
Future<Data, Error> { [weak self] promise in
self?.passthroughSubject
.sink(receiveCompletion: { completion in
// My logic
}, receiveValue: { value in
// My logic
})
.store(in: &self.cancellableSet)
self?.triggerBackgroundProcess()
}.eraseToAnyPublisher()
Works the way I want but the subscriber is called only once (logical).
Deffered:
Deferred<AnyPublisher<Data, Error>>(createPublisher: { [weak self] in
defer {
self?.triggerBackgroundProcess()
}
return passthroughSubject.eraseToAnyPublisher()
}
Debugger shows that everything is correct: first return then trigger but the subscriber is not called for the first time.
receiveSubscription:
passthroughSubject
.handleEvents(receiveSubscription: { [weak self] subscription in
self?.triggerBackgroundProcess()
})
.eraseToAnyPublisher()
The same effect as with Deffered.
Is it even possible what I want to achieve?
Or, it is better to create a public publisher subscribe it and receive results from background process. And the fetchResults() function doesn't return anything?
Thanks in advance for your help.
You can write your own type that conforms to Publisher and wraps a PassthroughSubject. In your implementation, you can start the background process when you get a subscription.
public struct MyPublisher: Publisher {
public typealias Output = Data
public typealias Failure = Error
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let subject = PassthroughSubject<Output, Failure>()
subject.subscribe(subscriber)
startBackgroundProcess(subject: subject)
}
private func startBackgroundProcess(subject: PassthroughSubject<Output, Failure>) {
DispatchQueue.global(qos: .utility).async {
print("background process running")
subject.send(Data())
subject.send(completion: .finished)
}
}
}
Note that this publisher starts a new background process for each subscriber. That is a common implementation. For example URLSession.DataTaskPublisher issues a new request for each subscriber. If you want multiple subscribers to share the output of a single request, you can use the .multicast operator, add multiple subscribers, and then .connect() the multicast publisher to start the background process once:
let pub = MyPublisher().multicast { PassthroughSubject() }
pub.sink(...).store(in: &tickets) // first subscriber
pub.sink(...).store(in: &tickets) // second subscriber
pub.connect().store(in: &tickets) // start the background process
It seems to me that your last bit of code is a perfectly viable solution: don't trigger the background process until you detect the subscription. Example:
let subject = PassthroughSubject<String, Never>()
var storage = Set<AnyCancellable>()
func start() {
self.subject
.handleEvents(receiveSubscription: {_ in
print("subscribed")
DispatchQueue.main.async {
self.doSomethingAsynchronous()
}
})
.sink { print("got", $0) }
.store(in: &storage)
}
func doSomethingAsynchronous() {
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
self.subject.send("bingo")
}
}
}

How do you aggregate data from DispatchQueue.concurrentPerform() using GCD?

How is one supposed to aggregate data when using Grand Central Dispatch's ConcurrentPerform()?
I am doing what is in the code below, but resultDictionary seems to lose all its data when the notify() block ends. Thus all I get is an empty dictionary that is returned from the function.
I am not sure why this is happening, because when I print or set a breakpoint I can see there is something in the resultDictionary before the block ends.
let getCVPDispatchQueue = DispatchQueue(label: "blarg",
qos: .userInitiated,
attributes: .concurrent)
let getCVPDispatchGroup = DispatchGroup()
var resultDictionary = dataIDToSRLParticleDictionary()
getCVPDispatchQueue.async { [weak self] in
guard let self = self else { return }
DispatchQueue.concurrentPerform(iterations: self.dataArray.count) { [weak self] (index) in
guard let self = self else { return }
let data = self.dataArray[index]
getCVPDispatchGroup.enter()
let theResult = data.runPartcleFilterForClosestParticleAndMaybeStopAudio()
switch theResult {
case .success(let CVParticle):
// If there was a CVP found, add it to the set.
if let theCVParticle = CVParticle {
self.dataIDsToCVPDictionary.addTodataIDToCVPDict(key: data.ID,
value: theCVParticle)
}
case .failure(let error):
os_log(.error, log: self.logger, "rundatasProcessing error: %s", error.localizedDescription)
self._isActive = false
}
getCVPDispatchGroup.leave()
}
getCVPDispatchGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
print("DONE with \(self.dataIDsToCVPDictionary.getDictionary.count)")
resultDictionary = self.dataIDsToCVPDictionary.getDictionary
print("resultDictionary has \(self.dataIDsToCVPDictionary.getDictionary.count)")
}
}
print("Before Return with \(resultDictionary.count)")
return resultDictionary
}
Not sure if this will help, but this is simple class I made to made accessing the dictionary thread safe.
class DATASynchronizedIDToParticleDictionary {
var unsafeDictionary: DATAIDToDATAParticleDictionary = DATAIDToDATAParticleDictionary()
let accessQueue = DispatchQueue(label: "blarg2",
qos: .userInitiated,
attributes: .concurrent)
var getDictionary: DATAIDToDATAParticleDictionary {
get {
var dictionaryCopy: DATAIDToDATAParticleDictionary!
accessQueue.sync {
dictionaryCopy = unsafeDictionary
}
return dictionaryCopy
}
}
func addToDATAIDToCVPDict(key: String, value: DATAParticle) {
accessQueue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.unsafeDictionary[key] = value
}
}
func clearDictionary() {
accessQueue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.unsafeDictionary.removeAll()
}
}
}
You said:
I am doing what is in the code below, but resultDictionary seems to lose all its data when the notify() block ends. Thus all I get is an empty dictionary that is returned from the function.
The issue is that you’re trying to return a value that is calculated asynchronously. You likely want to shift to a completion block pattern.
As an aside, the dispatch group is not necessary. Somewhat ironically, the concurrentPerform is synchronous (i.e. it doesn’t proceed until the parallelized for loop is finished). So there’s no point in using notify if you know that you won’t get to the line after the concurrentPerform until all the iterations are done.
I’d also discourage having the concurrentPerform loop update properties. It exposes you to a variety of problems. E.g. what if the main thread was interacting with that object at the same time? Sure, you can synchronize your access, but it may be incomplete. It’s probably safer to have it update local variables only, and have the caller do the property update in its completion handler block. Obviously, you can go ahead and update properties (esp if you want to update your UI to reflect the in-flight progress), but it adds an additional wrinkle to the code that might not be necessary. Below, I’ve assumed it wasn’t necessary.
Also, while I appreciate the intent behind all of these [weak self] references, they’re really not needed, especially in your synchronization class DATASynchronizedIDToParticleDictionary. We often use weak references to avoid strong reference cycles. But if you don’t have strong references, they just add overhead unless you have some other compelling need.
OK, so let’s dive into the code.
First, I’d retire the specialized DATASynchronizedIDToParticleDictionary with a general-purpose generic:
class SynchronizedDictionary<Key: Hashable, Value> {
private var _dictionary: [Key: Value]
private let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".dictionary", qos: .userInitiated, attributes: .concurrent)
init(_ dictionary: [Key: Value] = [:]) {
_dictionary = dictionary
}
var dictionary: [Key: Value] {
queue.sync { _dictionary }
}
subscript(key: Key) -> Value? {
get { queue.sync { _dictionary[key] } }
set { queue.async(flags: .barrier) { self._dictionary[key] = newValue } }
}
func removeAll() {
queue.async(flags: .barrier) {
self._dictionary.removeAll()
}
}
}
Note, I’ve removed the unnecessary weak references. I’ve also renamed addToDATAIDToCVPDict and clearDictionary with a more natural subscript operator and a removeAll method that more closely mirrors the interface of the underlying Dictionary type. It results in more natural looking code. (And because this is a generic, we can use it for any dictionary that needs this sort of low level synchronization.)
Anyway, you can now declare a synchronized rendition of the dictionary like so:
let particles = SynchronizedDictionary(dataIDToSRLParticleDictionary())
And when I want to update the dictionary with some value, you can do:
particles[data.ID] = theCVParticle
And when I want retrieve actual underlying, wrapped dictionary, I can do:
let finalResult = particles.dictionary
While we’re at it, since we might want to keep track of an array of errors that needs to be synchronized, I might add an array equivalent type:
class SynchronizedArray<Value> {
private var _array: [Value]
private let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".array", qos: .userInitiated, attributes: .concurrent)
init(_ dictionary: [Value] = []) {
_array = dictionary
}
var array: [Value] {
queue.sync { _array }
}
subscript(index: Int) -> Value {
get { queue.sync { _array[index] } }
set { queue.async(flags: .barrier) { self._array[index] = newValue } }
}
func append(_ value: Value) {
queue.async(flags: .barrier) {
self._array.append(value)
}
}
func removeAll() {
queue.async(flags: .barrier) {
self._array.removeAll()
}
}
}
We can now turn our attention to the main routine. So rather than returning a value, we’ll instead give it an #escaping completion handler. And, as discussed above, we’d retire the unnecessary dispatch group:
func calculateAllClosestParticles(completion: #escaping ([String: CVParticle], [Error]) -> Void) {
let queue = DispatchQueue(label: "blarg", qos: .userInitiated, attributes: .concurrent)
let particles = SynchronizedDictionary(dataIDToSRLParticleDictionary())
let errors = SynchronizedArray<Error>()
queue.async {
DispatchQueue.concurrentPerform(iterations: self.dataArray.count) { index in
let data = self.dataArray[index]
let result = data.runPartcleFilterForClosestParticleAndMaybeStopAudio()
switch result {
case .success(let cvParticle):
// If there was a CVP found, add it to the set.
if let cvParticle = cvParticle {
particles[data.ID] = cvParticle
}
case .failure(let error):
errors.append(error)
}
}
DispatchQueue.main.async {
completion(particles.dictionary, errors.array)
}
}
}
Now, I don’t know what the right types were for the dictionary, so you might need to adjust the parameters of the completion. And you didn’t provide the rest of the routines, so I may have some details wrong here. But don’t get lost in the details, but just note the scrupulous avoidance of properties within the concurrentPerform and the passing of the results back in the completion handler.
You’d call it like so:
calculateAllClosestParticles { dictionary, errors in
guard errors.isEmpty else { return }
// you can access the dictionary and updating the model and UI here
self.someProperty = dictionary
self.tableView.reloadData()
}
// but don't try to access the dictionary here, because the asynchronous code hasn't finished yet
//
FWIW, while I used the reader-writer pattern you did in your example, in my experience, NSLock is actually more performant for quick synchronizations, especially when you are using concurrentPerform that might tie up all of the cores on your CPU, e.g.
class SynchronizedDictionary<Key: Hashable, Value> {
private var _dictionary: [Key: Value]
private let lock = NSLock()
init(_ dictionary: [Key: Value] = [:]) {
_dictionary = dictionary
}
var dictionary: [Key: Value] {
lock.synchronized { _dictionary }
}
subscript(key: Key) -> Value? {
get { lock.synchronized { _dictionary[key] } }
set { lock.synchronized { _dictionary[key] = newValue } }
}
func removeAll() {
lock.synchronized {
_dictionary.removeAll()
}
}
}
Where
extension NSLocking {
func synchronized<T>(_ closure: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try closure()
}
}
Bottom line, you don’t want to force context switches for synchronization if you don’t have to.
When doing concurrent perform, if you have many dataPoints and if the time required by each call to runPartcleFilterForClosestParticleAndMaybeStopAudio is modest, you might want to consider “striding”, doing several datapoint in each iteration. It’s beyond the scope of this question, but just a FYI.
Not exactly sure what I did, but I moved the
resultDictionary = self.dataIDsToCVPDictionary.getDictionary
outside the first async block and that seem to allowed the data to be retained/remain for the function return.

Waiting until the task finishes

How could I make my code wait until the task in DispatchQueue finishes? Does it need any CompletionHandler or something?
func myFunction() {
var a: Int?
DispatchQueue.main.async {
var b: Int = 3
a = b
}
// wait until the task finishes, then print
print(a) // - this will contain nil, of course, because it
// will execute before the code above
}
I'm using Xcode 8.2 and writing in Swift 3.
If you need to hide the asynchronous nature of myFunction from the caller, use DispatchGroups to achieve this. Otherwise, use a completion block. Find samples for both below.
DispatchGroup Sample
You can either get notified when the group's enter() and leave() calls are balanced:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
a = 1
group.leave()
}
// does not wait. But the code in notify() is executed
// after enter() and leave() calls are balanced
group.notify(queue: .main) {
print(a)
}
}
or you can wait:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
Note: group.wait() blocks the current queue (probably the main queue in your case), so you have to dispatch.async on another queue (like in the above sample code) to avoid a deadlock.
Completion Block Sample
func myFunction(completion: #escaping (Int)->()) {
var a = 0
DispatchQueue.main.async {
let b: Int = 1
a = b
completion(a) // call completion after you have the result
}
}
// on caller side:
myFunction { result in
print("result: \(result)")
}
In Swift 3, there is no need for completion handler when DispatchQueue finishes one task.
Furthermore you can achieve your goal in different ways
One way is this:
var a: Int?
let queue = DispatchQueue(label: "com.app.queue")
queue.sync {
for i in 0..<10 {
print("Ⓜ️" , i)
a = i
}
}
print("After Queue \(a)")
It will wait until the loop finishes but in this case your main thread will block.
You can also do the same thing like this:
let myGroup = DispatchGroup()
myGroup.enter()
//// Do your task
myGroup.leave() //// When your task completes
myGroup.notify(queue: DispatchQueue.main) {
////// do your remaining work
}
One last thing: If you want to use completionHandler when your task completes using DispatchQueue, you can use DispatchWorkItem.
Here is an example how to use DispatchWorkItem:
let workItem = DispatchWorkItem {
// Do something
}
let queue = DispatchQueue.global()
queue.async {
workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
// Here you can notify you Main thread
}
Swift 5 version of the solution
func myCriticalFunction() {
var value1: String?
var value2: String?
let group = DispatchGroup()
group.enter()
//async operation 1
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value1 = //out of async task
group.leave()
}
group.enter()
//async operation 2
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value2 = //out of async task
group.leave()
}
group.wait()
print("Value1 \(value1) , Value2 \(value2)")
}
Use dispatch group
dispatchGroup.enter()
FirstOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.enter()
SecondOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.wait() // Waits here on this thread until the two operations complete executing.
In Swift 5.5+ you can take advantage of Swift Concurrency which allows to return a value from a closure dispatched to the main thread
func myFunction() async {
var a : Int?
a = await MainActor.run {
let b = 3
return b
}
print(a)
}
Task {
await myFunction()
}
Swift 4
You can use Async Function for these situations. When you use DispatchGroup(),Sometimes deadlock may be occures.
var a: Int?
#objc func myFunction(completion:#escaping (Bool) -> () ) {
DispatchQueue.main.async {
let b: Int = 3
a = b
completion(true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
myFunction { (status) in
if status {
print(self.a!)
}
}
}
Somehow the dispatchGroup enter() and leave() commands above didn't work for my case.
Using sleep(5) in a while loop on the background thread worked for me though. Leaving here in case it helps someone else and it didn't interfere with my other threads.

Check if on correct dispatch queue in Swift 3

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.
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/
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
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)
}
}
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
One related option is to set a Main Queue / UI Queue precondition:
dispatchPrecondition(condition: .onQueue(DispatchQueue.main))
/*
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")
}