Почему у меня возникают проблемы с глубокой копией в Objective C? - PullRequest
1 голос
/ 27 февраля 2009

Я предполагаю, что мое понимание того, как выполнить глубокое копирование, еще не пришло. То же самое с некоторой неоптимальной обработкой памяти, которую я выполняю ниже. Этот код ниже, вероятно, изображает мелкую копию, и я полагаю, что это может быть моей проблемой. У меня есть код печенья для примера, который выглядит следующим образом:

NSArray *user = [[xmlParser createArrayWithDictionaries:dataAsXML
                                              withXPath:kUserXPath] retain];
if([user count] > 0) {
    self.name = [[user valueForKey:@"name"] copy];
}

// Crash happens if I leave the next line un-commented.
// But then we have a memory leak.
[user release]; 

[xmlParser release];

К сожалению, когда я комментирую [user release], код работает, но у нас есть очевидная утечка памяти. Метод createArrayWithDictionaries:withXPath: был переработан вчера вечером, когда SO-сообщество помогло мне лучше понять управление памятью. Вот как это выглядит:

- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument 
                               withXPath:(NSString *)XPathStr {
    NSError *theError = nil;
    NSMutableArray *dictionaries = [NSMutableArray array];
    CXMLDocument *theXMLDocument = [CXMLDocument alloc];
    theXMLDocument = [theXMLDocument initWithXMLString:xmlDocument
                                               options:0
                                                 error:&theError]; 
    NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];

    for (CXMLElement *xmlElement in nodes) {
        NSArray *attributes = [xmlElement attributes];
        NSMutableDictionary *attributeDictionary;
        attributeDictionary = [NSMutableDictionary dictionary];
        for (CXMLNode *attribute in attributes) {
            [attributeDictionary setObject:[attribute stringValue]
                                    forKey:[attribute name]];
        }

        [dictionaries addObject:attributeDictionary];
    }

    [theXMLDocument release];
    return dictionaries;
} 

Я предполагаю, что здесь может возникнуть несколько проблем:

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

С NSZombieEnabled я вижу следующее:

*** -[CFString respondsToSelector:]:
     message sent to deallocated instance 0x1ae9a0

Кроме того, последний вызов, когда обратная трассировка показывает, что происходит сбой, содержит следующий код в отдельном модуле от двух других методов:

User *u = self.user;
NSString *uri = [NSString stringWithFormat:@"%@/user/%@/%@",
                         [self groupName], u.userId, kLocationsUri];

Между всем автоматическим выпуском / копированием / сохранением, происходящим между клиентским кодом и createArrayWithDictionaries:withXPath, я немного сбит с толку относительно реальной проблемы здесь. Еще раз спасибо за помощь в понимании.

Ответы [ 2 ]

1 голос
/ 27 февраля 2009

ОК, вам не нужно сохранять возвращаемое значение из createArrayWithDictionaries:, поскольку вы его не храните. Возвращаемое значение автоматически высвобождается. Я настоятельно рекомендую прочитать о том, как работает автоматический выпуск. Вы сохраняете только то, что собираетесь хранить в своем объекте.

Также user - это NSArray. Если вы вызовете [user valueForKey:@"name"], вы получите еще одно NSArray значений, представляющих значения клавиши name для каждого из объектов в users. Кроме того, как определяется свойство name вашего объекта? Если вы объявили его как copy или retain (я считаю, что retain является значением по умолчанию, если вы не укажете его самостоятельно), вам не нужно копировать или сохранять значение. Действительно, метод доступа должен всегда отвечать за управление памятью, а не вызывающий. Если вы написали свой собственный метод доступа (т. Е. Вы не использовали ключевое слово @synthesize), вам необходимо убедиться, что вы управляете памятью там.

Полагаю, вы хотели написать что-то вроде этого:

    NSArray *user = [xmlParser createArrayWithDictionaries:dataAsXML withXPath:kUserXPath];
    if ([user count] > 0)
        self.name = [[user objectAtIndex:0] objectForKey:@"name"];

    [xmlParser release];

Я думаю, что ваши проблемы связаны с неправильным пониманием того, как управление памятью работает в Objective-C.

Надеюсь, это поможет.

0 голосов
/ 27 февраля 2009

Произошла автоматическая разблокировка массива словарей, поэтому мое приложение зависло.

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

Если вызывающая сторона собирается сохранить его в свойстве, он должен использовать синтаксис self.dictionaries = […], а не dictionaries = […]. Первый - это доступ к свойству, который вызывает метод установки; последний является прямым присваиванием переменной экземпляра.

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

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

Я бы рекомендовал сделать этот рекурсивный метод экземпляром метода элемента. Что-то вроде:

- (NSDictionary *) dictionaryRepresentation {
    NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
    for (CXMLNode *attribute in attributes) {
        [attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
    }

    NSArray *childElements = [self childElements];

    return [NSDictionary dictionaryWithObjectsAndKeys:
        attributeDictionary, @"attributes",
        [childElements valueForKey:@"dictionaryRepresentation"], @"childElements",
        nil];
}

Затем вы заменяете цикл в createArrayWithDictionaries:withXPath: аналогичным сообщением valueForKey:. Я оставлю вас, чтобы заполнить его.

valueForKey: является основным методом кодирования значения ключа. В обоих местах мы используем удобную реализацию NSArray .

(Если использование valueForKey: все еще не имеет смысла, вам следует прочитать Руководство по программированию KVC *1029*. KVC жизненно важен в современном какао, поэтому вам нужно прочитать это раньше или позже.)

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