iOS 14.5 trouble updating label text during networking loop - swift

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.

Related

IOBluetooth: deviceInquiryStarted and deviceInquiryComplete never called

I'm scanning for Bluetooth devices on macOS 13.1 using IOBluetoothDeviceInquiry. The scanning itself works, but only the deviceInquiryDeviceFound delegate method gets called. For the remaining methods:
deviceInquiryStarted never fires, even though the inquiry obviously starts;
deviceInquiryComplete never fires, and indeed the inquiry never seems to end (the program keeps outputting Found peer... console logs);
When updateNewDeviceNames is set to true, the delegate methods related to updating device names are nonetheless never called.
Setting inquiryLength has no effect. The documentation states:
if you have the inquiry object updating device names for you, the
whole inquiry process could be much longer than the specified length...
If you -must- have a strict inquiry length, disable name updates.
But scanning continues indefinitely even when I set updateNewDeviceNames to false.
Here's my code:
import IOBluetooth
class Inquiry: NSObject {
private lazy var inquiry: IOBluetoothDeviceInquiry = {
let inquiry: IOBluetoothDeviceInquiry = IOBluetoothDeviceInquiry()
inquiry.delegate = self
inquiry.updateNewDeviceNames = false
return inquiry
}()
lazy var foundDevices: [Any]! = self.inquiry.foundDevices()
private var continuation: CheckedContinuation<Void, Never>?
func start() async -> () {
await withCheckedContinuation { continuation in
self.continuation = continuation
self.inquiry.start()
}
}
}
extension Inquiry: IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry!) {
print("inquiry started")
}
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry!, error: IOReturn, aborted: Bool) {
print("inquiry complete")
continuation?.resume()
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry!, device: IOBluetoothDevice!) {
print("device found: \(device.addressString!)")
}
}
let inquiry: Inquiry = Inquiry()
await inquiry.start()
print(inquiry.foundDevices!)
As I'm writing a command line tool, I wrap the start method in a continuation. Other than that it's pretty much identical to other examples of IOBluetoothDeviceInquiry usage I've found (e.g. this one, which has the same problems when I try running it).
I'm really at a loss as to why this isn't working, any help would be greatly appreciated!

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.

Why text field can not be refresh in a method?

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.

Observe a string and get from API with RxSwift

I have a MVVM test project to experiment RxSwift. I have a UItextfield a button. User write a food name, click on the button and a get from an API is triggered to get all recipes with that food.
View model
struct FoodViewModel
var foodIdentifier: Variable<String> = Variable<String>("")
init() {
foodIdentifier.asObservable().subscribe(onNext: { (identifier) in
self.getRecipes() // Get from API
})
}
}
ViewController
class FoodViewController: UIViewController {
#IBOutlet weak var foodTextField: UITextField!
#IBAction func setCurrentRace(_ sender: Any) {
viewModel.foodIdentifier.value = foodTextField.text!
}
}
After compile I got an error
Closure cannot implicitly capture a mutating self parameter
What I'm doing wrong ? I think it's because of struct of FoodViewModel. If yes, how can I achieve that using struct ?
-- EDIT
I wrote all of the below but forgot to answer your explicit question... The reason you are getting the error is because you are trying to capture self in a closure where self is a struct. If this were allowed, you would be capturing a copy of the view model that you haven't even finished constructing. Switching your view model to a class alleviates the problem because you are no longer capturing a copy, but the object itself for later use.
Here is a better way to set up a view model. You didn't give all the necessary information so I took some liberties...
First we need a model. I don't know exactly what should be in a Recipe so you will have to fill it in.
struct Recipe { }
Next we have our view model. Note that it doesn't directly connect with anything in the UI or the server. This makes testing very easy.
protocol API {
func getRecipies(withFood: String) -> Observable<[Recipe]>
}
protocol FoodSource {
var foodText: Observable<String> { get }
}
struct FoodViewModel {
let recipes: Observable<[Recipe]>
init(api: API, source: FoodSource) {
recipes = source.foodText
.flatMapLatest({ api.getRecipies(withFood: $0) })
}
}
In real code, you aren't going to want to make a new server call every time the user types a letter. There are a lot of examples on the web that explain how to build in a delay that waits until the user stops typing before making the call.
Then you have the actual view controller. You didn't mention what you wanted to do with the results of the server call. Maybe you want to bind the result to a table view? I'm just printing the results here.
class FoodViewController: UIViewController, FoodSource {
#IBOutlet weak var foodTextField: UITextField!
var api: API!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = FoodViewModel(api: api, source: self)
viewModel.recipes.subscribe(onNext: {
print($0)
}).disposed(by: bag)
}
var foodText: Observable<String> {
return foodTextField.rx.text.map { $0 ?? "" }.asObservable()
}
let bag = DisposeBag()
}
Notice how we avoid having to make an IBAction. when you are coding up a view controller with Rx, you will find that almost all the code ends up in the viewDidLoad method. This is because with Rx, you are mainly just worried about wiring everything up. Once the observables are wired up, user action will cause things to happen. It's more like programming a spreadsheet. You just put in the formulas and link the observables together. User's data entry takes care of the actual action.
The above is just one way of setting everything up. This method matches closely with Srdan Rasic's model: http://rasic.info/a-different-take-on-mvvm-with-swift/
You could also turn the food view model into a pure function like this:
struct FoodSink {
let recipes: Observable<[Recipe]>
}
func foodViewModel(api: API, source: FoodSource) -> FoodSink {
let recipes = source.foodText
.flatMapLatest({ api.getRecipies(withFood: $0) })
return FoodSink(recipes: recipes)
}
One takeaway from this... Try to avoid using Subjects or Variables. Here's a great article that helps determine when using a Subject or Variable is appropriate: http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx

JavacriptCore WebKit EXC_BAD_ACCESS crash after executing callback with data from background thread

I'm currently trying to debug crashes in a JavascriptCore implementation of an interface for native code to perform some work on behalf of the javascript code in the WebView.
The crash sometimes occurs very soon after launching the application, other times it may take a few minutes of executing hundreds of calls to the native code for it to occur.
These are the top two lines for the backtrace of every crash:
(lldb) thread backtrace
* thread #1: tid = 0x37960c, 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x700001a2c000)
frame #0: 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15
Here is a simplified version of my view controller:
class MyViewController: NSViewController, WebFrameLoadDelegate {
let worker = Worker()
// other setup code...
func webView(webView: WebView!, didCreateJavaScriptContext context: JSContext!, forFrame frame: WebFrame!) {
context.setObject(worker, forKeyedSubscript: "ClientWorker")
}
}
The JSExport protocol itself, and implementation of the code performing work. For testing I removed the actual work and just return a dictionary with dummy data, and the crash still occurs.
#objc protocol WorkerJSExports: JSExport {
func doWork(params: [String:AnyObject], callback: JSValue)
}
#objc class Worker: NSObject, WorkerJSExports {
func doWork(params: [String:AnyObject], callback: JSValue) {
executeBackground(callback) {
return [
"test": "data"
]
}
}
private func executeBackground(callback: JSValue!, f: ()->([String:AnyObject])) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
let result = f()
dispatch_sync(dispatch_get_main_queue()) {
self.executeCallback(callback, result: result)
}
}
}
private func executeCallback(callback: JSValue!, result: AnyObject) {
callback.context.evaluateScript("setTimeout").callWithArguments([callback, 0, result])
}
}
The executeBackground and executeCallback methods are helper functions, and executeCallback is making use of setTimeout in response to what I read in this SO post/answer: JavaScriptCore -- Passing a function as a parameter to ObjC and it seems to have resolved other crashes related to locking.
When I swap out executeBackground for the following function that runs just on the main thread, I have not been able to replicate the crash:
private func executeMain(callback: JSValue!, f: ()->([String:AnyObject])) {
dispatch_async(dispatch_get_main_queue()) {
self.executeCallback(callback, result: f())
}
}
It seems that there is some sort of issue that occurs when passing data created in a background thread into the WebView, but after pouring through the documentation I'm uncertain that what could be. The only taboo I found mentioned was passing data between multiple JSVirtualMachine instances, which doesn't seem applicable since I'm only interacting with a single WebView instance. Any assistance in figuring this out is greatly appreciated!
Update
I seem to have solved the issue by switching out the use of Grand Central Dispatch directly for NSOperationQueues. After changing executeBackground to the following, the crashes have not recurred.
private let workerQueue = NSOperationQueue()
private func executeAsync(callback: JSValue!, f: ()->([String:AnyObject])) {
self.workerQueue.addOperationWithBlock({
let result = f()
NSOperationQueue.mainQueue().addOperationWithBlock({
self.executeCallback(callback, result: result)
})
})
}
Though I can't really prove that this has fixed the crash, we've done fairly extensive testing of this functionality and haven't seen it again. The reason I didn't post this an an answer to my own question is that I'm at a loss as to why exactly this is different and/or better. If anyone has insight into what the change to NSOperationQueues may have solved, it would be very much appreciated!