Objective-C Language
키 값 코딩 / 키 값 관찰
수색…
가장 일반적인 실제 생활 키 값 코딩 예
키 값 코딩은 NSKeyValueCoding 프로토콜을 사용하여 NSObject에 통합됩니다.
이게 무슨 뜻이야?
이는 모든 id 객체가 valueForKey 메소드와 valueForKeyPath 등 다양한 변형을 호출 할 수 있음을 의미합니다. '
또한 모든 id 객체가 setValue 메소드와 다양한 변형을 호출 할 수 있음을 의미합니다.
예:
id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];
int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];
예외 :
위의 예에서는 MyClass에 myNumber라는 NSNumber 속성이 있다고 가정합니다. myNumber가 MyClass 인터페이스 정의에 나타나지 않으면 NSUndefinedKeyException을 2 행 및 5 행 모두에서 발생시킬 수 있습니다 - 일반적으로 다음과 같이 알려져 있습니다.
this class is not key value coding-compliant for the key myNumber.
왜 이렇게 강력합니까?
클래스의 인터페이스 없이도 클래스의 속성에 동적으로 액세스 할 수있는 코드를 작성할 수 있습니다. 즉, NSObject에서 파생 된 객체의 속성에서 값을 표시 할 수 있습니다. 단, 속성 이름은 런타임에 동적으로 제공되어야합니다.
위의 예제에서 코드는 MyClass가 사용 가능하지 않고 호출 코드에서 사용할 수있는 id 유형 obj 없이도 작동 할 수 있습니다.
주요 가치 관찰
핵심 가치 관찰 설정.
이 경우 관찰자가 소유하고있는 객체의 contentOffset
을 관찰하고 싶습니다.
//
// Class to observe
//
@interface XYZScrollView: NSObject
@property (nonatomic, assign) CGPoint contentOffset;
@end
@implementation XYZScrollView
@end
//
// Class that will observe changes
//
@interface XYZObserver: NSObject
@property (nonatomic, strong) XYZScrollView *scrollView;
@end
@implementation XYZObserver
// simple way to create a KVO context
static void *XYZObserverContext = &XYZObserverContext;
// Helper method to add self as an observer to
// the scrollView's contentOffset property
- (void)addObserver {
// NSKeyValueObservingOptions
//
// - NSKeyValueObservingOptionNew
// - NSKeyValueObservingOptionOld
// - NSKeyValueObservingOptionInitial
// - NSKeyValueObservingOptionPrior
//
// can be combined:
// (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
NSString *keyPath = NSStringFromSelector(@selector(contentOffset));
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew;
[self.scrollView addObserver: self
forKeyPath: keyPath
options: options
context: XYZObserverContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if (context == XYZObserverContext) { // check the context
// check the keyPath to see if it's any of the desired keyPath's.
// You can observe multiple keyPath's
if ([keyPath isEqualToString: NSStringFromSelector(@selector(contentOffset))]) {
// change dictionary keys:
// - NSKeyValueChangeKindKey
// - NSKeyValueChangeNewKey
// - NSKeyValueChangeOldKey
// - NSKeyValueChangeIndexesKey
// - NSKeyValueChangeNotificationIsPriorKey
// the change dictionary here for a CGPoint observation will
// return an NSPoint, so we can take the CGPointValue of it.
CGPoint point = [change[NSKeyValueChangeNewKey] CGPointValue];
// handle point
}
} else {
// if the context doesn't match our current object's context
// we want to pass the observation parameters to super
[super observeValueForKeyPath: keyPath
ofObject: object
change: change
context: context];
}
}
// The program can crash if an object is not removed as observer
// before it is dealloc'd
//
// Helper method to remove self as an observer of the scrollView's
// contentOffset property
- (void)removeObserver {
NSString *keyPath = NSStringFromSelector(@selector(contentOffset));
[self.scrollView removeObserver: self forKeyPath: keyPath];
}
@end
KVC 데이터 쿼리
if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
return YES;
} else {
return NO;
}
KVC를 사용하여 저장된 값을 로컬 변수로 검색하거나 캐스팅하지 않고도 빠르고 쉽게 쿼리 할 수 있습니다.
콜렉션 연산자
Collection Operators 는 KVC 키 경로에서 "컬렉션 유형"속성 (즉, NSArray
, NSSet
및 유사한)에 대한 작업을 수행하는 데 사용될 수 있습니다. 예를 들어, 수행 할 공통 작업은 컬렉션의 개체를 계산하는 것입니다. 이를 위해 @count
콜렉션 연산자 를 사용합니다 :
self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5
이것은 여기에서 완전히 중복되지만 ( count
속성에 액세스했을 수도 있음), 거의 필요하지는 않지만 때때로 유용 할 수 있습니다. 있다, 그러나, 훨씬 더 유용, 즉 일부 수집 사업자 @max
, @min
, @sum
, @avg
과 @unionOf
가족. 이들 사업자는 제대로 작동하려면 연산자 뒤에 별도의 키 경로를 필요로하는 것이 중요하다. 다음은 이들의 목록과 함께 작동하는 데이터 유형입니다.
운영자 | 데이터 형식 |
---|---|
@count | (없음) |
@max | NSNumber , NSDate , int (및 관련) 등 |
@min | NSNumber , NSDate , int (및 관련) 등 |
@sum | NSNumber , int (및 관련), double (및 관련) 등 |
@avg | NSNumber , int (및 관련), double (및 관련) 등 |
@unionOfObjects | NSArray , NSSet 등 |
@distinctUnionOfObjects | NSArray , NSSet 등 |
@unionOfArrays | NSArray<NSArray*> |
@distinctUnionOfArrays | NSArray<NSArray*> |
@distinctUnionOfSets | NSSet<NSSet*> |
@max
및 @min
은 컬렉션에있는 객체의 속성 중 가장 높은 값 또는 가장 낮은 값을 각각 반환합니다. 예를 들어 다음 코드를 살펴보십시오.
// “Point” class used in our collection
@interface Point : NSObject
@property NSInteger x, y;
+ (instancetype)pointWithX:(NSInteger)x y:(NSInteger)y;
@end
...
self.points = @[[Point pointWithX:0 y:0],
[Point pointWithX:1 y:-1],
[Point pointWithX:5 y:-6],
[Point pointWithX:3 y:0],
[Point pointWithX:8 y:-4],
];
NSNumber *maxX = [self valueForKeyPath:@"[email protected]"];
NSNumber *minX = [self valueForKeyPath:@"[email protected]"];
NSNumber *maxY = [self valueForKeyPath:@"[email protected]"];
NSNumber *minY = [self valueForKeyPath:@"[email protected]"];
NSArray<NSNumber*> *boundsOfAllPoints = @[maxX, minX, maxY, minY];
...
Key-Value Coding 콜렉션 연산자의 힘으로 4 줄의 코드와 순수한 Foundation에서 배열의 모든 점을 캡슐화하는 사각형을 추출 할 수있었습니다.
이러한 비교는 객체에 대해 compare:
메소드를 호출하여 이루어 지므로 자신의 클래스를이 연산자와 호환되도록하려면이 메소드를 구현해야합니다.
@sum
은 아마 추측 할 수 있듯이 속성의 모든 값을 합산합니다.
@interface Expense : NSObject
@property NSNumber *price;
+ (instancetype)expenseWithPrice:(NSNumber *)price;
@end
...
self.expenses = @[[Expense expenseWithPrice:@1.50],
[Expense expenseWithPrice:@9.99],
[Expense expenseWithPrice:@2.78],
[Expense expenseWithPrice:@9.99],
[Expense expenseWithPrice:@24.95]
];
NSNumber *totalExpenses = [self valueForKeyPath:@"[email protected]"];
여기서 @sum
을 사용하여 배열의 모든 비용의 총 가격을 찾습니다. 우리가 대신 각 비용에 대해 지불하는 평균 가격을 찾고 싶다면 @avg
를 사용할 @avg
.
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
마지막으로, @unionOf
제품군이 있습니다. 이 가족에는 5 개의 서로 다른 연산자가 있지만, 각각은 서로 거의 차이가 없지만 거의 동일하게 작동합니다. 먼저 @unionOfObjects
가 있습니다.이 배열은 배열에있는 객체의 속성 배열을 반환합니다.
// See "expenses" array above
NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]
@distinctUnionOfObjects
는 @unionOfObjects
와 동일하게 @unionOfObjects
하지만 중복을 제거합니다.
NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]
마지막으로 @unionOf
패밀리의 마지막 3 개 연산자가 한 단계 더 깊숙이 들어가서 이중 중첩 배열에 포함 된 속성에 대해 발견 된 값의 배열을 반환합니다.
NSArray<NSArray<Expense*,Expense*>*> *arrayOfArrays =
@[
@[ [Expense expenseWithPrice:@19.99],
[Expense expenseWithPrice:@14.95],
[Expense expenseWithPrice:@4.50],
[Expense expenseWithPrice:@19.99]
],
@[ [Expense expenseWithPrice:@3.75],
[Expense expenseWithPrice:@14.95]
]
];
// @unionOfArrays
NSArray<NSNumber*> allPrices = [arrayOfArrays valueForKeyPath:
@"@unionOfArrays.price"];
// Equal to @[ @19.99, @14.95, @4.50, @19.99, @3.75, @14.95 ];
// @distinctUnionOfArrays
NSArray<NSNumber*> allPrices = [arrayOfArrays valueForKeyPath:
@"@distinctUnionOfArrays.price"];
// Equal to @[ @19.99, @14.95, @4.50, @3.75 ];
이 예제에서 누락 된 것은 @distinctUnionOfSets
, 이것은 @distinctUnionOfArrays
와 똑같은 기능을합니다. 대신 NSSet
사용하고 리턴합니다. (세트에서 모든 오브젝트가 구별되어야하므로 별다른 distinct
이 없습니다.)
그리고 그게 다야! 콜렉션 운영자는 올바르게 사용된다면 정말 강력 할 수 있으며, 불필요하게 루프를 돌지 않아도됩니다.
마지막으로 주목할 점은 추가 속성 접근없이 NSNumber
의 배열에서 표준 컬렉션 연산자를 사용할 수도 있다는 것입니다. 이렇게하려면 객체를 반환하는 self
의사 속성에 액세스합니다.
NSArray<NSNumber*> *numbers = @[@0, @1, @5, @27, @1337, @2048];
NSNumber *largest = [numbers valueForKeyPath:@"@max.self"];
NSNumber *smallest = [numbers valueForKeyPath:@"@min.self"];
NSNumber *total = [numbers valueForKeyPath:@"@sum.self"];
NSNumber *average = [numbers valueForKeyPath:@"@avg.self"];