Глыбокая копія супраць дробнай копіі - і як вы можаце іх выкарыстоўваць у Swift

Капіраванне аб'екта заўсёды было важнай часткай парадыгмы кадавання. Няхай гэта будзе ў Swift, Objective-C, JAVA ці іншай мове, нам заўсёды трэба капіяваць аб'ект для выкарыстання ў розных кантэкстах.

У гэтым артыкуле мы падрабязна абмяркуем, як капіяваць розныя тыпы дадзеных у Swift і як яны паводзяць сябе ў розных умовах.

Значэнне і тыпы даведкі

Усе тыпы дадзеных у Swift шырока падпадзяляюцца на дзве катэгорыі, а менавіта тыпы значэння і тыпы спасылак.

  • Тып значэння - кожны асобнік захоўвае унікальную копію сваіх дадзеных. Тыпы дадзеных, якія падпадаюць пад гэтую катэгорыю, ўключаюць - усе асноўныя тыпы дадзеных, struct, enum, масіў, корцежы.
  • Тып спасылкі - асобнікі, якія дзеляцца адной копіяй дадзеных, а тып звычайна вызначаецца як клас.

Самая адметная асаблівасць абодвух тыпаў заключаецца ў іх капіравальным паводзінах.

Што такое глыбокая і дробная копія?

Да прыкладу, будзь то тып значэння альбо тып спасылкі, можна скапіяваць адным з наступных спосабаў:

Глыбокая копія - дублюе ўсё

  • З глыбокай копіі любы аб'ект, на які паказвае крыніца, капіруецца, а копія паказваецца па прызначэнні. Так будуць створаны два цалкам асобныя аб'екты.
  • Калекцыі - глыбокая копія калекцыі - гэта дзве калекцыі з усімі элементамі арыгінальнай калекцыі.
  • Менш схільныя да ўмоваў гонкі і добра працуюць у шматпаточнай асяроддзі - змены аднаго аб'екта не будуць мець ніякага ўплыву на іншы аб'ект.
  • Тыпы значэння капіююцца глыбока.

У прыведзеным вышэй кодзе,

  • Радок 1: arr1 - масіў (тып значэння) радкоў
  • Радок 2: arr1 прызначаецца для arr2. Гэта створыць глыбокую копію arr1, а затым прызначыць яе копіі arr2
  • Радкі 7 да 11: любыя змены, зробленыя ў arr2, не адлюстроўваюцца ў arr1.

Вось што такое глыбокая копія - цалкам асобныя асобнікі. Гэтая ж канцэпцыя працуе з усімі тыпамі значэння.

У некаторых сцэнарыях, калі тып значэння змяшчае ўкладзеныя тыпы эталонаў, глыбокая копія выяўляе іншы тып паводзін. Мы гэта ўбачым у наступных раздзелах.

Дробная копія - Дубліруе як мага менш

  • З дробнай копіі любы аб'ект, на які паказвае крыніца, таксама паказваецца па прызначэнні. Такім чынам, у памяці будзе створаны толькі адзін аб'ект.
  • Калекцыі - Дробная копія калекцыі - гэта копія структуры калекцыі, а не элементаў. Дзве калекцыі з дробнай копіяй падзяляюць асобныя элементы.
  • Больш хутка - капіюецца толькі спасылка.
  • Капіраванне тыпаў спасылак стварае дробную копію.

У прыведзеным вышэй кодзе,

  • Радкі ад 1 да 8: тып класа адрасоў
  • Радок 10: a1 - асобнік тыпу Address
  • Радок 11: a1 прызначаецца a2. Гэта створыць дробную копію a1, а потым прысвоіць яе копіі a2, гэта значыць капіюецца ў a2 толькі спасылку.
  • Радкі 16 да 19: любыя змены, зробленыя ў a2, напэўна будуць адлюстроўвацца ў a1.

На прыведзенай вышэй ілюстрацыі мы бачым, што і a1, і a2 паказваюць на адзін і той жа адрас памяці.

Капіраванне тыпаў спасылак глыбока

На сённяшні дзень мы ведаем, што кожны раз, калі мы спрабуем скапіяваць тып спасылкі, капіюецца толькі спасылка на аб'ект. Новы аб'ект не створаны. Што рабіць, калі мы хочам стварыць цалкам асобны аб'ект?

Мы можам стварыць глыбокую копію тыпу спасылкі, выкарыстоўваючы метад copy (). Паводле дакументацыі,

copy () - вяртае аб'ект, які вяртаецца копіяй (з :).

Гэта зручны метад для класаў, якія прымаюць пратакол NSCopying. Выключэнне складаецца, калі для копіі няма рэалізацыі (з :).

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

У прыведзеным вышэй кодзе,

  • Радкі з 1 па 14: Тып класа адрасу адпавядае NSCopying і рэалізуе копію (з метадам :)
  • Радок 16: a1 - асобнік тыпу Address
  • Радок 17: a1 прызначаецца a2 з дапамогай метаду copy (). Гэта створыць глыбокую копію a1, а потым прызначыць яе копіі a2, гэта значыць будзе створаны зусім новы аб'ект.
  • Радкі 22-25: любыя змены, зробленыя ў a2, не адлюстроўваюцца ў a1.

Як відаць з прыведзенай ілюстрацыі, і a1, і a2 паказваюць на розныя месцы памяці.

Давайце разгледзім яшчэ адзін прыклад. На гэты раз мы ўбачым, як гэта працуе з укладзенымі тыпамі спасылак - тыпам спасылкі, які змяшчае іншы тып спасылкі.

У прыведзеным вышэй кодзе,

  • Радок 22: глыбокая копія р1 прызначаецца р2 метадам copy (). Гэта азначае, што любое змяненне аднаго з іх не павінна мець ніякага ўплыву на іншае.
  • Радкі 27-28: імя і горад p2 мяняюцца. Яны не павінны адлюстроўваць у p1.
  • Радок 30: імя p1 чакаецца, але яго горад? Павінна быць "Мумбаі", ці не так? Але мы не можам бачыць, што гэта адбываецца. "Бангалор" быў толькі для p2, ці не так? Так ... дакладна.

Глыбокая копія…! Такога не чакалі ад вас. Вы сказалі, што капіруеце ўсё. І зараз вы паводзіце сябе так. Чаму о, чаму ..?! Што мне цяпер рабіць?

Не панікуйце. Давайце паглядзім, якія адрасы памяці павінны сказаць у гэтым пытанні.

З прыведзенай вышэй ілюстрацыі мы бачым гэта

  • p1 і p2 паказваюць на розныя месцы памяці, як чакалася.
  • Але зменныя іх адрасы па-ранейшаму паказваюць на тое ж месца. Гэта азначае, што нават пасля капіравання іх глыбока скапіруюцца толькі спасылкі - гэта значыць, дробная копія, вядома.

Звярніце ўвагу: кожны раз, калі мы капіруем тып спасылкі, па змаўчанні ствараецца дробная копія, пакуль мы дакладна не ўкажам, што яе трэба капіраваць глыбока.

Функцыя копіі (з зонай: NSZone? = nil) -> Любая
{
    хай чалавек = Асоба (імя, імя, асабісты адрас)
    вярнуць чалавека
}

У вышэйзгаданым спосабе, які мы рэалізавалі раней для класа Person, мы стварылі новы асобнік, скапіяваўшы адрас з self.address. Гэта будзе капіяваць толькі спасылку на адрасны аб'ект. Вось чаму адрасы p1 і p2 паказваюць на адно і тое ж месца.

Такім чынам, капіраванне аб'екта метадам copy () не прывядзе да стварэння сапраўднай глыбокай копіі.

Для поўнага дубліравання эталоннага аб'екта: тып спасылкі разам з усімі ўкладзенымі тыпамі спасылак павінен быць скапіяваны метадам copy ().

хай чалавек = Асоба (саманазва, self.address.copy () як? адрас)

Выкарыстоўваючы прыведзены вышэй код у функцыі копіі (з зонай: NSZone? = Nil) -> Любы метад прымусіць усё працаваць. Гэта бачна з ніжэй ілюстрацыі.

True Deep Copy - тыпы даведкі і значэння

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

А як жа укладзены тып спасылкі ў тыпе значэння, то ёсць масіў аб'ектаў альбо зменная тып эталону ў структуры ці, магчыма, кортай? Ці можам мы гэта вырашыць, выкарыстоўваючы copy ()? На самой справе мы не можам. Метад copy () патрабуе рэалізацыі пратакола NSCopying, які працуе толькі для падкласаў NSObject. Тыпы значэння не падтрымліваюць спадчыну, таму мы не можам выкарыстоўваць copy () з імі.

У радку 2 толькі глыбока капіюецца структура arr1, але аб'екты адрасоў, якія знаходзяцца ўнутры яе, усё яшчэ дробна скапіруюцца. Вы можаце бачыць гэта з карты памяці ніжэй.

І элементы, і arr1 і arr2 абодва, паказваюць на аднолькавыя месцы памяці. Гэта адбываецца па той жа прычыне - тыпы даведкі дробна капіююцца па змаўчанні.

Серыялізацыя і затым дэсерыялізацыя аб'екта заўсёды стварае зусім новы аб'ект. Ён сапраўдны як для тыпаў значэння, так і для тыпаў спасылак.

Вось некаторыя API, якія мы можам выкарыстоўваць для серыялізацыі і дэсерыялізацыі дадзеных:

  1. NSCoding - пратакол, які дазваляе кадзіраваць і дэкадаваць аб'ект для архівавання і распаўсюджвання. Ён будзе працаваць толькі з аб'ектамі тыпу класа, паколькі яму патрабуецца атрыманне ў спадчыну ад NSObject.
  2. Codable - Зрабіце свае тыпы дадзеных кадаваемымі і расшыфроўваемымі для сумяшчальнасці з знешнімі прадстаўленнямі, такімі як JSON. Ён будзе працаваць для абодвух тыпаў значэнняў - структура, масіў, кортай, асноўныя тыпы дадзеных, як эталоны тыпу - клас.

Давайце перабудуем клас адрасоў яшчэ крыху, каб адпавядаць пратаколу Codable і выдаліць увесь код NSCopying, які мы дадалі раней у фрагменце кода 3.

У вышэйзгаданым кодзе радкі 11–13 ствараюць сапраўдную глыбокую копію arr1. Ніжэй прыведзена ілюстрацыя, якая дае ясную карціну месцаў памяці.

Капіяваць на пісаць

Copy on write - гэта аптымізацыйная тэхніка, якая дапамагае павялічыць прадукцыйнасць пры капіраванні тыпаў значэнняў.

Скажам, мы капіруем адзін String або Int альбо, магчыма, любы іншы тып значэння. У гэтым выпадку мы не сутыкнемся з любымі важнымі праблемамі з прадукцыйнасцю. А як быць, калі мы капіруем масіў з тысяч элементаў? Ці ўсё ж такі не ўзнікнуць праблемы з прадукцыйнасцю? Што рабіць, калі мы проста скапіруем яго і не ўнясем ніякіх зменаў у гэтую копію? Хіба гэта не дадатковая памяць у гэтым выпадку?

Тут прыходзіць канцэпцыя Copy in Write - пры капіраванні кожная спасылка паказвае на адзін і той жа адрас памяці. І толькі калі адна з спасылак змяняе асноўныя дадзеныя, Свіфт фактычна капіюе арыгінальны асобнік і ўносіць змены.

То бок, павольная копія альбо дробная копія, новая копія не будзе створаная, пакуль мы не ўнясем змены ў адзін з аб'ектаў.

У прыведзеным вышэй кодзе,

  • Радок 2: arr2 прызначана глыбокая копія arr1
  • Радкі 4 і 5: arr1 і arr2 па-ранейшаму паказваюць на адзін і той жа адрас памяці
  • Радок 7: змены, унесеныя ў arr2
  • Радкі 9 і 10: arr1 і arr2 цяпер паказваюць на розныя месцы памяці

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

Далейшае чытанне

Не забудзьцеся прачытаць іншыя мае артыкулы:

  1. Усё пра Codable у Swift 4
  2. Усё, што вы заўсёды хацелі ведаць пра апавяшчэнні ў iOS
  3. Пафарбуйце яго з ГРАДЫЯНТЫ - iOS
  4. Кадаванне для iOS 11: як перацягваць у калекцыі і табліцы
  5. Усё, што вам трэба ведаць пра Today Extensions (віджэт) у iOS 10
  6. Выбар UICollectionViewCell зрабіўся простым .. !!

Не саромейцеся пакідаць каментарыі ў выпадку ўзнікнення пытанняў.