@escaping, @available In Swift 3

In Swift 2, you may have run into the @noescape attribute. Have you ever taken the time to understand what it means? In Swift 3, @noescape has been removed from the language. Why is that? And why does Swift 3 introduce @escaping? Answering these questions is the focus of this article.

What Is the Meaning Of @noescape?

Even though @noescape is deprecated in Swift 3, it is useful to understand what it means. Why is that? The answer is simple. The @noescape attribute is applied by default in Swift 3. Let us start form the beginning.

What Is an Escaping Closure?

You first need to understand what an escaping closure is. The definition is very simple and easy to understand. If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping. It is also said that the closure argument escapes the function body. With this definition in mind, the name of the term escaping closure is well chosen. Right?

If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping.

In Swift 2, you could mark a function parameter with the @noescape attribute, telling the compiler that the closure passed to the function is not allowed to escape the function body. Take a look at the following example. Note that this example is written in Swift 2.

import HealthKit

class HealthKitManager: NSObject {

    private let healthStore = HKHealthStore()

    func requestAuthorization(completion: (Bool, NSError?) -> Void) {
        var shareTypes = Set<HKSampleType>()
        var readTypes = Set<HKSampleType>()

        // Add Workout Type
        shareTypes.insert(HKSampleType.workoutType())
        readTypes.insert(HKSampleType.workoutType())

        // Request Authorization
        healthStore.requestAuthorizationToShareTypes(shareTypes, readTypes: readTypes, completion: completion)
    }

}

You can see that the requestAuthorization(_:) function accepts one parameter, a closure. In Swift 2, closures can by default escape the function body and that is why the above example doesn’t cause the compiler any issues. Note that the completion parameter is passed as an argument to the requestAuthorizationToShareTypes(_:readTypes:completion:) method of HKHealthStore. This method is asynchronous, which means the closure is invoked after requestAuthorization(_:) returns. In other words, the closure we pass to requestAuthorization(_:) is escaping. It escapes the function body of requestAuthorization(_:).

Things become interesting if we add the @noescape attribute to the parameter of requestAuthorization(_:). This is what the example looks like with the @noescape attribute.

import HealthKit

class HealthKitManager: NSObject {

    private let healthStore = HKHealthStore()

    func requestAuthorization(@noescape completion: (Bool, NSError?) -> Void) {
        var shareTypes = Set<HKSampleType>()
        var readTypes = Set<HKSampleType>()

        // Add Workout Type
        shareTypes.insert(HKSampleType.workoutType())
        readTypes.insert(HKSampleType.workoutType())

        // Request Authorization
        healthStore.requestAuthorizationToShareTypes(shareTypes, readTypes: readTypes, completion: completion)
    }

}

We explicitly tell the compiler that the completion parameter should not be allowed to escape the function body. The result is that the compiler throws an error, explaining what is wrong.

Swift 3

Let me show you what the above example looks like in Swift 3. This will show you what has changed and what you need to know about escaping closures in Swift 3.

import HealthKit

class HealthKitManager: NSObject {

    private let healthStore = HKHealthStore()

    func requestAuthorization(@noescape completion: (Bool, Error?) -> Void) {
        var shareTypes = Set<HKSampleType>()
        var readTypes = Set<HKSampleType>()

        // Add Workout Type
        shareTypes.insert(HKSampleType.workoutType())
        readTypes.insert(HKSampleType.workoutType())

        // Request Authorization
        healthStore.requestAuthorization(toShare: shareTypes, read: readTypes, completion: completion)
    }
    
}

The compiler immediately informs us that @noescape is the default in Swift 3 and suggests to remove @noescape. In fact, @noescape is deprecated and you should no longer use it in Swift 3.

Because the closure we pass to requestAuthorization(completion:) (note that the method signature is different in Swift 3) is escaping, we need to mark the parameter as escaping. We use a new attribute for this, @escaping. This is a direct result of SE–0103, a Swift evolution proposal that proposes to make non-escaping closures the default. This is a very welcome change if you ask me.

import HealthKit

class HealthKitManager: NSObject {

    private let healthStore = HKHealthStore()

    func requestAuthorization(completion: @escaping (Bool, Error?) -> Void) {
        var shareTypes = Set<HKSampleType>()
        var readTypes = Set<HKSampleType>()

        // Add Workout Type
        shareTypes.insert(HKSampleType.workoutType())
        readTypes.insert(HKSampleType.workoutType())

        // Request Authorization
        healthStore.requestAuthorization(toShare: shareTypes, read: readTypes, completion: completion)
    }
    
}

You may have noticed that the @escaping attribute precedes the type of the parameter, not the name. This too is new in Swift 3.

What Is the Meaning of @escaping?

This brings us to the meaning of the @escaping attribute. Because closures are by default non-escaping in Swift 3, escaping closures need to be marked as such. And the @escaping attribute lets us do that.

The error of the compiler disappears by marking the closure as escaping, using the @escaping attribute.

Why Is This Important?

There are several benefits to make closures non-escaping by default. The most obvious benefits are performance and the ability for the compiler to optimize your code. If the compiler knows that a closure is non-escaping, it can take care of many of the nitty-gritty details of memory management.

This also means that you can use the self keyword without issues in non-escaping closures because the closure is invoked before the function returns. There is no need to use a weak reference to self in the closure. This is a nice benefit you get for free.

@AVAILABLE

In iOS 8, Apple changed the APIs for working with notification settings. If you are working on an application that supports an earlier version of iOS, then you are forced to use the deprecated APIs for registering for remote notifications. Fortunately, Swift makes this easy and safe.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    if #available(iOS 8.0, *) {
        // Create User Notification Settings
        let userNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)

        // Register User Notification Settings
        application.registerUserNotificationSettings(userNotificationSettings)

    } else {
        // Register for Remote Notification Type
        application.registerForRemoteNotifications(matching: [.alert, .badge, .sound])
    }

    return true
}

The syntax is clear and concise. You use #available followed by a comma separated list of platform names and versions. The asterisk at the end of the list is required. It is a placeholder for future platforms.

How You Can Use It?

The availability syntax only works if the APIs you are using support it. Fortunately, it is easy to add support to your own libraries for the availability syntax.

In the example below, we use the @available attribute to indicate that the CircularView class is only available on iOS 8 and higher.

import UIKit

@available(iOS 8.0, *)
class CircularView: UIView {

    @available(iOS 8.0, *)
    func someMethod() {
        ...
    }

    @available(iOS 9.0, *)
    func anotherMethod() {
        ...
    }
    
}

You can use the @available attribute for any symbol, including classes, structures, enumerations, and methods. Nesting is important, though. For example, a method of a class cannot be more available than the class itself. In the above example, the compiler will throw an error if we set the availability of someMethod() to iOS 7.0.

At the time of writing, the availability syntax supports iOS, OS X, tvOS, and watchOS, including extensions for these platforms.

  • iOS
  • iOSApplicationExtension
  • OSX
  • OSXApplicationExtension
  • tvOS
  • tvOSApplicationExtension
  • watchOS
  • watchOSApplicationExtension
Advertisements
@escaping, @available In Swift 3

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s