Realm accessed from incorrect thread - dispatch async - swift

I'm trying to do some sorting on a different thread, however i keep getting Realm accessed from incorrect thread. Below is what i'm tried so far.
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
self.organizationArray = GetOrganization.sortOrganizationsByDistanceFromLocation(realm.objects(Organization), location: self.lastLocation!)
dispatch_async(dispatch_get_main_queue()) {
self.lastLoadedPage = self.lastLoadedPage + 1
if numberOfResults < self.limit {
//Hide FooterView
self.moreDataAvailable = false
self.hideTableViewFooter()
}
}
}

Assuming that you're calling the shown code snippet from the main thread, e.g. from a view controller as it seems, you're likely using a RLMRealm instance from the main thread on another thread.
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// Get a Realm for the current thread
RLMRealm *realm = [RLMRealm defaultRealm];
// do some task with that instance instead
self.organizationArray = GetOrganization.sortOrganizationsByDistanceFromLocation(realm.objects(Organization), location: self.lastLocation!)
// …

Related

CAMetalLayer initialization only works on main thread

I do have a straight forward CAMetalLayer setup, except the CAMetalLayer is initialized on a global thread, which looks like this (call init, setup basic properties and add as sublayer):
DispatchQueue.global().async {
let renderLayer = CAMetalLayer()
renderLayer.isOpaque = false
renderLayer.frame = frame
renderLayer.drawableSize = drawableSize
renderLayer.framebufferOnly = false
renderLayer.device = metalDevice
renderLayer.pixelFormat = metalPixelFormat
renderLayer.contentsScale = contentScale
DispatchQueue.main.async {
view.layer.addSublayer(renderLayer)
}
}
This code runs without any runtime errors, but unfortunately when rendering to the CAMetalLayer drawable there is no render output shown on the screen. Tests show, that the CAMetalLayer drawable does have a proper texture (with desired size) and the data is written to it as expected, but not shown on screen.
To my surprise running the CAMetalLayer initialization on the main thread fixes this problem. So the following is a solution:
DispatchQueue.global().async {
var renderLayer: CAMetalLayer!
DispatchQueue.main.sync {
renderLayer = CAMetalLayer()
}
renderLayer.isOpaque = false
renderLayer.frame = frame
renderLayer.drawableSize = drawableSize
renderLayer.framebufferOnly = false
renderLayer.device = metalDevice
renderLayer.pixelFormat = metalPixelFormat
renderLayer.contentsScale = contentScale
DispatchQueue.main.async {
view.layer.addSublayer(renderLayer)
}
}
As I want to be able to run the CAMetalLayer creation completely on the background thread I would like to avoid calling CAMetalLayer() on the main thread. Also, I couldn't find any requirement for CAMetalLayer to be initialized on the main thread in the documentation.
So my questions are:
Is it possible to run the init CAMetalLayer() on a background thread and if so how?
If it is not possible: why?
Additional Info: I am using XCode 11 and running the code on iOS 13.1. I also had the same issue with XCode 10 and iOS version 12.

check if mapView has finished rendering?

So I know swift has mapViewDidFinishRenderingMap(_:fullyRendered:), but I have no clue how I can use that function to only add my mapView onto the view hierarchy when it finishes rendering.
Right now, even with the DispatchGroup .enter() and .leave(), the view.addSubview(mapView) is getting called before it finishes rendering, which results in my MKPolylines not showing up on the map when it loads.
How can I make sure that the map finishes rendering before view.addSubview(mapView) is called?
#Koen basically what my viedDidLoad() is doing:
override func viewDidLoad() {
super.viewDidLoad()
mapView = MKMapView()
let leftMargin:CGFloat = 0
let topMargin:CGFloat = 0
let mapWidth:CGFloat = view.frame.size.width
let mapHeight:CGFloat = view.frame.size.height
mapView?.frame = CGRect(x: leftMargin, y: topMargin, width: mapWidth, height: mapHeight)
mapView?.mapType = MKMapType.standard
mapView?.isZoomEnabled = true
mapView?.isScrollEnabled = true
mapView?.delegate = self
mapView?.showsScale = true
// I have this to make sure they run in order only after the previous block is finished because of dependencies
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
// gets the routes as polylines and adds to mapView
self.fetchRoutes()
group.leave()
}
goup.notify(queue: .main) {
group.enter()
DispatchQueue.main.async {
// depending on the route data, adds the stops as annotations to mapView
self.fetchAnnotations()
group.leave()
}
group.notify(queue: .main) {
// the idea was that once both route and stops are added to the mapView, display mapView to screen
self.view.addSubview(mapView)
}
}
}
Issue was solved by moving all of my data fetching code into a single function and using DispatchGroup to only call addSubview(mapView) after the fetching finishes.
Was not able to find out the root cause, but the issue was solved.

Activity indicator while creating csv file

I am trying to show an activity indicator while creating a csv file, but it does not show. I am guessing I should use dispatch_async somehow, but I cant figure out how to do this in swift 3.
var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
override func viewDidLoad() {
super.viewDidLoad()
// activity indicator
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 100 ,y: 200,width: 50,height: 50)) as UIActivityIndicatorView
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func writeToCsv() {
self.activityIndicator.startAnimating() // start the animation
let fileName = "events.csv"
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
var csvText = self.name! + "\n"
csvText += "Date,Start time,End time\n"
// create rest of comma-separated string
for event in self.events! {
let newLine = "\(event.date),\(event.startTime),\(event.endTime)\n"
csvText.append(newLine)
}
// write to csv
do {
try csvText.write(to: path!, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("Failed to create file")
print(error)
}
// create and present view controller with send options
let vc = UIActivityViewController(activityItems: [path as Any], applicationActivities: [])
self.present(vc, animated: true, completion: nil)
self.activityIndicator.stopAnimating() // stop the animation
}
Err, alright bit hard to answer this without a bit more context about your view setup. First of all, make sure your activity indicator is visible without calling the writeCsv method, so you know your view hierarchy is correct. ( I.E. It could be that it is hidden behind some other subview )
Next, in Swift3 Dispatch has been changed to a newer API. I'm not sure whether on OSX they use the raw libdispatch Swift wrapper, but in any case you access it like this:
Background default queue:
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { /* code */ }
Main thread:
DispatchQueue.main.async { /* Mainthread code ( UIKit stuff ) */ }
Your own custom queue for CSV generation blocks:
let queue = DispatchQueue(label: "csvgenerator.queue")
queue.async { /* code */ }
Now for your animating / stopAnimation, make sure you call your UIKit related code from the mainthread to prevent weird glitechs and or crashes
Namely:
DispatchQueue.main.async {
self.activityIndicator?.startAnimating()
}
Another good idea might be to use NSOperationQueue instead. It internally uses GCD I believe, but it does integrate very well into iOS and might make some of the dispatching a lot easier to implement. I myself always use GCD instead, but I have never really had long queeu's of work that needed to be done. One of the advantages of NSOperationQueue is that it is a lot more user friendly in terms of cancelling dispatch blocks.
An interesting session video about NSOperationQueue in the WWDC app written by Dave Delong: WWDC Videos 2015
A small minor changes I'd make to your writeCSV method:
guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) else {
// Should throw an error here, or whatever is handy for your app
return
}
Try to avoid forced unwrapping at all stages where possible.
In methods that have this, you can for instance add "throws" to the end of the function definition so you can use try without the do and catch block, while also being able to throw errors in your guard statement so whatever calls writeCsv can catch the error and more easily display it to the user.

UI Changes in swift, CoreAnimation: warning, deleted thread with uncommitted CATransaction

I'm relatively new to swift and wondering if anyone could help with this issue.
I'm trying to make the label on a button change to a loading spinner during a service call, and then change to the response message of that call shortly after.
I get this error in my log:
CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
Thanks for the help. I've read about these core animation errors, but I'm not sure what I'm doing wrong as everything here is done asynchronously.
Here is the corrected code, thanks #Pierce:
self.pastebinButton.isEnabled = false
self.pastebinButton.title = ""
self.pastebinProgressIndicator.startAnimation(nil)
pastebinAPI.postPasteRequest(urlEscapedContent: urlEscapeText(txt: text)) { pasteResponse in
DispatchQueue.main.async {
self.pastebinProgressIndicator.stopAnimation(nil)
if pasteResponse.isEmpty {
self.pastebinButton.title = "Error"
} else {
self.pastebinButton.title = "Copied!"
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
self.pastebinButton.title = "Pastebin"
self.pastebinButton.isEnabled = true
})
So you're calling the DispatchQueue.main.async before you've even moved outside the main thread. This is unnecessary. Also once you're working on the background thread you are updating some UI (your button title) without dispatching back to the main thread. Never update UI on a background thread.
if !text.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines).isEmpty {
self.pastebinButton.title = ""
self.pastebinProgressIndicator.startAnimation(nil)
pastebinAPI.postPasteRequest(urlEscapedContent: urlEscapeText(txt: text)) { pasteResponse in
// Clean up your DispatchQueue blocks
DispatchQueue.main.async {
self.pastebinProgressIndicator.stopAnimation(nil)
if pasteResponse.isEmpty {
self.pastebinButton.title = "Error"
} else {
self.pastebinButton.title = "Copied!"
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
self.pastebinButton.title = "Pastebin"
self.pastebinButton.isEnabled = true
})
}
} else {
Utility.playFunkSound()
}

Swift: How to let background thread wait for user input?

I have a background thread which needs some user input. Since it is not recommended to call an NSAlert window from a background thread, I like to do this in the main thread. But how can I let the background thread wait till the NSAlert window is closed?
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// some background task
dispatch_async(dispatch_get_main_queue()) {
// UI task asking for user input:
let alert = NSAlert()
alert.messageText = "Some text"
alert.informativeText = "Some information"
alert.addButtonWithTitle("Yes")
alert.addButtonWithTitle("No")
result = alert.runModal()
}
// some background task, treating user input (Yes/No)
}
The runModal method is synchronous. If you want to wait for it to finish, rather than dispatching asynchronously to the main queue with dispatch_async, you can most easily achieve what you asked for by dispatching it synchronously with dispatch_sync, e.g.:
let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(queue) {
// some background task
var result: NSModalResponse!
dispatch_sync(dispatch_get_main_queue()) { // note, `dispatch_sync`
// UI task asking for user input:
let alert = NSAlert()
...
result = alert.runModal()
}
// some background task, treating user input (Yes/No)
}
A more subtle problem rests with the synchronous nature runModal. This will block the main thread. This is not generally a good idea. It can interfere with other tasks your app might be performing (timers, animations, network operations using the main queue, etc.) that might require the main thread.
So, instead of the synchronous runModal, you might consider presenting the NSAlert asynchronously with beginSheetModalForWindow, with a completion handler that dispatches additional background processing to the background queue, e.g.:
let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(queue) {
// some background task
dispatch_async(dispatch_get_main_queue()) { // note, `dispatch_async` is OK here
// UI task asking for user input:
let alert = NSAlert()
...
alert.beginSheetModalForWindow(NSApp.mainWindow!) { result in
dispatch_async(queue) {
// some background task, treating user input (Yes/No)
}
}
}
}
This achieves what you want (triggering additional background tasks when the modal is dismissed), but has the virtue of not blocking the main thread at all.
You can use semaphore.
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// some background task
//Create a semaphore with count = 0
let semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_main_queue()) {
// UI task asking for user input:
let alert = NSAlert()
alert.messageText = "Some text"
alert.informativeText = "Some information"
alert.addButtonWithTitle("Yes")
alert.addButtonWithTitle("No")
result = alert.runModal()
//Signal semaphore
dispatch_semaphore_signal(semaphore)
}
//Wait for semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
// some background task, treating user input (Yes/No)
}
You should not "wait" in the main thread. But background threads are other things.