Глубокое копирование 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];
            }
        }
    }
}

но мне хотелось бы более четкое и емкое решение.

ivanTheTerrible
источник
44
Глубокие и поверхностные копии @Genericrich - это довольно хорошо определенные термины в разработке программного обеспечения. Google.com может помочь
Эндрю Грант,
1
возможно, некоторая путаница связана с тем, что поведение -copyнеизменяемых коллекций изменилось между Mac OS X 10.4 и 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (прокрутите вниз до «Неизменяемые коллекции и поведение копирования»)
user102008
1
@AndrewGrant Если подумать и с уважением, я не согласен с тем, что глубокая копия - это четко определенный термин. В зависимости от того, какой источник вы читаете, неясно, является ли неограниченная рекурсия во вложенные структуры данных требованием операции «глубокого копирования». Другими словами, вы получите противоречивые ответы о том, является ли операция копирования, которая создает новый объект, члены которого являются неглубокими копиями членов исходного объекта, операцией «глубокого копирования» или нет. См. Stackoverflow.com/a/6183597/1709587 для обсуждения этого (в контексте Java, но это все равно актуально).
Марк Эмери
@AndrewGrant Я должен сделать резервную копию @MarkAmery и @Genericrich. Глубокая копия хорошо определена, если корневой класс, используемый в коллекции, и все его элементы являются копируемыми. Это не относится к NSArray (и другим коллекциям objc). Если элемент не реализуется copy, что нужно поместить в «глубокую копию»? Если элемент является другой коллекцией, copyфактически не дает копии (того же класса). Так что я думаю, что совершенно справедливо спорить о типе копии, которая требуется в конкретном случае.
Николай Рухе
@NikolaiRuhe. Если элемент не реализует NSCopying/ -copy, то он не может быть скопирован - поэтому вам никогда не следует пытаться сделать его копию, потому что это не та возможность, для которой он был разработан. С точки зрения реализации Какао, некопируемые объекты часто имеют какое-то внутреннее состояние C, к которому они привязаны, поэтому взлом прямой копии объекта может привести к состоянию гонки или хуже. Итак, чтобы ответить «что поместить в« глубокую копию »» - оставлено исх. Единственное, что можно положить куда угодно, когда у вас есть не NSCopyingобъект.
Slipp D. Thompson

Ответы:

210

В документации Apple о глубоких копиях прямо говорится:

Если вам нужна только одноуровневая копия:

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

Приведенный выше код создает новый массив, члены которого являются неглубокими копиями членов старого массива.

Обратите внимание, что если вам нужно глубоко скопировать всю вложенную структуру данных - то, что связанные документы Apple называют настоящей глубокой копией - этого подхода будет недостаточно. Пожалуйста, посмотрите другие ответы здесь.

Франсуа П.
источник
Кажется, это правильный ответ. API заявляет, что каждый элемент получает сообщение [element copyWithZone:], которое может быть тем, что вы видели. Если вы действительно видите, что отправка [NSMutableArray copyWithZone: nil] не выполняет глубокого копирования, то массив массивов может некорректно копироваться с использованием этого метода.
Эд Марти,
7
Я не думаю, что это сработает так, как ожидалось. Из документов Apple: «Метод copyWithZone: выполняет неглубокую копию . Если у вас есть коллекция произвольной глубины, передача YES в качестве параметра флага приведет к неизменной копии первого уровня ниже поверхности. Если вы передадите NO, изменчивость первый уровень не затронут. В любом случае изменчивость всех более глубоких уровней не затронута ». Вопрос SO касается глубоко изменяемых копий.
Джо Д'Андреа
7
это НЕ глубокая копия
Дайдж-Джан
9
Это неполный ответ. Это приводит к одноуровневой глубокой копии. Если в массиве есть более сложные типы, он не будет обеспечивать полную копию.
Кэмерон Лоуэлл Палмер
7
Это может быть полная копия, в зависимости от того, как copyWithZone:реализовано в принимающем классе.
devios1 08
62

Единственный известный мне способ легко сделать это - заархивировать, а затем немедленно разархивировать ваш массив. Это похоже на взлом, но на самом деле это явно предлагается в документации Apple по копированию коллекций , в которой говорится:

Если вам нужна настоящая глубокая копия, например, когда у вас есть массив массивов, вы можете заархивировать, а затем разархивировать коллекцию, при условии, что все содержимое соответствует протоколу NSCoding. Пример этого метода показан в листинге 3.

Листинг 3 Настоящая глубокая копия

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

Загвоздка в том, что ваш объект должен поддерживать интерфейс NSCoding, поскольку он будет использоваться для хранения / загрузки данных.

Версия Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Эндрю Грант
источник
12
Использование NSArchiver и NSUnarchiver - очень тяжелое решение с точки зрения производительности, если ваши массивы большие. Написание универсального метода категории NSArray, который использует протокол NSCopying, сделает свое дело, вызывая простое «сохранение» неизменяемых объектов и настоящую «копию» изменяемых.
Никита Жук,
Хорошо быть осторожным с расходами, но действительно ли NSCoding будет дороже, чем NSCopying, использованный в initWithArray:copyItems:методе? Этот обходной путь архивирования / разархивирования кажется очень полезным, учитывая, сколько классов управления соответствуют NSCoding, но не NSCopying.
Wienke
Я настоятельно рекомендую вам никогда не использовать этот подход. Сериализация никогда не бывает быстрее, чем просто копирование памяти.
Бретт,
1
Если у вас есть настраиваемые объекты, обязательно реализуйте encodeWithCoder и initWithCoder, чтобы они соответствовали протоколу NSCoding.
user523234 02
Очевидно, что при использовании сериализации настоятельно рекомендуется на усмотрение программиста.
VH-NZZ
34

Копировать по умолчанию дает неглубокую копию

Это потому, что вызов copy- это то же самое, что copyWithZone:NULLи копирование с зоной по умолчанию. copyВызов не приводит к глубокой копии. В большинстве случаев это даст вам неглубокую копию, но в любом случае это зависит от класса. Для подробного обсуждения я рекомендую темы программирования коллекций на сайте разработчиков Apple.

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

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

NSCoding это рекомендуемый Apple способ предоставить полную копию

Для истинной глубокой копии (массива массивов) вам потребуется NSCodingзаархивировать / разархивировать объект:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Кэмерон Лоуэлл Палмер
источник
1
Это верно. Независимо от популярности или преждевременной оптимизации памяти, производительности. Кстати, этот сердерсерский хак для глубокого копирования используется во многих других языковых средах. Если нет obj dedupe, это гарантирует хорошую глубокую копию, полностью отделенную от оригинала.
1
Вот менее изменчивый URL-адрес документа Apple 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 по мере необходимости.

Ответ @ AndrewGrant, предполагающий использование архивирования / разархивирования с ключом, является неэффективным, но правильным и чистым способом достижения этого для произвольных объектов. Эта книга даже заходит так далеко, что предлагает добавить категорию ко всем объектам, которые делают именно это, для поддержки глубокого копирования.

Бен Зотто
источник
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];
Mrug
источник
1
В NSJSONReadingMutableContainersэтом вопросе вы должны указать вариант использования.
Николай Рухе