Ricerca…


Esempio più comune di codifica dei valori chiave della vita reale

La Key Value Coding è integrata in NSObject usando il protocollo NSKeyValueCoding .

Cosa significa?

Significa che qualsiasi oggetto id è in grado di chiamare il metodo valueForKey e le sue varie varianti come valueForKeyPath ecc. '

Significa anche che qualsiasi oggetto id può invocare il metodo setValue e le sue varie varianti.

Esempio:

id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];

int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];

eccezioni:

Sopra l'esempio si presuppone che MyClass abbia una proprietà NSNumber chiamata myNumber. Se myNumber non viene visualizzato nella definizione dell'interfaccia MyClass, è possibile generare un'eccezione NSUndefinedKeyException ad entrambe le righe 2 e 5, popolarmente conosciute come:

this class is not key value coding-compliant for the key myNumber.

Perché questo è così potente:

Puoi scrivere codice che può accedere alle proprietà di una classe in modo dinamico, senza bisogno di interfaccia per quella classe. Ciò significa che una vista tabella può visualizzare valori da qualsiasi proprietà di un oggetto derivato NSObject, a condizione che i suoi nomi di proprietà siano forniti dinamicamente in fase di runtime.

Nell'esempio sopra, il codice può anche funzionare senza che MyClass sia disponibile e il tipo di oggetto obj sia disponibile per chiamare il codice.

Key Value Observing

Impostazione dell'osservazione del valore chiave.

In questo caso, vogliamo osservare contentOffset su un oggetto di proprietà del nostro osservatore

//
// 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

Richiesta di dati KVC

if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
    return YES;
} else {
    return NO;
}

Puoi interrogare i valori memorizzati usando KVC in modo rapido e semplice, senza bisogno di recuperarli o lanciarli come variabili locali.

Operatori di raccolta

Gli operatori di raccolta possono essere utilizzati in un percorso chiave KVC per eseguire un'operazione su una “raccolta di tipo” proprietà (cioè NSArray , NSSet e simili). Ad esempio, un'operazione comune da eseguire è contare gli oggetti in una raccolta. Per ottenere ciò, si utilizza l' operatore di raccolta @count :

self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5

Anche se questo è completamente ridondante qui (potremmo avere appena avuto accesso alla proprietà count ), può essere utile occasionalmente, sebbene sia raramente necessario. Esistono tuttavia alcuni operatori di raccolta molto più utili, ovvero @max , @min , @sum , @avg e la famiglia @unionOf . È importante notare che questi operatori richiedono anche un percorso di chiave separato a seguito del corretto funzionamento dell'operatore. Ecco una lista di loro e il tipo di dati con cui lavorano:

Operatore Tipo di dati
@count (nessuna)
@max NSNumber , NSDate , int (e relativi), ecc.
@min NSNumber , NSDate , int (e relativi), ecc.
@sum NSNumber , int (e related), double (e related), ecc.
@avg NSNumber , int (e related), double (e related), ecc.
@unionOfObjects NSArray , NSSet , ecc.
@distinctUnionOfObjects NSArray , NSSet , ecc.
@unionOfArrays NSArray<NSArray*>
@distinctUnionOfArrays NSArray<NSArray*>
@distinctUnionOfSets NSSet<NSSet*>

@max e @min restituiranno rispettivamente il valore più alto o più basso di una proprietà di oggetti nella raccolta. Ad esempio, guarda il seguente codice:

// “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 sole 4 linee di codice e pure Foundation, con la potenza degli operatori di raccolta di codifica Key-Value, siamo stati in grado di estrarre un rettangolo che incapsula tutti i punti del nostro array.

È importante notare che questi confronti sono fatti invocando il metodo di compare: sugli oggetti, quindi se si desidera rendere la propria classe compatibile con questi operatori, è necessario implementare questo metodo.

@sum , come puoi probabilmente intuire, @sum tutti i valori di una proprietà.

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

Qui, abbiamo usato @sum per trovare il prezzo totale di tutte le spese nell'array. Se invece volessimo trovare il prezzo medio che @avg per ogni spesa, possiamo usare @avg :

NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];

Infine, c'è la famiglia @unionOf . Ci sono cinque diversi operatori in questa famiglia, ma funzionano tutti in gran parte lo stesso, con solo piccole differenze tra ciascuno. Innanzitutto, c'è @unionOfObjects che restituirà un array delle proprietà degli oggetti in un array:

// See "expenses" array above

NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
    @"[email protected]"];

// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]

@distinctUnionOfObjects funziona allo stesso modo di @unionOfObjects , ma rimuove i duplicati:

NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
    @"[email protected]"];

// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]

Infine, gli ultimi 3 operatori della famiglia @unionOf andranno più in profondità e restituiranno una serie di valori trovati per una proprietà contenuta all'interno di array annidati:

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

Quello mancante da questo esempio è @distinctUnionOfSets , tuttavia funziona esattamente come @distinctUnionOfArrays , ma funziona con e restituisce invece NSSet s (non esiste una versione non distinct perché in un set, ogni oggetto deve essere distinto comunque).

E questo è tutto! Gli operatori di raccolta possono essere davvero potenti se utilizzati correttamente e possono aiutare a evitare di dover eseguire il loop inutilmente.

Un'ultima nota: puoi anche utilizzare gli operatori di raccolta standard sugli array di NSNumber s (senza ulteriore accesso alla proprietà). Per fare ciò, accedi alla self pseudo-property che restituisce appena l'oggetto:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow