Why does NSCollectionView.makeItem sometimes crash with EXC_BAD_ACCESS? - nscollectionview

For some reason, my app just started doing this after months of not doing so. This popped up after a seemingly-unrelated use of OperationQueue (I always use main, so it's done on Thread 1).
As you can see, I call the exact same function 6 times in a row, and only on the 4th time does it fail. I see no pattern...

I haven't done very extensive digging or testing, but it seems a block like this was the culprit:
collectionView.performBatchUpdates({
collectionView.reloadItems()
}, completionHandler: { [weak self] _ in
// cleanup
})
Seems the collection view doesn't like doing much more than insert+remove inside the batch update context. Changing it to this seems to have cured the crash:
collectionView.reloadItems()
// cleanup

Related

The callback inside Task is automatically called on the main thread

Once upon the time, before Async/Await came, we use to make a simple request to the server with URLSession dataTask. The callback being not automatically called on the main thread and we had to dispatch manually to the main thread in order to perform some UI work. Example:
DispatchQueue.main.async {
// UI work
}
Omitting this will lead to the app to crash since we try to update the UI on different queue than the main one.
Now with Async/Await things got easier. We still have to dispatch to the main queue using MainActor.
await MainActor.run {
// UI work
}
The weird thing is that even when I don't use the MainActor the code inside my Task seems to run on the main thread and updating the UI seems to be safe.
Task {
let api = API(apiConfig: apiConfig)
do {
let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
self.posts = posts
self.tableView.reloadData()
print(Thread.current.description)
} catch {
// Handle error
}
}
I was expecting my code to lead to crash since I am trying to update the table view theorically not from the main thread but the log says I am on the main thread. The print logs the following:
<_NSMainThread: 0x600003bb02c0>{number = 1, name = main}
Does this mean there is no need to check which queue we are in before performing UI stuff?
Regarding Task {…}, that will “create an unstructured task that runs on the current actor” (see Swift Concurrency: Unstructured Concurrency). That is a great way to launch an asynchronous task from a synchronous context. And, if called from the main actor, this Task will also be on the main actor.
In your case, I would move the model update and UI refresh to a function that is marked as running on the main actor:
#MainActor
func update(with posts: [Post]) async {
self.posts = posts
tableView.reloadData()
}
Then you can do:
Task {
let api = API(apiConfig: apiConfig)
do {
let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
self.update(with: posts)
} catch {
// Handle error
}
}
And the beauty of it is that if you’re not already on the main actor, the compiler will tell you that you have to await the update method. The compiler will tell you whether you need to await or not.
If you haven’t seen it, I might suggest watching WWDC 2021 video Swift concurrency: Update a sample app. It offers lots of practical tips about converting code to Swift concurrency, but specifically at 24:16 they walk through the evolution from DispatchQueue.main.async {…} to Swift concurrency (e.g., initially suggesting the intuitive MainActor.run {…} step, but over the next few minutes, show why even that is unnecessary, but also discuss the rare scenario where you might want to use this function).
As an aside, in Swift concurrency, looking at Thread.current is not reliable. Because of this, this practice is likely going to be prohibited in a future compiler release.
If you watch WWDC 2021 Swift concurrency: Behind the scenes, you will get a glimpse of the sorts of mechanisms underpinning Swift concurrency and you will better understand why looking at Thread.current might lead to all sorts of incorrect conclusions.

In Swift, if Thread.current.isMainThread == false, then is it safe to DispatchQueue.main.sync recursively once?

In Swift, if Thread.current.isMainThread == false, then is it safe to DispatchQueue.main.sync recursively once?
The reason I ask is that, in my company's app, we had a crash that turned out to be due to some UI method being called from off the main thread, like:
public extension UIViewController {
func presentModally(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
// some code that sets presentation style then:
present(viewControllerToPresent, animated: flag, completion: completion)
}
}
Since this was getting called from many places, some of which would sometimes call it from a background thread, we were getting crashes here and there.
Fixing all the call sites was not feasible due to the app being over a million lines of code, so my solution to this was simply to check if we're on the main thread, and if not, then redirect the call to the main thread, like so:
public extension UIViewController {
func presentModally(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
guard Thread.current.isMainThread else {
DispatchQueue.main.sync {
presentModally(viewControllerToPresent, animated: flag, completion: completion)
}
return
}
// some code that sets presentation style then:
present(viewControllerToPresent, animated: flag, completion: completion)
}
}
The benefits of this approach seem to be:
Preservation of execution order. If the caller is off the main thread, we'll redirect onto the main thread, then execute the same function before we return -- thus preserving the normal execution order that the would have happened had the original function been called from the main thread, since functions called on the main thread (or any other thread) execute synchronously by default.
Ability to implicitly reference self without compiler warnings. In Xcode 11.4, performing this call synchronously also satisfies the compiler that it's OK to implicitly retain self, since the dispatch context will be entered then exited before the original function call returns -- so we don't get any new compiler warnings from this approach. That's nice and clean.
More focused diffs via less indentation. It avoids wrapping the entire function body in a closure (like you'd normally see done if Dispatch.main.async { ... } was used, where the whole body must now be indented a level deeper, incurring whitespace diffs in your PR that can lead to annoying merge conflicts and make it harder for reviewers to distinguish the salient elements in GitHub's PR diff views).
Meanwhile the alternative, DispatchQueue.main.async, would seem to have the following drawbacks:
Potentially changes expected execution order. The function would return before executing the dispatched closure, which in turn means that self could have deallocated before it runs. That means we'd have to explicitly retain self (or weakify it) to avoid a compiler warning. It also means that, in this example, present(...) would not get called before the function would return to the caller. This could cause the modal to pop-up after some other code subsequent to the call site, leading to unintended behavior.
Requirement of either weakifying or explicitly retaining self. This is not really a drawback but it's not as clean, stylistically, as being able to implicitly retain self.
So the question is: are these assumptions all correct, or am I missing something here?
My colleagues who reviewed the PR seemed to feel that using "DispatchQueue.main.sync" is somehow inherently bad and risky, and could lead to a deadlock. While I realize that using this from the main thread would indeed deadlock, here we explicitly avoid that here using a guard statement to make sure we're NOT on the main thread first.
Despite being presented with all the above rationale, and despite being unable to explain to me how a deadlock could actually happen given that the dispatch only happens if the function gets called off the main thread to begin with, my colleagues still have deep reservations about this pattern, feeling that it could lead to a deadlock or block the UI in unexpected ways.
Are those fears founded? Or is this pattern perfectly safe?
This pattern is definitely not “perfectly” safe. One can easily contrive a deadlock:
let group = DispatchGroup()
DispatchQueue.global().async(group: group) {
self.presentModally(controller, animated: true)
}
group.wait()
Checking that isMainThread is false is insufficient, strictly speaking, to know whether it’s safe to dispatch synchronously to the main thread.
But that’s not the real issue. You obviously have some routine somewhere that thinks it’s running on the main thread, when it’s not. Personally, I’d be worried about what else that code did while operating under this misconception (e.g. unsynchronized model updates, etc.).
Your workaround, rather than fixing the root cause of the problem, is just hiding it. As a general rule, I would not suggest coding around bugs introduced elsewhere in the codebase. You really should just figure out where you’re calling this routine from a background thread and resolve that.
In terms of how to find the problem, hopefully the stack trace associated with the crash will tell you. I’d also suggest adding a breakpoint for the main thread checker by clicking on that little arrow next to it in the scheme settings:
Then exercise the app and if it encounters this issue, it will pause execution at the offending line, which can be very useful in tracking down these issues. That often is much easier than reverse-engineering from the stack trace.
I agree with the comments that you have some structural difficulties with your code.
But there are still times in which I need code to run on the main thread and I don't know if I'm already on the main thread or not. This has occurred often enough that I wrote a ExecuteOnMain() function just for this:
dispatch_queue_t MainSequentialQueue( )
{
static dispatch_queue_t mainQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if HAS_MAIN_RUNLOOP
// If this process has a main thread run loop, queue sequential tasks to run on the main thread
mainQueue = dispatch_get_main_queue();
#else
// If the process doesn't execute in a run loop, create a sequential utility thread to perform these tasks
mainQueue = dispatch_queue_create("main-sequential",DISPATCH_QUEUE_SERIAL);
#endif
});
return mainQueue;
}
BOOL IsMainQueue( )
{
#if HAS_MAIN_RUNLOOP
// Return YES if this code is already executing on the main thread
return [NSThread isMainThread];
#else
// Return YES if this code is already executing on the sequential queue, NO otherwise
return ( MainSequentialQueue() == dispatch_get_current_queue() );
#endif
}
void DispatchOnMain( dispatch_block_t block )
{
// Shorthand for asynchronously dispatching a block to execute on the main thread
dispatch_async(MainSequentialQueue(),block);
}
void ExecuteOnMain( dispatch_block_t block )
{
// Shorthand for synchronously executing a block on the main thread before returning.
// Unlike dispatch_sync(), this won't deadlock if executed on the main thread.
if (IsMainQueue())
// If this is the main thread, execute the block immediately
block();
else
// If this is not the main thread, queue the block to execute on the main queue and wait for it to finish
dispatch_sync(MainSequentialQueue(),block);
}
A bit late, but I had a need for this type of solution too. I had some common code that could be invoked from both the main thread and background threads, and updated the UI. My solution to the generic use case was:
public extension UIViewController {
func runOnUiThread(closure: #escaping () -> ()) {
if Thread.isMainThread {
closure()
} else {
DispatchQueue.main.sync(execute: closure)
}
}
}
Then to call it from a UIViewController:
runOnUiThread {
code here
}
As others have pointed out, this is not completely safe. You might have some code on background thread that is invoked from the main thread, synchronously. If that background code then calls the code above, it will attempt to run on the main thread and will create a deadlock. The main thread is waiting for the background code to execute, and the background code will wait for the main thread to be free.

Why does DispatchSemaphore.wait() block this completion handler?

So I've been playing about with NetworkExtension to to make a toy VPN implementation and I ran into an issue with the completion handlers/asynchronously running code. I'll run you through my train of thought/expirments and would appreciate any pointers at areas where I am mistaken, and how to resolve this issue!
Here's the smallest reproducible bit of code (obviously you will need to import NetworkExtension):
let semaphore = DispatchSemaphore(value: 0)
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
semaphore.signal()
}
print("1 before")
semaphore.wait()
print("3 after")
With my understanding of semaphores and asynchronous code I'd expect the printouts to occur in the order:
1 before
2 during
3 after
However the program hangs at "1 before". If I remove the semaphore.wait() line, the printout occurs as expected in the order: 1, 3, 2 (as the closure runs later).
So after a bit of digging around with the debugger, it looks like the semaphore trap loop is blocking up execution. This sparked me to read around a bit into queues, and I discovered that changing it to the following works:
// ... as before
DispatchQueue.global().async {
semaphore.wait()
print("3 after")
}
This makes some sense as the blocking .wait() call is now being called asynchronously in a separate thread. However, this solution is not desired for me as in my actual implementation I am actually capturing the results from the closure and returning them later, in something that looks like this:
let semaphore = DispatchSemaphore(value: 0)
var results: [NETunnelProviderManager]? = nil
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
results = managers
semaphore.signal()
}
print("1 before")
// DispatchQueue.global().async {
semaphore.wait()
print("3 after")
// }
return results
Obviously I cannot return data from from the async closure, and moving the return out of it would make it defunct. Acdditionally, adding another semaphore to make things synchronous exhibits the same issue as before just moving the problem along in a chain.
As a result, I decided to try putting the .loadAllFromPreferences() call and completion handler in an async closure and leave everything else as in the original code snippet:
// ...
DispatchQueue.global().async {
NETunnelProviderManager.loadAllFromPreferences { loadedManagers, error in
print("2 during")
semaphore.signal()
}
}
// ...
However this does not work and the .wait() call is never passed - as before. I assume that somehow the sempahore is still blocking the thread and not allowing anything to execute, meaning whatever in the system is managing the queue is not running the async block? However I'm clutching at straws here, and fear my original conclusion may not have been right.
This is where I'm starting to get out of my depth, so I'd like to know what is actually going on, and what resolution would you recommend to get the results from .loadAllFromPreferences() in a synchronous manner?
Thanks!
From the documentation for NETunnelProviderManager loadAllFromPreferences:
This block will be executed on the caller’s main thread after the load operation is complete
So we know that the completion handler is on the main thread.
We also know that the call to DispatchSemaphore wait will block whatever thread it is running on. Given this evidence, you must be calling all of this code from the main thread. Since your call to wait is blocking the main thread, the completion handler can never be called because the main thread is blocked.
This is made clear by your attempt to call wait on some global background queue. That allows the completion block to be called because your use of wait is no longer blocking the main thread.
And your attempt to call loadAllFromPreferences from a global background queue doesn't change anything because its completion block is still called on the main thread and your call to wait is still on the main thread.
It's a bad idea to block the main thread at all. The proper solution is to refactor whatever method this code is in to use its own completion handler instead of trying to use a normal return value.

Swift - Application crash when using two different OperationQueues with KVO

I'm getting two type of information with JSON and I'm adding "operations" to 2 different Operation Queues Classes with addObserver(forKeyPath:"operations"...).
In the function observeValue I'm checking if operationQueue1.operations.isEmpty and then I refresh my information in UI. I'm doing the same thing with if else with operationQueue2, but when the 2 operations are started in sometime the application crash with error message: *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <AppName.ViewController 0x102977800> for the key path "operations" from <AppName.OperationQueue1 0x1c4a233c0> because it is not registered as an observer.'
I don't have problem when only 1 operation is started. Any suggestions?
func getInfo1(){//runned in viewDidLoad
operationQueue1.addObserver(forKeyPath:"operations"...)
operationQueue1.dataTask(URL:"..."....){
DispatchQueue.main.async{
NotificationCenter.default.postNotification(NSNotification.Name(rawValue: "NewDataReceived1", userInfo:infoFromTheWebsite)
}
}
}
func NewDataReceived1(){
here I add the information to arrays to be loaded in tableView1
}
HERE IS THE CODE FOR 2ND INFO WHICH IS THE SAME
override func observeValue(forKeyPath keyPath: String?, ....){
if(object as? operationQueue1 == operationQueue1Class && keyPath == "operations" && context == context1){
if(operationQueue1.operations.isEmpty){
DispatchQueue.main.async{
operationQueue1..removeObserver(self, forKeyPath:"operations")
Timer.scheduled("refreshingTableInformation1")
}
}
}else if(operationQueue2....){
SAME AS OPERATION 1, BUT USING DIFFERENT FUNC TO REFRESH TABLE INFORMATION AND THE TABLES ARE DIFFERENT
}else{
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
func refreshingTableInformation1(){
tableView1.reloadData()
Timer.scheduled("getInfo1", repeat:false)
}
func refreshingTableInformation2(){
tableView2.reloadData()
Timer.scheduled("getInfo2", repeat:false)
}
Sometimes it works 10 secs and crash and sometimes works for more than 60 seconds and then crash...
Your code here is vulnerable to race conditions. Consider the following scenario:
getInfo1() is called, which adds an operation to operationQueue1.
The operation completes, which means your KVO observation is called. The queue is now empty, so your observation schedules removal of your observer on the main dispatch queue.
Now, before the operation you've submitted to the main queue is able to run, something else calls getInfo1(), which adds a new operation to operationQueue1, which completes before the operation you queued in step 2 has had the chance to run (hey, maybe the main queue was busy with something; it's easy for this to happen since it's a serial queue).
Your observation for the first call of getInfo1() is called again while the queue is empty, causing another deregister block to be submitted to the main queue.
The two deregister blocks finally get to execute on the main queue. The second one crashes the program, since you've already deregistered your observer.
You could probably fix this problem (assuming the code doesn't have more problems of this nature) by using Swift 4's block-based observers instead, and setting the observer to nil instead of explicitly deregistering it. However, I propose that KVO is the wrong tool for what you're trying to do. As the instructions for the old "Crystal Quest" game used to say, it's a bit like using an anti-aircraft gun to kill a mosquito.
From what I can see from the code above, it looks like you're using KVO just to schedule a notification for when an operation or a group of operations you submit to the queue finishes. Depending on what your dataTask method actually does, here's what I'd do instead:
If you submit only one operation: set the operation's completionBlock property to a closure that refreshes your table information.
If you submit more than one operation: Make a new BlockOperation which refreshes your table information, and call addDependency on that operation with every other operation you submit to the queue. Then, submit that operation.
This will provide you with a much cleaner and more trouble-free way of monitoring completion for your tasks. And since you don't need the queue to completely empty anymore, you might not even have to use two separate queues anymore, depending on what else you're doing with them.

UI glitches despite the fact that UI operations run on main thread

I have a bug (which I meet second time already) in our project where I simply add a view at a top of UIViewController's view. Nothing outstanding, something like this:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.displayEmailNotificationsIfNeeded()
}
The bug is that for some reason Auto Layout works incorrectly and doesn't add it at the top, but around 60pt lower than needed. I suspect that ~60pt comes from manual top constraint adjustment to include navigation bar and status bar, but that's not really important.
The fact is that the problem disappears if I run the method explicitly on the main queue, like this:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
print("is main thread: ", NSThread.isMainThread())
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("is main thread inside block: ", NSThread.isMainThread())
self.displayEmailNotificationsIfNeeded()
})
}
Print statements return true for both cases. It's not really awful, but just out of curiosity I want to understand what causes this. Is there a way to debug this situation and understand why explicitly performing operations on main thread fixes some UI glitches?
An educated guess - it's not that the displayEmailNotificationsIfNeeded isn't running on the main thread anyway without you adding it to the queue explicitly, it's more a matter of timing. There might be elements moving around as a result of other constraints in your storyboard that are in a different state once viewDidAppear finishes executing. Adding the execution block asynchronously lets viewDidAppear finish (and anything else running synchronously on the main queue) before executing your code.
Hope this helps.
This is not a certain answer as I need to reproduce the bug to be certain but here is what I think might be happening.
When you execute the code directly, layout is not fully ready (it depends on other code you might have) and thus wrong layout. When you explicitly run the code in dispatch_async you are running it on main thread too but on a later point in time and till then layout gets ready and you see correct result.
Also, execute this code and you should be able to understand.
print("1");
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("2");
})
print("3");
Try running it in viewWillLayoutSubviews(). Your view controllers .view should be fully laid out at this point. Note that this method may be called multiple times (ex: if .view gets resized).