Objective-C Language
Key Value Coding / Key Value Observing
Sök…
Det vanligaste exemplet med kodning av verkligt liv
Key Value Coding är integrerad i NSObject med NSKeyValueCoding- protokollet.
Vad betyder detta?
Det betyder att varje ID-objekt kan kalla metoden ValueForKey och dess olika varianter som valueForKeyPath etc..
Det betyder också att alla ID-objekt kan åberopa setValue-metoden och dess olika varianter också.
Exempel:
id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];
int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];
undantag:
Ovanstående exempel antar att MyClass har en NSNumber-egenskap som heter myNumber. Om myNumber inte visas i MyClass-gränssnittsdefinitionen, kan en NSUndefinedKeyException höjas på kanske båda linjerna 2 och 5 - populärt känd som:
this class is not key value coding-compliant for the key myNumber.
Varför detta är så kraftfullt:
Du kan skriva kod som kan få tillgång till egenskaper i en klass dynamiskt utan att behöva gränssnitt för den klassen. Detta innebär att en tabellvy kan visa värden från alla egenskaper hos ett NSObject-härledt objekt, förutsatt att dess egendomnamn tillhandahålls dynamiskt vid körning.
I exemplet ovan kan koden också fungera utan att MyClass är tillgängligt och id-typ obj är tillgängligt för samtalskod.
Observera viktigt värde
Ställa in observering av nyckelvärde.
I det här fallet vill vi observera contentOffset
på ett objekt som vår observatör äger
//
// 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
Fråga efter KVC-data
if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
return YES;
} else {
return NO;
}
Du kan fråga värden lagrade med KVC snabbt och enkelt utan att behöva hämta eller casta dessa som lokala variabler.
Insamlingsoperatörer
Insamlingsoperatörer kan användas i en KVC-nyckelväg för att utföra en operation på en "samlingstyp" -egenskap (dvs. NSArray
, NSSet
och liknande). Till exempel är en vanlig åtgärd att utföra att räkna föremål i en samling. För att uppnå detta använder @count
operatören för @count
collection :
self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5
Även om detta är helt överflödigt här (vi kunde just ha åtkomst till count
), kan det vara användbart ibland, men det är sällan nödvändigt. Det finns dock vissa samlingsoperatörer som är mycket mer användbara, nämligen @max
, @min
, @sum
, @avg
och @unionOf
familjen. Det är viktigt att notera att dessa operatörer också kräver en separat nyckelväg som följer operatören för att fungera korrekt. Här är en lista över dem och vilken typ av data de arbetar med:
Operatör | Data typ |
---|---|
@count | (ingen) |
@max | NSNumber , NSDate , int (och relaterat), etc. |
@min | NSNumber , NSDate , int (och relaterat), etc. |
@sum | NSNumber , int (och relaterat), double (och relaterat), etc. |
@avg | NSNumber , int (och relaterat), double (och relaterat), etc. |
@unionOfObjects | NSArray , NSSet , etc. |
@distinctUnionOfObjects | NSArray , NSSet , etc. |
@unionOfArrays | NSArray<NSArray*> |
@distinctUnionOfArrays | NSArray<NSArray*> |
@distinctUnionOfSets | NSSet<NSSet*> |
@max
och @min
returnerar det högsta respektive lägsta värdet för en egenskap av objekt i samlingen. Titta till exempel på följande 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];
...
På bara fyra kodrader och ren Foundation, med kraften från Key-Value Coding-insamlingsoperatörer, kunde vi extrahera en rektangel som omsluter alla punkter i vår grupp.
Det är viktigt att notera att dessa jämförelser görs genom att åberopa metoden compare:
på objekten, så om du någonsin vill göra din egen klass kompatibel med dessa operatörer måste du implementera den här metoden.
@sum
kommer, som du antagligen kan gissa, lägga till alla värden på en egenskap.
@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]"];
Här använde vi @sum
att hitta det totala priset för alla utgifter i matrisen. Om vi istället ville hitta det genomsnittliga priset vi betalar för varje kostnad, kan vi använda @avg
:
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
Äntligen finns det @unionOf
familjen. Det finns fem olika operatörer i den här familjen, men alla fungerar mestadels på samma sätt, med bara små skillnader mellan var och en. Först finns det @unionOfObjects
som kommer att returnera en matris med egenskaper för objekt i en matris:
// See "expenses" array above
NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]
@distinctUnionOfObjects
fungerar på samma sätt som @unionOfObjects
, men det tar bort dubbletter:
NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]
Och slutligen kommer de sista 3 operatörerna i @unionOf
familjen att gå ett steg djupare och returnera en mängd värden som hittas för en egenskap som finns inne i dually-kapslade matriser:
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 ];
Den saknas detta exempel är @distinctUnionOfSets
, men detta fungerar exakt på samma sätt som @distinctUnionOfArrays
, men fungerar med och returnerar NSSet
s istället (det finns ingen icke- distinct
version grund i en uppsättning, måste varje objekt vara tydlig i alla fall).
Och det är allt! Insamlingsoperatörer kan vara riktigt kraftfulla om de används på rätt sätt och kan hjälpa till att undvika att behöva slinga igenom saker i onödan.
En sista anmärkning: du kan också använda standarduppsamlingsoperatörerna på matriser av NSNumber
s (utan ytterligare tillgång till egendom). För att göra detta får du åtkomst till den self
pseudo-egenskapen som just returnerar objektet:
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"];