Objective-C Language
Schlüsselwertkodierung / Schlüsselwertbeobachtung
Suche…
Beispiel für die Codierung des Schlüsselwerts für das wirkliche Leben
Die Schlüsselwertcodierung wird mithilfe des NSKeyValueCoding- Protokolls in NSObject integriert .
Was bedeutet das?
Dies bedeutet, dass jedes id-Objekt in der Lage ist, die valueForKey-Methode und ihre verschiedenen Varianten wie valueForKeyPath usw. aufzurufen. '
Dies bedeutet auch, dass jedes id-Objekt die Methode setValue und seine verschiedenen Varianten aufrufen kann.
Beispiel:
id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];
int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];
Ausnahmen:
Im obigen Beispiel wird davon ausgegangen, dass MyClass eine NSNumber-Eigenschaft namens myNumber hat. Wenn myNumber nicht in der MyClass-Schnittstellendefinition angezeigt wird, kann eine NSUndefinedKeyException in möglicherweise beiden Zeilen 2 und 5 ausgelöst werden.
this class is not key value coding-compliant for the key myNumber.
Warum das so mächtig ist:
Sie können Code schreiben, der dynamisch auf die Eigenschaften einer Klasse zugreifen kann, ohne dass eine Schnittstelle für diese Klasse erforderlich ist. Dies bedeutet, dass eine Tabellensicht Werte aus beliebigen Eigenschaften eines von NSObject abgeleiteten Objekts anzeigen kann, vorausgesetzt, ihre Eigenschaftsnamen werden zur Laufzeit dynamisch angegeben.
Im obigen Beispiel kann der Code auch funktionieren, ohne dass MyClass verfügbar ist und der ID-Typ obj für den aufrufenden Code verfügbar ist.
Schlüsselwertbeobachtung
Schlüsselwertbeobachtung einrichten.
In diesem Fall möchten wir das contentOffset
auf einem Objekt beobachten, das unser Beobachter besitzt
//
// 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-Daten abfragen
if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
return YES;
} else {
return NO;
}
Sie können mit KVC gespeicherte Werte schnell und einfach abfragen, ohne dass diese als lokale Variablen abgerufen oder umgewandelt werden müssen.
Betreiber der Sammlung
Collection-Operatoren können in einem KVC-Schlüsselpfad verwendet werden, um eine Operation für eine Eigenschaft vom Typ "Collection" NSArray
(z. B. NSArray
, NSSet
und Ähnliches). Eine häufig auszuführende Operation besteht beispielsweise darin, die Objekte in einer Sammlung zu zählen. Um dies zu erreichen, verwenden Sie den @count
Collection-Operator :
self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5
Während dies hier völlig überflüssig ist (wir hätten gerade auf die count
zugreifen können), kann es gelegentlich nützlich sein, obwohl es selten notwendig ist. Es gibt jedoch einige Erfassungsoperatoren, die wesentlich nützlicher sind, nämlich @max
, @min
, @sum
, @avg
und die @unionOf
Familie. Es ist wichtig zu beachten , dass diese Betreiber benötigen auch einen separaten Schlüsselpfad nach dem Bediener richtig zu funktionieren. Hier eine Liste von ihnen und die Art der Daten, mit denen sie arbeiten:
Operator | Datentyp |
---|---|
@count | (keiner) |
@max | NSNumber , NSDate , int (und verwandte) usw. |
@min | NSNumber , NSDate , int (und verwandte) usw. |
@sum | NSNumber , int (und verwandt), double (und verwandt) usw. |
@avg | NSNumber , int (und verwandt), double (und verwandt) usw. |
@unionOfObjects | NSArray , NSSet usw. |
@distinctUnionOfObjects | NSArray , NSSet usw. |
@unionOfArrays | NSArray<NSArray*> |
@distinctUnionOfArrays | NSArray<NSArray*> |
@distinctUnionOfSets | NSSet<NSSet*> |
@max
und @min
geben den höchsten bzw. niedrigsten Wert einer Eigenschaft von Objekten in der Auflistung zurück. Sehen Sie sich beispielsweise den folgenden Code an:
// “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];
...
Mit nur vier Zeilen Code und pure Foundation konnten wir mit der Funktion der Key-Value-Coding-Erfassungsoperatoren ein Rechteck extrahieren, das alle Punkte in unserem Array enthält.
Beachten Sie, dass diese Vergleiche durch Aufrufen der Methode compare:
für die Objekte durchgeführt werden. Wenn Sie also Ihre eigene Klasse mit diesen Operatoren kompatibel machen möchten, müssen Sie diese Methode implementieren.
@sum
, wie Sie sich vermutlich @sum
können, alle Werte einer Eigenschaft.
@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]"];
Hier haben wir mit @sum
den Gesamtpreis aller Ausgaben im Array ermittelt. Wenn wir stattdessen den Durchschnittspreis ermitteln wollten, den wir für jede @avg
zahlen, können wir @avg
:
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
Schließlich gibt es noch die @unionOf
Familie. Es gibt fünf verschiedene Operatoren in dieser Familie, aber alle arbeiten im Wesentlichen gleich, wobei nur kleine Unterschiede bestehen. Zunächst gibt es @unionOfObjects
das ein Array der Eigenschaften von Objekten in einem Array @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
funktioniert genauso wie @unionOfObjects
, entfernt aber Duplikate:
NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]
Und schließlich gehen die letzten 3 Operatoren der @unionOf
Familie noch einen Schritt weiter und geben ein Array von Werten zurück, die für eine Eigenschaft gefunden wurden, die in doppelt geschachtelten Arrays enthalten ist:
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 ];
In diesem Beispiel fehlt @distinctUnionOfSets
, dieses funktioniert jedoch genauso wie @distinctUnionOfArrays
, funktioniert jedoch mit NSSet
und gibt NSSet
(es gibt keine nicht- distinct
Version, da in einer Menge jedes Objekt anders sein muss).
Und das ist es! Erfassungsoperatoren können bei richtiger Verwendung sehr leistungsfähig sein und dazu beitragen, unnötiges Durchlaufen von Daten zu vermeiden.
Noch ein letzter Hinweis: Sie können die Standard-Collection-Operatoren auch für Arrays von NSNumber
(ohne zusätzlichen Zugriff auf Eigenschaften). Dazu greifen Sie auf die self
Pseudo-Eigenschaft zu, die das Objekt gerade zurückgibt:
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"];