Manjusaka

Manjusaka

A Brief Introduction to the New Security Features in Swift 3.0

After the release of Swift, developers have emphasized that safety and optional types are among the most important features of Swift. They provided a representation mechanism for nil and required a clear syntax for using instances that may be nil.

Optional types mainly consist of the following two:

  1. Optional
  2. ImplicitlyUnwrappedOptional

The first approach is a safe one: it requires us to unwrap the optional type variable to access the underlying value. The second approach is an unsafe one: we can directly access its underlying value without unwrapping the optional type variable. For example, using ImplicitlyUnwrappedOptional when the variable value is nil may lead to exceptions.

Below is an example illustrating this issue:


    let x: Int! = nil
    print(x) // Crash! `x` is nil!

In Swift 3.0, Apple improved the implementation of ImplicitlyUnwrappedOptional, making it safer than before. This raises the question: what improvements did Apple make to ImplicitlyUnwrappedOptional in Swift 3.0 that enhance the safety of Swift? The answer lies in optimizations made by Apple in the compiler's type inference process for ImplicitlyUnwrappedOptional.

Usage in Swift 2.x#

Let's understand the changes through an example.

    struct Person {
        let firstName: String
        let lastName: String

        init!(firstName: String, lastName: String) {
            guard !firstName.isEmpty && !lastName.isEmpty else {
                return nil
            }
            self.firstName = firstName
            self.lastName = lastName
        }
    }

Here we created a struct Person with a flawed initializer. If we do not provide values for first name and last name during initialization, the initialization will fail.

In init!(firstName: String, lastName: String), we used ! instead of ? for initialization. Unlike Swift 3.0, in Swift 2.x, we used init! to utilize ImplicitlyUnwrappedOptional. Regardless of the version of Swift we are using, we should be cautious with init!. Generally, if you can tolerate exceptions that arise from referencing a generated instance that is nil, then you can use init!. Because if the corresponding instance is nil, using init! will cause the program to crash.

In .*, this initializer will generate an ImplicitlyUnwrappedOptional<Person>. If initialization fails, all instances based on Person will raise exceptions.

For example, in Swift 2.x, the following code will crash at runtime.

    // Swift 2.x

    let nilPerson = Person(firstName: "", lastName: "Mathias")
    nilPerson.firstName // Crash!

Note that due to the implicit unwrapping in the initializer, we do not need to use type binding (translator's note 1: optional binding) or optional chaining (translator's note 2: optional chaining) to ensure that nilPerson can be used normally.

New Approach in Swift 3.0#

In Swift 3.0, there is a slight change. The ! in init! indicates that initialization may fail, and if successful, the generated instance will be forcefully implicitly unwrapped. Unlike Swift 2.x, the instance generated by init! is optional rather than ImplicitlyUnwrappedOptional. This means you need to perform type binding or optional chaining for the instance based on different underlying values.


    // Swift 3.0

    let nilPerson = Person(firstName: "", lastName: "Mathias")
    nilPerson?.firstName

In the above example, nilPerson is an instance of type Optional<Person>. This means that if you want to access the value inside normally, you need to unwrap nilPerson. In this case, manual unwrapping is a very good choice.

Safe Type Declaration#

This change may be confusing. Why does using init! generate an instance of Optional type? Isn’t the ! in init! supposed to indicate the generation of ImplicitlyUnwrappedOptional?

The answer lies in the dependency between safety and declaration. In the above code (let nilPerson = Person(firstName: "", lastName: "Mathias")), the compiler will rely on type inference for nilPerson.

In Swift 2.x, the compiler would treat nilPerson as ImplicitlyUnwrappedOptional<Person>. Logically, we have become accustomed to this compilation method, and it makes sense to some extent. In short, in Swift 2.x, to use ImplicitlyUnwrappedOptional, you needed to initialize the instance with init!.

However, to some extent, this approach is quite unsafe. Frankly, we never explicitly intended for nilPerson to be an ImplicitlyUnwrappedOptional instance, because if the compiler infers some unsafe type information in the future that leads to program deviations, you would also bear some responsibility.

Swift 3.0 addresses this safety issue by treating ImplicitlyUnwrappedOptional as optional when we do not explicitly declare an ImplicitlyUnwrappedOptional.

Limiting the Passing of ImplicitlyUnwrappedOptional Instances#

One clever aspect of this approach is that it limits the passing of implicitly unwrapped optional instances. Refer back to our previous code about Person, and consider some of our previous practices in Swift 2.x:


    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`; we can access `firstName` directly
    let anotherMatt = matt // `anotherMatt` is also `ImplicitlyUnwrappedOptional<person>`

anotherMatt is an instance of the same type as matt. You may have anticipated that this is not an ideal situation. In the code, the instance of ImplicitlyUnwrappedOptional has been passed. We must be cautious about the new unsafe code that arises from this.

For example, what if we perform some asynchronous operations in the above code?

    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`, and so we can access `firstName` directly
    ... // Stuff happens; time passes; code executes; `matt` is set to nil
    let anotherMatt = matt // `anotherMatt` has the same type: `ImplicitlyUnwrappedOptional<person>`

In the above example, anotherMatt is an instance with a value of nil, which means any operation that directly accesses its underlying value will lead to a crash. This type of access is exactly what ImplicitlyUnwrappedOptional recommends. So, would it be better if we replaced anotherMatt with Optional<Person>?

Let's see how the same code would behave in Swift 3.0.


    // Swift 3.0

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt?.firstName // `matt` is `Optional<person>`

    let anotherMatt = matt // `anotherMatt` is also `Optional<person>`

If we do not explicitly declare that we are generating an instance of ImplicitlyUnwrappedOptional, the compiler will default to using the safer Optional.

Type Inference Should Be Safe#

The biggest benefit of this change is that the compiler's type inference does not compromise the safety of our code. If we choose some less safe methods when necessary, we must make explicit declarations. This way, the compiler will no longer make automatic judgments.

At times, if we do need to use an instance of ImplicitlyUnwrappedOptional, we simply need to make an explicit declaration.

    // Swift 3.0

    let runningWithScissors: Person! = Person(firstName: "Edward", lastName: "") // Must explicitly declare Person!
    let safeAgain = runningWithScissors // What’s the type here?

runningWithScissors is an instance with a value of nil because we provided an empty string for lastName during initialization.

Note that the declared instance runningWithScissors is an instance of ImplicitlyUnwrappedOptional<Person>. In Swift 3.0, Swift allows us to use both Optional and ImplicitlyUnwrappedOptional. However, we must make explicit declarations to inform the compiler that we are using ImplicitlyUnwrappedOptional.

Fortunately, the compiler no longer automatically treats safeAgain as an instance of ImplicitlyUnwrappedOptional. Instead, the compiler will treat the variable safeAgain as an instance of Optional. In this process, Swift 3.0 effectively limits the propagation of unsafe instances.

A Few Final Words#

The changes to ImplicitlyUnwrappedOptional may stem from the fact that we often interact with APIs written in Objective-C on macOS or iOS, where certain return values may be nil, which is unsafe for Swift.

Thus, Swift is working to avoid such unsafe situations. Many thanks to the Swift developers for their improvements to ImplicitlyUnwrappedOptional. We can now write robust code with great ease. Perhaps one day in the future, ImplicitlyUnwrappedOptional will completely disappear from our sight.

In Conclusion#

If you want to know more about this topic, you can find useful information from this proposal. You can gain insights from the proposal's author in the issue and learn more details through the specific changes. There are also links to related community discussions there.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.