Objective-C Language
Codificación de valor clave / Observación de valor clave
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"];