Handling large operations Swift - swift

I'm working with NSAttributed strings with large amount of characters 100 000, 1m. How should I handle operations like iterating all of the characters, changing color, foreground and background. It works fine but it's slow, it freezes for a while and then works fine.

Modify them on a background thread. You can add a progress UI for the user
DispatchQueue.global().async {
// modify attributed string
DispatchQueue.main.async {
// update UI
}
}

Related

How to run multi methods in main thread synchronously?

I have a long CoreData process (GlobalData.shared.resetData function) that takes around 4 seconds and I want to present a loading indicator.
Both action (core data process and showing loading indicator) must run in Main Queue,
Do you know why the showing loading indicator always happened after CoreData process?
#IBAction func resetTapped(_ sender: UIButton) {
tableView.backgroundColor = .green
showLoading(loadingText: nil)
GlobalData.shared.resetData(completion: {
self.refreshGlobalData()
})
}
I added that line to change table background color for testing. Background color always changed after core data process (GlobalData.shared.resetData function).
When you change the background color like that, you're changing the "model", but you won't see the results until the next time the main thread redraws, which won't happen until your code relinquishes control of the run loop (finishes running). You could try dispatch_async-ing the Core Data op, which may allow the run loop to draw before starting your operation. But really, as commenters have mentioned, don't do something that takes four seconds on the main thread.

NSImageView update delayed when set from an NSTimer

I'm working on a project in Swift that requires strict control of image display and removal timings in certain sections. I'm finding that when I set the image property of an NSImageView from inside a block that's fired by a Timer, the actual display of the image on the screen is delayed by up to a second after the assignment is complete. (This is measured by eyeballing it and using a stopwatch to gauge the time between when an NSLog line is written and when the image actually appears on-screen.)
Triggering image display with a click appears to happen instantaneously, whether it's done by setting the image property of an existing NSImageView, or constructing one on the spot and adding it as a subview.
I have attempted to reduce the behavior down to a fairly simple test case, which, after basic setup of the view (loading the images into variables and laying out several target image locations, which are NSImageViews stored to the targets array), sets a Timer, with an index into the targets array stored in its userInfo property, to trigger the following method:
#objc func testATimer(fromTimer: Timer) {
if let targetLocation = fromTimer.userInfo as? Int {
NSLog("Placing target \(targetLocation)")
targets[targetLocation].image = targetImage
OperationQueue.main.addOperation {
let nextLocation = targetLocation + 1
if (nextLocation < self.targets.count) {
NSLog("Setting timer for target \(nextLocation)")
let _ = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.testATimer(fromTimer:)), userInfo: nextLocation, repeats: false)
}
}
}
}
The amount of time observed between the appearance of the log entry "Placing target x" and that of the image that is set to be displayed in the very next line seems to average between 0.3 and 0.6 seconds. This is far beyond the delay that this project can tolerate, as the image will only be on screen for a total of 1 second at a time.
My best guess as to why this is happening is that it is something to do with Timers running on a separate thread from the main display thread; however, I do not have enough experience with multi-threaded programming to know exactly how to mitigate that.
Any assistance would be most appreciated, and if I've left out information that would make answering easier (or possible) I'm more than happy to give it.
Well, after poking at it with some helpful people on in #macdev on irc.freenode.net, I found that the source of the problem was the program scaling the image down on the fly every time it set it to the NSImageView. Reducing the size of the image ahead of time solved the problem. (As did setting the image once, then hiding and showing the NSImageView instead.)

BLE characteristic write value occasionally jumps to a random value

Using Core Bluetooth with Swift 3, I use CBPeripheral.writeValue() to write a characteristic to the Central, and I do this when the value of a UISlider changes. I was noticing that, even when dragging the slider slowly, occasionally a jump in value would be seen on the Central. I thought perhaps some over-the-air corruption was occurring, so I changed the characteristic to write the same value three times. Now, only if all the values received by the Central match will it act on them. Here's the current code:
#IBAction func slideValChanged(_ sender: UISlider)
{
let sliderVal = UInt8(sender.value.rounded(FloatingPointRoundingRule.down))
if (sliderVal != self.sliderVal)
{
self.sliderVal = sliderVal
self.bytes.removeAll()
self.bytes = [self.sliderVal, self.sliderVal, self.sliderVal]
DispatchQueue.global(qos: .userInitiated).async
{
self.data = Data(bytes: self.bytes)
self.peripheral.writeValue(self.data, for: self.writeCharacteristic!, type: CBCharacteristicWriteType.withResponse)
print("Write values sent:", self.bytes[0], self.bytes[1], self.bytes[2])
}
}
}
Even with this, I still see the value jump, and not to anything particular. The print() statement always prints the same (and correct) number three times. Similarly, on the Central, when the value jumps, I receive three equal but incorrect values. How can this be? All I can think is that something in Core Bluetooth is changing the value before it is put on air, but I'm not sure, and I don't know where to focus my attention.
As you are using CBCharacteristicWriteType.withResponse, even slow movement of your slider will send to many packets.
When you write to the writeCharacteristic command you need to wait for a callback before calling writeCharacteristic again. The callback will indicate that your data has been buffered in the Central BLE-stack and the stack is ready to receive additional data. Without this Your data can be corrupted or even lose your connection.

Simple waiting for value from async thread

I've got 2 basic methods - viewDidLoad and viewDidAppear. According to my App philosophy, when view controller loads, it fetches data from base and starts to sort it with some predicates. Fetching process is long, so I dispatched it to global queue. When my view appears, it obviously do not get the value from array(which compiles in load method) and crashes. So I need viewDidAppear to wait till at least one object will be appended to array.
Kind of semaphores or temp values?
Thanks in advance!
P.S. Each item in array represent struct with data which composes UI. User interact with this UI, so it has to be loaded once with the first item from array. To switch to next item, user just clicks "next" and UI changes according to next item from array. That's why I want the data to fetch in background and allow user to work immediately. (It's impossible to jump on 5th, 10th or 1001st element immediately, there will be enough time to fetch data before user gets on these page numbers)
P.P.S Still no right decision :(
You should using a nested dispatch block, like so:
func fetch(completion block:(() -> Void)?) {
// Run fetch on background thread, to prevent the main thread (and hence your UI) from being 'blocked'.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//
// Fetch data...
//
dispatch_async(dispatch_get_main_queue(), {
block?()
})
})
}
fetch(completion: {
// Update your UI
})

preparing view elements on a background thread

OK, so I know you're not supposed to directly interact with view elements from any thread other than the main thread.
But can you do stuff in a background thread that will be used by a view?
In particular, I have a pretty substantial algorithm that ends up spitting out a string. If I want that string to become the text of a UITextView, do I need to run this whole algorithm on the main thread? Or can it be done in the background ?
You can certainly run it in the background, just like a graphical application might render images in the background. Once you have the string ready, GCD is your friend:
- (void)backgroundStringGenerator
{
NSString *expensiveString = ... // do string generation algorithm
dispatch_async(dispatch_get_main_queue(), ^{
theLabel.text = expensiveString;
});
}