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"];


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow