I know I can use GCD or an OperationQueue to do concurrent workload but I've been trying to look for a way to call multiple functions and have them start at the same time.
My project here is to trigger multiple webcams at once to have synchronised photos coming from different cameras.
I had found this post about how to display multiple feeds from multiple webcams at once here: Run multiple AVCaptureSessions or add multiple inputs
I am unsure of how I will be able to trigger a photo capture from these as of yet, but I first wanted to see how I would go about synchronising the call to the function.
My theoretical solution would be something like this:
create concurrent operation queue
make it so I can start the operation queue manually rather than start each operation automatically as it would be added to the queue (how ?)
add an operation that would take a picture from the associated video input. Once for each camera
start operation queue
wait for operations to finish
continue workflow
Is this possible ?
If not is calling multiple methods truly at once even possible ?
If even this isn't possible how would I go about synchronising the photo capture, maybe recording a short video, use timestamp right before the start of the recording to adjust delay and capture frames at a specific time in the resulting videos ?
Also in the comments, what is the tag for a MacOS application built in swift ? It's my first time asking for this rather than iOS so it would help find people who might be able to help.
Thanks for any insight you might be able to give me !
You are on the right track (NS)OperationQueue is made for this. Below is an example:
func operation1() {
print("\(#function) starts")
sleep(1)
print("\(#function) ends")
}
func operation2() {
print("\(#function) starts")
sleep(2)
print("\(#function) ends")
}
func operation3() {
print("\(#function) starts")
sleep(3)
print("\(#function) ends")
}
#IBAction func start(_ sender: Any) {
let operation = BlockOperation(block: {})
operation.addExecutionBlock { self.operation1() }
operation.addExecutionBlock { self.operation2() }
operation.addExecutionBlock { self.operation3() }
let endOperation = BlockOperation { self.allFinished() }
endOperation.addDependency(operation)
let queue = OperationQueue()
queue.addOperations([operation, endOperation], waitUntilFinished: false)
}
func allFinished() {
print("all finished")
}
operation1, 2, 3 can start in arbitrary order but the finishing order is always 1, 2, 3, and then allFinished is triggered.
Related
What I am dealing with: web page + backend (php), swift app (wkwebview + some native components).
What I am trying to achieve: I need to let the web page know my app is changing it's state, by executing some java scripts (one script for "enter background mode", another one for "enter foreground", and so on).
What is my issue: I can't handle the case of app termination. Whenever the user is killing the app by home-double-click/swipe up, ApplicationWillTerminate method from AppDelegate is being called, but it fails to execute webView.evaluateJavaScript from here, as far as I was able to understand - due to the closure / completion handler is has, for it is async by design.
All other cases are covered, works fine, and with the last one for termination I am stuck.
I am playing around with the following code:
func applicationWillTerminate(_ application: UIApplication) {
if let callback = unloadListenerCallback {
if callback == "" {
debugPrint("got null callback - stop listening")
unloadListenerCallback = nil
}
else {
jsCallbackInjection(s: callback) //executing JS callback injection into WebView
}
}
}
unloadListenerCallback is the string? variable, works as a charm in other cases (just to mention it for clarity). jsCallbackInjection is the function, also works perfectly elsewhere:
func jsCallbackInjection(s: String) {
let jscallbackname = s
HomeController.instance.webView?.evaluateJavaScript(jscallbackname) { (result, error) in
if error == nil {
print(result as Any)
print("Executed js callback: \(jscallbackname)")
} else {
print("Error is \(String(describing: error)), js callback is: \(jscallbackname)")
}
}
}
How do I made this code working, if possible? Any ideas, suggestions, tips or tricks? If not possible, any ideas on "how do I let my web counterpart know my app was terminated"? What do I do from here?
This is a cross-post from Apple Dev forum, where it is sitting for >1 week unattended.
Answering my own question:
I was not able to find a way of running JS from ApplicationWillTerminate.
However, I found a way of solving my issue, which is - instead of running JS, I am posting to my web service like that:
func applicationWillTerminate(_ application: UIApplication) {
let semaphore = DispatchSemaphore(value: 0)
//setup your request here - Alamofire in my case
DispatchQueue.global(qos: .background).async {
//make your request here
onComplete: { (response: Update) in
//handle response if needed
semaphore.signal()
},
onFail: {
//handle failure if needed
semaphore.signal()
})
}
semaphore.wait(timeout: .distantFuture)
}
This way, I am able to consistently report my app termination to the web page. I was lucky enough to have ajax already set up on the other end of a pipe, which I am just POSTing into with the simple AF request, so I don't need to struggle with JS anymore.
Basing on what I was able to find, there is NO suitable way of managing JS execution, due to
with semaphores, as soon as webview and it's methods are to be handled in main thread, you'll deadlock by using semaphore.wait()
no way I was able to find to run evaluateJavaScript synchronously
no way I was able to find to run the JS itself from JavaScriptCore, or some other way, within the same session and context
However, if someone still can contribute and provide solution, I'll be happy to accept it!
I have a class Complicated, where (it's not a real code):
class BeReadyInSomeTime {
var someData: SomeData
var whenDone: () -> Void
var isDone: Bool = false
var highRes: [LongCountedStuff] = []
init(data:SomeData, whenDone: #escaping () - >Void) {
self.someData = someData
self.whenDone = whenDone
... prepare `highRes` in background...
{ makeHighRes() }
... and when done set `isDone` to `true`, fire `whenDone()`
}
func reset(data:SomeData) {
self.someData = someData
self.isDone = false
self.highRes = []
... forget **immediately** about job from init or reset, start again
{ makeHighRes() }
... and when done set `isDone` to `true`, fire `whenDone()`
}
var highResolution:AnotherType {
if isDone {
return AnotherType(from: highRes)
} else {
return AnotherType(from: someData)
}
}
func makeHighRes() {
var result = [LongCountedStuff]
// prepare data, fast
let some intermediateResult = almost ()
self.highRes = result
}
func almost() -> [LongCountedStuff] {
if isNice {
return countStuff(self.someData)
} else {
return []
}
func countStuff(stuff:[LongCountedStuff], deep:Int = 0) -> [LongCountedSuff] {
if deep == deep enough {
return stuff
} else {
let newStuff = stuff.work
count(newStuff, deep: deep+1)
}
}
Making highRes array is a recurrent function which calls itself many times and sometimes it takes seconds, but I need feedback as fast as possible (and it will be one of someData elements, so I'm safe). As far I know, I can only 'flag' DispatchWorkItem that's cancelled. If I deliver new data by reset few times per second (form mouse drag) whole block is counted in background as many times as data was delivered. How to deal with this kind of problem? To really break counting highRes?
If you have a routine that is constantly calling another framework and you want to stop it at the end of one iteration and before it starts the next iteration, then wrapping this in an Operation and checking isCancelled is a good pattern. (You can also use GCD and DispatchWorkItem and use its isCancelled, too, but I find operations do this more elegantly.)
But if you’re saying you not only want to cancel your loop, but also hope to stop the consuming call within that framework, then, no, you can’t do that (unless the framework provides some cancelation mechanism of its own). But there is no preemptive cancellation. You can’t just stop a time consuming calculation unless you add checks inside that calculation to check to see if it has been canceled.
I’d also ask whether the recursive pattern is right here. Do you really need the results of one calculation in order to start the next? If so, then a recursive (or iterative) pattern is fine. But if the recursive operation is just to pass the next unit of work, then a non-recursive pattern might be better, because it opens up the possibility of doing calculations in parallel.
For example, you might create a concurrent queue with a maxConcurrencyCount of some reasonable value (e.g. 4 or 6). Then wrap each individual processing task in its own Operation subclass and have each check its respective isCancelled. Then you can just add all the operations up front, and let the queue handle it from there. And when you want to stop them, you can tell the queue to cancelAllOperations. It’s a relative simple pattern, allows you to do calculations in parallel, and is cancelable. But this obviously only works if a given operations is not strictly dependent upon the results of the prior operation(s).
Problem is how to wait for an async query on HealthKit to return a result BEFORE allowing execution to move on. The returned data is critical for further execution.
I know this has been asked/solved many times and I have read many of the posts, however I have tried completion handlers, Dispatch sync and Dispatch Groups and have not been able to come up with an implementation that works.
Using completion handler
per Wait for completion handler to finish - Swift
This calls a method to run a HealthKit Query:
func readHK() {
var block: Bool = false
hk.findLastBloodGlucoseInHealthKit(completion: { (result) -> Void in
block = true
if !(result) {
print("Problem with HK data")
}
else {
print ("Got HK data OK")
}
})
while !(block) {
}
// now move on to the next thing ...
}
This does work. Using "block" variable to hold execution pending the callback in concept seems not that different from blocking semaphores, but it's really ugly and asking for trouble if the completion doesn't return for whatever reason. Is there a better way?
Using Dispatch Groups
If I put Dispatch Group at the calling function level:
Calling function:
func readHK() {
var block: Bool = false
dispatchGroup.enter()
hk.findLastBloodGlucoseInHealthKit(dg: dispatchGroup)
print ("Back from readHK")
dispatchGroup.notify(queue: .main) {
print("Function complete")
block = true
}
while !(block){
}
}
Receiving function:
func findLastBloodGlucoseInHealthKit(dg: DispatchGroup) {
print ("Read last HK glucose")
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: glucoseQuantity!, predicate: nil, limit: 10, sortDescriptors: [sortDescriptor]) { (query, results, error) in
// .... other stuff
dg.leave()
The completion executes OK, but the .notify method is never called, so the block variable is never updated, program hangs and never exits from the while statement.
Put Dispatch Group in target function but leave .notify at calling level:
func readHK() {
var done: Bool = false
hk.findLastBloodGlucoseInHealthKit()
print ("Back from readHK")
hk.dispatchGroup.notify(queue: .main) {
print("done function")
done = true
}
while !(done) {
}
}
Same issue.
Using Dispatch
Documentation and other S.O posts say: “If you want to wait for the block to complete use the sync() method instead.”
But what does “complete” mean? It seems that it does not mean complete the function AND get the later async completion. For example, the below does not hold execution until the completion returns:
func readHK() {
DispatchQueue.global(qos: .background).sync {
hk.findLastBloodGlucoseInHealthKit()
}
print ("Back from readHK")
}
Thank you for any help.
Yes, please don't fight the async nature of things. You will almost always lose, either by making an inefficient app (timers and other delays) or by creating opportunities for hard-to-diagnose bugs by implementing your own blocking functions.
I am far from a Swift/iOS expert, but it appears that your best alternatives are to use Grand Central Dispatch, or one of the third-party libraries for managing async work. Look at PromiseKit, for example, although I haven't seen as nice a Swift Promises/Futures library as JavaScript's bluebird.
You can use DispatchGroup to keep track of the completion handler for queries. Call the "enter" method when you set up the query, and the "leave" at the end of the results handler, not after the query has been set up or executed. Make sure that you exit even if the query is completed with an error. I am not sure why you are having trouble because this works fine in my app. The trick, I think, is to make sure you always "leave()" the dispatch group no matter what goes wrong.
If you prefer, you can set a barrier task in the DispatchQueue -- this will only execute when all of the earlier tasks in the queue have completed -- instead of using a DispatchGroup. You do this by adding the correct options to the DispatchWorkItem.
I try to refresh a text field to provide information. But i realize i can not update this display till the code ends. I tried to make it async but no success. Can someone to explain me with a simple example?
I show you this simple code. The text field named txtLog only show "Nothing else" when count of i (100000) is ended. Why?
#IBAction func elimineDoublons(_ sender: Any) {
DispatchQueue.main.async {
self.txtLog.stringValue="Nothing else"
}
for i in 0...100000{
print(i)
}
}
Can you explain me or show me a simple example? Please...
PS: Sorry for my english, i'm french...
You get this behaviour because your for loop is running in the main queue: #IBAction functions are always running in the main queue. So, you are blocking the queue in which you want to run self.txtLog.stringValue="Nothing else". The UI is always updated in the main thread, so GCD (Grand Central Dispatch) waits for the end of your loop before running this code.
Keep in mind that UI components can only be properly manipulated on the main thread.
Therefore, the for loop must be running in a background queue. For instance, replace your code by this one:
#IBAction func elimineDoublons(_ sender: Any) {
txtLog.stringValue = "Nothing else"
DispatchQueue.global(qos: .background).async(elimineDoublons)
}
private func elimineDoublons() {
for i in 0...100000 {
print(i)
}
}
Note that we have defined two functions with the same name, but their signatures are not the same, this is why it works correctly. Do not change the signatures, it may not compile anymore.
I'm looping through a table's rows, for each of them I'm doing a couple of async calls like fetching data from API, copying files, running shell script... How do I wait for the result until going to the next one.
Also I'm new to Swift, not sure if this is the best way to handle a group of async tasks. Should I use concurrency in this case ?
tableView.selectedRowIndexes.forEach { row in
myData.fetch(url: urlList[row]) { res in
self.anotherAsyncCall(res) { data in
//continue to deal with next row now
}
}
}
If you really want to do this sequentially, the easiest way is to perform your tasks recursively, actually invoking the next task in the completion handler of the prior one:
processNext(in: tableView.selectedRowIndexes) {
// do something when they're all done
}
Where:
func processNext(in rows: [Int], completion: #escaping () -> Void) {
guard let row = rows.first else {
completion()
return
}
myData.fetch(url: urlList[row]) { res in
self.anotherAsyncCall(res) { data in
//continue to deal with next row now
self.processNext(in: Array(rows.dropFirst()), completion: completion)
}
}
}
But I agree with GoodSp33d that the other approach is to wrap this asynchronous process in a custom, asynchronous, Operation subclass.
But this begs the question why you want to do these sequentially. You will pay a significant performance penalty because of the inherent network latency for each request. So the alternative is to let them run concurrently, and use dispatch group to know when they're done:
let group = DispatchGroup()
tableView.selectedRowIndexes.forEach { row in
group.enter()
myData.fetch(url: urlList[row]) { res in
self.anotherAsyncCall(res) { data in
//continue to deal with next row now
group.leave()
}
}
}
group.notify(queue: .main) {
// do something when they're all done
}
Whether you can run these concurrently (or to what degree) is a function of what you're doing inside various asynchronous methods. But I would suggest you think hard about making this work concurrently, as the performance is likely to be much better.
If you are using some promise library, just use the all function.
Here is some Document about promise.all()
And PromiseKit use when instead,
you can read about the faq and the tutorial about when for more information.
If you want to do that without any promise library, here is the pseudocode:
var results = []
rows.forEach {row in
fetch(row) {res in
results.push(res)
if(results.length == rows.length) {
// do something using the results here
}
}
}