Objective-C Language
Kodowanie wartości klucza / Obserwacja wartości klucza
Szukaj…
Najczęstszy przykład kodowania wartości klucza rzeczywistego
Kodowanie wartości klucza jest zintegrowane z NSObject przy użyciu protokołu NSKeyValueCoding .
Co to znaczy?
Oznacza to, że każdy obiekt id może wywoływać metodę valueForKey i jej różne warianty, takie jak valueForKeyPath itp. ”
Oznacza to również, że każdy obiekt id może wywoływać metodę setValue i jej różne warianty.
Przykład:
id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];
int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];
Wyjątki:
Powyższy przykład zakłada, że MyClass ma właściwość NSNumber o nazwie myNumber. Jeśli mojaNumer nie pojawia się w definicji interfejsu MyClass, wyjątek NSUndefinedKeyEx może zostać zgłoszony w obu liniach 2 i 5 - popularnie zwanych:
this class is not key value coding-compliant for the key myNumber.
Dlaczego jest to tak potężne:
Możesz pisać kod, który może dynamicznie uzyskiwać dostęp do właściwości klasy, bez potrzeby używania interfejsu dla tej klasy. Oznacza to, że widok tabeli może wyświetlać wartości z dowolnych właściwości obiektu pochodnego NSObject, pod warunkiem, że nazwy jego właściwości są dostarczane dynamicznie w czasie wykonywania.
W powyższym przykładzie kod może również działać bez dostępu do MyClass i wywołania kodu typu id.
Obserwacja kluczowych wartości
Konfigurowanie obserwacji kluczowych wartości.
W tym przypadku chcemy obserwować contentOffset
na obiekcie, którego właścicielem jest nasz obserwator
//
// 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
Zapytanie o dane KVC
if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
return YES;
} else {
return NO;
}
Możesz szybko i łatwo wyszukiwać wartości zapisane przy użyciu KVC, bez potrzeby pobierania lub rzutowania ich jako zmienne lokalne.
Operatorzy kolekcji
Kolekcja Operatorzy mogą być wykorzystywane w ścieżce kluczowego KVC wykonać operację na „kolekcja typu” własności (tj NSArray
, NSSet
i podobnych). Na przykład częstą operacją do wykonania jest zliczanie obiektów w kolekcji. Aby to osiągnąć, użyj operatora kolekcji @count
:
self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5
Chociaż jest to tutaj całkowicie zbędne (moglibyśmy uzyskać dostęp do właściwości count
), może być przydatne czasem, chociaż rzadko jest konieczne. Istnieje jednak kilka operatorów zbierania, które są znacznie bardziej przydatne, a mianowicie @max
, @min
, @sum
, @avg
i rodzina @unionOf
. Ważne jest, aby pamiętać, że operatory te wymagają również oddzielną ścieżkę klucza operatora po prawidłowo funkcjonować. Oto ich lista i rodzaj danych, z którymi pracują:
Operator | Typ danych |
---|---|
@count | (Żaden) |
@max | NSNumber , NSDate , int (i pokrewnych), etc. |
@min | NSNumber , NSDate , int (i pokrewnych), etc. |
@sum | NSNumber , int (i powiązane), double (i powiązane) itp. |
@avg | NSNumber , int (i powiązane), double (i powiązane) itp. |
@unionOfObjects | NSArray , NSSet , etc. |
@distinctUnionOfObjects | NSArray , NSSet , etc. |
@unionOfArrays | NSArray<NSArray*> |
@distinctUnionOfArrays | NSArray<NSArray*> |
@distinctUnionOfSets | NSSet<NSSet*> |
@max
i @min
zwrócą odpowiednio najwyższą lub najniższą wartość właściwości obiektów w kolekcji. Na przykład spójrz na następujący kod:
// “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];
...
W zaledwie 4 liniach kodu i czystej bazie, dzięki mocy operatorów zbierania kodowania klucz-wartość byliśmy w stanie wyodrębnić prostokąt, który otacza wszystkie punkty w naszej tablicy.
Ważne jest, aby pamiętać, że tych porównań dokonuje się, wywołując metodę compare:
na obiektach, więc jeśli kiedykolwiek chcesz uczynić własną klasę kompatybilną z tymi operatorami, musisz zaimplementować tę metodę.
@sum
, jak można się domyślać, @sum
wszystkie wartości właściwości.
@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]"];
Tutaj użyliśmy @sum
aby znaleźć całkowitą cenę wszystkich wydatków w tablicy. Jeśli zamiast tego chcielibyśmy znaleźć średnią cenę, którą płacimy za każdy wydatek, możemy użyć @avg
:
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
Wreszcie jest rodzina @unionOf
. W tej rodzinie jest pięciu różnych operatorów, ale wszyscy oni działają w większości tak samo, z niewielkimi różnicami między nimi. Po pierwsze, @unionOfObjects
które zwrócą tablicę właściwości obiektów w tablicy:
// See "expenses" array above
NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]
@distinctUnionOfObjects
działa tak samo jak @unionOfObjects
, ale usuwa duplikaty:
NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]
I na koniec 3 ostatnie operatory z rodziny @unionOf
pójdą o krok głębiej i zwrócą tablicę wartości znalezionych dla właściwości zawartej w podwójnie zagnieżdżonych tablicach:
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 ];
Jednej brakuje tego przykładu jest @distinctUnionOfSets
, jednak działa dokładnie tak samo jak @distinctUnionOfArrays
, ale działa z i zwraca NSSet
s zamiast (nie ma nie- distinct
wersję, ponieważ w zestawie, każdy obiekt musi być wyraźny i tak).
I to wszystko! Operatory kolekcji mogą być naprawdę potężne, jeśli są właściwie używane, i mogą pomóc uniknąć niepotrzebnego przechodzenia między rzeczami.
Ostatnia uwaga: możesz także użyć standardowych operatorów kolekcji na tablicach NSNumber
(bez dodatkowego dostępu do właściwości). Aby to zrobić, uzyskujesz dostęp do pseudo-własności self
która po prostu zwraca obiekt:
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"];