タイプのオブジェクトがありid
、指定された の値が含まれているかどうかを知りたいkeyPath
:
[myObject valueForKeyPath:myKeyPath];
@try{ } @catch{}
ここで、指定されたキーパスが見つからない場合の例外を回避するために、ブロックにラップします。これを行うより良い方法はありますか?例外を処理せずに、指定されたキーパスが存在するかどうかを確認しますか?
どうもありがとう、
ステファン
タイプのオブジェクトがありid
、指定された の値が含まれているかどうかを知りたいkeyPath
:
[myObject valueForKeyPath:myKeyPath];
@try{ } @catch{}
ここで、指定されたキーパスが見つからない場合の例外を回避するために、ブロックにラップします。これを行うより良い方法はありますか?例外を処理せずに、指定されたキーパスが存在するかどうかを確認しますか?
どうもありがとう、
ステファン
これを試すことができます:
if ([myObject respondsToSelector:NSSelectorFromString(myKeyPath)])
{
}
ただし、特にブール値の場合は、ゲッターに対応しない場合があります。これがうまくいかない場合は、お知らせください。リフレクションを使用して何かを書きます。
オブジェクトが独自のカスタム クラスである場合は、オブジェクトをオーバーライドvalueForUndefinedKey:
して、キーパスが存在しない場合に返されるものを定義できます。
この動作を任意のクラスに合理的に簡単に移植できるはずです。私は自信を持って提示しますが、保証はありませんが、次のコードを使用して、例外をスローしない実装を任意のクラスに追加できますvalueForUndefinedKey:
。アプリの起動時に、クラスごとに 1 行の集中化されたコードを使用できます。さらに多くのコードを節約したい場合は、この動作が必要なすべてのクラスを NSManagedObject の共通サブクラスから継承し、これをその共通クラスに適用すると、すべてのサブクラスが動作を継承します。詳細は後述しますが、コードは次のとおりです。
ヘッダー ( NSObject+ValueForUndefinedKeyAdding.h
):
@interface NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler;
@end
実装 ( NSObject+ValueForUndefinedKeyAdding.m
):
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler
{
Class clazz = self;
if (clazz == nil)
return;
if (clazz == [NSObject class] || clazz == [NSManagedObject class])
{
NSLog(@"Don't try to do this to %@; Really.", NSStringFromClass(clazz));
return;
}
SEL vfuk = @selector(valueForUndefinedKey:);
@synchronized([NSObject class])
{
Method nsoMethod = class_getInstanceMethod([NSObject class], vfuk);
Method nsmoMethod = class_getInstanceMethod([NSManagedObject class], vfuk);
Method origMethod = class_getInstanceMethod(clazz, vfuk);
if (origMethod != nsoMethod && origMethod != nsmoMethod)
{
NSLog(@"%@ already has a custom %@ implementation. Replacing that would likely break stuff.",
NSStringFromClass(clazz), NSStringFromSelector(vfuk));
return;
}
if(!class_addMethod(clazz, vfuk, handler, method_getTypeEncoding(nsoMethod)))
{
NSLog(@"Could not add valueForUndefinedKey: method to class: %@", NSStringFromClass(clazz));
}
}
}
@end
次に、AppDelegate クラス (または実際にはどこにでもありますが、リストからクラスを追加または削除するときにどこにあるかがわかるように、中心的な場所に配置することはおそらく理にかなっています) に、この機能をクラスに追加するこのコードを配置します。起動時に選択:
#import "MyAppDelegate.h"
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import "MyOtherClass1.h"
#import "MyOtherClass2.h"
#import "MyOtherClass3.h"
static id ExceptionlessVFUKIMP(id self, SEL cmd, NSString* inKey)
{
NSLog(@"Not throwing an exception for undefined key: %@ on instance of %@", inKey, [self class]);
return nil;
}
@implementation MyAppDelegate
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MyOtherClass1 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass2 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass3 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
});
}
// ... rest of app delegate class ...
@end
ここで行っているのはvalueForUndefinedKey:
、クラス MyOtherClass1、2、および 3 にカスタム実装を追加することです。私が提供した実装例では、単にNSLog
s を指定して nil を返しますが、コードを変更することで、実装を変更して、必要なことを行うことができます。でExceptionlessVFUKIMP
。を削除してNSLog
nil を返すと、質問に基づいて、必要なものが得られると思います。
このコードはメソッドを入れ替えることはありません。存在しない場合にのみ追加します。valueForUndefinedKey:
誰かがクラスにそのメソッドを入れた場合、それが引き続き呼び出されることが期待されるため、独自のカスタム実装を既に持っているクラスでこれが使用されないようにチェックを入れました。また、NSObject/NSManagedObject 実装からの例外がスローされることを期待する AppKit コードが存在する可能性があることにも注意してください。(確かなことはわかりませんが、検討する可能性はあります。)
NSManagedObject
valueForUndefinedKey:
デバッガーでそのアセンブリをステップ実行するためのカスタム実装を提供しますが、それが行うように見えるのは、わずかに異なるメッセージでほぼ同じ例外をスローすることだけです。その 5 分間のデバッガーの調査に基づいて、これをサブクラスで使用しても安全であるべきだと思いますが、NSManagedObject
100% 確実ではありません。注意してください。
また、現状では、このアプローチを使用する場合、keyPath が有効で状態がたまたま であるために が返されているのか、またはkeyPath が無効で接ぎ木されているためにvalueForKey:
返されているのかを知る良い方法がありません。ハンドラで nil が返されました。そのためには、実装固有の別のことを行う必要があります。(おそらく、または他のセンチネル値を返すか、チェックできるスレッドローカルストレージにフラグを設定しますが、この時点では、実際にはそれよりもはるかに簡単ですか?)知っておくべきこと.nil
nil
nil
[NSNull null]
@try/@catch
これは私にとってはかなりうまくいくようです。お役に立てば幸いです。
これを解決する簡単な方法はありません。Key Value Coding (KVC) は、そのように使用することを意図していません。
1つ確かなことは、メモリリークなどの可能性が非常に高いため、使用@try-@catch
は本当に悪いことです.ObjC / iOSの例外は、通常のプログラムフローを意図していません. @try-@catch
それらは非常に高価でもあります ( IIRCのスローとセットアップの両方)。
Foundation/NSKeyValueCoding.h
ヘッダーを見ると、コメント/ドキュメントの
- (id)valueForKey:(NSString *)key;
機能するためにどのメソッドを実装する必要があるかを明確に示しています-valueForKey:
。これは、ivar への直接アクセスを使用する場合もあります。そこに記載されている順序でそれぞれを確認する必要があります。キーパスを取得し、それに基づいて分割し.
、後続の各オブジェクトの各部分を確認する必要があります。ivar にアクセスするには、ObjC ランタイムを使用する必要があります。を見てくださいobjc/runtime.h
。
ただし、これはすべてハックです。おそらくあなたが望むのは、オブジェクトが何らかの正式なプロトコルを実装し、-conformsToProtocol:
呼び出す前にチェックすることです。
あなたのキーパスはランダムな文字列ですか、それともそれらの文字列はあなたの管理下にありますか? 何を達成しようとしていますか?間違った問題を解いていませんか?
私はこれが安全な方法で可能であるとは信じていません (つまり-valueForUndefinedKey:
、他の人々のクラスをいじったり、それに似たものをいじったりすることなく)。Mac 側では、無効なキー パスをデフォルト値で置き換えるように設定できる Cocoa Bindings が、不適切なキー パスに起因する例外を単純にキャッチするからです。Apple のエンジニアでさえ、キー パスが有効かどうかを試行して例外をキャッチせずにテストする方法を持っていない場合、そのような方法は存在しないと想定する必要があります。