我有一个类型的对象,id
想知道它是否包含给定的值keyPath
:
[myObject valueForKeyPath:myKeyPath];
现在,我将它包装到一个@try{ } @catch{}
块中,以避免在找不到给定的 keypath 时出现异常。有没有更好的方法来做到这一点?检查给定的 keypath 是否存在而不处理异常?
非常感谢,
斯特凡
我有一个类型的对象,id
想知道它是否包含给定的值keyPath
:
[myObject valueForKeyPath:myKeyPath];
现在,我将它包装到一个@try{ } @catch{}
块中,以避免在找不到给定的 keypath 时出现异常。有没有更好的方法来做到这一点?检查给定的 keypath 是否存在而不处理异常?
非常感谢,
斯特凡
你可以试试这个:
if ([myObject respondsToSelector:NSSelectorFromString(myKeyPath)])
{
}
但是,这可能与您拥有的 getter 不对应,尤其是当它是布尔值时。如果这对您不起作用,请告诉我,我会使用反射为您写一些东西。
对于NSManagedObjects
,一个简单的解决方案是查看对象的实体描述并查看是否存在具有该键名的属性。如果有,您也可以将其带到下一步,看看该值是什么类型的属性。
这是一个简单的方法,给定 anyNSManagedObject
和 anyNSString
作为键,将始终返回一个NSString
:
- (NSString *)valueOfItem:(NSManagedObject *)item asStringForKey:(NSString *)key {
NSEntityDescription *entity = [item entity];
NSDictionary *attributesByName = [entity attributesByName];
NSAttributeDescription *attribute = attributesByName[key];
if (!attribute) {
return @"---No Such Attribute Key---";
}
else if ([attribute attributeType] == NSUndefinedAttributeType) {
return @"---Undefined Attribute Type---";
}
else if ([attribute attributeType] == NSStringAttributeType) {
// return NSStrings as they are
return [item valueForKey:key];
}
else if ([attribute attributeType] < NSDateAttributeType) {
// this will be all of the NSNumber types
// return them as strings
return [[item valueForKey:key] stringValue];
}
// add more "else if" cases as desired for other types
else {
return @"---Unacceptable Attribute Type---";
}
}
如果键无效或无法将值转换为字符串,则该方法将返回NSString
错误消息(更改这些块以针对这些情况执行您想要的任何操作)。
所有NSNumber
属性类型都作为它们的stringValue
表示返回。要处理其他属性类型(例如:日期),只需添加额外的“else if”块。(NSAttributeDescription
有关更多信息,请参阅类参考)。
如果对象是您的自定义类,您可以覆盖valueForUndefinedKey:
您的对象,以定义当键路径不存在时返回的内容。
应该可以合理地简单地将这种行为移植到任意类上。我有信心但不保证提供以下代码,您应该能够使用这些代码valueForUndefinedKey:
向任何类添加非异常抛出实现,在应用程序启动时每个类有一个集中的代码行。如果你想节省更多的代码,你可以让所有你想要这个行为的类继承自 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:
因为如果有人将该方法放在他们的类中,就会期望它会继续被调用。另请注意,可能有 AppKit 代码预计将抛出 NSObject/NSManagedObject 实现的异常。(我不确定,但可以考虑。)
NSManagedObject
valueForUndefinedKey:
为在调试器中单步执行其程序集提供了一个自定义实现,它似乎所做的只是抛出大致相同的异常,但消息略有不同。基于那 5 分钟的调试器调查,我觉得将它与NSManagedObject
子类一起使用应该是安全的,但我不能 100% 确定——其中可能有一些我没有发现的行为。谨防。
此外,就目前而言,如果您使用这种方法,您没有一个很好的方法来知道是否因为 keyPath 有效并且状态恰好是而valueForKey:
返回,或者它是否因为 keyPath 无效并且被嫁接而返回 -处理程序返回零。为此,您需要做一些不同的、特定于实现的事情。(也许返回或其他一些哨兵值,或者在线程本地存储中设置一些您可以检查的标志,但在这一点上,它真的比 ' 容易得多吗?)只是需要注意的事情。nil
nil
nil
[NSNull null]
@try/@catch
这对我来说似乎很有效;希望对你有用。
没有简单的方法可以解决这个问题。键值编码 (KVC) 不打算以这种方式使用。
有一件事是肯定的:使用@try-@catch
真的很糟糕,因为你很可能会泄漏内存等。ObjC / iOS 中的异常不适用于正常的程序流程。它们也非常昂贵(投掷和设置@try-@catch
IIRC)。
如果您查看Foundation/NSKeyValueCoding.h
标题,请查看注释/文档
- (id)valueForKey:(NSString *)key;
明确说明需要实施哪些方法-valueForKey:
才能发挥作用。这甚至可以使用直接 ivar 访问。您必须按照那里描述的顺序检查每一项。您需要获取关键路径,根据.
每个后续对象的每个部分对其进行拆分并检查每个部分。要访问 ivars,您需要使用 ObjC 运行时。看objc/runtime.h
。
不过,所有这些都是千篇一律的。您可能想要的是让您的对象实现一些正式的协议,然后-conformsToProtocol:
在调用之前进行检查。
您的关键路径是随机字符串还是这些字符串在您的控制之下?你想达到什么目的?你在解决错误的问题吗?
我不相信这是可能的安全方式(即没有-valueForUndefinedKey:
在其他人的课程上捣乱或类似的东西)。我之所以这么说是因为在 Mac 方面,Cocoa Bindings(可以设置为用默认值替换无效键路径)只是捕获由错误键路径导致的异常。如果即使 Apple 的工程师也无法在不尝试并捕获异常的情况下测试关键路径是否有效,我不得不假设这种方法不存在。