Swift #доступное ключевое слово против responsesToSelector

Согласно предварительная версия документации Swift 2 теперь есть ключевое слово #available, которое можно использовать с оператором if let или guard для проверки доступности API. Это дает следующий пример:

let locationManager = CLLocationManager()
if #available(iOS 8.0, OSX 10.10, *) {
    locationManager.requestWhenInUseAuthorization()
}

Or

let locationManager = CLLocationManager()
guard #available(iOS 8.0, OSX 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()

То, что он (документ) утверждает, эквивалентно следующему коду Objective-C:

if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) {
  // Method is available for use.
} else {
  // Method is not available.
}

Очевидно, что respondsToSelector: работает только с подклассами NSObject, тогда как ключевое слово #available будет работать даже для «чистого» кода Swift, поэтому я ценю это преимущество.

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

В качестве более конкретного примера я имею в виду, когда Apple представила firstObject на NSArray в iOS 7, но задним числом сделала его доступным обратно в iOS 4 (где он был доступен, но приватно). Любой код, использующий respondsToSelector:, работал бы на iOS ‹ 7, но, очевидно, проверка версии не удалась.

Есть ли какие-то преимущества в переходе на ключевое слово #available, которое я пропустил?


person Steve Wilford    schedule 11.09.2015    source источник
comment
@holex Оба они являются решениями времени выполнения.   -  person Sulthan    schedule 11.09.2015
comment
Я думаю, причина в том, что Swift не позволяет нам создавать ссылки на функции или методы (кроме текстовых), и это тоже опасно. Использование любого типа respondsToSelector в Swift выглядит уродливо, потому что вам нужно указать имя метода в виде строки (также в соглашении об именах методов Obj-C) :) Я не уверен, что #available является улучшением, но это поможет согласованности кода, потому что рано или поздно останутся Swift только фреймворки. С другой стороны, проверка версии доступна только для системной версии, поэтому для внешних библиотек вам все равно придется использовать проверку селектора.   -  person Sulthan    schedule 11.09.2015
comment
Внешние библиотеки @Sulthan (Swift 2) могут аннотировать свой собственный API с помощью ключевого слова @availability. В качестве примечания я не уверен в совместимости использования if #available... с методами Objective-C, аннотированными NS_AVAILABLE. Я полностью согласен с тем, что respondsToSelector: имеет свои проблемы даже в Objective-C, и необходимость указывать селектор в виде строки в Swift просто ужасна. Я за лучший способ, но #доступный кажется (мне) скорее шагом в сторону, чем шагом вперед.   -  person Steve Wilford    schedule 11.09.2015
comment
У меня такое же чувство по этому поводу.   -  person Sulthan    schedule 11.09.2015
comment
Насколько я знаю, ситуация с методом firstObject была бы невозможна со Swift. И поскольку это невозможно, я бы предположил, что метод if #available подходит для всех случаев в Swift.   -  person Adam    schedule 11.09.2015


Ответы (2)


Большим преимуществом является то, что компилятор Swift 2 в Xcode 7 сравнивает доступность классов, методов, свойств и т. д. с целью развертывания вашего проекта.

Использование respondsToSelector всегда было подвержено ошибкам. Для цели-C,

if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) {

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

В Swift это еще хуже, потому что селекторы могут быть указаны только как строки, и компилятор ничего не проверяет.

В Swift 2/Xcode 7 соответствующий код

let locationManager = CLLocationManager()
if locationManager.respondsToSelector("requestWhenInUseAuthorization") {
    locationManager.requestWhenInUseAuthorization()
}

больше не компилируется, если цель развертывания меньше iOS 8, сообщение об ошибке

error: 'requestWhenInUseAuthorization()' is only available on iOS 8.0 or newer
            locationManager.requestWhenInUseAuthorization()
                            ^
note: add 'if #available' version check

С

let locationManager = CLLocationManager()
if #available(iOS 8.0, OSX 10.10, *) {
    locationManager.requestWhenInUseAuthorization()
}

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

person Martin R    schedule 11.09.2015
comment
Да, я видел это в документах, я думаю, что на мой вопрос нет ответа. Мне просто кажется странным, что они не предоставили что-то вроде синтаксиса необязательного метода протокола для проверки API: if locationManager.requestWhenInUseAuthorization? { locationManager.requestWhenInUseAuthorization() } else { // method is not available } - person Steve Wilford; 11.09.2015
comment
@SteveWilford: if #available работает не только с классами и методами, но и со свойствами. Если я правильно помню, что-то вроде if someInstance.someOptionalProperty? { ... } невозможно в Swift. Было обсуждение на SO или на форуме разработчиков Apple, но я его снова не нашел. - person Martin R; 11.09.2015
comment
Хороший вопрос, я не учел необязательные свойства. if #someInstance.someOptionalProperty { ... } начинает становиться глупым, наверное, поэтому я не инженер-компилятор. Может быть, после нескольких встреч с #available я к этому привыкну... - person Steve Wilford; 11.09.2015

respondsToSelector проверяет среду выполнения, чтобы увидеть, присутствует ли селектор. #available проверяет опубликованный SDK, чтобы убедиться, что он включен. В видео WWDC они прямо сказали, что многие общедоступные API начинаются как частные API в более ранних версиях ОС. Это означает, что в некоторых случаях это не будет #available, даже если это respondsToSelector

person Sean Howell    schedule 11.05.2016