Глубокое копирование NSArray

Есть ли встроенная функция, которая позволяет мне глубоко копировать 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];
            }
        }
    }
}

но я хотел бы более чистое, более лаконичное решение.


person ivanTheTerrible    schedule 15.03.2009    source источник
comment
Глубокие и мелкие копии @Genericrich - это довольно четко определенные термины в разработке программного обеспечения. Google.com может помочь   -  person Andrew Grant    schedule 15.03.2009
comment
возможно, некоторая путаница связана с тем, что поведение -copy в неизменяемых коллекциях изменилось между Mac OS X 10.4 и 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/ (прокрутите вниз до неизменяемых коллекций и поведения копирования)   -  person user102008    schedule 15.01.2011
comment
@AndrewGrant Если подумать и при всем уважении, я не согласен с тем, что термин глубокая копия является четко определенным. В зависимости от того, какой источник вы читаете, неясно, является ли неограниченная рекурсия во вложенные структуры данных требованием операции «глубокого копирования». Другими словами, вы получите противоречивые ответы о том, является ли операция копирования, создающая новый объект, члены которого являются мелкими копиями элементов исходного объекта, операцией «глубокого копирования» или нет. См. stackoverflow.com/a/6183597/1709587 для некоторого обсуждения этого (в контексте Java, но это относится ко всем такой же).   -  person Mark Amery    schedule 02.09.2013
comment
@AndrewGrant Я должен сделать резервную копию @MarkAmery и @Genericrich. Глубокая копия определена корректно, если корневой класс, используемый в коллекции, и все ее элементы являются копируемыми. Это не относится к NSArray (и другим коллекциям объектов). Если элемент не реализует copy, что нужно поместить в глубокую копию? Если элемент является другой коллекцией, copy на самом деле не дает копию (того же класса). Так что я думаю, что совершенно справедливо спорить о том, какой тип копии требуется в конкретном случае.   -  person Nikolai Ruhe    schedule 12.10.2015
comment
@NikolaiRuhe Если элемент не реализует NSCopying/-copy, то он не может быть скопирован, поэтому вам никогда не следует пытаться сделать его копию, потому что это не та возможность, для которой он был разработан. С точки зрения реализации Cocoa, некопируемые объекты часто имеют некоторое внутреннее состояние C, к которому они привязаны, поэтому взлом прямой копии объекта может привести к условиям гонки или хуже. Итак, чтобы ответить «что должно быть помещено в «глубокую копию»» — сохраненный ref. Единственное, что вы можете поместить куда угодно, когда у вас есть объект, отличный от NSCopying.   -  person Slipp D. Thompson    schedule 11.02.2017


Ответы (6)


Как Документация Apple о глубоких копиях прямо указывает:

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

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

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

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

person François P.    schedule 15.03.2009
comment
Кажется, это правильный ответ. В API указано, что каждый элемент получает сообщение [element copyWithZone:], что может быть тем, что вы видели. Если вы на самом деле видите, что отправка [NSMutableArray copyWithZone:nil] не выполняет глубокое копирование, то массив массивов может неправильно копироваться с помощью этого метода. - person Ed Marty; 15.03.2009
comment
Хм, я получил *** Завершение приложения из-за необработанного исключения «NSInvalidArgumentException», причина: «-[Turn copyWithZone:]: нераспознанный селектор отправлен в экземпляр 0x4e327a0» (у меня есть массив поворотов). - person quantumpotato; 21.03.2011
comment
@quantumpotato: вам нужно реализовать протокол NSCopying в вашем классе Turn. (Да, я понимаю, что это очень поздно.) - person Wevah; 08.06.2011
comment
Я не думаю, что это сработает, как ожидалось. Из документации Apple: метод copyWithZone: выполняет поверхностную копию. Если у вас есть коллекция произвольной глубины, передача YES для параметра флага выполнит неизменяемую копию первого уровня под поверхностью. Если вы передадите НЕТ, изменчивость первого уровня не изменится. В любом случае изменчивость всех более глубоких уровней не затрагивается. Вопрос SO касается глубоких изменчивых копий. - person Joe D'Andrea; 28.10.2011
comment
почему initWithArray не задокументирован в Справочник по классу NSMutableArray? - person resting; 10.09.2012
comment
Это неполный ответ. В результате получается одноуровневая глубокая копия. Если в массиве есть более сложные типы, он не предоставит глубокую копию. - person Cameron Lowell Palmer; 13.03.2013
comment
@resting Потому что это метод NSArray, а не уникальный для NSMutableArray. - person Mark Amery; 02.09.2013
comment
Это может быть глубокой копией, в зависимости от того, как copyWithZone: реализовано в принимающем классе. - person devios1; 08.01.2014

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

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

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

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

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

Версия Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
person Andrew Grant    schedule 15.03.2009
comment
Использование NSArchiver и NSUarchiver — очень тяжелое решение с точки зрения производительности, если ваши массивы большие. Написание общего метода категории NSArray, который использует протокол NSCopying, добьется цели, вызывая простое «сохранение» неизменяемых объектов и реальную «копию» изменяемых. - person Nikita Zhuk; 15.03.2009
comment
Хорошо быть осторожным в отношении расходов, но действительно ли NSCoding будет дороже, чем NSCopying, используемый в методе initWithArray:copyItems:? Этот обходной путь архивирования/разархивирования кажется очень полезным, учитывая, сколько классов элементов управления соответствуют NSCoding, но не NSCopying. - person Wienke; 28.07.2012
comment
Я настоятельно рекомендую вам никогда не использовать этот подход. Сериализация никогда не бывает быстрее, чем просто копирование памяти. - person Brett; 13.08.2012
comment
Если у вас есть пользовательские объекты, обязательно реализуйте encodeWithCoder и initWithCoder, чтобы они соответствовали протоколу NSCoding. - person user523234; 02.05.2013
comment
Очевидно, что при использовании сериализации настоятельно рекомендуется соблюдать осторожность программиста. - person VH-NZZ; 26.02.2015

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

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

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

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

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

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

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
person Cameron Lowell Palmer    schedule 23.02.2013
comment
Это верно. Независимо от популярности или преждевременно оптимизированных крайних случаев с памятью, производительность. Кроме того, этот хак ser-derser для глубокого копирования используется во многих других языковых средах. Если нет дедупликации obj, это гарантирует хорошую глубокую копию, полностью отдельную от оригинала. - person ; 02.06.2013
comment
Вот менее изменчивый URL-адрес документа Apple разработчика. apple.com/library/mac/#documentation/cocoa/conceptual/ - person ; 02.06.2013

Для словаря

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

Для массива

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

person Darshit Shah    schedule 14.10.2015

Нет, для этого в фреймворках нет ничего встроенного. Коллекции Cocoa поддерживают мелкие копии (с методами copy или arrayWithArray:), но даже не говорят о концепции глубокого копирования.

Это связано с тем, что «глубокую копию» становится трудно определить, поскольку содержимое ваших коллекций начинает включать ваши собственные настраиваемые объекты. Означает ли «глубокая копия», что каждый объект в графе объектов является уникальной ссылкой относительно каждого объекта в исходном графе объектов?

Если бы существовал какой-то гипотетический протокол NSDeepCopying, вы могли бы настроить его и принимать решения во всех ваших объектах, но, к сожалению, его нет. Если бы вы контролировали большинство объектов в своем графе, вы могли бы сами создать этот протокол и реализовать его, но при необходимости вам нужно было бы добавить категорию в классы Foundation.

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

person Ben Zotto    schedule 02.11.2013

У меня есть обходной путь, если вы пытаетесь получить глубокую копию для данных, совместимых с 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];
person Mrug    schedule 24.07.2015
comment
Вы должны указать NSJSONReadingMutableContainers для варианта использования в этом вопросе. - person Nikolai Ruhe; 12.10.2015