Understand how different APIs calls to your closures can affect your app.
- Swift Standard Library
Many of the APIs you use in Swift take a closure—or a function passed as an instance—as a parameter. Because closures can contain code that interacts with multiple parts of an app, it's important to understand the different ways closures can be called by the APIs you pass them to. Closures you pass to APIs can be called synchronously (immediately) or asynchronously (sometime later). They may be called once, many times, or never.
Understand the Results of Synchronous and Asynchronous Calls
When you pass a closure to an API, consider when that closure will be called relative to the other code in your app. In synchronous APIs, the result of calling the closure will be available immediately after you pass the closure. In asynchronous APIs, the result won't be available until sometime later; this difference affects how you write code both in your closure as well as the code following your closure.
The example below defines two functions,
later(_:). You can call both functions the same way: with a trailing closure and no other arguments. Both
later(_:) accept a closure and call it, but
later(_:) waits a couple seconds before calling its closure.
later(_:) functions represent the two most common categories of APIs you'll encounter in methods from app frameworks that take closures: synchronous APIs like
now(_:), and asynchronous APIs like
Because calling a closure can change the local and global state of your app, the code you write on the lines after passing a closure needs to be written with a careful consideration of when that closure is called. Even something as simple as printing a sequence of letters can be affected by the timing of a closure call:
Running the code in the example above usually prints the letters in the order
A. Even though the line that prints
A is first in the code, it's ordered later in the output. The ordering difference happens due to the way the
later(_:) functions are defined. You need to know how each function calls its closure if you write code that relies on a specific execution order.
You'll need to consider this kind of time-based execution problem frequently when using APIs that take closures. In many cases, only one sequence of calls is correct for your app, so it's important to think through what the state of your app will be, given the APIs you're using. Use API names and parameter names along with documentation to determine whether an API is synchronous or asynchronous.
A common timing mistake is expecting the results of an asynchronous call to be available within the calling synchronous code. For example, the
later(_:) method above is comparable to the
data method, which is also asynchronous. A timing scenario you should avoid is calling the
data method within your app's
view method and attempting to use the results outside of the closure you pass as the completion handler.
Don't Write Code That Makes a One-Time Change in a Closure That's Called Multiple Times
If you're going to pass a closure to an API that might call it multiple times, omit code that's intended to make a one-time change to external state.
The example below creates a
File and an array of data lines to write to the file that the handle refers to:
To write each line to the file, pass a closure to the
When you're finished using a
File, close it using
close. The correct placement of the call to
close is outside of the closure:
If you misunderstand the requirements of
close, you might place the call inside the closure. Doing so crashes your app:
Don't Put Critical Code in a Closure That Might Not Be Called
If there's a chance that a closure you pass to an API won't be called, don't put code that's critical to continuing your app in the closure.
The example below defines a
Lottery enumeration that randomly picks a winning number and calls a completion handler if the right number is guessed:
Writing code that depends on the completion handler being called is dangerous. There's no guarantee that the random guess will be correct, so important actions like paying bills—scheduled for after you win the lottery—might never happen.