设计问题:
我有一个包含NSMutableArray *的ViewController A. ViewController A负责向用户显示Map,当用户与此地图交互时,视图控制器A用坐标对象填充NSMutableArray *。
NSMutableArray *中包含的信息稍后应显示在UITable中,该UITable包含在与ViewController B相关联的另一个视图中。
ViewController B访问由A填充的NSMutableArray的最正确方法是什么? (A保留对NSMutableArray *的引用。)
应该有几种方法可以做到这一点,但为了让自己保持纯粹的MVC,我真的很想知道你的意见。
答案 0 :(得分:2)
最正确的方法是什么? ViewController B访问 由A?
填充的NSMutableArray
我会做一些简单的事情,如果它引起问题,只会回到决定。简单的事情可能是在控制器A的公共接口中公开数组并发送有关数组更新的通知,以便B可以观察:
@interface A
@property(readonly) NSArray *foos;
@implementation
- (void) updateFoos {
NSString *const key = @"foos";
[self willChangeValueForKey:key];
[foos doSomething];
[self didChangeValueForKey:key];
}
@interface B
@implementation
- (void) afterSettingA {
[a addObserver:self forKeyPath:@"foos" options:0 context:NULL];
}
- (void) observeValueForKeyPath: (NSString*) keyPath ofObject: (id) object
change: (NSDictionary*) change context: (void*) context
{
NSAssert([keyPath isEqualToString:@"foos"], @"Somethind fishy going on.");
// update what depends on foos
}
另一个简单的解决方案是将数组转换为一个完整的模型类,您可以将它们连接到A和B.(连接必须在控制器外部完成,以避免过度耦合。您可以使用Interface Builder,一个'工厂'类,可以将对象连接在一起或任何其他适合的东西。)
@interface Foo
@property(readonly) NSArray* items;
@implementation
- (void) updateItems {
// send KVO notifications just as above
}
@interface A
@property(retain) Foo *fooModel;
@interface B
@property(retain) Foo *fooModel;
@interface Factory
@implementation
- (void) wireObjects {
A *a = [[A alloc] init];
B *b = [[B alloc] init];
Foo *fooModel = [[Foo alloc] init];
[a setFooModel:fooModel];
[b setFooModel:fooModel];
// Of course the A and B would be member variables of this
// class or you would return a root of the constructed object
// graph from this method, otherwise it would not make sense.
}
在第一个解决方案中,B控制器必须有一个指向A的指针,以便它可以订阅KVO通知。控制器之间的这种连接最好保存在别的地方而不是代码中,即。 B不应该创建A的实例。(这样你就会引入A和B之间的紧密耦合。不太可测试等。)如果你已经在Interface Builder中实例化控制器,那么这是给B指针的最佳位置答:(只需为B中的A创建IBOutlet
。)
具有单独模型类的第二个解决方案是“更干净”的MVC,并且不需要控制器彼此了解 - 它们都依赖于模型类。您也可以实例化模型并将其链接到Interface Builder中的控制器。
顺便说一句:如果B想要观察A的某些属性的变化,则必须在设置了A的链接后进行订阅。一个简单但非常错误的方法是在B的viewDidLoad
方法中订阅它很方便,但是如果之后A的链接发生了变化,则通知不会相应地改变。更难但正确的订阅方式是在A的设置器中 - 当某人设置新的A时,您取消对旧A的通知订阅并订阅新的A.
答案 1 :(得分:0)
在您的场景中,应该有人负责(MASTER)创建/更新填充坐标的NSMutableArray。在您的情况下,这个主人似乎是控制器A.
然后,您希望另一个对象(控制器B)使用相同的信息(模型)。这个对象是来自控制器A的一种从属:他需要知道何时更改模型以相应地显示它(作为列表)。
我会这样做:控制器A可以设置一个委托,这个委托应该实现一个协议,其中模型的任何更新(由A执行)被通知给委托(B)。协议的粗略定义可以是-(void)modelHasChanged:(NSArray*)theNewModel
控制器B只能对模型进行只读访问:即使控制器A操纵NSMutableArray(更改内容),控制器B也只将其视为不可变的NSArray:这确保只有控制器A才是该模型的真正主控并且B不能改变其内容。
您还可以选择另一种方法:拆分管理模型的对象(NSMutableArray)和表示它的两种方式:作为地图(控制器A),或作为列表(controllerB) 然后你将有3个抽象:
1个操纵NSMutableArray的主人
1个地图的控制器A,您可以在其上设置要显示为NSArray的模型(只读访问前一个主控操作的同一个实例)。
1个控制器B,用于列表,您可以将模型设置为NSArray(只读访问前一个主控操作的同一个实例)。
答案 2 :(得分:0)
只是为答案添加一些内容:
您使用iPhone标记了此问题,因此我认为这是您正在开发的平台。
因此,如果您想使用正确的标准iPhone MVC设计,这听起来像是带有两个UIViewControllers的UINavigationController的完美用例。
让你的AppDelegate实例化一个UINavigationController,并将ViewControllerA设置为root。
为ViewControllerB创建一个新的init / dealloc,其中有一个本地实例var来保存坐标。
- (id)initWithCoords:(NSArray *)coords {
if (self = [super initWithNibName:@"name of your nib" bundle:[NSBundle mainBundle]]) {
coordinates = [coords retain];
}
return self;
}
- (void)dealloc {
[coordinates release];
[super dealloc];
}
现在,只要您想使用选定的coords从ViewControllerA加载ViewControllerB,您只需调用如下方法:
- (void)actionForCoords {
ViewControllerB *bCon = [[ViewControllerB alloc] initWithCoords:[NSArray arrayWithArray:selectedCoords]];
[self.navigationController pushViewController:bCon animated:YES];
[bCon release];
}
这样导航控制器可以在其堆栈上处理大部分工作,提供干净/简单的对象管理和动画。如果你从ViewControllerB回弹到ViewControllerA(例如使用navCon的默认“后退”实现),那么ViewControllerA仍将保留在堆栈上供你使用和修改,同时释放ViewControllerB以释放资源(但准备再次加载新的coords通过“actionForCoords”)。