Prior to iOS 11 I was able to reload a table views data on the fly without having to dispatch the reload to the main thread. However, now that I am testing on a device that has iOS 11 installed it seems I have to dispatch every UI update to the main thread in order for it to work. Or else I end up with empty UI elements. i.e. table views with all the rows but no labels, or buttons with no titles, etc.
Does anyone know what changed in iOS 11 to require this? Is there a way to turn it off?
Since times immemorial Apple has stressed that all interactions with UI elements must happen on the main/GUI thread. Failing to heed that advice would result in weird behavior: sometimes it would work fine, other times it wouldn't. Clearly, now they've changed something to make the behavior more predictable -- it simply doesn't work any more in your specific case and perhaps more generally.
At any rate, I see no reason why one would not simply do what's spelled out in the documentation and ensure that your UI gets updated from the main thread.
Related
I have an app that's been available in the app store for a few years that uses Core Data. When the app is launched, the user is taken to the app's home screen where a few buttons are shown. Clicking any of the buttons takes the user to another screen that shows data that is loaded from Core Data in a UITableView (each button takes the user to different data). This is basic functionality that's been working in my app just fine the entire time the app's been available.
At some point last year, I noticed that at random times, the data saved in the app sometimes wouldn't load right away. The first time this happened, I clicked one of the buttons and was taken to a blank screen. I went back to the home screen and tried other buttons, and each screen was blank, so I thought all of my data had been wiped somehow. I proceeded to head back to the home screen and just sat there thinking for about 15 seconds, then I clicked one of the buttons again, but this time the data appeared. All of the other screens had data now as well.
I haven't updated the app in quite a while, so my code is unchanged. I don't recall there being a new iOS release around the time I started observing the issue, but it's possible.
This has happened a few more times since I first discovered it (3 or 4 times total over the last 6 months), but there seems to be no pattern at all. Once the data finally loads during these situations, it always loads right away from that point forward. This has made it impossible for me to debug it, because I can't force this situation to happen (99% of the time the data loads immediately).
Has anyone encountered this before, or have any recommendations on how to proceed? I've witnessed this on the Simulator and once on an actual device.
Random delays in updating your UI are a classic sign of attempting to update the UI from outside the main queue. It'll work, usually, but it may be delayed, and the delays won't be consistent. Make sure that when you try to update the UI, you're on the main queue. Use DispatchQueue.main.async() if necessary.
Assuming that you're using Xcode 9, make sure the main thread checker is turned on. That'll help you find this kind of problem when you're running the app from Xcode. Go to the target pop-up at the top of Xcode's window and select "Edit scheme...". In the "Run" section of the window, go to "Diagnostics" and make sure "Main Thread Checker" is turned on.
So, I haven't been able to figure this out yet, because when I pause execution... Xcode doesn't really show what's going on:
This freeze happens when I scroll a very long UITableView around in the center of its scrollable area constantly, so that it's constantly reusing cells. I think that perhaps there is something not being retained for a dispatch block that might happen there, but I'm not yet sure since no data is displayed using the standard debugger and its a fairly complex view. (reference: https://stackoverflow.com/a/23501382/963901 )
I'm going to attempt some manual diagnosis here, but is there any other way of approaching this with Instruments or something, perhaps?
To investigate this sort of problem I'd suggest the following:
Disable the setting in Xcode's debug navigator pane that hides intermediate stack frames from you. It's one of the buttons at the bottom right of the pane that shows the stack trace.
Look at the newly-revealed portion of the backtrace to see what code is blocking on the semaphore.
Look at the other threads in your program to see which appear to be doing work related to the code discovered in step 2.
I'm making a third party keyboard for iOS and without autocorrect, everything runs smooth as butter, especially on older devices such as the iPhone 5 and 4s. Unfortunately, as soon as I re-enable autocorrect, the process starts to lag on the aforementioned devices. How can I prevent this?
How I set up my autocorrect
Every time the user presses a key, the word being typed is run through a function that a) tries to complete the word b) tries to correct the word. The words completed and stored are stored in an array and the first element of each array is set as the text of the label in the suggestion bar.
The process itself is pretty quick and the results are logged almost instantly, but typing fast is when the trouble starts.
My Thoughts
Which GCD method should I use to speed things up? dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE) and dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY) seem like good candidates but I've never used GCD and would like others' opinions. Also, is there any other way of speeding up such a process? It doesn't seem like other third party keyboards are having this problem so I'm probably doing something wrong :(
Thanks in advance!
You are definitely on the right track. You should not be running the code that you want to make suggestions in the same thread that you have the user interaction as this will clog up the main thread.
So, I would call dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE) to do all of your analyzation. Just remember, when you update the labels on your keyboard you have to go back to the main queue... For example.
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
//do all of your code that you need to get the values for the labels.
dispatch_async(dispatch_get_main_queue(),{
//update the labels with the value you got.
})
}
My app's MasterView includes a table view which is filled with 10 cells containing some core data objects like a "title" (previously downloaded from the internet, see next paragraph). Thanks to the template "Master-Detail Application" I can make use of the built-in NSFetchedResultsController and its methods which take care of the core data handling (e.g. deleting rows). When running the app, the cells get configured with the existent core data objects. At this point I can delete those entries like one would expect.
In addition, my app also has a refresh button. When tapped, it loads some new data from the internet and converts it into objects that are saved to core data. The cells configure themselves again the same way they did before and the UITableView gets a refresh. Again I can delete an entry but now, as soon as I've deleted one which then disappears normally, the table freezes (not the whole app) in the sense that its cells are not tappable anymore/don't react to gestures. I can refresh and that work (10 entries again after having deleted one before) but the cells keep being untappable. I have to stop the app to return to normal functionality.
When restarting, the one entry deleted before is - like expected - gone, 9 entries are left. Again everything works fine at this point, I can delete multiple ones without a problem. But as soon as a manual refresh took place, we're at the same point as before when deleting an entry now.
A common pattern when missing something specific? I'm happy to post some code but I have no idea right now which parts would help...
If the app is really freezing, common sources of this sort of problem are deadlocks. If you're using dispatch_sync anywhere, that's a common source of problems (notably using dispatch_sync from the main thread to something that eventually does another dispatch_sync back to the main thread). If you're using NSLock or #synchronized, and call them recursively, you can also deadlock that way, too.
Another source of problem is an infinite loop (such as an error in a while clause that is never satisfied). If you have such a loop in the main thread, that will freeze the UI. (Anything that blocks the main thread will freeze the UI.)
You might be able to diagnose where it's freezing by running the app through the debugger in Xcode, and when the app freezes, press the debugger's "pause" button. While it will often pause in the middle of some fairly cryptic assembler code, you can often look at the stack traces to figure out where the app was when it froze.
If it's simply that you're not seeing your UI updated as a result of your actions, try making sure that the reloadData method is called on the main queue, if it's not, e.g.:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
Not sure if related, but I run into a freezing problem with table view with header and footer view that were reloaded manually after Core Data fetch request. Table view was reloaded right after the fetch and sometimes was stopping respond to touches. Delaying the tableView.reloadData() by 0.1 second fixed the problem completely.
(Without header and footer view, everything worked fine).
So, I've got an iOS app which works nicely - usually, except the UI occasionally blocks. Is there a standard technique for diagnosing sporadically unresponsive UI?
In my case, I've got LRResty pulling data asynchronously, and the resultant data is parsed by NSXMLParser into some NSMutableArray objects.
I'd pin it on the XML parsing, but the UI doesn't block consistently in place. It blocks primarily after tapping the back button on my UINavigationBar, but also the initial table view doesn't scroll until the search bar is interacted with. Could there be a parser still working, or a request still working?
I use "Time Profiler" in instruments to see which methods are causing the unresponsive UI
(source: bmxmdb.com)
.
I just run it without touching anything, do the action in the app that you need to test, then stop recording, and after a lot of drilling down you find the offending method(s) this is my app accessing the DB.