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 optional type variables to access the underlying value. The second approach is an unsafe one: we can directly access the 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 of 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 the optimizations Apple made 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 the exceptions that arise when referencing a generated instance that is nil, then you can use init!. Because using init! when the corresponding instance is nil 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 initialization is 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 type Optional? Isn't it said that ! in init! indicates generating ImplicitlyUnwrappedOptional?

The answer lies in the relationship between safety and declaration. In the above code (let nilPerson = Person(firstName: "", lastName: "Mathias")), the type of nilPerson will depend on the compiler's inference.

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, if you wanted to use ImplicitlyUnwrappedOptional, you needed to initialize the instance with init!.

However, to some extent, this approach is quite unsafe. To be honest, we never explicitly indicated that nilPerson should be an ImplicitlyUnwrappedOptional instance, because if the compiler infers some unsafe type information in the future, leading to program errors, 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.

Restricting the Passing of ImplicitlyUnwrappedOptional Instances#

One clever aspect of this approach is that it restricts the passing of implicitly unwrapped optional instances. Referencing our earlier code about Person, let's also 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.

For example, in the above code, what would happen if we performed some asynchronous operations?

    // 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 direct access to its underlying value will lead to a crash. This type of access is exactly what ImplicitlyUnwrappedOptional recommends. So what if we replace anotherMatt with Optional<Person>? Would the situation be better?

Let's try the same code 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 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 indeed need to use an instance of type ImplicitlyUnwrappedOptional, we only 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 ImplicitlyUnwrappedOptional instance. Instead, the compiler will treat the safeAgain variable as an Optional instance. 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 operate on 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 more conveniently. Perhaps one day in the future, ImplicitlyUnwrappedOptional will completely disappear from our view.

In Conclusion#

If you want to know more about this topic, you can find some useful information from this proposal. You can gain insights from the author's thoughts 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.