Подтвердить что ты не робот

Глубокая изменчивая копия NSMutableDictionary

Я пытаюсь создать глубокую копию 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 release] там или нет?
  • Вот как я буду называть этот метод:

    self.namesForAlphabets = [self.allNames mutableDeepCopy];

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

4b9b3361

Ответ 1

ВАЖНО. Вопрос (и мой код ниже) касается очень конкретного случая, в котором 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;
}

Ответ 2

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

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);

Ответ 3

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

Что-то вроде этого:

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

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

Ответ 4

Другим методом, который я видел (что совсем не очень эффективно), является использование объекта 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];
}

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

Ответ 5

Попытка выяснить, проверив 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

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

Ответ 6

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

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

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDict, kCFPropertyListMutableContainers));

Ответ 7

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

    NSMutableDictionary* mutableDict = (NSMutableDictionary *)
      CFBridgingRelease(
          CFPropertyListCreateDeepCopy(kCFAllocatorDefault, 
           (CFDictionaryRef)someNSDict, 
           kCFPropertyListMutableContainersAndLeaves));

Ответ 8

Полезные ответы здесь, но 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

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