NFurness-esristaff

Threading and the iOS Runtime SDK

Blog Post created by NFurness-esristaff Employee on Jan 19, 2019

The Runtime SDK does a lot of work behind the scenes to make it as simple as possible for you to write great, interactive mapping apps with fast, smooth user interfaces.

 

Getting out of the way

Key to that is making sure that when you ask Runtime to do something asynchronous (query some features, autocomplete an address, load a service definition etc.), that it gets off the thread you called from as quickly as possible and does its work on another thread. Runtime does this really well and you should never see it get in your way while it's doing something.

 

But when it's done doing that something, which thread does Runtime use to let you know?

 

The good news is iOS and Runtime work together so you might never have to worry about this. But you're a good developer, and you're doing some carefully thought out threading yourself, so you want to know the details, right?

 

UI and the main thread

Where you need to know which thread you're on is if you're updating your app's UI. In iOS, all UI updates must be done on the main thread (or the Main Thread Checker will come after you and your UI won't behave). So if you're not on the main thread and you want to enable that button and update that label, you need to fix that.

 

Luckily, any time iOS enters your code because of a user interaction or view-related hook (e.g. viewDidLoad()), you will already find yourself on the main thread. For a lot of developers this means not having to worry about threading at all until they build something cpu-intensive, at which point they'll need to push that work onto another thread¹.

 

Efficient behavior

Even though Runtime will use other threads to get out of your way, there are some reasons it's better not to switch threads if possible.

  • Context switching between threads takes up CPU cycles.
  • There are some common workflows where even though the pattern is asynchronous, it's quite possible Runtime already has the answer for you and can hand it over immediately. In those cases, context switching would be a waste of time.

 

Adding it all together

These considerations combine to help Runtime determine how to call back to you on the various callback blocks, observers, and change handlers provided by the Runtime SDK.

 

Here's a quick rundown of the ways the ArcGIS Runtime SDK for iOS might call back to you, and the thread you should expect to be called back on:

 

Operation/HandlerThreadNotes

Explicit Calls:

Main | Any
  • If you call from Main, Runtime will call back on Main.
  • If you don't call from Main, Runtime could call back on any thread.

 

Calling Runtime from the main thread is a pretty good indicator that you're calling because of a user interaction, so it's reasonable to expect that you'd want to update some UI when Runtime responds (and that always needs to happen on the main thread).

 

But if you didn't call Runtime from the main thread, maybe there isn't a pair of eyes waiting for the result so Runtime skips the overhead of making sure it's back on the main thread when it responds, ensuring your robots can continue at full speed.

AGSGeoViewTouchDelegate

Main
  • Runtime will always call back on Main.

 

Just as iOS enters your code on the main thread when there's some user interaction, Runtime makes sure to do the same through the AGSGeoViewTouchDelegate. So if you're handling a user tapping on the map, for example, it's safe to update your UI directly.

 

However, if you're observing some property that happens to be changing because of user interaction (e.g. using KVO to monitor mapScale), the "State Feedback" rule below applies.

AGSLoadableMain
  • Runtime will always call back on Main².

 

More often than not, customer code calls into load() or retryLoad() from the main thread. If an item is already loaded, Runtime can then respond immediately with no context switching required. It makes the loadable pattern very fast for the case where, as the result of a user interaction, you need to ensure something is loaded before doing something else (e.g. determining a feature layer's renderer). In that case, only the very first interaction will incur a performance penalty, but you write your code once and it'll handle the first time or the 100th.

State Feedback:

Any
  • Runtime could call back on any thread.

 

Context switching back to the main thread could mean that feedback is returned out of order or be delayed, so Runtime will provide that feedback immediately on whichever thread is current. If you need to update your UI as a result, you should dispatch your UI code to the main thread yourself with something like:

DispatchQueue.main.async {
    /* update the UI */
}

 

Summary

The above can largely be summarized like this:

  • Things that update status or state (progress, KVO, etc.) can happen on any thread.
  • Deliberate actions (Tasks and Jobs) that are called from the main thread will receive their status updates and results on the main thread. If they're called from some other thread, the thread used to respond could be any thread.
  • AGSLoadable and AGSGeoViewTouchDelegate will always call back on the main thread.

 

Let me know in the comments if you have questions about any of the topics brought up here. This can be a complex issue, but iOS and the Runtime SDK make sure that you usually don't need to worry about it.

 


¹ iOS includes a powerful thread abstraction API (Grand Central Dispatch, or GCD) that's worth learning about to help with concurrent programming and slick app experiences. Here's a great 2-part tutorial.

² Note: this load() behavior was updated at release 100.3 to provide a number of performance optimizations. Up until release 100.2.1, Runtime would promise to call back on the main thread only if you called in from the main thread (identical to the current Task/Job behavior).

Outcomes