Objective-C Language
Key Value Codering / Key Value Observing
Zoeken…
Meest voorkomende real life key value codering Voorbeeld
Key Value Coding is geïntegreerd in NSObject met behulp van het NSKeyValueCoding- protocol.
Wat dit betekent?
Het betekent dat elk id-object in staat is om de methode valueForKey en de verschillende varianten zoals valueForKeyPath enz. Aan te roepen. '
Het betekent ook dat elk id-object de methode setValue en de verschillende varianten ervan ook kan oproepen.
Voorbeeld:
id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];
int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];
Uitzonderingen:
Bovenstaand voorbeeld veronderstelt dat MyClass een NSNumber-eigenschap heeft met de naam myNumber. Als myNumber niet wordt weergegeven in de definitie van de MyClass-interface, kan een NSUndefinedKeyException worden verhoogd op mogelijk beide regels 2 en 5 - in de volksmond bekend als:
this class is not key value coding-compliant for the key myNumber.
Waarom dit zo krachtig is:
U kunt code schrijven die dynamisch toegang heeft tot eigenschappen van een klasse, zonder dat u een interface voor die klasse nodig hebt. Dit betekent dat een tabelweergave waarden kan weergeven van alle eigenschappen van een van NSObject afgeleid object, op voorwaarde dat de eigenschapsnamen tijdens runtime dynamisch worden opgegeven.
In het bovenstaande voorbeeld kan de code ook werken zonder dat MyClass beschikbaar is en id type obj beschikbaar is voor het aanroepen van code.
Sleutelwaarde observeren
Waarneming van sleutelwaarde instellen.
In dit geval willen we de contentOffset
observeren op een object dat onze waarnemer bezit
//
// 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-gegevens opvragen
if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
return YES;
} else {
return NO;
}
U kunt snel en gemakkelijk waarden opvragen die zijn opgeslagen met KVC, zonder deze als lokale variabelen op te halen of te casten.
Collection Operators
Operators verzameling kan worden gebruikt in een KVC sleutelpad een bewerking op een “verzameling type” eigenschap (dwz NSArray
, NSSet
en dergelijke). Een veelgebruikte handeling is bijvoorbeeld het tellen van de objecten in een verzameling. Om dit te bereiken, gebruikt u de operator @count
collection :
self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5
Hoewel dit hier volledig overbodig is (we hadden net de count
eigenschap kunnen openen), kan het soms nuttig zijn, hoewel het zelden nodig is. Er zijn echter enkele verzamelingsexploitanten die veel nuttiger zijn, namelijk @max
, @min
, @sum
, @avg
en de familie @unionOf
. Het is belangrijk op te merken dat deze operatoren ook een afzonderlijk sleutelpad nodig hebben om de operator correct te laten werken. Hier is een lijst met hen en het type gegevens waarmee ze werken:
operator | Data type |
---|---|
@count | (geen) |
@max | NSNumber , NSDate , int (en aanverwant), etc. |
@min | NSNumber , NSDate , int (en aanverwant), etc. |
@sum | NSNumber , int (en gerelateerd), double (en gerelateerd), etc. |
@avg | NSNumber , int (en gerelateerd), double (en gerelateerd), etc. |
@unionOfObjects | NSArray , NSSet , etc. |
@distinctUnionOfObjects | NSArray , NSSet , etc. |
@unionOfArrays | NSArray<NSArray*> |
@distinctUnionOfArrays | NSArray<NSArray*> |
@distinctUnionOfSets | NSSet<NSSet*> |
@max
en @min
retourneren respectievelijk de hoogste of laagste waarde van een eigenschap van objecten in de verzameling. Kijk bijvoorbeeld naar de volgende code:
// “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];
...
In slechts 4 coderegels en pure Foundation, met de kracht van Key-Value Coding-verzameloperatoren, konden we een rechthoek extraheren die alle punten in onze array inkapselt.
Het is belangrijk op te merken dat deze vergelijkingen worden gemaakt door de methode compare:
op de objecten aan te roepen, dus als u ooit uw eigen klasse compatibel wilt maken met deze operatoren, moet u deze methode implementeren.
@sum
zal, zoals je waarschijnlijk kunt raden, alle waarden van een eigenschap optellen.
@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 hebben we @sum
gebruikt om de totale prijs van alle uitgaven in de array te vinden. Als we in plaats daarvan de gemiddelde prijs willen vinden die we voor elke uitgave betalen, kunnen we @avg
:
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
Eindelijk is er de familie @unionOf
. Er zijn vijf verschillende operators in deze familie, maar ze werken allemaal grotendeels hetzelfde, met slechts kleine verschillen tussen beide. Ten eerste is er @unionOfObjects
die een array met de eigenschappen van objecten in een array retourneert:
// See "expenses" array above
NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]
@distinctUnionOfObjects
werkt hetzelfde als @unionOfObjects
, maar het verwijdert duplicaten:
NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]
En ten slotte zullen de laatste 3 operators in de familie @unionOf
een stap dieper gaan en een reeks waarden retourneren die zijn gevonden voor een eigenschap in dubbel geneste arrays:
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 ];
Het exemplaar dat in dit voorbeeld @distinctUnionOfSets
, is @distinctUnionOfSets
, maar dit werkt precies hetzelfde als @distinctUnionOfArrays
, maar werkt in plaats daarvan met NSSet
s (er is geen niet- distinct
versie omdat in een set elk object toch moet worden onderscheiden).
En dat is het! Collection-operators kunnen erg krachtig zijn als ze correct worden gebruikt, en kunnen helpen voorkomen dat dingen onnodig doorlopen.
Nog een laatste opmerking: u kunt ook de standaardverzameloperatoren gebruiken op arrays van NSNumber
s (zonder extra toegang tot eigendommen). Om dit te doen, hebt u toegang tot de self
pseudo-eigenschap die zojuist het object retourneert:
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"];