Buscar..


Ejemplo más común de codificación de valor clave de la vida real

La codificación de valor clave se integra en NSObject mediante el protocolo NSKeyValueCoding .

¿Lo que esto significa?

Significa que cualquier objeto de identificación es capaz de llamar al método valueForKey y sus diversas variantes como valueForKeyPath, etc.

También significa que cualquier objeto id puede invocar el método setValue y sus diversas variantes también.

Ejemplo:

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

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

Excepciones:

El ejemplo anterior supone que MyClass tiene una propiedad NSNumber llamada myNumber. Si myNumber no aparece en la definición de la interfaz de MyClass, puede generarse una NSUndefinedKeyException en posiblemente las líneas 2 y 5, conocida popularmente como:

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

Por qué esto es TAN poderoso:

Puede escribir código que pueda acceder a las propiedades de una clase dinámicamente, sin necesidad de interfaz para esa clase. Esto significa que una vista de tabla puede mostrar valores de cualquier propiedad de un objeto derivado de NSObject, siempre que sus nombres de propiedad se proporcionen dinámicamente en tiempo de ejecución.

En el ejemplo anterior, el código también puede funcionar sin que MyClass esté disponible y el tipo de identificación obj esté disponible para el código de llamada.

Valor clave observando

Configuración de la observación del valor clave.

En este caso, queremos observar el contentOffset en un objeto que posee nuestro observador

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

Consulta de datos KVC

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

Puede consultar los valores almacenados mediante KVC de forma rápida y sencilla, sin necesidad de recuperarlos o convertirlos en variables locales.

Operadores de cobro

Los operadores de colección se pueden usar en una ruta de acceso de clave KVC para realizar una operación en una propiedad de "tipo de colección" (es decir, NSArray , NSSet y similares). Por ejemplo, una operación común a realizar es contar los objetos en una colección. Para lograr esto, utiliza el operador de colección @count :

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

Si bien esto es completamente redundante aquí (podríamos haber accedido a la propiedad de count ), en ocasiones puede ser útil, aunque rara vez es necesario. Hay, sin embargo, algunos operadores de recolección que son mucho más útiles, a saber @max , @min , @sum , @avg y la @unionOf familia. Es importante tener en cuenta que estos operadores también requieren una ruta de acceso por separado que siga al operador para funcionar correctamente. Aquí hay una lista de ellos y el tipo de datos con los que trabajan:

Operador Tipo de datos
@count (ninguna)
@max NSNumber , NSDate , int (y relacionado), etc.
@min NSNumber , NSDate , int (y relacionado), etc.
@sum NSNumber , int (y relacionado), double (y relacionado), etc.
@avg NSNumber , int (y relacionado), double (y relacionado), etc.
@unionOfObjects NSArray , NSSet , etc.
@distinctUnionOfObjects NSArray , NSSet , etc.
@unionOfArrays NSArray<NSArray*>
@distinctUnionOfArrays NSArray<NSArray*>
@distinctUnionOfSets NSSet<NSSet*>

@max y @min devolverán el valor más alto o más bajo, respectivamente, de una propiedad de objetos en la colección. Por ejemplo, mira el siguiente código:

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

...

En solo 4 líneas de código y Foundation puro, con el poder de los operadores de recopilación de Key-Value Coding, pudimos extraer un rectángulo que encapsula todos los puntos de nuestra matriz.

Es importante tener en cuenta que estas comparaciones se realizan invocando el método compare: en los objetos, por lo que si alguna vez desea que su propia clase sea compatible con estos operadores, debe implementar este método.

@sum , como probablemente pueda adivinar, sumará todos los valores de una propiedad.

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

Aquí, usamos @sum para encontrar el precio total de todos los gastos en la matriz. Si en su lugar quisiéramos encontrar el precio promedio que estamos pagando por cada gasto, podemos usar @avg :

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

Finalmente, está la familia @unionOf . Hay cinco operadores diferentes en esta familia, pero todos funcionan casi igual, con solo pequeñas diferencias entre cada uno. Primero, hay @unionOfObjects que devolverá una matriz de las propiedades de los objetos en una matriz:

// See "expenses" array above

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

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

@distinctUnionOfObjects funciona igual que @unionOfObjects , pero elimina duplicados:

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

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

Y finalmente, los últimos 3 operadores de la familia @unionOf irán un paso más allá y devolverán una serie de valores encontrados para una propiedad contenida dentro de matrices anidadas:

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

El que falta en este ejemplo es @distinctUnionOfSets , sin embargo, esto funciona exactamente igual que @distinctUnionOfArrays , pero funciona con NSSet s (no hay distinct versión que no sea distinct porque en un conjunto, cada objeto debe ser distinto de todos modos).

¡Y eso es! Los operadores de recolección pueden ser realmente poderosos si se usan correctamente, y pueden ayudar a evitar tener que recorrer cosas innecesariamente.

Una última nota: también puede usar los operadores de recopilación estándar en matrices de NSNumber s (sin acceso adicional a la propiedad). Para hacer esto, accede a la self pseudopropiedad que simplemente devuelve el objeto:

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow