Waiting for completion handler to complete, before continuing - ios

I have an array of 9 images and I'd like to save them all to the user's camera roll. You can do that with UIImageWriteToSavedPhotosAlbum. I wrote a loop to save each image. The problem with this is that for some reason, it will only save the first five. Now, order is important, so if an image fails to save, I want to retry and wait until it succeeds, rather than have some unpredictable race.
So, I implement a completion handler, and thought I could use semaphores like so:
func save(){
for i in (0...(self.imagesArray.count-1)).reversed(){
print("saving image at index ", i)
semaphore.wait()
let image = imagesArray[i]
self.saveImage(image)
}
}
func saveImage(_ image: UIImage){
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
//due to some write limit, only 5 images get written at once.
if let error = error {
print("trying again")
self.saveImage(image)
} else {
print("successfully saved")
semaphore.signal()
}
}
The problem with my code is it gets blocked out after the first save and semaphore.signal never gets called. I'm thinking my completion handler is supposed to be called on the main thread, but is already being blocked by the semaphore.wait(). Any help appreciated. Thanks

As others have pointed out, you want to avoid waiting on the main thread, risking deadlocking. So, while you can push it off to a global queue, the other approach is to employ one of the many mechanisms for performing a series of asynchronous tasks. Options include asynchronous Operation subclass or promises (e.g. PromiseKit).
For example, to wrap the image saving task in an asynchronous Operation and add them to an OperationQueue you could define your image save operation like so:
class ImageSaveOperation: AsynchronousOperation {
let image: UIImage
let imageCompletionBlock: ((NSError?) -> Void)?
init(image: UIImage, imageCompletionBlock: ((NSError?) -> Void)? = nil) {
self.image = image
self.imageCompletionBlock = imageCompletionBlock
super.init()
}
override func main() {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
imageCompletionBlock?(error)
complete()
}
}
Then, assuming that you had an array, images, i.e. that was a [UIImage], you could then do:
let queue = OperationQueue()
queue.name = Bundle.main.bundleIdentifier! + ".imagesave"
queue.maxConcurrentOperationCount = 1
let operations = images.map {
return ImageSaveOperation(image: $0) { error in
if let error = error {
print(error.localizedDescription)
queue.cancelAllOperations()
}
}
}
let completion = BlockOperation {
print("all done")
}
operations.forEach { completion.addDependency($0) }
queue.addOperations(operations, waitUntilFinished: false)
OperationQueue.main.addOperation(completion)
You can obviously customize this to add retry logic upon error, but that is likely not needed now because the root of the "too busy" problem was a result of too many concurrent save requests, which we've eliminated. That only leaves errors that are unlikely to solved by retrying, so I probably wouldn't add retry logic. (The errors are more likely to be permissions failures, out of space, etc.) But you can add retry logic if you really want. More likely, if you have an error, you might want to just cancel all of the remaining operations on the queue, like I have above.
Note, the above subclasses AsynchronousOperation, which is just an Operation subclass for which isAsynchronous returns true. For example:
/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `completeOperation()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `completeOperation()` is called.
public class AsynchronousOperation : Operation {
private let syncQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".opsync")
override public var isAsynchronous: Bool { return true }
private var _executing: Bool = false
override private(set) public var isExecuting: Bool {
get {
return syncQueue.sync { _executing }
}
set {
willChangeValue(forKey: "isExecuting")
syncQueue.sync { _executing = newValue }
didChangeValue(forKey: "isExecuting")
}
}
private var _finished: Bool = false
override private(set) public var isFinished: Bool {
get {
return syncQueue.sync { _finished }
}
set {
willChangeValue(forKey: "isFinished")
syncQueue.sync { _finished = newValue }
didChangeValue(forKey: "isFinished")
}
}
/// Complete the operation
///
/// This will result in the appropriate KVN of isFinished and isExecuting
public func complete() {
if isExecuting { isExecuting = false }
if !isFinished { isFinished = true }
}
override public func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
main()
}
}
Now, I appreciate operation queues (or promises) is going to seem like overkill for your situation, but it's a useful pattern that you can employ wherever you have a series of asynchronous tasks. For more information on operation queues, feel free to refer to the Concurrency Programming Guide: Operation Queues.

Year ago I faced Same problem
You should try to put your code in Dispatach.global queue it will surely help
Reason : I really don't know about reason too , I think it what semaphore it is , May be it is required to execute in Background Thread to sync wait and signal

As Mike alter mentioned, using a Dispatch.global().async helped solve the problem. The code now looks like this:
func save(){
for i in (0...(self.imagesArray.count-1)).reversed(){
DispatchQueue.global().async { [unowned self] in
self.semaphore.wait()
let image = self.imagesArray[i]
self.saveImage(image)
}
}
}
I suspect the problem was that the completion handler gets executed in the main thread, which was already locked by the semaphore.wait() called initially. So when the completion occurs, semaphore.signal() never gets called.
This is solved by running the tasks an asynchronous queue.

Related

EXC_BAD_ACCESS KERN_INVALID_ADDRESS crash in addOperation of OperationQueue

I have asynchronous operation implementation like this:
class AsyncOperation: Operation {
private enum State: String {
case ready, executing, finished
fileprivate var keyPath: String {
return "is\(rawValue.capitalized)"
}
}
private let stateAccessQueue = DispatchQueue(label: "com.beamo.asyncoperation.state")
private var unsafeState = State.ready
private var state: State {
get {
return stateAccessQueue.sync {
return unsafeState
}
}
set {
willChangeValue(forKey: newValue.keyPath)
stateAccessQueue.sync(flags: [.barrier]) {
self.unsafeState = newValue
}
didChangeValue(forKey: newValue.keyPath)
}
}
override var isReady: Bool {
return super.isReady && state == .ready
}
override var isExecuting: Bool {
return state == .executing
}
override var isFinished: Bool {
return state == .finished
}
override var isAsynchronous: Bool {
return true
}
override func start() {
if isCancelled {
if !isFinished {
finish()
}
return
}
state = .executing
main()
}
public final func finish() {
state = .finished
}
}
App will download multiple resources with API. Implementation of downloader is managed by operation queue and custom asynchronous operation. Here is implementation of asynchronous downloader operation:
final class DownloaderOperation: AsyncOperation {
private let downloadTaskAccessQueue = DispatchQueue(label: "com.download.task.access.queue")
private var downloadTask: URLSessionDownloadTask?
private var threadSafeDownloadTask: URLSessionDownloadTask? {
get {
return downloadTaskAccessQueue.sync {
return downloadTask
}
}
set {
downloadTaskAccessQueue.sync(flags: [.barrier]) {
self.downloadTask = newValue
}
}
}
private let session: URLSession
let download: Download
let progressHandler: DownloadProgressHandler
init(session: URLSession,
download: Download,
progressHandler: #escaping DownloadProgressHandler) {
self.session = session
self.download = download
self.progressHandler = progressHandler
}
override func main() {
let bodyModel = DownloaderRequestBody(fileUrl: download.fileUrl.absoluteString)
let bodyData = (try? JSONEncoder().encode(bodyModel)) ?? Data()
var request = URLRequest(url: URL(string: "api url here")!)
request.httpMethod = "POST"
request.httpBody = bodyData
let task = session.downloadTask(with: request)
task.countOfBytesClientExpectsToSend = 512
task.countOfBytesClientExpectsToSend = 1 * 1024 * 1024 * 1024 // 1GB
task.resume()
threadSafeDownloadTask = task
DispatchQueue.main.async {
self.download.progress = 0
self.download.status = .inprogress
self.progressHandler(self.model, 0)
}
}
override func cancel() {
super.cancel()
threadSafeDownloadTask?.cancel()
}
}
And downloader implementation is here:
final class MultipleURLSessionDownloader: NSObject {
private(set) var unsafeOperations: [Download: DownloaderOperation] = [:]
private(set) var progressHandlers: [Download: DownloadProgressHandler] = [:]
private(set) var completionHandlers: [Download: DownloadCompletionHandler] = [:]
private let operationsAccessQueue = DispatchQueue(label: "com.multiple.downloader.operations")
private(set) var operations: [Download: DownloaderOperation] {
get {
return operationsAccessQueue.sync {
return unsafeOperations
}
}
set {
operationsAccessQueue.sync(flags: [.barrier]) {
self.unsafeOperations = newValue
}
}
}
private lazy var downloadOperationQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "com.multiple.downloader.queue"
queue.maxConcurrentOperationCount = 2
return queue
}()
private(set) var urlSession = URLSession.shared
init(configuration: URLSessionConfiguration = URLSessionConfiguration.background(withIdentifier: "com.multiple.downloader.session")) {
super.init()
configuration.isDiscretionary = false
configuration.sessionSendsLaunchEvents = true
configuration.timeoutIntervalForResource = 120
configuration.timeoutIntervalForRequest = 10
self.urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
deinit {
debugPrint("[MultipleURLSessionDownloader] deinit")
}
func startDownload(downloads: [Download],
progressHandler: #escaping DownloadProgressHandler,
completionHandler: #escaping DownloadCompletionHandler) {
for download in downloads {
startDownload(
download: download,
progressHandler: progressHandler,
completionHandler: completionHandler
)
}
}
func cancelDownload(download: Download) {
cancelOperation(download: download)
}
func cancelAllDownloads() {
for download in operations.keys {
cancelOperation(download: download)
}
urlSession.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach {
$0.cancel()
}
uploadTasks.forEach {
$0.cancel()
}
downloadTasks.forEach {
$0.cancel()
}
}
}
private func startDownload(download: Download,
progressHandler: #escaping DownloadProgressHandler,
completionHandler: #escaping DownloadCompletionHandler) {
download.status = .default
progressHandlers[download] = progressHandler
completionHandlers[download] = completionHandler
if let operation = operations[download] {
if operation.isExecuting,
let inProgressDownload = operations.keys.first(where: { $0.id == download.id }) {
progressHandlers[download]?(inProgressDownload, inProgressDownload.progress ?? 0)
}
} else {
let operation = DownloaderOperation(
session: urlSession,
download: download) {[weak self] (progressDownload, progress) in
self?.progressHandlers[progressDownload]?(progressDownload, progress)
}
operations[download] = operation
downloadOperationQueue.addOperation(operation)
}
}
private func cancelOperation(download: Download) {
download.status = .cancelled
operations[download]?.cancel()
callCompletion(download: download, error: nil)
}
private func callCompletion(download: Download,
error: DownloadError?) {
operations[download]?.finish()
operations[download] = nil
progressHandlers[download] = nil
download.progress = nil
let handler = completionHandlers[download]
completionHandlers[download] = nil
handler?(download, error)
}
}
There is 2 cancel method:
cancelDownload(download: Download)
cancelAllDownloads()
If download is canceled and tried to download multiple times app is crashed and crash log is here:
If download is cancelledAll and tried to download multiple times app is crashed and crash log is here:
And the strange thing here is if I open app by running from Xcode the crash is not happened.
This is happening only when if I open app from device without running by Xcode.
For now I fixed replecaing operation queue with dispatch queue like this:
downloadOperationQueue.addOperation(operation)
downloadDispatchQueue.async {
operation.start()
}
And this is working fine without any crash.
I think crash is happening on addOperation method of OperationQueue, but I don't know the reason.
It does not make much sense to use operations in conjunction with a background URLSession. The whole idea of background URLSession is network requests proceed if the user leaves the app, and even if the app has been terminated in the course of its normal lifecycle (e.g., jettisoned due to memory pressure). But an operation queue cannot survive the termination of an app. If you really want to use operations with background session, you will need to figure out how you will handle network requests that are still underway, but for which there is no longer any operation to represent that request.
Let us set that aside and look at the operation and diagnose the crash.
A critical issue is the isFinished KVO when the operation is canceled. The documentation is very clear that if the operation has started and is subsequently canceled, you must perform the isFinished KVO notifications. If the task not yet started. If you do that, you can receive a warning:
went isFinished=YES without being started by the queue it is in
That would seem to suggest that one should not issue isFinished KVO notifications if an unstarted operation is canceled. But, note you absolutely must set the underlying state to finished, or else the unstarted canceled operations will be retained by the operation queue.
But what is worse, that performing the isFinished KVO notifications for unstarted operations it can lead to NSKeyValueDidChangeWithPerThreadPendingNotifications in the stack trace, just like you showed in your question. Here is my stack trace from my crash:
So, all of that said, I discovered two work-arounds:
Use locks for synchronization rather than GCD. They are more performant, anyway, and in my experiments, avoid the crash. (This is an unsatisfying solution because it is unclear as to whether it really solved the root problem, or just moved the goal-posts sufficiently, such that the crash no longer manifests itself.)
Alternatively, when you set the state to .finished, only issue the isFinished KVO if the operation was currently running. (This is also a deeply unsatisfying solution, as it is contemplated nowhere in Apple’s documentation. But it silences the above warning and eliminates the crash.)
For example:
func finish() {
if isExecuting {
state = .finished // change state with KVO
} else {
synchronized { _state = .finished } // change state without KVO
}
}
Basically, that sets the state to finished either way, with KVO notifications if the operation is executing, and without if not yet started when it is canceled.
So, you might end up with:
open class AsynchronousOperation: Operation {
override open var isReady: Bool { super.isReady && state == .ready }
override open var isExecuting: Bool { state == .executing }
override open var isFinished: Bool { state == .finished }
override open var isAsynchronous: Bool { true }
private let lock = NSLock()
private var _state: State = .ready
private var state: State {
get { synchronized { _state } }
set {
let oldValue = state
guard oldValue != .finished else { return }
willChangeValue(forKey: oldValue.keyPath)
willChangeValue(forKey: newValue.keyPath)
synchronized { _state = newValue }
didChangeValue(forKey: newValue.keyPath)
didChangeValue(forKey: oldValue.keyPath)
}
}
override open func start() {
if isCancelled {
finish()
return
}
state = .executing
main()
}
open func finish() {
state = .finished
}
}
// MARK: - State
private extension AsynchronousOperation {
enum State: String {
case ready, executing, finished
fileprivate var keyPath: String {
return "is\(rawValue.capitalized)"
}
}
}
// MARK: - Private utility methods
private extension AsynchronousOperation {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock.lock()
defer { lock.unlock() }
return try block()
}
}
A few notes on the above:
If we refer to Listing 2-7 the old Concurrency Programming Guide: Operations, they illustrate (in Objective-C) the correct KVO notifications that must take place when we, for example, transition from “executing” to “finished”:
- (void)completeOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
So, as we transition from executing to finished, we have to perform the notification for both isExecuting and isFinished. But your state setter is only performing the KVO for the newValue (thus, only isFinished, neglecting to perform the isExecuting-related KVO notifications). This is likely unrelated to your problem at hand, but is important, nonetheless.
If you were to synchronize with GCD serial queue, the barrier becomes redundant. This lingering barrier was probably a legacy of a previous rendition using a reader-writer pattern. IMHO, the transition to a serial queue was prudent (as the reader-writer just introduces more problems than its negligible performance difference warrants). But if we eliminate the concurrent queue, we should also remove the redundant barrier. Or just eliminate GCD altogether as shown above.
I guard against state changes after the operation is already finished. This is a bit of defensive-programming adapted from Apple’s “Advanced NSOperations” implementation (which, admittedly, is no longer available on Apple’s site).
I would not recommend making finish a final function. While unlikely, it is possible that a subclass might want to add functionality. So I removed final.
I moved the GCD synchronization code into its own method, synchronized, so that I could easily switch between different synchronization mechanisms.
Given that Operation is an open class, I did the same here.

How to suspend dispatch queue inside for loop?

I have play and pause button. When I pressed play button, I want to play async talking inside for loop. I used dispatch group for async method's waiting inside for loop. But I cannot achieve pause.
startStopButton.rx.tap.bind {
if self.isPaused {
self.isPaused = false
dispatchGroup.suspend()
dispatchQueue.suspend()
} else {
self.isPaused = true
self.dispatchQueue.async {
for i in 0..<self.textBlocks.count {
self.dispatchGroup.enter()
self.startTalking(string: self.textBlocks[i]) { isFinished in
self.dispatchGroup.leave()
}
self.dispatchGroup.wait()
}
}
}
}.disposed(by: disposeBag)
And i tried to do with operationqueue but still not working. It is still continue talking.
startStopButton.rx.tap.bind {
if self.isPaused {
self.isPaused = false
self.talkingQueue.isSuspended = true
self.talkingQueue.cancelAllOperations()
} else {
self.isPaused = true
self.talkingQueue.addOperation {
for i in 0..<self.textBlocks.count {
self.dispatchGroup.enter()
self.startTalking(string: self.textBlocks[i]) { isFinished in
self.dispatchGroup.leave()
}
self.dispatchGroup.wait()
}
}
}
}.disposed(by: disposeBag)
Is there any advice?
A few observations:
Pausing a group doesn’t do anything. You suspend queues, not groups.
Suspending a queue stops new items from starting on that queue, but it does not suspend anything already running on that queue. So, if you’ve added all the textBlock calls in a single dispatched item of work, then once it’s started, it won’t suspend.
So, rather than dispatching all of these text blocks to the queue as a single task, instead, submit them individually (presuming, of course, that your queue is serial). So, for example, let’s say you had a DispatchQueue:
let queue = DispatchQueue(label: "...")
And then, to queue the tasks, put the async call inside the for loop, so each text block is a separate item in your queue:
for textBlock in textBlocks {
queue.async { [weak self] in
guard let self = self else { return }
let semaphore = DispatchSemaphore(value: 0)
self.startTalking(string: textBlock) {
semaphore.signal()
}
semaphore.wait()
}
}
FYI, while dispatch groups work, a semaphore (great for coordinating a single signal with a wait) might be a more logical choice here, rather than a group (which is intended for coordinating groups of dispatched tasks).
Anyway, when you suspend that queue, the queue will be preventing from starting anything queued (but will finish the current textBlock).
Or you can use an asynchronous Operation, e.g., create your queue:
let queue: OperationQueue = {
let queue = OperationQueue()
queue.name = "..."
queue.maxConcurrentOperationCount = 1
return queue
}()
Then, again, you queue up each spoken word, each respectively a separate operation on that queue:
for textBlock in textBlocks {
queue.addOperation(TalkingOperation(string: textBlock))
}
That of course assumes you encapsulated your talking routine in an operation, e.g.:
class TalkingOperation: AsynchronousOperation {
let string: String
init(string: String) {
self.string = string
}
override func main() {
startTalking(string: string) {
self.finish()
}
}
func startTalking(string: String, completion: #escaping () -> Void) { ... }
}
I prefer this approach because
we’re not blocking any threads;
the logic for talking is nicely encapsulated in that TalkingOperation, in the spirit of the single responsibility principle; and
you can easily suspend the queue or cancel all the operations.
By the way, this is a subclass of an AsynchronousOperation, which abstracts the complexity of asynchronous operation out of the TalkingOperation class. There are many ways to do this, but here’s one random implementation. FWIW, the idea is that you define an AsynchronousOperation subclass that does all the KVO necessary for asynchronous operations outlined in the documentation, and then you can enjoy the benefits of operation queues without making each of your asynchronous operation subclasses too complicated.
For what it’s worth, if you don’t need suspend, but would be happy just canceling, the other approach is to dispatching the whole for loop as a single work item or operation, but check to see if the operation has been canceled inside the for loop:
So, define a few properties:
let queue = DispatchQueue(label: "...")
var item: DispatchWorkItem?
Then you can start the task:
item = DispatchWorkItem { [weak self] in
guard let textBlocks = self?.textBlocks else { return }
for textBlock in textBlocks where self?.item?.isCancelled == false {
let semaphore = DispatchSemaphore(value: 0)
self?.startTalking(string: textBlock) {
semaphore.signal()
}
semaphore.wait()
}
self?.item = nil
}
queue.async(execute: item!)
And then, when you want to stop it, just call item?.cancel(). You can do this same pattern with a non-asynchronous Operation, too.

DispatchQueue Main async reference problem. (Reference gone)

I have a function for getting user session. I successfully get the session. But inside the DispatchQueue i lose task object (task:AWSTask< AWSCognitoIdentityUserSession >).
Before DispatchQueue it is not null but in the dispatch DispatchQueue it is null. Some how i am losing the reference.
What is the best way to get and object reference from outside of the DispatchQueue. (I dont want to create a general variable in the class.)
BTW this is not happening all the time.
var pool:AWSCognitoIdentityUserPool
override init(){
pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
super.init()
}
func getUserPool() -> AWSCognitoIdentityUserPool {
return pool
}
func getUserSession(completition: #escaping () -> Void)
{
let user = pool.currentUser()!
let task = user.getSession()
task.continueWith{ (task:AWSTask<AWSCognitoIdentityUserSession>) in
{
DispatchQueue.main.async
{
if(task.result != nil && task.error == nil)
{
/*
There are some calculations here
*/
completition()
}
}
}
}
}

How to ensure to run some code on same background thread?

I am using realm in my iOS Swift project. Search involve complex filters for a big data set. So I am fetching records on background thread.
But realm can be used only from same thread on which Realm was created.
I am saving a reference of results which I got after searching Realm on background thread. This object can only be access from same back thread
How can I ensure to dispatch code at different time to the same thread?
I tried below as suggested to solve the issue, but it didn't worked
let realmQueue = DispatchQueue(label: "realm")
var orginalThread:Thread?
override func viewDidLoad() {
super.viewDidLoad()
realmQueue.async {
self.orginalThread = Thread.current
}
let deadlineTime = DispatchTime.now() + .seconds(2)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
self.realmQueue.async {
print("realm queue after some time")
if self.orginalThread == Thread.current {
print("same thread")
}else {
print("other thread")
}
}
}
}
Output is
realm queue after some time
other thread
Here's a small worker class that can works in a similar fashion to async dispatching on a serial queue, with the guarantee that the thread stays the same for all work items.
// Performs submitted work items on a dedicated thread
class Worker {
// the worker thread
private var thread: Thread?
// used to put the worker thread in the sleep mode, so in won't consume
// CPU while the queue is empty
private let semaphore = DispatchSemaphore(value: 0)
// using a lock to avoid race conditions if the worker and the enqueuer threads
// try to update the queue at the same time
private let lock = NSRecursiveLock()
// and finally, the glorious queue, where all submitted blocks end up, and from
// where the worker thread consumes them
private var queue = [() -> Void]()
// enqueues the given block; the worker thread will execute it as soon as possible
public func enqueue(_ block: #escaping () -> Void) {
// add the block to the queue, in a thread safe manner
locked { queue.append(block) }
// signal the semaphore, this will wake up the sleeping beauty
semaphore.signal()
// if this is the first time we enqueue a block, detach the thread
// this makes the class lazy - it doesn't dispatch a new thread until the first
// work item arrives
if thread == nil {
thread = Thread(block: work)
thread?.start()
}
}
// the method that gets passed to the thread
private func work() {
// just an infinite sequence of sleeps while the queue is empty
// and block executions if the queue has items
while true {
// let's sleep until we get signalled that items are available
semaphore.wait()
// extract the first block in a thread safe manner, execute it
// if we get here we know for sure that the queue has at least one element
// as the semaphore gets signalled only when an item arrives
let block = locked { queue.removeFirst() }
block()
}
}
// synchronously executes the given block in a thread-safe manner
// returns the same value as the block
private func locked<T>(do block: () -> T) -> T {
lock.lock(); defer { lock.unlock() }
return block()
}
}
Just instantiate it and let it do the job:
let worker = Worker()
worker.enqueue { print("On background thread, yay") }
You have to create your own thread with a run loop for that. Apple gives an example for a custom run loop in Objective C. You may create a thread class in Swift with that like:
class MyThread: Thread {
public var runloop: RunLoop?
public var done = false
override func main() {
runloop = RunLoop.current
done = false
repeat {
let result = CFRunLoopRunInMode(.defaultMode, 10, true)
if result == .stopped {
done = true
}
}
while !done
}
func stop() {
if let rl = runloop?.getCFRunLoop() {
CFRunLoopStop(rl)
runloop = nil
done = true
}
}
}
Now you can use it like this:
let thread = MyThread()
thread.start()
sleep(1)
thread.runloop?.perform {
print("task")
}
thread.runloop?.perform {
print("task 2")
}
thread.runloop?.perform {
print("task 3")
}
Note: The sleep is not very elegant but needed, since the thread needs some time for its startup. It should be better to check if the property runloop is set, and perform the block later if necessary. My code (esp. runloop) is probably not safe for race conditions, and it's only for demonstration. ;-)

How can an NSOperationQueue wait for two async operations?

How can I make an NSOperationQueue (or anything else) wait for two async network calls with callbacks? The flow needs to look like this
Block Begins {
Network call with call back/block begins {
first network call is done
}
}
Second Block Begins {
Network call with call back/block begins {
second network call is done
}
}
Only run this block once the NETWORK CALLS are done {
blah
}
Here's what I have so far.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
__block NSString *var;
[queue addOperation:[NSBlockOperation blockOperationWithBlock:^{
[AsyncReq get:^{
code
} onError:^(NSError *error) {
code
}];
}]];
[queue addOperation:[NSBlockOperation blockOperationWithBlock:^{
[AsyncReq get:^{
code
} onError:^(NSError *error) {
code
}];
}]];
[queue waitUntilAllOperationsAreFinished];
//do something with both of the responses
Do you have to use NSOperation Queue? Here's how you can do it w/ a dispatch group:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[AsyncReq get:^{
code
dispatch_group_leave(group);
} onError:^(NSError *error) {
code
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[AsyncReq get:^{
code
dispatch_group_leave(group);
} onError:^(NSError *error) {
code
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"Both operations completed!")
});
Using Grand Central Dispatch and DispatchGroup
With Swift 3, in the simplest cases where you don't need fine grained control on tasks states, you can use Grand Central Dispatch and DispatchGroup. The following Playground code shows how it works:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
group.enter()
// Perform some asynchronous operation
let queue1 = DispatchQueue(label: "com.example.imagetransform")
queue1.async {
print("Task One finished")
group.leave()
}
group.enter()
// Perform some asynchronous operation
let queue2 = DispatchQueue(label: "com.example.retrievedata")
queue2.async {
print("Task Two finished")
group.leave()
}
group.notify(queue: DispatchQueue.main, execute: { print("Task Three finished") })
The previous code will print "Task Three finished" once the two asynchronous tasks are both finished.
Using OperationQueue and Operation
Using OperationQueue and Operation for your request tasks requires more boilerplate code but offers many advantages like kvoed state and dependency.
1. Create an Operation subclass that will act as an abstract class
import Foundation
/**
NSOperation documentation:
Operation objects are synchronous by default.
At no time in your start method should you ever call super.
When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread.
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
start, asynchronous, executing, finished.
*/
open class AbstractOperation: Operation {
#objc enum State: Int {
case isReady, isExecuting, isFinished
func canTransition(toState state: State) -> Bool {
switch (self, state) {
case (.isReady, .isExecuting): return true
case (.isReady, .isFinished): return true
case (.isExecuting, .isFinished): return true
default: return false
}
}
}
// use the KVO mechanism to indicate that changes to `state` affect other properties as well
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
return [#keyPath(state) as NSObject]
}
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
return [#keyPath(state) as NSObject]
}
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
return [#keyPath(state) as NSObject]
}
// A lock to guard reads and writes to the `_state` property
private let stateLock = NSLock()
private var _state = State.isReady
var state: State {
get {
stateLock.lock()
let value = _state
stateLock.unlock()
return value
}
set (newState) {
// Note that the KVO notifications MUST NOT be called from inside the lock. If they were, the app would deadlock.
willChangeValue(forKey: #keyPath(state))
stateLock.lock()
if _state == .isFinished {
assert(_state.canTransition(toState: newState), "Performing invalid state transition from \(_state) to \(newState).")
_state = newState
}
stateLock.unlock()
didChangeValue(forKey: #keyPath(state))
}
}
override open var isExecuting: Bool {
return state == .isExecuting
}
override open var isFinished: Bool {
return state == .isFinished
}
var hasCancelledDependencies: Bool {
// Return true if this operation has any dependency (parent) operation that is cancelled
return dependencies.reduce(false) { $0 || $1.isCancelled }
}
override final public func start() {
// If any dependency (parent operation) is cancelled, we should also cancel this operation
if hasCancelledDependencies {
finish()
return
}
if isCancelled {
finish()
return
}
state = .isExecuting
main()
}
open override func main() {
fatalError("This method has to be overriden and has to call `finish()` at some point")
}
open func didCancel() {
finish()
}
open func finish() {
state = .isFinished
}
}
2. Create your operations
import Foundation
open class CustomOperation1: AbstractOperation {
override open func main() {
if isCancelled {
finish()
return
}
// Perform some asynchronous operation
let queue = DispatchQueue(label: "com.app.serialqueue1")
let delay = DispatchTime.now() + .seconds(5)
queue.asyncAfter(deadline: delay) {
self.finish()
print("\(self) finished")
}
}
}
import Foundation
open class CustomOperation2: AbstractOperation {
override open func main() {
if isCancelled {
finish()
return
}
// Perform some asynchronous operation
let queue = DispatchQueue(label: "com.app.serialqueue2")
queue.async {
self.finish()
print("\(self) finished")
}
}
}
3. Usage
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// Declare operations
let operation1 = CustomOperation1()
let operation2 = CustomOperation2()
let operation3 = CustomOperation1()
// Set operation3 to perform only after operation1 and operation2 have finished
operation3.addDependency(operation2)
operation3.addDependency(operation1)
// Launch operations
let queue = OperationQueue()
queue.addOperations([operation2, operation3, operation1], waitUntilFinished: false)
With this code, operation3 is guaranteed to always be performed last.
You can find this Playground on this GitHub repo.
AsyncOperation maintains its state w.r.t to Operation own state.
Since the operations are of asynchronous nature. We need to explicitly define the state of our operation.
Because the execution of async operation returns immediately after it's called.
So in your subclass of AsyncOperation you just have to set the state of the operation as finished in your completion handler
class AsyncOperation: Operation {
public enum State: String {
case ready, executing, finished
//KVC of Operation class are
// isReady, isExecuting, isFinished
var keyPath: String {
return "is" + rawValue.capitalized
}
}
//Notify KVO properties of the new/old state
public var state = State.ready {
willSet {
willChangeValue(forKey: newValue.keyPath)
willChangeValue(forKey: state.keyPath)
}
didSet{
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
}
}
}
extension AsyncOperation {
//have to make sure the operation is ready to maintain dependancy with other operation
//hence check with super first
override open var isReady: Bool {
return super.isReady && state == .ready
}
override open var isExecuting: Bool {
return state == .executing
}
override open var isFinished: Bool {
return state == .finished
}
override open func start() {
if isCancelled {
state = .finished
return
}
main()
state = .executing
}
override open func cancel() {
super.cancel()
state = .finished
} }
Now to call if from your own Operation class
Class MyOperation: AsyncOperation {
override main() {
request.send() {success: { (dataModel) in
//waiting for success closure to be invoked before marking the state as completed
self.state = .finished
}
}
}

Resources