Правильный способ создания новых объектов, которые являются копиями объектов NSDictionary и NSArray, определенных в делегате приложения

Мне интересно, как правильно сделать копию объекта, определенного в делегате приложения или одноэлементном объекте. Короче говоря, я делаю приложение, которое требует от пользователя входа в систему. Это представление входа в систему представляет собой просто контроллер модального представления поверх «реального» приложения, которое состоит из контроллера панели вкладок и некоторых контроллеров табличного представления. После успешного входа в систему отправляется запрос данных на удаленный сервер, а контроллер модального представления закрывается, открывая контроллер панели вкладок и табличные представления, содержащие XML-данные. Для анализа входящих данных я создал одноэлементный объект с именем DataParser, который имеет интерфейс

...

@interface DataParser : NSObject {
    // Data objects that hold the data obtained from XML files
    NSMutableDictionary *personnel;       
    NSMutableDictionary *schedule;          
    NSMutableDictionary *today;
}

@property (nonatomic, retain) NSMutableDictionary *personnel;
@property (nonatomic, retain) NSMutableDictionary *schedule;
@property (nonatomic, retain) NSMutableDictionary *today;

...

Теперь в этих словарях я храню (изменяемые) словари и массивы, содержащие объекты NSString с проанализированными XML-данными. Поскольку я не хочу изменять эти исходные объекты, содержащие проанализированные данные (то есть я хочу изменять их только на этапе входа в систему, но не в любом из контроллеров табличного представления), я создаю новый объект словаря, который содержит копия содержимого одного из приведенных выше словарей в каждом контроллере tableview. Так, например, в loadView контроллера представления с именем ScheduleViewController у меня есть

...

@interface ScheduleViewController : UITableViewController {

    NSDictionary *copyOfSchedule;
}

@property (nonatomic, retain) NSDictionary *copyOfSchedule;

...

@end


@implementation ScheduleViewController

@synthesize copyOfSchedule;

- (void)loadView {
    [super loadView];

    DataParser *sharedSingleton = [DataParser sharedInstance];
    self.copyOfSchedule = [NSDictionary dictionaryWithDictionary:sharedSingleton.schedule];
}

...

Теперь это работает нормально. Однако единственная трудность возникает, когда пользователь «выходит из системы», что влечет за собой возвращение контроллера модального представления входа в стек. Когда пользователь снова нажимает кнопку входа в систему, на сервер отправляется новый запрос данных XML, и словари в одноэлементном объекте обновляются (новыми) данными (я проверяю, содержат ли они какие-либо данные, если да, я вызываю removeAllObjects перед снова заполняя их вновь проанализированными данными). На этом этапе словари во всех контроллерах представления также должны быть обновлены, однако я не совсем уверен, как это сделать правильно. Я заметил, что в этом случае loadView не всегда вызывается снова, поэтому для этого я добавил тот же код, что и выше, в loadView для каждого метода viewWillAppear. Однако после перехода туда и обратно между различными представлениями или перехода назад и вперед между дочерними представлениями табличного представления пару раз я получаю сообщение об ошибке EXC_BAD_ACCESS. Я подозреваю, что это связано с неправильным сохранением копий исходных словарей, но, похоже, я не могу найти решение этой проблемы. Вместо использования DictionaryWithDictionary, который, как я подозреваю, в любом случае является неправильным, я также попробовал другой подход, когда вместо использования объектов типа NSDictionary в ScheduleViewController я использую NSMutableDictionary. Так:

...

@interface ScheduleViewController : UITableViewController {
    NSMutableDictionary *copyOfSchedule;
}

@property (nonatomic, retain) NSMutableDictionary *copyOfSchedule;

...

@end


@implementation ScheduleViewController

@synthesize copyOfSchedule;

- (void)loadView {
    [super loadView];

    DataParser *sharedSingleton = [DataParser sharedInstance];
    self.copyOfSchedule = [[NSMutableDictionary alloc] initWithDictionary:sharedSingleton.schedule];
}

- (void)viewWillAppear {
    DataParser *sharedSingleton = [DataParser sharedInstance];
    [self.copyOfSchedule removeAllObjects];
    [self.copyOfSchedule addEntriesFromDictionary:sharedSingleton.schedule];

    [self.tableView reloadData];
}

...

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


person MJD    schedule 25.08.2011    source источник
comment
Этот вопрос ясен как грязь. Кажется, вы спрашиваете о разнице между глубокими и поверхностными копиями словарей, но я не могу понять, какой из них вам нужен. Предложите вам удалить все посторонние вещи о контроллерах входа и просмотра; сведите свой вопрос к тому, что вы хотите сделать со словарем. Вы просто хотите скопировать сам словарь и сделать так, чтобы новый словарь указывал на те же объекты, что и оригинал, или вам нужна глубокая копия, в которой также копируются объекты в словаре?   -  person Caleb    schedule 25.08.2011
comment
Простите за это. Думал, что дам немного больше справочной информации о том, чего я хочу достичь, но да, вы правы: мне нужна глубокая копия словаря со всеми объектами (словарями и массивами) в нем.   -  person MJD    schedule 25.08.2011


Ответы (2)


Глубокие копии часто делаются рекурсивно. Один из способов сделать это — добавить методы -deepCopy в NSDictionary и NSArray. Версия словаря может выглядеть так:

- (NSDictionary*)deepCopy
{
    NSMutableDictionary *temp = [self mutableCopy];
    for (id key in temp) {
        id item = [temp objectForKey:key];
        if ([item respondsToSelector:@sel(deepCopy)] {
            // handle deep-copyable items, i.e. dictionaries and arrays
            [temp setObject:[item deepCopy] forKey:key]
        }
        else if ([item respondsToSelector:@(copy)]) {
            // most data objects implement NSCopyable, so will be handled here
            [temp setObject:[item copy] forKey:key];
        }
        else {
            // handle un-copyable items here, maybe throw an exception
        }
    }
    NSDictionary *newDict = [[temp copy] autorelease];
    [temp release]
    return newDict;
}

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

Обратите внимание, что представления нельзя копировать.

person Caleb    schedule 25.08.2011
comment
Еще один класс (и подклассы, конечно), который нельзя копировать, — это NSManagedObject, т. е. объекты из Core Data. - person Monolo; 25.08.2011
comment
@Калеб. Спасибо! Я только что провел несколько простых тестов с предложенным вами решением, и до сих пор я больше не получаю ошибок, которые у меня были раньше. Теперь мне просто нужно посмотреть, сработает ли это, когда я изменю его в большем масштабе, но у меня есть большие надежды. Спасибо за вашу помощь. - person MJD; 25.08.2011

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

Иметь такое свойство, как

@property (...) NSArray* myArray;

Когда вы вычисляете содержимое myArray, используйте изменяемый массив для его создания, например

NSMutableArray* myMutableArray = [NSMutableArray array];

Когда вы закончите создание массива, просто используйте

self.myArray = [NSArray arrayWithArry:myMutableArray]; 
person gnasher729    schedule 13.03.2014