Dispatch Queues - Nesting, Optimisation & Control - swift

Can I have your advice on the approved approach?
I have four processes that need to run sequentially:
calculateProcess1()
calculateProcess2()
calculateProcess3()
calculateProcess4()
These calculate data and update progress bars (circular) and other screen literals using dispatch queues to make the UI update.
Run them as is and lo and behold they all fire off simultaneously (what a surprise!).
How do I make the top level calls run in sequence (as above) and do I need to make changes to my DispatchQueues in the processes for updating the literals, e.g.:
DispatchQueue.main.sync {
let progressPercentage = (day*100/365)
self.progressLabel.text = String(Int(progressPercentage))+"%"
}
The initiating processes (calculateProcess1-4()) need to run from main. Should this be in ViewDidLoad, a button action or what is the most secure method?

A one approach would be to use Operation and OperationQueue. Setting maxConcurrentOperationCount: Int to 1 on the OperationQueue, will force operations to be performed in a sequence, one after another.
(Previously called NSOperation and NSOperationQueue)
This blogpost can be helpful: https://nshipster.com/nsoperation/
And of course awesome WWDC15 session: Advanced NSOperations

1- Don't sync in main thread as it'll cause a runtime crash , it should be async
DispatchQueue.main.sync {
2-
Should this be in ViewDidLoad, a button action or what is the most secure method?
it's up to you there is no thing related to security here , implement it as your UX
3- to run them in dequence create a custom Queue then dispatch them in it as it' ll run serially if the code inside all these methods run in the same thread of the queue meaning you don't internally dispatch inside another queue , you can also use DispatchGroup to be notified when all are done

If you want to keep it simple you can provide callback blocks to your calculateProcess methods and simply nest them.
calculateProcess1() {
calculateProcess2() {
calculateProcess3() {
calculateProcess4() {}
}
}
}
Should this be in ViewDidLoad, a button action or what is the most
secure method?
viewDidLoad is probably what you want. Keep in mind if you instantiate a new viewController of the same type and show it, it will happen again, once, for that new controller as well. Also, if you are doing anything with view frames, those are not guaranteed to be laid out in viewDidLoad, but if you are simply updating text then it should be fine.

Related

Do you need to use, and can you use, perform() and performAndWait() inside performBackgroundTask()?

If I am performing CoreData operations (delete local persistent data, fetch new data from online, save to persistent store) inside a storeContainer.performBackgroundTask() { context in ... } block,
1) Do I NEED to use context.perform() { } inside this to ensure it is thread safe?
2) CAN I use context.performAndWait() { } for part or all of the function inside the curly brackets if I wish to ensure, for example, deletion occurs before downloading and re-saving?
I'm having user crashes associated with CoreData saving which don't appear on testing. I suspect I am failing to understand something about CoreData. I haven't managed to find the answer to this question elsewhere in tutorials or StackOverflow despite searching for ages!
The main job of performBackgroundTask is to create an appropriate background context and call that context on respective queue. You don't need to use "perform" again to switch to private queue.
performAndWait is useful when ever you are on main queue but context is private and you want to finish the database update to move forward(similar cases). You don't need to call performAndWait inside perform because code inside perform executes serially. There is no harm in using though.

Swift: synchronously perform code in background; queue.sync does not work as I would expect

I would like to perform some code synchronously in the background, I really thought this is the way to go:
let queue = DispatchQueue.global(qos: .default)
queue.async {
print("\(Thread.isMainThread)")
}
but this prints true unless I use queue.async. async isn't possible as then the code will be executed in parallel. How can I achieve running multiple blocks synchronously in the background?
What I would like to achieve: synchronize events in my app with the devices calendar, which happens in the background. The method which does this can be called from different places multiple times so I would like to keep this in order and in the background.
Async execution isn't your problem, since you only care about the order of execution of your code blocks relative to each other but not relative to the main thread. You shouldn't block the main thread, which is in fact DispatchQueue.main and not DispatchQueue.global.
What you should do is execute your code on a serial queue asynchronously, so you don't block the main thread, but you still ensure that your code blocks execute sequentially.
You can achieve this using the following piece of code:
let serialQueue = DispatchQueue(label: "serialQueue")
serialQueue.async{ //call this whenever you need to add a new work item to your queue
//call function here
}
DispatchQueue is not equal to a Thread. Think of it as of a kind of abstraction over the thread pool.
That being said, main queue is indeed "fixed" on the main thread. And that is why, when you synchronously dispatch a work item from the main queue, you are still on the main thread.
To actually execute sync code in the background, you have to already be in the background:
DispatchQueue.global().async {
DispatchQueue.global().sync {
print("\(Thread.isMainThread)")
}
}
This will print false.
Also, as user #rmaddy correctly pointed out in comments, doing any expensive tasks synchronously from the main queue might result in your program becoming unresponsive, since the main thread is responsible for the UI updates.

Wait for a value to change and then perform a block

I have the following Method:
-(void) waitForStatusChangeAndPerformBlock:(MyBlockType)successBlock;
This is what the method should do:
Check if some status has the right value
If it does invoke the block successBlock
If not wait for the status to change to a given value and then invoke the block successBlock
I thought about KVO to check if the value has changed, but then I would have to store the block in some instance variable, or worse, an array, and I would loose the context of the current method call. So what I really want is something like this:
-(void) waitForStatusChangeAndPerformBlock:(MyBlockType)successBlock{
if(self.status == kDesiredStatus){
successBlock;
} else {
[TheMagicDispatchWaitUntil:(self.status == kDesiredStatus) andThenDoThis:^{
successBlock;
}];
}
}
Is there a way to achieve this without KVO or other helper methods?
If you want a theead to wait on an event - a message, timer, or whatever, one really nice way to do that is to use a Concurrent NSOperation. Those objects run on a separate thread, but have a runLoop so they can block in a the "normal" fashion inside the runloop callback waiting for something to happen.
That said, these do take a bit of finesse to get working. I have a demo project on gthub that lets you explore concurrent NSOperations (and there are others too).
Another nice way to block until something has done (on a thread) is to use "dispatch_wait()", which waits on all blocks that have been queued belonging to a group. This technique is pretty easy to pick up - you create a dispatch group and use the standard queues or create your own queue, then queue blocks using the dispatch_group functions. Once all are queued, you then dispatch_wait(forever) for the blocks to finish.
If you are doing just a simple routine and you don't have to call this method often, why don't you just use a while statement?
while (self.status != kDesiredStatus);
do {TheMagicDispatch}
succesBlock;

How to make NSOperation con-current with out adding to NSOperationQueue in iPhone

I am trying to create an NSOperation by subclassing from NSOperation.I want my operation to be executed on a separate thread as well as it should support canceling option i.e i should be able to stop the thread(or the operation) at any point of time.I tried adding my operation instance to NSOperationQueue,everything is working fine but "the operation" is being executed after some time which makes my application slow.So i tried running my operation alone by calling [theOperation start]; its pretty fast but executing on the main thread.How to make the NSOperation run on separate thread with canceling option please help.
either:
1) do not make it an NSOperation and explicitly create a thread.
2) use a second operation queue and/or increase the priority.
3) use a container type for the result. when you need it immediately, cancel the background load operation and load manually.
I believe you need to implement - (BOOL)isConcurrent:
- (BOOL)isConcurrent
{
return YES;
}
But there are implications. Read the Apple reference docs on NSOperation, particularly the section Subclassing Notes. Also note that if you want the operation to be cancellable, you need to implement the cancelling behaviour yourself: eg, in your main selector you need to check if the operation has been cancelled, and immediately return if it has.

Main thread's execution context

I need ideas on the following -
In the main thread at some point of execution say Point A(sequential logic), I need to remember the state of execution and delegate the execution of some other logic onto another thread, and let the main thread handle the UI events etc. When the delegated logic completes on the other thread then the flow of execution should continue from the point A and should recollect the entire execution context and proceed as if it never paused there.
Regards,
Sunil Phani Manne
It's hard to implement this exactly the way you're saying (for example do(things)... yield(other_thread); ...do(more_things);.
Here are a couple other options I can think of (you'd have to implement these yourself, using delegates or notifications for example; I'm just giving a basic outline of how it would work):
do(things)
[object doStuffOnOtherThreadWithCallback:^{ // block-based
do(more_things)...
}];
or...
do(things)
[object doStuffOnOtherThreadWithCallbackTarget:self // target/selector-based
selector:#selector(callbackSelector)];
}
- (void)callbackSelector {
do(more_things)...
}
One option you have is encapsulating the whole sequential logic that comes after Point A in your delegate and then execute it on the main thread when the secondary thread ends.
In other words, when you start the thread by calling, e.g.
[NSThread detachNewThreadSelector:sel toTarget:target withObject:delegate]
you can implement your target target so that it has a specific selector completion that is called at the end of sel on the main thread, like this (this is the your delegate class):
#implementation YOURDelegateClass {
.....
-(void)completion {
}
-(void)sel {
...
...
[self performSelectorOnMainThread:#selector(#"completion") withObject:self];
}
}
Of course you have many sub-options available here, like using a different call to start the background execution, etc.
The important point is that: you have to encapsulate in a selector all the logic that comes after Point A, and that you have to schedule the execution of this selector on the main thread, in order to get back to your context (although your context will have changed in the meantime because you will also have updated the UI).
EDIT:
Having to schedule the execution on the main thread defeats blocks from being suitable for this kind of callback. On the other side, block have the advantage that they in some limited sense give you access to the same lexical context in which the block was defined (which is roughly what you call context).
A workaround for this could be the following. Before detaching the new thread, store in a delegate the block you would like to execute at completion:
typedef void(^CustomBlock)(void);
#property (nonatomic, copy) CustomBlock customBlock;
....
int a = ...
delegate.customBlock = ^{
NSLog(#"hello %d.....", a);
}
[NSThread detachNewThreadSelector:sel...
....
-(void)completion {
[self customBlock];
}
Of course, you only get the context preservation that is guaranteed to you by block. But here you hit against a limit of the language.
If you need more context preservation, then the only possibility is encapsulating that context in your delegate class ivars.
One thing is for sure. There, most probably, isn't any direct feature in Cocoa that does that. Since you're saying that you can't duplicate the resources onto the new thread (for a very good reason), I am going to suggest that you make use of NSUndoManager. For every change you make in the thread, push an undo operation for that change onto the undo manager. At the end of the thread, execute all the undo operations in the undo manager object. This should, if done correctly, restore your state. Now, since the idea is untested, there could be a chance that not all actions can be undone. You will have to check that out first.