Objective-C Language
キー値コーディング/キー値観察
サーチ…
最も一般的な実生活キー値のコーディング例
キー値コーディングは、 NSKeyValueCodingプロトコルを使用してNSObjectに統合されています。
これはどういう意味ですか?
これは、任意の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にmyNumberというNSNumberプロパティがあると仮定しています。 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 NSSet
"コレクション型"プロパティ(つまりNSArray
、 NSSet
など)で操作を実行するために使用できます。たとえば、実行する一般的な操作は、コレクション内のオブジェクトを数えることです。これを実現するには、 @count
コレクション演算子を使用し@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];
...
Key-Value Codingコレクション演算子の力を借りて、4行のコードと純粋なFoundationで、配列のすべての点をカプセル化する長方形を抽出することができました。
これらの比較はオブジェクトに対して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
を使用して、配列内のすべての費用の合計価格を@sum
ました。代わりに、私たちが各費用のために支払っている平均価格を見つけたければ、私は@avg
を使うことができます:
NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];
最後に、 @unionOf
ファミリがあります。このファミリには5つの異なる演算子がありますが、それぞれの演算子はほとんど同じですが、それぞれの演算子の違いはわずかです。まず、配列内のオブジェクトのプロパティの配列を返す@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 ]
最後に、 @unionOf
ファミリの最後の3つの演算子は一歩進んで、二重にネストされた配列に含まれるプロパティの値の配列を返します。
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
、これは@distinctUnionOfSets
とまったく同じ@distinctUnionOfArrays
、代わりにNSSet
を使用して返されます(セットではすべてのオブジェクトが区別されなければならないため、非distinct
バージョンはありません)。
以上です!コレクション演算子は、正しく使用すると本当に強力なものになり、不必要なものをループする必要を避けるのに役立ちます。
最後の注意: NSNumber
の配列に標準コレクション演算子を使用することもできます(追加のプロパティアクセスなし)。これを行うには、オブジェクトを返すだけの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"];