Singleton NSMutableArray由NSArrayController在多个NIB中访问 [英] Singleton NSMutableArray accessed by NSArrayController in multiple NIB's

查看:215
本文介绍了Singleton NSMutableArray由NSArrayController在多个NIB中访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

预警 - 代码示例有点长...



我有一个单例NSMutableArray,可以从我的应用程序中的任何地方访问。我想能够从多个NIB文件引用 NSMutableArray ,但是通过 NSArrayController 对象绑定到UI元素。初始创建不是问题。当NIB被加载并且一切正常时,我可以引用单例 NSMutableArray



但是,通过添加或删除对象来更改 NSMutableArray 不会启动KVO来更新 NSArrayController 实例。我意识到改变控制器的背后被认为是可可土地的一部分,但我没有看到任何其他方式通过程序更新 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 ----



在此简化示例中,数组中对象的头和模型:



file:

  @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 = @新对象名称;
myId = 0;

return self;
}

@end

现在想象两个NIB文件适当的 NSArrayController 实例。我们将它们称为 myArrayControllerInNibOne myArrayControllerInNib2 。 NIB控制器的 init 中的每个数组控制器设置数组的内容:

  //在NIB中一个init 
[myArrayControllerInNibOne setContent:[[MyGlobals sharedMyGlobals] .globalArray];

//在NIB中两个init
[myArrayControllerInNibTwo setContent:[[MyGlobals sharedMyGlobals] .globalArray];

当每个NIB初始化 NSArrayController 到共享数组,我可以看到在UI中的数组内容,如你所期望的。我有一个单独的后台线程,当内容基于外部事件更改时更新全局数组。当需要在这个后台线程中添加对象时,我只需将它们添加到数组中,如下所示:

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

这是事情分开的地方。我不能在全局数组上调用 willChangeValueForKey didChangeValueForKey ,因为共享实例没有键值(我应该在单例类中添加这个)



我可以触发 NSNotification NIB控制器,并做一个 [myArrayControllerInNibOne rearrangeObjects] ;或者将内容设置为 nil ,然后将内容重新分配给数组 - 但这两者看起来都像黑客和。此外,将 NSArrayController 设置为 nil ,然后回到全局数组会导致UI内的可视闪烁作为内容已清除并重新填充。



我知道我可以直接添加到 NSArrayController 但我看不到a)如何其他 NSArrayController 实例将被更新和b)我不想绑定我的后台线程类显式地一个NIB实例应该我必须)。



我认为正确的方法是关闭KVO通知以某种方式围绕 addObject 在后台线程中,或者向存储在全局数组中的对象添加一些内容。但我很失望。



注意,我不使用Core Data。



解决方案

预警 - 回答有点长...



使用为您的域建模的对象。你不需要单例或全局变量,你需要一个常规类的常规实例。你的全局数组中存储什么对象?创建一个代表模型部分的类。



如果你使用NSMutableArray作为存储,它应该是你的类的内部,而不是外部对象可见。例如,如果你正在建模动物园,不要做



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



do do

  [doc addAnimal:tomTheZebra ]; 

不要尝试观察可变数组 - 要观察对象的to-many属性。例如。而不是

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

您需要

  [doc addObserver:_controller forKeyPath :@animalsoptions:0 context:nil]; 

其中doc符合许多属性'anaimals'的kvo。



为了使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 /绑定通知也在背景上传递,这意味着你将尝试更新你的GUI在背景上,这绝对不能做。使数组静态不以任何方式帮助我恐怕 - 你必须想出一个战略为这..最简单的方法是 performSelectorOnMainThread 但是除此之外是另一个问题完全。线程很难。



关于静态数组 - 只是停止使用静态,你不需要它。不是因为你有2个笔尖,2个窗口或任何东西。你有一个实例代表你的模型,并传递一个指针给你的viewControllers,windowController,不管。没有单例/静态变量有助于测试,当然你应该这样做。


Early warning - code sample a little long...

I have a singleton NSMutableArray that can be accessed from anywhere within my application. I want to be able to reference the NSMutableArray from multiple NIB files but bind to UI elements via NSArrayController objects. Initial creation is not a problem. I can reference the singleton NSMutableArray when the NIB gets loaded and everything appears fine.

However, changing the NSMutableArray by adding or removing objects does not kick off KVO to update the NSArrayController instances. I realize that "changing behind the controller's back" is considered a no-go part of Cocoa-land, but I don't see any other way of programmatically updating the NSMutableArray and letting every NSArrayController be notified (except it doesn't work of course...).

I have simplified classes below to explain.

Simplified singleton class header:

@interface MyGlobals : NSObject {
    NSMutableArray * globalArray;
}

@property (nonatomic, retain) NSMutableArray * globalArray;

Simplified singleton method:

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 ----

In this simplified example the header and model for objects in the array:

Header file:

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

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

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

@end

Method file:

@implementation MyModel

@synthesize myId;
@synthesize myName;

-(id)init {

[super init];

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

return self;
}

@end

Now imagine two NIB files with appropriate NSArrayController instances. We'll call them myArrayControllerInNibOne and myArrayControllerInNib2. Each array controller in the init of the NIB controller sets the content of the array:

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

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

When each NIB initializes the NSArrayController binds correctly to the shared array and I can see the array content in the UI as you would expect. I have a separate background thread that updates the global array when content changes based on an external event. When objects need to be added in this background thread, I simply add them to the array as follows:

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

This is where things fall apart. I can't call a willChangeValueForKey and didChangeValueForKey on the global array because the shared instance doesn't have a key value (should I be adding this in the singleton class?)

I could fire off an NSNotification and catch that in the NIB controller and either do a [myArrayControllerInNibOne rearrangeObjects]; or set the content to nil and reassign the content to the array - but both of these seems like hacks and. moreover, setting the NSArrayController to nil and then back to the global array causes a visual flash within the UI as the content is cleared and re-populated.

I know I could add directly to the NSArrayController and the array gets updated, but I don't see a) how the other NSArrayController instances would be updated and b) I don't want to tie my background thread class explicitly to a NIB instance (nor should I have to).

I think the correct approach is to either fire off the KVO notification somehow around the addObject in the background thread, or add something to the object that is being stored in the global array. But I'm at a loss.

As a point of note I am NOT using Core Data.

Any help or assistance would be very much appreciated.

解决方案

Early warning - answer a little long…

Use objects that model your domain. You have no need for singletons or globals, you need a regular instance of a regular class. What Objects are your storing in your global array? Create a class that represents that part of your model.

If you use an NSMutableArray as storage it should be internal to your class and not visible to outside objects. eg if you are modelling a zoo, don't do

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

do do

[doc addAnimal:tomTheZebra];

Dont try to observe a mutable array - you want to observe a to-many property of your object. eg. instead of

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

you want

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

where doc is kvo compliant for the to-many property 'anaimals'.

To make doc kvo compliant you would need to implement these methods (Note - you don't need all these. Some are optional but better for performance)

- (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;

Ok, that looks pretty scary but it's not that bad, like i said you don't need them all. See here. These methods dont need to be part of the interface to your model, you could just add:-

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

and write them in terms of the kvc accessors. The key point is it's not the array that sends notifications when it is changed, the array is just the storage behind the scenes, it is your model class that send the notifications that objects have been added or removed.

You may need to restructure your app. You may need to forget about NSArrayController altogether.

Aaaaaannnnnyyywaaayyy… all this gets you nothing if you do this

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

or this [doc addAnimal:tomTheZebra];

from a background thread. You can't do this. NSMutableArray isn't thread safe. If it seems to work then the best that will happen is that the kvo/binding notification is delivered on the background as well, meaning that you will try to update your GUI on the background, which you absolutely cannot do. Making the array static does not help in any way i'm afraid - you must come up with a strategy for this.. the simplest way is performSelectorOnMainThread but beyond that is another question entirely. Threading is hard.

And about that static array - just stop using static, you don't need it. Not because you have 2 nibs, 2 windows or anything. You have an instance that represents your model and pass a pointer to that to you viewControllers, windowControllers, whatever. Not having singletons/static variables helps enormously with testing, which of course you should be doing.

这篇关于Singleton NSMutableArray由NSArrayController在多个NIB中访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆