Singleton NSMutableArray, доступный NSArrayController в нескольких NIB

Раннее предупреждение - пример кода немного длинный...

У меня есть одноэлементный NSMutableArray, к которому можно получить доступ из любого места в моем приложении. Я хочу иметь возможность ссылаться на NSMutableArray из нескольких файлов NIB, но привязываться к элементам пользовательского интерфейса через объекты NSArrayController. Первоначальное создание не проблема. Я могу сослаться на синглтон NSMutableArray, когда загружается NIB, и все выглядит нормально.

Однако изменение NSMutableArray путем добавления или удаления объектов не запускает KVO для обновления экземпляров NSArrayController. Я понимаю, что «изменение за спиной контроллера» считается запретной частью Cocoa-land, но я не вижу другого способа программно обновить NSMutableArray и позволить каждому NSArrayController быть уведомленным (за исключением того, что это не работает курс...).

Я упростил классы ниже, чтобы объяснить.

Упрощенный заголовок одноэлементного класса:

@interface MyGlobals : NSObject {
    NSMutableArray * globalArray;
}

@property (nonatomic, retain) NSMutableArray * globalArray;

Упрощенный одноэлементный метод:

static MyGlobals *sharedMyGlobals = nil;

@implementation MyGlobals

@synthesize globalArray;

+(MyGlobals*)sharedDataManager {
    @synchronized(self) {
    if (sharedMyGlobals == nil)
    [[[self alloc] init] autorelease];
}

return sharedMyGlobals;
}

-(id) init {
if(self = [super init]) {
        self.globals = [[NSMutableArray alloc] init];
    }
    return self
}

// ---- allocWithZone, copyWithZone etc clipped from example ----

В этом упрощенном примере заголовок и модель для объектов в массиве:

Заголовочный файл:

@interface MyModel : NSObject {
NSInteger myId;
NSString * myName;
}

@property (readwrite) NSInteger myId;
@property (readwrite, copy) NSString * myName;

-(id)initWithObjectId:(NSInteger)newId objectName:(NSString *)newName;

@end

Файл метода:

@implementation MyModel

@synthesize myId;
@synthesize myName;

-(id)init {

[super init];

myName  = @"New Object Name";
myId    = 0;

return self;
}

@end

Теперь представьте два файла NIB с соответствующими экземплярами NSArrayController. Мы назовем их myArrayControllerInNibOne и myArrayControllerInNib2. Каждый контроллер массива в init контроллера NIB устанавливает содержимое массива:

// In NIB one init
[myArrayControllerInNibOne setContent: [[MyGlobals sharedMyGlobals].globalArray];

// In NIB two init
[myArrayControllerInNibTwo setContent: [[MyGlobals sharedMyGlobals].globalArray];

Когда каждый NIB инициализируется, NSArrayController правильно привязывается к общему массиву, и я могу видеть содержимое массива в пользовательском интерфейсе, как и следовало ожидать. У меня есть отдельный фоновый поток, который обновляет глобальный массив при изменении содержимого на основе внешнего события. Когда объекты нужно добавить в этот фоновый поток, я просто добавляю их в массив следующим образом:

[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];

Вот где вещи разваливаются. Я не могу вызвать willChangeValueForKey и didChangeValueForKey в глобальном массиве, потому что общий экземпляр не имеет значения ключа (должен ли я добавлять это в класс singleton?)

Я мог запустить NSNotification и поймать это в контроллере NIB и либо выполнить [myArrayControllerInNibOne rearrangeObjects]; или установите для содержимого значение nil и переназначьте содержимое массиву, но оба они выглядят как хаки и. кроме того, установка NSArrayController в nil, а затем обратно в глобальный массив вызывает визуальную вспышку в пользовательском интерфейсе, поскольку содержимое очищается и повторно заполняется.

Я знаю, что могу добавить непосредственно к NSArrayController, и массив будет обновлен, но я не вижу а) как будут обновляться другие экземпляры NSArrayController и б) я не хочу явно привязывать класс фонового потока к экземпляру NIB (и я не должен).

Я думаю, что правильный подход состоит в том, чтобы либо каким-то образом отключить уведомление KVO вокруг addObject в фоновом потоке, либо добавить что-то к объекту, который хранится в глобальном массиве. Но я в растерянности.

Следует отметить, что я НЕ использую Core Data.

Любая помощь или помощь будут очень признательны.


person Hooligancat    schedule 24.06.2010    source источник


Ответы (1)


Раннее предупреждение - ответ немного длинный…

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

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

[[[MyGlobals sharedMyGlobals].globalArray] addObject:tomTheZebra];

do do

[doc addAnimal:tomTheZebra];

Не пытайтесь наблюдать за изменяемым массивом - вы хотите наблюдать за свойством «многие» вашего объекта. например. вместо

[[[MyGlobals sharedMyGlobals].globalArray] addObserver:_controller]

вы хотите

[doc addObserver:_controller forKeyPath:@"animals" options:0 context:nil];

где doc соответствует kvo для свойства to-many 'animals'.

Чтобы сделать doc kvo совместимым, вам нужно будет реализовать эти методы (Примечание: вам все это не нужно. Некоторые из них необязательны, но лучше для производительности)

- (NSArray *)animals;
- (NSUInteger)countOfAnimals;
- (id)objectInAnimalsAtIndex:(NSUInteger)i; 
- (id)AnimalsAtIndexes:(NSIndexSet *)ix;
- (void)insertObject:(id)val inAnimalsAtIndex:(NSUInteger)i;
- (void)insertAnimals:atIndexes:(NSIndexSet *)ix;
- (void)removeObjectFromAnimalsAtIndex:(NSUInteger)i;
- (void)removeAnimalsAtIndexes:(NSIndexSet *)ix;
- (void)replaceObjectInAnimalsAtIndex:(NSUInteger)i withObject:(id)val;
- (void)replaceAnimalsAtIndexes:(NSIndexSet *)ix withAnimals:(NSArray *)vals;

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

- (void)addAnimal:(id)val;
- (void)removeAnimal:(id)val;

и запишите их в терминах аксессоров kvc. Ключевым моментом является то, что это не массив, который отправляет уведомления при его изменении, массив - это просто хранилище за кулисами, это класс вашей модели, который отправляет уведомления о том, что объекты были добавлены или удалены.

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

Аааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааакаявая… ничего она тебе не даст, если ты это сделаешь

[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];

или это [doc addAnimal:tomTheZebra];

из фоновой ветки. Вы не можете этого сделать. NSMutableArray не является потокобезопасным. Если кажется, что это сработает, то лучшее, что произойдет, это то, что уведомление kvo/binding также будет доставлено в фоновом режиме, а это означает, что вы попытаетесь обновить свой графический интерфейс в фоновом режиме, чего вы абсолютно не можете сделать. Я боюсь, что сделать массив статическим никак не поможет - вы должны придумать стратегию для этого. Самый простой способ - performSelectorOnMainThread, но помимо этого - совсем другой вопрос. Резьба тяжелая.

А насчет статического массива - просто перестаньте использовать статический, он вам не нужен. Не потому, что у вас есть 2 пера, 2 окна или что-то в этом роде. У вас есть экземпляр, который представляет вашу модель и передает указатель на него вам viewControllers, windowControllers, что угодно. Отсутствие синглетонов/статических переменных очень помогает при тестировании, что, конечно же, вы должны делать.

person hooleyhoop    schedule 25.06.2010
comment
mustISignUp - Спасибо за ответ. Отвечая на ваш вопрос, скажем, у меня есть модель человека с именем, возрастом, датой рождения и т. д. Я создал класс, который мы будем называть сотрудниками. Я переместил NSMutableArray из синглтона в служащих и сделал его статическим NSMutableArray. Добавление объектов в массив выполняется вызовом сотрудников, например. [employeeInstance addNewEmployee:@George withAge: 22 пол:@мужской]. У сотрудников он вызывает [personInstance initWithName:newName age:newAge gender:newGender]. Куда мне добавить addObserver KVO? - person Hooligancat; 25.06.2010
comment
Наверняка должен быть KVC, который запускается моделью человека, когда объект добавляется в массив (я назову его employeeArray), или, по крайней мере, он должен срабатывать в классе сотрудников, где живет employeeArray? Я реализовал методы доступа countOfEmployeesArray, objectInEmployeesArrayAtIndex, insertObject, removeObjectFromEmployeesArrayAtIndex, replaceObjectInEmployeesArrayAtINdex и addEmployeesArrayObject для класса сотрудников, и все же KVC не срабатывает. Мне нужен статический, потому что мне нужно ссылаться на один и тот же массив из нескольких файлов NIB и элементов управления пользовательского интерфейса. - person Hooligancat; 25.06.2010
comment
О, еще одна вещь ... если я обращаюсь к изменяемому массиву как к статическому, не блокируется ли он до тех пор, пока не будут внесены какие-либо изменения, что устраняет проблему с потоками? - person Hooligancat; 25.06.2010
comment
хорошо, я немного расширил. Надеюсь, поможет. Дайте мне знать, как это происходит. - person hooleyhoop; 25.06.2010
comment
mustISignUp - я ценю помощь. Кажется, это сводится к двум вопросам. Позвольте мне решить первую проблему — хранение изменяемого массива информации в памяти без наличия общего массива в глобальном пространстве или статического изменяемого массива в классе. Я упускаю что-то фундаментальное. Как вы можете ссылаться на один и тот же изменяемый массив, если каждый раз, когда вы создаете экземпляр класса, вы не ссылаетесь на что-то на глобальном или статическом уровне? Если бы у меня было два экземпляра класса без статического или общего, у меня было бы два изменяемых массива - каждый не замечал другого... - person Hooligancat; 27.06.2010
comment
По общему признанию, это не имеет большого отношения к вашей проблеме с ArrayController, но я начал это так. Статическая переменная просто не является объектно-ориентированным способом делать что-то. Если у меня есть класс Zoo, и я создаю два экземпляра, bronxZoo и londonZoo, добавление жирафа в londonZoo не должно добавлять объект в bronxZoo, не так ли? Это будет побочным эффектом bit.ly/bPmbpt. Они должны, как вы говорите, «не обращать внимания на других» — так почему вы думаете, что это проблема? Кажется, это потому, что вы хотите ПОЛУЧИТЬ это значение (жираф) из другого места в вашем коде, когда вы должны СКАЗАТЬ другим объектам. - person hooleyhoop; 27.06.2010
comment
mustISignUp - согласен - мы тут не по теме. Уместно отказаться от вопроса о зоопарке по этому вопросу. Я могу создать новый вопрос, который касается статической проблемы :-) Итак... уведомление NSArrayController об обновлениях количества элементов в NSMutableArray. На каком-то уровне изменяется NSmutableArray. Я могу зафиксировать момент времени, когда это происходит, и запустить событие KVC, говорящее: «Эй, мы обновились». Я мог бы использовать KVO для прослушивания события в контроллере с NSArrayController, но установка содержимого на nil, а затем установка содержимого обратно в NSMutableArray выглядит как взлом. - person Hooligancat; 30.06.2010
comment
mustISignUp — Скомпрометировано. Я пошел с вашим методом, но NSMutableArray был экземпляром статического класса. Обернул установщик в блокировку для обеспечения безопасности потоков, а затем передал указатель на каждый из NSArrayController. Кажется, работает разумно хорошо. Спасибо. - person Hooligancat; 31.07.2010