The following Swift class is built to emulate Java Thread sleep and interrupt methods with GDC. The sleep method creates a semaphore, waits for it to be signaled and returns true when sleep ended prematurely. The interrupt method signals the semaphore to get the thread out of sleep:
public protocol GDCRunnable {
func run()
}
public class GDCThread {
private var semaphore : dispatch_semaphore_t?;
private let queue : dispatch_queue_t;
private let runnable : GDCRunnable?;
public init(_priority :Int = DISPATCH_QUEUE_PRIORITY_DEFAULT,
runnable : GDCRunnable? ) {
self.runnable = runnable
queue = dispatch_get_global_queue(_priority, 0)
}
public func start() {
dispatch_async(queue) {
self.run();
}
}
public func run() {
if runnable != nil {runnable!.run()}
}
public func sleep(_timeoutMillis : Int) -> Bool {
objc_sync_enter(self)
semaphore = dispatch_semaphore_create(1)
objc_sync_exit(self)
let signaled = (dispatch_semaphore_wait(semaphore,
dispatch_time(DISPATCH_TIME_NOW, Int64(_timeoutMillis*1000000))) != 0)
if !signaled {
dispatch_semaphore_signal(semaphore);
}
objc_sync_enter(self)
semaphore = nil;
objc_sync_exit(self)
return signaled
}
public func interrupt () {
objc_sync_enter(self)
if let currentSemaphore = semaphore {
dispatch_semaphore_signal(currentSemaphore)
}
objc_sync_exit(self)
}
}
As you can see, I put some objc_sync_enter and objc_sync_exit (though it's most probably redundant), but it didn't help: with iPhone 6
emulator it works great, but iPad Retina emulator gives BAD INSTRUCTION CODE on dispatch_semaphore_wait. Any suggestions?
Related
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.
I have to run 1000 async calculations. Since the API has a limit of 50 requests/min I have to split it up into chunks of 50 and wait for a minute after processing once chunk. Eventually I want to print the results.
resultsArray = [Double]()
// chunked is an extension
points.chunked(into: 50).forEach { pointsChunk in
pointsChunk.forEach { pointsPair
// this function is async
service.calculate(pointsPair) { result in
resultsArray.append(result)
}
}
// wait for a minute before continuing with the next chunk
}
// after all 1000 calculations are done, print result
print(resultsArray)
I did try finding a solution with using DispatchGroup but struggled on how to incorporate a timer:
let queue = DispatchQueue(label: "MyQueue", attributes: .concurrent)
let chunkGroup = DispatchGroup()
let workGroup = DispatchGroup()
points.chunked(into: 50).forEach { pointsChunk in
chunkGroup.enter()
pointsChunk.forEach { routePointsPair in
workGroup.enter()
// do something async and in the callback:
workGroup.leave()
}
workGroup.notify(queue: queue) {
do { sleep(60) }
chunkGroup.leave()
}
}
chunkGroup.notify(queue: .main) {
print(resultArray)
}
This just executes all chunks at once instead of delayed by 60 seconds.
What I have implemented in a similar situation is manual suspending and resuming of my serial queue.
my queue reference:
public static let serialQueue = DispatchQueue(label: "com.queue.MyProvider.Serial")
func serialQueue() -> DispatchQueue {
return MyProvider.serialQueue
}
suspend queue:
func suspendSerialQueue() -> Void {
self.serialQueue().suspend()
}
resume queue after delay:
func resumeSerialQueueAfterDelay(seconds: Double) -> Void {
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + seconds) {
self.serialQueue().resume()
}
}
This way I have full control over when I suspend and when I resume the queue and I can spread out many API calls evenly over longer period of time.
self.serialQueue().async {
self.suspendSerialQueue()
// API call completion block {
self.resumeSerialQueueAfterDelay(seconds: delay)
}
}
Not sure if this is what you were looking for, but maybe you can adapt my example to your needs.
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 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
}
}
}
I'm moving my code from regular GCD to NSOperationQueue because I need some of the functionality. A lot of my code relies on dispatch_after in order to work properly. Is there a way to do something similar with an NSOperation?
This is some of my code that needs to be converted to NSOperation. If you could provide an example of converting it using this code, that would be great.
dispatch_queue_t queue = dispatch_queue_create("com.cue.MainFade", NULL);
dispatch_time_t mainPopTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeRun * NSEC_PER_SEC));
dispatch_after(mainPopTime, queue, ^(void){
if(dFade !=nil){
double incriment = ([dFade volume] / [self fadeOut])/10; //incriment per .1 seconds.
[self doDelayFadeOut:incriment with:dFade on:dispatch_queue_create("com.cue.MainFade", 0)];
}
});
NSOperationQueue doesn't have any timing mechanism in it. If you need to set up a delay like this and then execute an operation, you'll want to schedule the NSOperation from the dispatch_after in order to handle both the delay and making the final code an NSOperation.
NSOperation is designed to handle more-or-less batch operations. The use case is slightly different from GCD, and in fact uses GCD on platforms with GCD.
If the problem you are trying to solve is to get a cancelable timer notification, I'd suggest using NSTimer and invalidating it if you need to cancel it. Then, in response to the timer, you can execute your code, or use a dispatch queue or NSOperationQueue.
You can keep using dispatch_after() with a global queue, then schedule the operation on your operation queue. Blocks passed to dispatch_after() don't execute after the specified time, they are simply scheduled after that time.
Something like:
dispatch_after
(
mainPopTime,
dispatch_get_main_queue(),
^ {
[myOperationQueue addOperation:theOperationObject];
}
);
Seven years late, but iOS 7 introduced this functionality to OperationQueue.
https://developer.apple.com/documentation/foundation/operationqueue/3329365-schedule
You could make an NSOperation that performs a sleep: MYDelayOperation. Then add it as a dependency for your real work operation.
#interface MYDelayOperation : NSOperation
...
- (void)main
{
[NSThread sleepForTimeInterval:delay]; // delay is passed in constructor
}
Usage:
NSOperation *theOperationObject = ...
MYDelayOperation *delayOp = [[MYDelayOperation alloc] initWithDelay:5];
[theOperationObject addDependency:delayOp];
[myOperationQueue addOperations:#[ delayOp, theOperationObject] waitUntilFinished:NO];
[operationQueue performSelector:#selector(addOperation:)
withObject:operation
afterDelay:delay];
Swift 4:
DispatchQueue.global().asyncAfter(deadline: .now() + 10 { [weak self] in
guard let `self` = self else {return}
self. myOperationQueue.addOperation {
//...code...
}
}
I used following code to get delayed call of operation:
class DelayedBlockOperation: Operation {
private let deadline: DispatchTime
private let block: (() -> Void)?
private let queue: DispatchQueue
override var isAsynchronous: Bool { true }
override var isExecuting: Bool {
get { _executing }
set {
willChangeValue(forKey: "isExecuting")
_executing = newValue
didChangeValue(forKey: "isExecuting")
}
}
private var _executing: Bool = false
override var isFinished: Bool {
get { _finished }
set {
willChangeValue(forKey: "isFinished")
_finished = newValue
didChangeValue(forKey: "isFinished")
}
}
private var _finished: Bool = false
init(deadline: DispatchTime,
queue: DispatchQueue = .global(),
_ block: #escaping () -> Void = { }) {
self.deadline = deadline
self.queue = queue
self.block = block
}
override func start() {
queue.asyncAfter(deadline: deadline) {
guard !self.isCancelled else {
self.isFinished = true
return
}
guard let block = self.block else {
self.isFinished = true
return
}
self.isExecuting = true
block()
self.isFinished = true
}
}
}
I was inspired by the gist