Нечетное обобщение и необязательное поведение в Swift 4.2 после обновления до iOS 12.2 (Xcode 10.2) - PullRequest
4 голосов
/ 28 марта 2019

Мы только что обновили Xcode до 10.2 (отсюда iOS 12.2 SDK) и начали видеть странное поведение в отношении поведения Swift Generics и Optionals.Мы сохранили версию Swift на уровне 4.2, поэтому обновление Swift 5 отсутствует.Единственным изменением было обновление до Xcode 10.2 с Xcode 10.1.

Вот пример кода, который иллюстрирует странности.Комментарии показывают, что изменилось между версиями.В идеале, не должно быть никаких изменений.

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))

Мы просмотрели примечания к выпуску Xcode 10.2, но не заметили никаких изменений в области Optionals или Generics.Действительно трудно понять, что вызывает это изменение в поведении между версиями.

Особенно интересно, как phone2 и phone3 ведут себя по-разному.В приведенном выше примере кода происходит несколько странных вещей, поэтому вопрос в том, знает ли кто-нибудь, что могло стать причиной поведенческих изменений в этом выпуске?

1 Ответ

3 голосов
/ 28 марта 2019

Это происходит из-за SE-0213: инициализация литерала с помощью принуждения , что означает, что Optional(nil) теперь обрабатывается как nil as Optional компилятором. Ранее с Optional(nil) вы получали бы упакованное значение nil, например, Int??.some(nil), однако теперь вы получаете только nil.

Так что для следующего:

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

компилятор обрабатывает его как:

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

что эквивалентно:

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

Поскольку вы указали общий параметр T равным Int?, параметр initial: принимает значение Int??. Поэтому, передавая nil, вы передаете Int??.none, и поэтому развертывание завершается неудачей.

Один из способов восстановить старое поведение - явно указать .init, чтобы заставить компилятор вызывать инициализатор:

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

Теперь вы передаете Int??.some(nil) параметру, и развертывание завершается успешно.

Однако я бы задал вопрос, почему вы имеете дело с дважды упакованными опциями - я настоятельно рекомендовал бы избегать их, если в этом нет крайней необходимости.

...