Why text field can not be refresh in a method? - swift

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.

Related

iOS 14.5 trouble updating label text during networking loop

I have a loop that updates a remote database over the network. To keep track of the updates I want to use a counter to update the text of a label after each update. The problem is that the label text only updates after the loop completes. I have tried many combinations of DispatchQueue and DispatchGroup with no success.
The code below illustrates this problem. Thank you for your help.
import UIKit
class ViewController: UIViewController {
var counter = 0
#IBOutlet weak var countLBL: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startClick(_ sender: Any) {
workloop()
}
func workloop() {
for _ in (0...3) {
networkTask()
counter += 1
countLBL.text = String(counter)
}
}
func networkTask() {
sleep(1)
}
}
Your basic assumptions and approach are wrong.
In all cases, if you execute a function, the UI changes you apply in that function will not be displayed to the screen until you exit the function and the app passes through the event loop.
Further, you should not be making synchronous network calls.
The idea that you'd have code
networkTask()
counter += 1
//update UI with info about completed network task
Is wrong. A function that performs a network task should be written to operate asynchrnously. (It will return immediately, go off and do the network task, and call a completion handler when the task is done. The code might look like this:
networkTask(completion: { results in
DispatchQueue.main.async {
// Code to display the results of the network operation to the UI
}
}
)
The idea of using async functions and completion handlers in Swift is widely covered by Apple, by lots and lots of development blogs, and by countless posts here on SO.

In XCTest: how to test that a function forced execution onto main thread

In the UI class I have a method that accesses UI elements, and hence is supposed to force itself onto a main thread. Here's a minimal example of what I mean:
class SomeUI {
func doWorkOnUI() {
guard Thread.isMainThread else {
DispatchQueue.main.async {
self.doWorkOnUI()
}
return
}
print("Doing the work on UI and running on main thread")
}
}
In the tests, of course there's no problem to test the case when doWorkOnUI() is already running on main thread. I just do this:
func testWhenOnMainThread() {
let testedObject = SomeUI()
let expectation = XCTestExpectation(description: "Completed doWorkOnUI")
DispatchQueue.main.async {
testedObject.doWorkOnUI()
expectation.fulfill()
}
wait(for: [expectation], timeout: 10.0)
// Proceed to some validation
}
That is: force execution onto main thread. Wait for it to complete. Do some checks.
But how to test the opposite case, i.e. ensure that function forced itself to run on main thread when called from the background thread?
For example if I do something like:
...
DispatchQueue.global(qos: .background).async {
testedObject.doWorkOnUI()
expectation.fulfill()
}
...
I just tested that function got executed from the background thread. But I didn't explicitly check that it ran on main thread. Of course, since this function accesses UI elements, the expectation is that it crashes if not forced on main thread. So is "no crash" the only testable condition here? Is there anything better?
When there is an outer closure in the background and an inner closure on the main thread, we want two tests:
Call the outer closure. Do a wait for expectations. Wait for 0.01 seconds. Check that the expected work was performed.
Call the outer closure. This time, don't wait for expectations. Check that the work was not performed.
To use this pattern, I think you'll have to change your code so that the tests can call the outer closure directly without having to do an async dance already. This suggests that your design is too deep to be testable without some changes.
Find a way for an intermediate object to capture the closure. That is, instead of directly calling DispatchQueue.global(qos: .background).async, make a type that represents this action. Then a Test Spy version can capture the closure instead of dispatching it to the background, so that your tests can invoke it directly. Then you can test the call back to main thread using async wait.

how to call multiple operations at the same time

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.

Swift 4. Wait for async result of HealthKit HKQuery before continuing execution

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.

Writing better Swift code

The title is on how to write better Swift code but, my real question is really what is better if I create a function, then call it when the button is clicked vs I write what I want to happen once the button is clicked .
Eg.
var thing = 0
func hi(){
// Do something
thing++
}
#IBAction func somethingHi(sender: AnyObject) {
println(hi)
}
vs
var thing = 0
#IBAction func othersomethingHI(sender: AnyObject) {
thing++
println(thing)
}
I know both do the same thing but, is one "better" written than the other?
If an IBAction does something that you might want to do at some other time then it should call a function that performs that action, so that "others" can effect the same thing without duplicating code. If not, implement it solely in the action.
If you're code is short and won't be reused, you can just put it inside the #IBAction func function.