2014-08-30 3 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

01 나는 RU이 나는이 "오류"를 얻을 때 23,168,464,437,673,181,239,093,210

는 :

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을 추가합니다. 그 시점에서 객체는 할당 해제됩니다. 하지만 removeObserver:forKeyPath:context:을 호출하여 관찰자로 self을 절대 삭제하지 마십시오. 당신은 그렇게해야합니다.

먼저 setBarModel에서 _barModel의 이전 값을 관찰자로 사용하여 self을 제거하십시오.

다음으로 동일한 작업을 수행하는 dealloc 메서드를 추가해야 할 수도 있습니다.

+1

Tom, 두 곳에서 제거를 수행하면 두 번 제거 될 수 있으며 예외가 발생합니다. 접근 자 내부에서 KVO 관찰을하는 이유 중 하나가 어렵습니다. – quellish

+0

@quellish, 틀렸어. 세터에서의 제거는 대체되는 참조 용입니다. '-dealloc'에서의 제거는 해제시 현재의 참조를위한 것입니다. –

0

코드에 여러 가지 문제가 있습니다. 관찰을 제거하지 못한 것에 대해 기록 된 특정 오류와 관련하여 Tom Harrington이 말한 것 외에도 :

"데이터"라는 (존재하지 않는) 속성에 대해 인덱싱 된 컬렉션 접근자가 구현되었습니다. 즉, 속성 이름이 있어야하는 이름에 "데이터"가 있습니다.

인덱스 된 컬렉션 속성은 addedLetters입니다. 따라서, 인덱스 모음 변이 접근이 있어야한다 : 당신이 정상 게터 (즉 -addedLetters)와 배열 형 공공 재산이 있기 때문에

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

당신은 정말, 비 돌연변이 접근이 필요하지 않습니다.

그런데이 속성의 유형은 NSMutableArray이어야합니다. 이 속성의 유형은 NSMutableArray 인 인스턴스 변수이어야합니다.즉, 유형 (속성과 반대 됨)의 변경 가능성은 공개 인터페이스를 통해 노출되어서는 안됩니다. 이 작업을 수행 할 때 수동으로 인스턴스 변수를 선언해야합니다 (속성의 유형과 달라야하고 자동 합성이 잘못됨). copy 대신 strong을 만들고 setter를 직접 구현하여 전달 된 불변의 배열의 변경 가능한 사본 : 올바른 이름으로 접근을 돌연변이 인덱스 모음을 실행하면

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

그는 "data"키에 대한 KVC 가변 배열 컬렉션 접근자를 구현합니다. 객체가'[thing mutableArrayValueForKey : @ "data"]'를 호출하면 KVC 접근자를 호출하는 변경 가능한 프록시 객체를 얻게되며, 이는 실제로는'addedLetters' 가변 배열을 수정합니다. KVC 접근 자에 대한 정보가 없습니다. – quellish

+0

@quellish : 좋아,하지만 그는 의도적으로 클래스에 "data"속성을 추가했거나 "data"속성을 가진 다른 구현의 코드를 복사하여 붙여 넣었다 고 생각합니까? 실제로, 그는 그가 링크 된 대답에서 복사됩니다. –

+0

상관 없어요. 그의 질문은 "내 의도를 추측하고 내 수업을 재구성하라"가 아니라 "이 문제를 해결하기 위해 중단 점을 설정하는 방법"입니다. 그는 분명히 복사기와 붙여 넣기가 아닌 다른 속성에 액세스하기 위해 접근자를 구현했습니다. 이는 그에게 달려 있습니다. 그는 그렇게할만한 이유가있을 수 있습니다. – quellish

관련 문제