Singleton NSMutableArray, к которому обращается NSArrayController в нескольких NIB - PullRequest
1 голос
/ 25 июня 2010

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

У меня есть одноэлементный 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 в глобальном массиве, потому что общий экземпляр не имеет значения ключа (я должен добавить это в класс синглтона?)

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

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

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

В качестве примечания я НЕ использую базовые данные.

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

1 Ответ

0 голосов
/ 25 июня 2010

Раннее предупреждение - ответь немного долго ...

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

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

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

сделать

[doc addAnimal:tomTheZebra];

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

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

хочешь

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

где doc соответствует kvo для свойства anamals для многих.

Чтобы сделать 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.

Aaaaaannnnnyyywaaayyy ... все это ничего не даст вам, если вы сделаете это

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

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

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

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

...