THERAMPAGE
THERAMPAGE
THERAMPAGE
Switch to the English version
Main   |   Blog   |   EngRead   |   Dragon: The Eater   |   Rampage CMS
Проблема “if case .some” в Swift
Как известно, поддержка опциональных типов в Swift реализована через тип enum под названием Optional. И когда мы пишем:
let a: Int? = 1 
это синтаксический сахар, а полная запись:
let a: Optional<Int> = .some(1) 
 По идее, все это знают, а язык хорошо прячет от нас детали реализации и однажды в голову вполне может придти идея завести в свой программе вот такой тип данных:
enum Progress {
    case none
    case some
}
Например, вам нужно отслеживать какой-то процесс. Вы инициализируете переменную значением .none, а затем, если есть какой-то прогресс, то значение меняется на .some. Лаконично и изящно: 
import Foundation
enum Progress {
    case none
    case some(Float)
}
class Processing {
    var loadingProgress: Progress?
    init(_ progress: Progress) {
        loadingProgress = progress
    }
    func start() {
        loadingProgress = .some(1)
    }
    func check() {
        if case .some(_) = loadingProgress {
            print("The loading process have some progress")
        }
    }
}
let p = Processing(.none)
p.start()
p.check() 
При запуске мы получим вполне ожидаемое сообщение:
The loading process have some progress
Отлично, все работает. Но внезапно вы замечаете странное поведение - некоторые условия работают неверно. В чем же дело? Давайте запустим без сразу проверку без вызова метода start()
let p = Processing(.none)
p.check()
Упс. Мы снова получили The loading process have some progress
Но ведь никакого прогресса не было. Давайте убедимся в этом и выведем значение переменной loadingProgress
let p = Processing(.none)
p.check()
print("The loading process have (p.loadingProgress!) progress")
Результатом будут две строки:
The loading process have some progress
The loading process have none progress
 
Вы наверное уже догадались в чем тут дело, так как я начал именно с описания работы опциональных типов. А вот без подсказки возможно было бы не так очевидно. Конечно дело в том, что переменная loadingProgress является Optional и именно с этим типом сравнивается значение .some и условие выполняется. Если мы изменим в нашем типе Progress значение .some например на .done, то проблема исчезнет. Можно придраться, что код не вполне оптимальный, но его целью является демонстрация проблемы. В большом проекте что-то такое вполне может проскользнуть и доставить массу проблем. Более того, если мы попробуем провернуть тот же фокус со значением .none, то у нас ничего не выйдет: 
if case .none = loadingProgress {
    print("The loading process have no progress")
}
Xcode выдаст предупреждение:
Assuming you mean 'Optional<Progress>.none'; did you mean 'Progress.none' instead?
В то время как .some выполниться без каких-либо предупреждений. 
 
¯\_(ツ)_/¯