2014-08-30 70 views
0

我試圖按照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 

當我RU這我得到這個「錯誤」:

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> 

我試圖按照錯誤的指示,但找不到在哪裏設置斷點。請幫我弄清楚這一點!

回答

1

該錯誤是相當具有描述性的。您將self添加爲BarModel對象的觀察者。在某個時候,對象被釋放。但是,您永遠不要通過致電removeObserver:forKeyPath:context:作爲觀察員刪除self。你需要這樣做。

首先,在setBarModel中,確保刪除self作爲先前值_barModel的觀察者。

接下來,您可能需要添加一個執行相同操作的dealloc方法。

+1

Tom,如果他在兩個地方刪除它,它可能會被刪除兩次,並導致一個例外 - 從訪問者內部進行KVO觀察的原因之一是困難的。 – quellish 2014-08-30 23:25:42

+0

@quellish,你錯了。在二傳手的去除將被替換的參考。 '-dealloc'中的刪除將用於在釋放時的當前引用。 – 2014-08-31 03:54:03

0

代碼存在多個問題。除了Tom Harrington關於未能刪除觀察記錄的具體錯誤之外所說的內容:

您爲名爲「data」的(不存在的)屬性實施了索引集合訪問器。也就是說,他們的名字中應該包含「Data」。

索引的收集屬性是addedLetters。因此,索引集合變異存取應該是:

- (void)insertObject:(id)object inAddedLettersAtIndex:(NSUInteger)index; 
- (void)removeObjectFromAddedLettersAtIndex:(NSUInteger)index; 

你並不真的需要非突變存取,因爲你有一個正常的吸氣劑(即-addedLetters)的陣列型公共財產。

順便說一句,該屬性的類型是NSMutableArray它不應該是。該屬性應該是NSArray類型,由NSMutableArray類型的實例變量支持。也就是說,類型(與屬性相反)的可變性不應通過公共接口公開。當你這樣做時,你必須手動聲明實例變量(因爲它應該與屬性的類型不同,並且自動合成會導致錯誤),請使屬性copy而不是strong,然後自己實現設置器來執行傳入的不可改變的陣列的可變副本:

- (void) setAddedLetters:(NSArray*)addedLetters 
{ 
    if (addedLetters != _addedLetters) 
     _addedLetters = [addedLetters mutableCopy]; 
} 

一旦實現了索引集合變異存取與正確的名稱,你只能使用這些方法變異集合(初始化後)。特別是,您的-addLetter:onSide:方法不能直接在_addedLetters實例變量上運行。 這個是使該屬性的類KVO兼容的部分。僅存在索引集合的變異訪問器不會有幫助。它們必須用於所有實際的突變。

您的+automaticallyNotifiesObserversForKey:的實施是多餘的。自動通知是默認的。

BarView類是在其_barModel對象上觀察關鍵路徑「arrayLetter」的鍵值,但這不是BarModel上屬性的名稱。我想你打算使用關鍵路徑「addedLetters」。

最後,爲了正確遵守MVC設計,您的視圖不應該引用您的模型。它應該有一個對控制器的參考。控制器可以將模型反映到視圖中(或者理論上,將不同內部設計的模型與視圖所期望的相適應)。或者,在更傳統的非KVO方法中,控制器實際上會在視圖發生變化時告訴視圖,併爲其提供應顯示的更新數據。

所以,你的控制器可以公開自己的addedLetters屬性:

@property (readonly, copy, nonatomic) NSArray* addedLetters; 

它可以被實現爲派生屬性,通過轉發到_barModel對象:

+ (NSSet*)keyPathsForValuesAffectingAddedLetters 
{ 
    return [NSSet setWithObject:@"barModel.addedLetters"]; 
} 
- (NSArray*)addedLetters 
{ 
    return self.barModel.addedLetters; 
} 

然後,該視圖引用控制器而不是模型,並且它將鍵值觀察控制器上的「addedLetters」鍵路徑。

+0

他實現了關鍵「數據」的KVC可變有序集合訪問器,這很好。如果一個對象調用'[mutableArrayValueForKey:@「data」]',它將得到一個可變的代理對象,它將調用他的KVC訪問器,這實際上會修改'addedLetters'可變數組。關於他的KVC訪問器沒有任何不符合要求。 – quellish 2014-08-31 05:00:34

+0

@quellish:好的,但是你認爲他是故意爲他的課堂添加一個「data」屬性,還是從另一個具有「data」屬性的實現中複製並粘貼了一些代碼?事實上,他們從他所聯繫的答案中複製而來。 – 2014-08-31 05:11:56

+0

沒關係。他的問題是「我如何設置斷點來解決這個問題」而不是「猜測我的意圖並重新組織我的班級」。他顯然實施了訪問者訪問不同的財產 - 而不是直接複製和粘貼 - 這取決於他。他可能有充足的理由這樣做。 – quellish 2014-08-31 05:16:41