Любы супраць AnyObject у Swift 3.0

Калі я ўпершыню сутыкнуўся з гэтымі псеўданімамі тыпу, разбіраючы дадзеныя JSON, я не ўяўляў, як іх адрозніць ці правільна рэалізаваць. Такім чынам, што яны? Any і AnyObject - гэта два спецыяльных тыпу ў Swift, якія выкарыстоўваюцца для працы з неспецыфічнымі тыпамі.

Згодна з дакументацыяй Swift Apple,

  • Любы можа прадстаўляць асобнік любога тыпу наогул, уключаючы тыпы функцый і дадатковыя тыпы.
  • AnyObject можа прадстаўляць асобнік любога тыпу класа.

Добра, проста - Любы выкарыстоўваецца для ўсіх тыпаў, AnyObject выкарыстоўваецца для тыпаў класа, ці не так?

Каб зразумець, як яны сапраўды паводзяць сябе ў кодзе, я вырашыў пагуляць з імі на дзіцячай пляцоўцы.

Любы прыклад

Любы дазволіў мне працаваць з сумессю розных тыпаў, уключаючы функцыі і неклассныя тыпы, такія як Int, String і Bool. Згодна з дакументацыяй, элементы гэтага масіва - гэта структуры тыпу значэння, таму тэарэтычна AnyObject у гэтых выпадках не павінен працаваць.

Каб пераканацца ў гэтым, я паспрабаваў уключыць радкі і інты, якія з'яўляюцца тыпамі значэння Swift, выкарыстоўваючы AnyObject.

Памылка AnyObjectArray пры ўключэнні тыпаў структур

Як і чакалася, кампілятар учыніў мне памылку, сказаўшы, што элементы не адпавядаюць тыпу AnyObject у масіве. Пачаўся!

Пасля гэтага здарылася дзіўная рэч, калі я паспрабаваў прытрымлівацца прапаноў кампілятара:

Элементы адлітыя ў AnyObject

Што ж адбылося ?! Як мне ўдалося выкарыстаць AnyObject на Ints і Strings, яўна адкінуўшы кожны элемент у AnyObject?

Затым я надрукаваў anyObjectArray у кансоль.

Друк anyObjectArray

Элемент "Прывітанне" для мяне відавочна выглядаў як радок, але ў яго не было цытат, як звычайнае значэнне String у Swift!

Далей я раздрукоўваў кожны элемент, выкарыстоўваючы цыкл для ўводу, каб праверыць яго сапраўдны тып, а не ягоны кастыраваны тып AnyObject.

Па-першае, я выкарыстаў аператар, каб даведацца, ці з'яўляюцца элементы Swift Struct ці не.

Праверка тыпаў элементаў у anyObjectArray 1

Гэта тып String! Тады як гэта можна кідаць на AnyObject? Зноў жа, радкі ў Swift - гэта структуры, а не тыпы класа. Такім чынам, тэарэтычна я не павінен мець магчымасць прадстаўляць іх як AnyObject.

Што?

Я быў зусім разгублены і вырашыў зрабіць яшчэ некалькі эксперыментаў з ім. На гэты раз я выкарыстаў NSNumber і NSString, якія з'яўляюцца тыпамі Objective-C, каб праверыць тып кожнага элемента.

Праверка тыпаў элементаў у anyObjectArray 2

Пачакайце, прывітанне таксама NSString і лікавыя элементы - NSNumber! І ... яны з'яўляюцца эталоннымі тыпамі ў Objective-C! Гэта была прычына, чаму Прывітанне не меў цытатаў на кансолі? Я напісаў яшчэ нейкі код, як паказана ніжэй, каб даведацца, ці правільна было маё меркаванне.

Друк масіва NSString і String ArrayПрывітанне без цытатаў на кансолі як NSString

Пацверджана! Элементы, якія перадаюцца ў масіў AnyObject, цяпер тыпы класа Objective-C: NSString і NSNumber.

Так ... Што насамрэч адбываецца пад капотам? Я працягваў капацца ў гэтай тэме і знайшоў найбольш праўдападобны адказ у дакуменце "Выкарыстанне Swift with Cocoa and Objective-C" (Swift 3.0.1).

У рамках сваёй сумяшчальнасці з Objective-C, Swift прапануе зручныя і эфектыўныя спосабы працы з рамкамі какава. Swift аўтаматычна пераўтварае некаторыя тыпы Objective-C у тыпы Swift, а некаторыя тыпы Swift у тыпы Objective-C. Тыпы, якія могуць быць пераўтвораны паміж Objective-C і Swift, называюцца пераадоленымі тыпамі.
У любым месцы, дзе вы можаце выкарыстоўваць злучаны тып спасылкі Objective-C, замест гэтага можна выкарыстоўваць тып значэння Swift. Гэта дазваляе скарыстацца функцыянальнасцю, даступнай для рэалізацыі эталоннага тыпу, натуральным чынам у кодзе Swift. Па гэтай прычыне вам амаль ніколі не трэба выкарыстоўваць масткі тыпу спасылкі непасрэдна ва ўласным кодзе. На самай справе, калі код Swift імпартуе API Objective-C, імпарцёр замяняе тыпы спасылак Objective-C адпаведнымі тыпамі значэння. Акрамя таго, калі код Objective-C імпартуе API Swift, імпарцёр таксама замяняе тыпы значэння Swift адпаведнымі тыпамі спасылак Objective-C ".

Іншымі словамі, кампілятар робіць усё магчымае, каб быць гнуткім у кіраванні такімі тыпамі шляхам аўтаматычнага пераўтварэння і пераадолення, адначасова не даючы нашаму прыкладанню лёгка разбурацца. Бліскучы!

Такім чынам, калі мы на самай справе выкарыстоўваем AnyObject? Як паказана ў дакументацыі Apple, AnyObject можа быць выкарыстаны для працы з аб'ектамі, якія паходзяць з класа, але якія не маюць агульнага каранёвага класа.

Але ці абавязкова выкарыстоўваць яго ў нашым кодзе?

Мой адказ на гэтае пытанне: Не.

Apple кажа:

У Swift 3 тып ідэнтыфікатара ў Objective-C зараз пераходзіць у любы тып Swift, які апісвае значэнне любога тыпу, няхай гэта будзе клас, enum, структура або любы іншы тып Swift. Гэта змяненне робіць API-аб'ектывы-C больш гнуткімі ў Swift, таму што тыпы значэння Swift могуць быць перададзены API-аб'ектывам-C і здабытыя як тыпы Swift, што выключае неабходнасць у ручных тыпах "скрыні".
Гэтыя перавагі распаўсюджваюцца і на калекцыі: Тыпы калекцый Objective-C NSArray, NSDictionary і NSSet, якія раней прымалі толькі элементы AnyObject, зараз могуць змяшчаць элементы любога тыпу. Для хэшаваных кантэйнераў, такіх як слоўнік і набор, ёсць новы тып AnyHashable, які можа ўтрымліваць значэнне любога тыпу, які адпавядае пратаколу Swift Hashable.

Здаецца, што любы адзін выдатна працуе ў пераадоленні гэтых двух моў у Swift 3 без выкарыстання AnyObject!

Такім чынам, што было канчатковым матывам гэтых змяненняў?

Сваімі словамі, Apple тлумачыць:

Swift 3 інтэрфейсы з аб'ектыўнымі API API больш магутны, чым папярэднія версіі. Напрыклад, Swift 2 адлюстраваў тып ідэнтыфікатара ў Objective-C да тыпу AnyObject у Swift, які звычайна можа ўтрымліваць толькі значэнні тыпаў класаў. Swift 2 таксама прадугледжвае няяўныя пераўтварэнні ў AnyObject для некаторых мастовых тыпаў значэнняў, такіх як String, Array, Dictionary, Set і некаторыя лічбы, як зручнасць, так што родныя тыпы Swift можна было лёгка выкарыстоўваць з API какава, які чакае NSString, NSArray, альбо іншыя класы кантэйнераў ад Foundation. Гэтыя пераўтварэнні былі супярэчлівыя астатняй частцы мовы, што абцяжарвала разуменне таго, што менавіта можа выкарыстоўвацца як AnyObject, у выніку чаго ўзнікаюць памылкі.

Варта настойваць, аднак, што нам, распрацоўнікам iOS, трэба быць максімальна канкрэтнымі ў плане выкарыстання тыпаў у кодзе.

Сапраўды, Apple рэкамендуе:

Выкарыстоўвайце Any і AnyObject толькі тады, калі вам відавочна патрэбныя паводзіны і магчымасці, якія яны забяспечваюць. Заўсёды лепш канкрэтызаваць тыпы, з якімі вы плануеце працаваць у сваім кодзе.

Падумайце пра гэты сцэнар: мы працуем з нумарам 12.5 у Свіфце. У гэтым выпадку мы б спецыяльна заявіць, што гэта тып Double або Float, а не абвясціць яго тыпам Any. Такім чынам, мы можам зручна атрымаць доступ да розных уласцівасцям і спосабам, даступным для гэтага канкрэтнага тыпу. У гэтым кантэксце мы будзем выкарыстоўваць AnyObject для класаў, таму што яны крыху больш спецыфічныя, чым любыя. Але зноў жа, выкарыстанне AnyObject - гэта проста варыянт.

Я спадзяюся, што гэта паведамленне ў блогу дапамагло многім з вас дзівосных распрацоўнікаў там па ўдакладненні Any і AnyObject. Упэўнена выкарыстоўвайце любую з Swift 3, працуючы з API-падтрымкай, якая падтрымліваецца Objective-C.

Дзякуй за чытанне і шчаслівае кадаванне!