Глибоке копіювання NSArray


119

Чи є якась вбудована функція, яка дозволяє мені глибоко копіювати NSMutableArray?

Я озирнувся, дехто каже, що [aMutableArray copyWithZone:nil]твори є глибокою копією. Але я спробував і, здається, це неглибока копія.

Зараз я вручну виконую копію за допомогою forциклу:

//deep copy a 9*9 mutable array to a passed-in reference array

-deepMuCopy : (NSMutableArray*) array 
    toNewArray : (NSMutableArray*) arrayNew {

    [arrayNew removeAllObjects];//ensure it's clean

    for (int y = 0; y<9; y++) {
        [arrayNew addObject:[NSMutableArray new]];
        for (int x = 0; x<9; x++) {
            [[arrayNew objectAtIndex:y] addObject:[NSMutableArray new]];

            NSMutableArray *aDomain = [[array objectAtIndex:y] objectAtIndex:x];
            for (int i = 0; i<[aDomain count]; i++) {

                //copy object by object
                NSNumber* n = [NSNumber numberWithInt:[[aDomain objectAtIndex:i] intValue]];
                [[[arrayNew objectAtIndex:y] objectAtIndex:x] addObject:n];
            }
        }
    }
}

але мені хотілося б більш чіткого, лаконічного рішення.


44
@Genericrich глибокі та дрібні копії досить чітко визначені терміни в розробці програмного забезпечення. Google.com може допомогти
Ендрю Грант

1
можливо, деяка плутанина полягає в тому, що поведінка -copyв незмінних колекціях змінилася між Mac OS X 10.4 та 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (прокрутіть униз до "Незмінні колекції та поведінка копіювання")
користувач102008

1
@AndrewGrant Щодо подальшої думки та поваги, я не погоджуюся, що глибока копія - це чітко визначений термін. Залежно від того, яке джерело ви читаєте, незрозуміло, чи необмежена рекурсія в вкладені структури даних є вимогою операції "глибокої копії". Іншими словами, ви отримаєте суперечливі відповіді на те, чи є операція копіювання, яка створює новий об'єкт, учасниками якого є дрібні копії членів оригінального об'єкта, є операцією "глибокої копії" чи ні. Дивіться stackoverflow.com/a/6183597/1709587, щоб ознайомитись із цим (у контексті Java, але це все одно актуально).
Марк Амері

@AndrewGrant Я повинен створити резервну копію @MarkAmery та @Genericrich. Глибока копія добре визначена, якщо кореневий клас, який використовується у колекції, та всі його елементи підлягають копіюванню. Це не стосується NSArray (та інших колекцій objc). Якщо елемент не реалізується copy, що потрібно помістити в "глибоку копію"? Якщо елемент іншої колекції, copyвін фактично не дає копії (того ж класу). Тому я вважаю, що цілком справедливо сперечатися про тип потрібної копії у конкретному випадку.
Микола Рухе

@NikolaiRuhe Якщо елемент не реалізує NSCopying/ -copy, він не підлягає копіюванню - тому ніколи не слід намагатися зробити його копію, оскільки це не є можливостями, які вона була спроектована. Що стосується реалізації какао, об'єкти, що не копіюються, часто мають певний стан резервного копіювання, до якого вони прив’язані, тому злом прямої копії об'єкта може призвести до перегонів або гірших умов. Отже, щоб відповісти "що потрібно помістити в" глибоку копію "" - Залишається реф. Єдине, що можна поставити куди завгодно, коли у вас немає NSCopyingоб'єкта.
Сліп Д. Томпсон,

Відповіді:


210

Як прямо вказано документації Apple про глибокі копії :

Якщо вам потрібна лише однорівнева копія:

NSMutableArray *newArray = [[NSMutableArray alloc] 
                             initWithArray:oldArray copyItems:YES];

Вищевказаний код створює новий масив, учасниками якого є дрібні копії членів старого масиву.

Зауважте, що якщо вам потрібно глибоко скопіювати всю вкладену структуру даних - те, що пов'язані документи Apple називають справжньою глибокою копією, - цього підходу буде недостатньо. Будь ласка, дивіться інші відповіді для цього.


Вірна відповідь. API повідомляє, що кожен елемент отримує повідомлення [елемент copyWithZone:], яке може бути тим, що ви бачили. Якщо ви насправді бачите, що надсилання [NSMutableArray copyWithZone: nil] не копіює, то масив масивів може скопіювати неправильно за допомогою цього методу.
Ед Марті

7
Я не думаю, що це буде працювати так, як очікувалося. З Документів Apple: "Метод copyWithZone: виконує дрібну копію . Якщо у вас є колекція довільної глибини, передача YES для параметра прапора виконає незмінну копію першого рівня нижче поверхні. Якщо ви передасте НЕ змінність Перший рівень не впливає. В будь-якому випадку змінність всіх глибших рівнів не впливає. "Питання" SO "стосується глибоких копій, що змінюються.
Джо Д'Андреа

7
це НЕ глибока копія
Daij-Djan

9
Це неповна відповідь. Це призводить до отримання однорівневої глибокої копії. Якщо в масиві є більш складні типи, він не надасть глибокої копії.
Камерон Лоуелл Палмер

7
Це може бути глибока копія, залежно від того, як copyWithZone:реалізовано в приймальному класі.
devios1

62

Єдиний спосіб, за яким я знаю, легко це зробити - це архівувати та негайно скасовувати архівний масив. Це здається, що це трохи хак, але насправді явно пропонується в Документації Apple про копіювання колекцій , де зазначено:

Якщо вам потрібна справжня глибока копія, наприклад, коли у вас є масив масивів, ви можете архівувати, а потім скасувати архівацію колекції за умови, що вміст відповідає протоколу NSCoding. Приклад цієї техніки показаний у Лістингу 3.

Лістинг 3 Справжня глибока копія

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject:oldArray]];

Суть полягає в тому, що ваш об’єкт повинен підтримувати інтерфейс кодування NSC, оскільки це буде використовуватися для зберігання / завантаження даних.

Версія Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))

12
Використання NSArchiver та NSUnarchiver - дуже важке рішення, якщо ваші масиви великі. Написання загального методу категорії NSArray, який використовує протокол NSCopying, дозволить зробити це, викликаючи просте "збереження" незмінних об'єктів та справжню "копію" змінних.
Микита Жук

Добре бути обережними щодо витрат, але чи дійсно буде NSCoding дорожче, ніж NSCopying, що використовується в initWithArray:copyItems:методі? Цей спосіб архівування / неархівірування здається дуже корисним, враховуючи, скільки класів управління відповідають NSCoding, але не NSCopying.
Wienke

Я настійно рекомендую ніколи не використовувати такий підхід. Серіалізація ніколи не буває швидшою, ніж просто копіювання пам'яті.
Бретт

1
Якщо у вас є власні об’єкти, переконайтеся, що потрібно застосувати encodeWithCoder та initWithCoder, щоб вони відповідали протоколу NSC кодування.
користувач523234

Очевидно, що розсуд програміста настійно рекомендується використовувати серіалізацію.
VH-NZZ

34

Копіювати за замовчуванням дає дрібну копію

Це тому, що дзвінок copyтакий же, як copyWithZone:NULLі копія із зоною за замовчуванням. copyВиклик не призводить до глибокої копії. У більшості випадків це дає вам дрібну копію, але в будь-якому випадку це залежить від класу. Для ретельного обговорення рекомендую теми програмування колекцій на веб-сайті Apple Developer.

initWithArray: CopyItems: дає однорівневу глибоку копію

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:someArray copyItems:YES];

NSCoding - це рекомендований Apple спосіб надання глибокої копії

Для справжньої глибокої копії (масив масивів) вам знадобиться NSCodingі архівувати / скасовувати архівний об’єкт:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

1
Це вірно. Незалежно від популярності чи передчасно оптимізованих кращих справ про пам’ять, продуктивність. Замість цього, цей серверний хакер для глибокої копії використовується у багатьох інших мовних середовищах. Якщо немає об’єму дедуппії, це гарантує хорошу глибоку копію, повністю відокремлену від оригіналу.

1
Ось менш гнилий Apple doc url developer.apple.com/library/mac/#documentation/cocoa/conceptual/…

7

Для диктонарів

NSMutableDictionary *newCopyDict = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)objDict, kCFPropertyListMutableContainers);

Для масиву

NSMutableArray *myMutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL, arrData, kCFPropertyListMutableContainersAndLeaves);


4

Ні, для цього немає нічого вбудованого. Колекції какао підтримують неглибокі копії (з copyабоarrayWithArray: методах) , але навіть не говорити про концепцію глибокої копії.

Це відбувається тому, що "глибоку копію" стає важко визначити, коли вміст ваших колекцій починається, включаючи власні власні об'єкти. Чи означає "глибока копія", що кожен об'єкт у графіку об'єкта є унікальним посиланням на кожного об'єкта в початковому графіку об'єкта?

Якби були якісь гіпотетичні NSDeepCopying протокол, ви могли б це налаштувати і приймати рішення у всіх своїх об'єктах, але, на жаль, немає. Якщо ви керували більшістю об’єктів у вашому графіку, ви можете створити цей протокол самостійно та реалізувати його, але вам потрібно буде додати категорію до класів Foundation за необхідності.

@ Відповідь ЕндрюГранта, що пропонує використання керованого архівування / скасування архіву, є неефективним, але правильним і чистим способом досягнення цього для довільних об'єктів. Ця книга навіть заходить так далеко, тому пропонуємо додати категорію до всіх об'єктів, яка робить саме це для підтримки глибокого копіювання.


2

У мене є вирішення проблеми, якщо намагаюся досягти глибокої копії даних, сумісних з JSON.

Просто візьміть NSDataв NSArrayвикористанні , NSJSONSerializationа потім відтворити JSON об'єкт, це створить абсолютно нову і свіжу копію NSArray/NSDictionaryз новими посиланнями і пам'ять про них.

Але переконайтесь, що об’єкти NSArray / NSDictionary та їхні діти повинні бути JSON-серіалізаційними.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];

1
У NSJSONReadingMutableContainersцьому запитанні потрібно вказати для випадку використання.
Микола Рухе
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.