多个valueForKey:调用给出与valueForKeyPath不同的结果:使用相同的键

时间:2013-01-07 20:39:10

标签: objective-c cocoa nsarray key-value-coding abperson

在OS X(Mac 10.8.2)中,我已将类别添加到ABPerson,以将电话号码作为Phone个对象的数组返回。 Phone对象有两个属性labelvalue。我想获取所有电话号码并过滤它们以仅包含label匹配输入的那些电话号码。

如果我对整个valueForKey:数组或使用密钥ABPerson@"phones"的个人使用嵌套@"label"调用,那么我会得到所需的结果。但是,如果我将valueForKey:连接到单个valueForKeyPath:,那么在搜索整个数组时仍然会得到正确的结果,但在评估单个人对象时

    #import <Cocoa/Cocoa.h>
    #import <AddressBook/AddressBook.h>

    @interface Phone : NSObject
    @property (retain) NSString *label;
    @property (retain) NSString *value;
    @end

    @implementation Phone
    - (void)dealloc {
        self.label = nil;
        self.value = nil;
        [super dealloc];
    }
    @end

    @interface ABPerson (phones)
    - (NSArray *)phones;
    @end

    @implementation ABPerson (phones)
    - (NSArray *)phones {
        NSMutableArray *phones = [NSMutableArray array];
        ABMultiValue *values = [self valueForProperty:kABPhoneProperty];
        for (NSInteger i = 0; i < [values count]; i++) {
            Phone *phone = [[Phone alloc] init];
            phone.label = ABLocalizedPropertyOrLabel([values labelAtIndex:i]);
            phone.value = [values valueAtIndex:i];
            [phones addObject:phone];
            [phone release];
        }
        return [NSArray arrayWithArray:phones];
    }
    @end

    int main(int argc, char *argv[]) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        NSArray *people = [[ABAddressBook sharedAddressBook] people];
        ABPerson *person = [people objectAtIndex:0];

        NSArray *a0 = [[people valueForKey:@"phones"] valueForKey:@"label"];
        NSArray *a1 = [people valueForKeyPath:@"phones.label"];
        NSLog(@"a0 == a1: %d", [a0 isEqualTo:a1]);

        NSArray *a2 = [[person valueForKey:@"phones"] valueForKey:@"label"];
        NSArray *a3 = [person valueForKeyPath:@"phones.label"];
        NSLog(@"a2 == a3: %d", [a2 isEqualTo:a3]);

        NSLog(@"people count                                         : %3ld", [people count]);
        NSLog(@"people phones label count   (valueForKey.valueForKey): %3ld", [a0 count]);
        NSLog(@"people phones label count           (valueForKeyPath): %3ld", [a1 count]);
        NSLog(@"person phones label count   (valueForKey.valueForKey): %3ld", [a2 count]);
        NSLog(@"person phones label count           (valueForKeyPath): %3ld", [a3 count]);
        NSLog(@"----------------------------------------------------------");
        NSLog(@"people phones label objectAtIndex:0 (valueForKeyPath): %@", [a1 objectAtIndex:0]);
        NSLog(@"person phones label         (valueForKey.valueForKey): %@", a2);
        NSLog(@"person phones label                 (valueForKeyPath): %@", a3);

        [pool release];
        return 0;
    }

输出:

2013-01-07 15:03:42.537 test[51919:303] a0 == a1: 1
2013-01-07 15:03:42.540 test[51919:303] a2 == a3: 0
2013-01-07 15:03:42.541 test[51919:303] people count                                         : 200
2013-01-07 15:03:42.543 test[51919:303] people phones label count   (valueForKey.valueForKey): 200
2013-01-07 15:03:42.544 test[51919:303] people phones label count           (valueForKeyPath): 200
2013-01-07 15:03:42.545 test[51919:303] person phones label count   (valueForKey.valueForKey):   2
2013-01-07 15:03:42.546 test[51919:303] person phones label count           (valueForKeyPath):   0
2013-01-07 15:03:42.547 test[51919:303] ----------------------------------------------------------
2013-01-07 15:03:42.548 test[51919:303] people phones label objectAtIndex:0 (valueForKeyPath): (
    mobile,
    home
)
2013-01-07 15:03:42.549 test[51919:303] person phones label         (valueForKey.valueForKey): (
    mobile,
    home
)
2013-01-07 15:03:42.551 test[51919:303] person phones label                 (valueForKeyPath): (null)

为什么[[person valueForKey:@"phones"] valueForKey:@"label"]不等于[person valueForKeyPath:@"phones.label"]?这很重要,因为我想使用完整的@"phone.label"密钥路径对所有联系人使用谓词过滤器,如下所示:

    NSString *value = @"home";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY phones.label == %@", value];
    NSArray *a4 = [people filteredArrayUsingPredicate:predicate];

在此示例中,a4始终为空(使用不带“ANY”标志的谓词仍会导致空的过滤数组)。我相信这是因为filteredArrayUsingPredicate:主要是评估valueForKeyPath:的每个对象(人),如上所述,这不适用于个人对象。这是对的吗?

如果某人能够了解valueForKeyPath:对象上ABPerson失败的原因,或者提供过滤返回所需结果的人员数组的谓词,我将非常感激。< / p>

1 个答案:

答案 0 :(得分:3)

ABRecord这是一个相当古老的问题。 (请参阅此thread on it from 2007;我相信可能会回到10.2中的原始代码。)它们会覆盖valueForKeyPath:,并且它不会以默认方式运行。密钥必须列在+properties中。虽然您可以添加自己的属性,但这并没有多大帮助。它将返回在数据库中找到的结果。它永远不会打电话给你的类别方法。并且添加自己的属性会修改AB数据库架构,因此无论如何它都是一个非常大的锤子。你绝对不应该将电话号码复制到一个单独的财产中。

我的建议是将ABPerson包装到另一个对象中并对其执行查询。这将使您更好地控制记录。

顺便说一句,对于明显的问题&#34;但是为什么当你在people上调用它时它会起作用?&#34;这是因为您正在使用另一个特别编写的valueForKeyPath:版本,即NSArray覆盖的版本。它会在每条记录上调用valueForKey:ABPerson不会覆盖valueForKey:,只会valueForKeyPath:。很奇怪,是吗?

如果这会给您带来麻烦,您应该打开雷达(bugreport.apple.com)。这就是改变这种情况的唯一方法。

这里的another blog帖子解决了同样的问题。他将ABPerson子类化,而不是包装它。由于ABPerson是免费桥接到ABPersonRef,并且没有明确表示您可以将其子类化,因此我可能会使用包装器。但是博客文章可能会给你一些关于这个问题的更深入的见解,而子类化也许没问题。有关此问题的更多信息,请参阅此问题的this even more ancient thread