Objective-C&KeyValueCoding:如何使用valueForKeyPath避免异常:?

swa*_*ner 10 objective-c ios5 kvc

我有一个类型的对象,id并想知道它是否包含给定的值keyPath:

[myObject valueForKeyPath:myKeyPath];
Run Code Online (Sandbox Code Playgroud)

现在,我将它包装到一个@try{ } @catch{}块中,以避免在找不到给定的keypath时出现异常.有没有更好的方法来做到这一点?检查给定的密钥路径是否存在而不处理异常?

非常感谢,

斯特凡

Ric*_*III 8

你可以试试这个:

if ([myObject respondsToSelector:NSSelectorFromString(myKeyPath)])
{
}
Run Code Online (Sandbox Code Playgroud)

但是,这可能与您拥有的getter不对应,特别是如果它是布尔值.如果这不适合你,请告诉我,我会用反射给你写一些东西.

  • 不幸的是,这不起作用,因为路径没有遍历...例如,如果`myObject`有一个属性`myChild`,它具有属性`mySubChild`,则无法识别keyPath`myChild.mySubChild` ... (2认同)

pau*_*ley 5

如果该对象是您的自定义类,您可以覆盖valueForUndefinedKey:您的对象,以定义当键路径不存在时返回的内容。


Elm*_*Cat 5

因为NSManagedObjects,一个简单的解决方案是查看对象的实体描述,看看是否有一个具有该键名的属性.如果有,您还可以将其带到下一步,并查看该值的属性类型.

这是一个简单的方法,给出任何NSManagedObject和任何NSString作为键,将始终返回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---";
    }
}
Run Code Online (Sandbox Code Playgroud)

如果密钥无效或者值无法生成字符串,则该方法返回NSString错误消息(将这些块更改为对这些情况执行任何操作).

所有NSNumber属性类型都作为其stringValue表示形式返回.要处理其他属性类型(例如:日期),只需添加其他"else if"块.(NSAttributeDescription有关更多信息,请参阅类参考).


ipm*_*mcc 4

应该可以相当简单地将这种行为移植到任意类上。我充满信心地(但没有保证)提供以下代码,您应该能够使用它来向任何类添加非异常抛出的实现valueForUndefinedKey:,并在应用程序启动时为每个类添加一行集中的代码。如果您想节省更多代码,您可以使所有您想要具有此行为的类继承自 NSManagedObject 的公共子类,然后将其应用于该公共类,并且您的所有子类都将继承该行为。之后有更多详细信息,但这是代码:

标题 ( NSObject+ValueForUndefinedKeyAdding.h):

@interface NSObject (ValueForUndefinedKeyAdding)

+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler;

@end
Run Code Online (Sandbox Code Playgroud)

执行 (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
Run Code Online (Sandbox Code Playgroud)

然后,在您的 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
Run Code Online (Sandbox Code Playgroud)

我在这里所做的是为valueForUndefinedKey:类 MyOtherClass1、2 和 3 添加自定义实现。我提供的示例实现仅NSLogs 并返回 nil,但您可以通过更改代码来更改实现以执行您想要的任何操作在ExceptionlessVFUKIMP。如果您删除NSLog,然后返回 nil,我怀疑根据您的问题您会得到您想要的。

这段代码永远不会混合方法,它只会在不存在的情况下添加一个。我已经进行了检查,以防止在已经具有自己的自定义实现的类上使用此valueForUndefinedKey:方法,因为如果有人将该方法放入其类中,则会期望它将继续被调用。另请注意,可能有 AppKit 代码期望从 NSObject/NSManagedObject 实现中抛出异常。(我不确定,但可以考虑。)

一些注意事项:

NSManagedObjectvalueForUndefinedKey:为在调试器中单步执行其程序集提供了一个自定义实现,它所做的只是抛出大致相同的异常,但消息略有不同。基于 5 分钟的调试器调查,我觉得在NSManagedObject子类中使用它应该是安全的,但我不是 100% 确定——其中可能存在一些我没有发现的行为。谨防。

另外,就目前情况而言,如果您使用这种方法,您没有一个好的方法来知道是否因为 keyPath 有效且状态恰好为 而valueForKey:返回,或者是否因为 keyPath 无效且嫁接而返回处理程序返回零。为此,您需要做一些不同的、特定于实现的事情。(也许返回或其他一些哨兵值,或者在线程本地存储中设置一些您可以检查的标志,但此时它真的比 容易得多吗?)只是需要注意的事情。nilnilnil[NSNull null]@try/@catch

这对我来说似乎很有效;希望它对您有用。

  • 我建议在 NSManagedObject 上覆盖 `valueForUndefinedKey:` 不会有问题。Apple 记录了您不应该(也可能不应该)在此处重写的方法:http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/Reference/NSManagedObject。 html (2认同)