глубоко изменяемая копия NSMutableDictionary - PullRequest
36 голосов
/ 23 декабря 2009

Я пытаюсь создать глубокую копию NSMutableDictionary и назначить ее другому NSMutableDictionary. Словарь содержит несколько массивов, каждый из которых содержит имена, а ключ представляет собой алфавит (первая буква этих имен). Итак, одна запись в словаре - «А» -> «Адам», «Яблоко». Вот что я увидел в книге, но я не уверен, что она работает:

- (NSMutableDictionary *) mutableDeepCopy
{
    NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity: [self count]];
    NSArray *keys = [self allKeys];

    for (id key in keys)
    {
        id oneValue = [self valueForKey:key]; // should return the array
        id oneCopy = nil;

        if ([oneValue respondsToSelector: @selector(mutableDeepCopy)])
        {
            oneCopy = [oneValue mutableDeepCopy];
        }
        if ([oneValue respondsToSelector:@selector(mutableCopy)])
        {
            oneCopy = [oneValue mutableCopy];
        }

        if (oneCopy == nil) // not sure if this is needed
        {   
            oneCopy = [oneValue copy];
        }
        [ret setValue:oneCopy forKey:key];

        //[oneCopy release];
    }
    return ret;
}
  • должен ли быть [релиз onecopy] или нет?
  • Вот как я собираюсь вызвать этот метод:

    self.namesForAlphabets = [self.allNames mutableDeepCopy];

Это будет хорошо? Или это приведет к утечке? (предположим, что я объявляю self.namesForAlphabets как свойство и освобождаю его в dealloc).

Ответы [ 8 ]

74 голосов
/ 23 декабря 2009

Из-за бесплатного моста вы также можете использовать функцию CoreFoundation CFPropertyListCreateDeepCopy:

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
11 голосов
/ 23 декабря 2009

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

Примерно так:

id DeepCopyViaArchiving(id<NSCoding> anObject)
{
    NSData* archivedData = [NSKeyedArchiver archivedDataWithRootObject:anObject];
    return [[NSKeyedUnarchiver unarchiveObjectWithData:archivedData] retain];
}

Хотя это не особенно эффективно.

9 голосов
/ 23 декабря 2009

ВАЖНО : оба вопроса (и мой код ниже) касаются очень специфического случая, в котором NSMutableDictionary содержит только массивы строк. Эти решения не будут работать для более сложных примеров. Для более общих решений случая см. Следующее:


Ответ для этого конкретного случая:

Ваш код должен работать, но вам определенно понадобится [oneCopy release]. Новый словарь сохранит скопированные объекты при добавлении их с помощью setValue:forKey, поэтому, если вы не вызовете [oneCopy release], все эти объекты будут сохранены дважды.

Хорошее эмпирическое правило: если вы alloc, retain или copy что-то, вы также должны release это.

Примечание: вот пример кода, который будет работать только для определенных случаев . Это работает, потому что ваш NSMutableDictionary содержит только массивы строк (дальнейшее глубокое копирование не требуется):

- (NSMutableDictionary *)mutableDeepCopy
{
    NSMutableDictionary * ret = [[NSMutableDictionary alloc]
                                  initWithCapacity:[self count]];

    NSMutableArray * array;

    for (id key in [self allKeys])
    {
        array = [(NSArray *)[self objectForKey:key] mutableCopy];
        [ret setValue:array forKey:key];
        [array release];
    }

    return ret;
}
8 голосов
/ 23 декабря 2009

Другая техника, которую я видел (которая не очень эффективна), - это использование объекта NSPropertyListSerialization для сериализации вашего словаря, затем вы десериализовываете его, но указываете, что вам нужны изменяемые листья и контейнеры.


NSString *errorString = nil;
NSData *binData = 
  [NSPropertyListSerialization dataFromPropertyList:self.allNames
                                             format:NSPropertyListBinaryFormat_v1_0
                                        errorString:&errorString];

if (errorString) {
    // Something bad happened
    [errorString release];
}

self.namesForAlphabets = 
 [NSPropertyListSerialization propertyListFromData:binData
                                  mutabilityOption:NSPropertyListMutableContainersAndLeaves
                                            format:NULL
                                  errorDescription:&errorString];

if (errorString) {
    // something bad happened
    [errorString release];
}

Опять же, это совсем не эффективно.

4 голосов
/ 07 ноября 2014

Попытка выяснить это путем проверки respondToSelector(@selector(mutableCopy)) не даст желаемых результатов, поскольку все объекты на основе NSObject реагируют на этот селектор (это часть NSObject). Вместо этого мы должны запросить, соответствует ли объект NSMutableCopying или хотя бы NSCopying. Вот мой ответ, основанный на этой сути , упомянутой в принятом ответе:

Для NSDictionary:

@implementation NSDictionary (MutableDeepCopy)

//  As seen here (in the comments): https://gist.github.com/yfujiki/1664847
- (NSMutableDictionary *)mutableDeepCopy
{
    NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count];

    NSArray *keys = [self allKeys];

    for(id key in keys) {
        id oneValue = [self objectForKey:key];
        id oneCopy = nil;

        if([oneValue respondsToSelector:@selector(mutableDeepCopy)]) {
            oneCopy = [oneValue mutableDeepCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) {
            oneCopy = [oneValue mutableCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){
            oneCopy = [oneValue copy];
        } else {
            oneCopy = oneValue;
        }

        [returnDict setValue:oneCopy forKey:key];
    }

    return returnDict;
}

@end

Для NSArray:

@implementation NSArray (MutableDeepCopy)

- (NSMutableArray *)mutableDeepCopy
{
    NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count];

    for(id oneValue in self) {
        id oneCopy = nil;

        if([oneValue respondsToSelector:@selector(mutableDeepCopy)]) {
            oneCopy = [oneValue mutableDeepCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) {
            oneCopy = [oneValue mutableCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){
            oneCopy = [oneValue copy];
        } else {
            oneCopy = oneValue;
        }

        [returnArray addObject:oneCopy];
    }

    return returnArray;
}

@end

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

1 голос
/ 24 сентября 2015

Для ARC - обратите внимание на kCFPropertyListMutableContainersAndLeaves для действительно глубокой изменчивости.

    NSMutableDictionary* mutableDict = (NSMutableDictionary *)
      CFBridgingRelease(
          CFPropertyListCreateDeepCopy(kCFAllocatorDefault, 
           (CFDictionaryRef)someNSDict, 
           kCFPropertyListMutableContainersAndLeaves));
1 голос
/ 03 сентября 2015

Думаю, я бы обновил ответ, если вы используете ARC.

Решение, предоставленное Weva, работает отлично. В настоящее время вы можете сделать это так:

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDict, kCFPropertyListMutableContainers));
0 голосов
/ 03 января 2017

Полезные ответы здесь, но CFPropertyListCreateDeepCopy не обрабатывает [NSNull null] в данных, что вполне нормально, например, для декодированных данных JSON.

Я использую эту категорию:

    #import <Foundation/Foundation.h>

    @interface NSObject (ATMutableDeepCopy)
    - (id)mutableDeepCopy;
    @end

Реализация (не стесняйтесь изменять / расширять):

    @implementation NSObject (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        return [self copy];
    }

    @end

    #pragma mark - NSDictionary

    @implementation NSDictionary (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        return [NSMutableDictionary dictionaryWithObjects:self.allValues.mutableDeepCopy
                                                  forKeys:self.allKeys.mutableDeepCopy];
    }

    @end

    #pragma mark - NSArray

    @implementation NSArray (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        NSMutableArray *const mutableDeepCopy = [NSMutableArray new];
        for (id object in self) {
            [mutableDeepCopy addObject:[object mutableDeepCopy]];
        }

        return mutableDeepCopy;
    }

    @end

    #pragma mark - NSNull

    @implementation NSNull (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        return self;
    }

    @end

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...