Ключова дума Swift #available срещу respondsToSelector

Според документация за предварителна версия на 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 е просто ужасно. Аз съм за по-хубав начин, но #available изглежда (на мен) по-скоро като странична стъпка, отколкото стъпка напред.   -  person Steve Wilford    schedule 11.09.2015
comment
Имам същото чувство за това.   -  person Sulthan    schedule 11.09.2015
comment
AFAIK Ситуацията с метода firstObject не би била възможна със Swift. И тъй като не е възможно, бих предположил, че методът if #available е добър за всички случаи в Swift.   -  person Adam    schedule 11.09.2015


Отговори (2)


Голямо предимство е, че компилаторът Swift 2 в Xcode 7 сравнява наличността на класове, методи, свойства, ... спрямо целта за внедряване на вашия проект.

Използването на respondsToSelector винаги е предразположено към грешки. За Objective-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