在尝试遵循MVC模式时,我无法实现键值观察器。我有一个控制器类,一个模型类和一个视图类。我从控制器类更新我的模型,我想在我的视图类中放置一个键值观察器,以监视NSMutableArray何时在模型中更改(如通过addObject),然后自动重绘自身。我在这篇帖子中使用了答案来指导我:How to add observer on NSMutableArray?
到目前为止代码:
从我的场景(如果重要的话,使用精灵套件)。字母的设置将从Ctrl类完成,这只是为了测试。
BarCtrl *barCtrl = [[BarCtrl alloc] init];
BarModel *barModel = [[BarModel alloc] init];
BarView *barView = [[BarView alloc] init];
barCtrl.barModel = barModel;
barCtrl.barView = barView;
barView.barModel = barModel;
ScrabbleDeck *sd = [[ScrabbleDeck alloc] init];
if([barModel addLetter:[sd getLetter] onSide:BarModelSideRight])
NSLog(@"Added letter");
BarModel.h
#import <Foundation/Foundation.h>
#import "Letter.h"
typedef NS_ENUM(int, BarModelSide) {
BarModelSideLeft,
BarModelSideRight
};
@interface BarModel : NSObject
@property (nonatomic, strong) NSMutableArray *addedLetters;
- (instancetype)init;
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side;
@end
BarModel.m
#import "BarModel.h"
@interface BarModel ()
@property (nonatomic) int capacity;
@end
@implementation BarModel
- (instancetype)init
{
self = [super init];
if (self) {
self.capacity = letterCapacity;
_addedLetters = [[NSMutableArray alloc] init];
}
return self;
}
// We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"arrayLetter"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side{
if([_addedLetters count] > _capacity){
return FALSE;
}
switch (side) {
case BarModelSideLeft:
[_addedLetters insertObject:letter atIndex:0];
return TRUE;
break;
case BarModelSideRight:
[_addedLetters addObject:letter];
return TRUE;
break;
default:
return FALSE;
break;
}
}
// These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
self.addedLetters[index] = object;
}
- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
[self.addedLetters removeObjectAtIndex:index];
}
- (id)objectInDataAtIndex:(NSUInteger)index
{
return self.addedLetters[index];
}
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
return [self.addedLetters objectsAtIndexes:indexes];
}
- (NSUInteger)countOfData
{
return [self.addedLetters count];
}
@end
BarView.h
#import <SpriteKit/SpriteKit.h>
#import "BarModel.h"
@interface BarView : SKSpriteNode
@property (nonatomic, strong) BarModel *barModel;
@end
BarView.m
#import "BarView.h"
@implementation BarView
static char MyObservationContext;
- (instancetype)init
{
self = [super init];
if (self) {
//_barModel = [[BarModel alloc] init];
}
return self;
}
-(void)setBarModel:(BarModel *)barModel{
if(_barModel != barModel)
_barModel = barModel;
[_barModel addObserver:self
forKeyPath:@"arrayLetter"
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:&MyObservationContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Check if our class, rather than superclass or someone else, added as observer
if (context == &MyObservationContext) {
// Check that the key path is what we want
if ([keyPath isEqualToString:@"arrayLetter"]) {
// Verify we're observing the correct object
if (object == self.barModel) {
[self draw:change];
}
}
}
else {
// Otherwise, call up to superclass implementation
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) draw: (NSDictionary*) change{
NSLog(@"KVO for our container property, change dictionary is %@", change);
}
@end
当我这样做时,我得到了这个“错误”:
2014-08-31 00:23:02.828 Testing[329:60b] Added letter
2014-08-31 00:23:02.830 Testing[329:60b] An instance 0x17803d340 of class BarModel was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x17804eb50> (
<NSKeyValueObservance 0x1780cf180: Observer: 0x178111670, Key path: arrayLetter, Options: <New: YES, Old: YES, Prior: NO> Context: 0x100101428, Property: 0x17804eb80>
我试图按照错误的说明操作但无法找到设置断点的位置。请帮我解决这个问题!
答案 0 :(得分:1)
该错误非常具有描述性。您添加self
作为BarModel
对象的观察者。在某些时候,该对象被取消分配。但是,您永远不会通过调用self
删除removeObserver:forKeyPath:context:
作为观察者。你需要这样做。
首先,在setBarModel
中,确保删除self
作为_barModel
之前值的观察者。
接下来,您可能需要添加一个执行相同操作的dealloc
方法。
答案 1 :(得分:0)
代码存在多个问题。除了Tom Harrington关于未能删除观察的特定错误所说的内容:
您为名为&#34; data&#34;的(不存在的)属性实现了索引集合访问器。也就是说,他们有&#34;数据&#34;在他们的名字中,属性名称应该是。
索引集合属性为addedLetters
。因此,索引集合变异访问器应该是:
- (void)insertObject:(id)object inAddedLettersAtIndex:(NSUInteger)index;
- (void)removeObjectFromAddedLettersAtIndex:(NSUInteger)index;
您并不真正需要非变异访问器,因为您有一个具有普通getter的数组类型公共属性(即-addedLetters
)。
顺便说一下,该属性的类型为NSMutableArray
,它不应该是。该属性应为NSArray
类型,由类型为NSMutableArray
的实例变量提供支持。也就是说,类型(与属性相对)的可变性不应通过公共接口公开。当你这样做时,你必须手动声明实例变量(因为它应该与属性的类型不同,自动合成将错误),使属性copy
而不是strong
,并自己实现setter来执行传入的不可变数组的可变副本:
- (void) setAddedLetters:(NSArray*)addedLetters
{
if (addedLetters != _addedLetters)
_addedLetters = [addedLetters mutableCopy];
}
一旦您使用正确的名称实现了索引集合变异访问器, 必须仅使用那些 方法来改变集合(在初始化之后)。特别是,您的-addLetter:onSide:
方法不能直接对_addedLetters
实例变量进行操作。 此是使该属性符合KVO类的部分。仅仅存在索引集合变异访问器并没有帮助。它们必须用于所有实际的突变。
+automaticallyNotifiesObserversForKey:
的实施是多余的。自动通知是默认通知。
BarView
类是观察关键路径的关键值&#34; arrayLetter&#34;在_barModel
对象上,但这不是BarModel
上的属性名称。我想你的意思是使用关键路径&#34; addedLetters&#34;。
最后,为了正确遵守MVC设计,您的视图不应该引用您的模型。它应该有一个控制器的参考。控制器可以将模型反映到视图中(或者,理论上,将不同内部设计的模型适应视图所期望的)。或者,在更传统的非KVO方法中,控制器实际上会告知视图何时发生了变化,并为其提供应显示的更新数据。
因此,您的控制器可以公开自己的addedLetters
属性:
@property (readonly, copy, nonatomic) NSArray* addedLetters;
它可以作为派生属性实现,转发到_barModel
对象:
+ (NSSet*)keyPathsForValuesAffectingAddedLetters
{
return [NSSet setWithObject:@"barModel.addedLetters"];
}
- (NSArray*)addedLetters
{
return self.barModel.addedLetters;
}
然后,视图将引用控制器而不是模型,它将键值观察&#34; addedLetters&#34;控制器上的关键路径。