Objective-C Language
Кодирование ключевого значения / наблюдение за ключевыми значениями
Поиск…
Наиболее распространенный пример кодирования ключевых значений реальной жизни
Key Coding Value интегрирован в NSObject с использованием протокола NSKeyValueCoding.
Что это значит?
Это означает, что любой объект id способен вызывать метод valueForKey и его различные варианты, такие как valueForKeyPath и т. Д.
Это также означает, что любой объект id может вызывать метод setValue и его различные варианты.
Пример:
id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];
int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];
Исключения:
Вышеприведенный пример предполагает, что MyClass имеет свойство NSNumber, называемое myNumber. Если myNumber не отображается в определении интерфейса MyClass, NSUndefinedKeyException может быть поднят, возможно, в обеих строках 2 и 5, которые широко известны как:
this class is not key value coding-compliant for the key myNumber.
Почему это так мощно:
Вы можете написать код, который может динамически обращаться к свойствам класса, без необходимости использования интерфейса для этого класса. Это означает, что представление таблицы может отображать значения из любых свойств производного объекта NSObject, при условии, что его имена свойств поставляются динамически во время выполнения.
В приведенном выше примере код также может работать без возможности MyClass, а тип id obj доступен для вызова кода.
Наблюдение за ключевыми значениями
Настройка наблюдения за ключевым значением.
В этом случае мы хотим наблюдать contentOffset
на объекте, которым владеет наш наблюдатель
//
// 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
if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
return YES;
} else {
return NO;
}
Вы можете быстро и легко запрашивать значения, хранящиеся в KVC, без необходимости извлекать их или использовать в качестве локальных переменных.
Операторы коллекции
Операторы коллекции могут использоваться в ключевом ключе KVC для выполнения операции над свойством «тип коллекции» (например, NSArray
, NSSet
и т. NSArray
). Например, общая операция для выполнения - это подсчет объектов в коллекции. Для этого вы используете оператор коллекции @count
:
self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5
В то время как это полностью избыточно здесь (мы могли бы просто получить доступ к свойству count
), это может быть полезно иногда, хотя это редко необходимо. Однако есть некоторые операторы сбора, которые гораздо полезнее, а именно @max
, @min
, @sum
, @avg
и @unionOf
. Важно отметить , что эти операторы также требуют отдельного ключа пути следующего оператора правильно функционировать. Вот список их и тип данных, с которыми они работают:
оператор | Тип данных |
---|---|
@count | (никто) |
@max | NSNumber , NSDate , int (и связанные) и т. Д. |
@min | NSNumber , NSDate , int (и связанные) и т. Д. |
@sum | NSNumber , int (и связанные), double (и связанные) и т. Д. |
@avg | NSNumber , int (и связанные), double (и связанные) и т. Д. |
@unionOfObjects | NSArray , NSSet и т. Д. |
@distinctUnionOfObjects | NSArray , NSSet и т. Д. |
@unionOfArrays | NSArray<NSArray*> |
@distinctUnionOfArrays | NSArray<NSArray*> |
@distinctUnionOfSets | NSSet<NSSet*> |
@max
и @min
возвращают самое высокое или низкое значение, соответственно, свойства объектов в коллекции. Например, посмотрите на следующий код:
// “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];
...
Всего в 4 строках кода и в чистом фонде с мощью операторов коллекции Key-Value Coding мы смогли извлечь прямоугольник, который инкапсулирует все точки в нашем массиве.
Важно отметить, что эти сравнения выполняются путем вызова метода compare:
для объектов, поэтому, если вы когда-либо захотите сделать свой собственный класс совместимым с этими операторами, вы должны реализовать этот метод.
@sum
, как вы, наверное, можете догадаться, добавить все значения свойства.
@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]"];
Здесь мы использовали @sum
чтобы найти общую стоимость всех затрат в массиве. Если бы мы вместо этого захотели найти среднюю цену, которую мы платим за каждый расход, мы можем использовать @avg
:
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
Наконец, есть семья @unionOf
. В этом семействе существует пять разных операторов, но все они работают в основном одинаково, с небольшими различиями между ними. Во-первых, есть @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
работает так же, как @unionOfObjects
, но удаляет дубликаты:
NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
@"[email protected]"];
// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]
И, наконец, последние 3 оператора в семье @unionOf
пройдут на один шаг глубже и вернут массив значений, найденных для свойства, содержащегося внутри двоично вложенных массивов:
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 ];
Один отсутствующие из этого примера @distinctUnionOfSets
, однако это функции точно так же , как @distinctUnionOfArrays
, но работает с и возвращает NSSet
s вместо (нет не- distinct
версий , поскольку в наборе, каждый объект должен быть отличным в любом случае).
И это все! Операторы коллекции могут быть очень мощными при правильном использовании и могут помочь избежать необходимости излишнего циклического перемещения файлов.
Последнее замечание: вы также можете использовать стандартные операторы коллекции в массивах NSNumber
s (без дополнительного доступа к свойствам). Для этого вы получаете доступ к self
псевдо-свойствам, которое просто возвращает объект:
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"];