简体   繁体   中英

Odd Generics & Optional Behavior in Swift 4.2 after upgrading to iOS 12.2 (Xcode 10.2)

We have just updated Xcode to 10.2 (hence iOS 12.2 SDK) and started seeing odd behavior with respect to the behavior of Swift Generics and Optionals . We have kept the Swift version at 4.2, so no Swift 5 update. The only change was updating to Xcode 10.2 from Xcode 10.1.

Here is a sample code that illustrates the oddities. The comments show what has changed between versions. Ideally, there shouldn't be any changes.

class Phone<T> {}

extension Phone {
    class func create(initial: T? = nil) -> Phone<T> {
        if let _ = initial { print("Regular: Unwrapping worked.") }
        return Phone()
    }
}

extension Phone where T == Void {
    class func create(initial: T? = nil) -> Phone<T> {
        if let _ = initial { print("T == Void: Unwrapping worked.") }
        return Phone()
    }
}

var phone: Phone<Int?> = Phone()
var phone2: Phone<Int?> = Phone()
var phone3: Phone<Int?> = Phone()

// unwrapping works iOS 12.1, doesn't work in 12.2
phone = Phone.create(initial: Optional(nil))

// unwrapping works iOS 12.1, doesn't work in 12.2
phone2 = Phone.create(initial: Optional<Int?>(nil))

// doesn't compile in iOS 12.1, unwrapping works in iOS 12.2
phone3 = Phone.create(initial: Optional<Int>(nil))

// doesn't compile in iOS 12.1, unwrapping doesn't work in 12.2 (uses the T == Void function)
let phone4 = Phone.create(initial: Optional(nil))

We have gone through the release notes of Xcode 10.2 but haven't spotted any changes around Optionals or Generics. It is really hard to understand what is causing this change in behavior between versions.

It is especially very interesting how phone2 and phone3 behave differently. There are few odd things happening in the code sample above, so the question is if anyone knows what might have caused the behavioral changes in this release?

This is due to SE-0213: Literal initialization via coercion which means that Optional(nil) is now treated as nil as Optional by the compiler. Previously with Optional(nil) , you'd get a wrapped nil value eg Int??.some(nil) , however now you get just nil .

So for the following:

let phone: Phone<Int?> = Phone.create(initial: Optional(nil))

the compiler is treating it as:

let phone: Phone<Int?> = Phone.create(initial: nil as Optional)

which is equivalent to:

let phone: Phone<Int?> = Phone.create(initial: nil)

Because you've specified the generic parameter T to be Int? , the initial: parameter takes an Int?? . Therefore by passing nil you're passing Int??.none , and therefore the unwrapping fails.

One way to restore the old behaviour is to specify .init explicitly in order to force the compiler to call the initialiser:

let phone: Phone<Int?> = Phone.create(initial: Optional.init(nil))

Now you're passing an Int??.some(nil) to the parameter and the unwrapping succeeds.

However I would question why you're dealing with doubly wrapped optionals in the first place – I would strongly encourage avoiding them unless absolutely necessary.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM