【问题标题】:Foundation Objective-c: Dictionary with array; dict with dictFoundation Objective-c:带数组的字典;带听写的听写
【发布时间】:2011-04-29 07:30:21
【问题描述】:

假设我有一个 NSDictionary,其中包含一个 NSArray 和一个 NSDictionary 的两个子集合:

NSMutableDictionary *mkDict(void){
    NSMutableDictionary *dict=[NSMutableDictionary dictionary];
    NSMutableDictionary *sub=[NSMutableDictionary dictionary];
    NSMutableArray *array= [NSMutableArray array];
    [dict setObject:array forKey:@"array_key"];
    [dict setObject:sub forKey:@"dict_key"];
    return dict;
}

有多种方法可以访问子集合的单个元素,我选择对其中三个进行计时。

第一种方式是通过访问父元素的key来间接访问子元素:

void KVC1(NSMutableDictionary *dict, int count){

    for(int i=0; i<count; i++){
        char buf1[40], buf2[sizeof buf1];
        snprintf(buf1,sizeof(buf1),"element %i", i);
        snprintf(buf2, sizeof buf2, "key %i", i);

        [[dict objectForKey:@"array_key"] 
          addObject:
             [NSString stringWithUTF8String:buf1]];
        [[dict objectForKey:@"dict_key"] 
          setObject:[NSString stringWithUTF8String:buf1] 
          forKey:[NSString stringWithUTF8String:buf2]];
    }
}

第二种是使用KeyPath访问:

void KVC2(NSMutableDictionary *dict, int count){

    for(int i=0; i<count; i++){
        char buf1[40], buf2[sizeof buf1], buf3[sizeof buf1];
        snprintf(buf1,sizeof(buf1),"element %i", i);
        snprintf(buf2, sizeof buf2, "key %i", i);
        snprintf(buf3, sizeof buf3, "dict_key.key %i",i);

        [dict insertValue:
             [NSString stringWithUTF8String:buf1] 
            atIndex:i inPropertyWithKey:@"array_key"];
        [dict setValue:
             [NSString stringWithUTF8String:buf1] 
            forKeyPath:
             [NSString stringWithUTF8String:buf3]];
    }
}

第三个,和第一个类似,是访问一个指向子元素的指针,然后使用那个指针:

void KVC3(NSMutableDictionary *dict, int count){

    NSMutableArray *subArray = [dict objectForKey:@"array_key"];
    NSMutableDictionary *subDict = [dict objectForKey:@"dict_key"];

    for(int i=0; i<count; i++){
        char buf1[40], buf2[sizeof buf1];
        snprintf(buf1,sizeof(buf1),"element %i", i);
        snprintf(buf2, sizeof buf2, "key %i", i);

        [subArray addObject:[NSString stringWithUTF8String:buf1]];
        [subDict 
           setObject:
            [NSString stringWithUTF8String:buf1] 
           forKey:
            [NSString stringWithUTF8String:buf2]];
    }
}

这是计时码:

#import <Foundation/Foundation.h>
#import <mach/mach_time.h>

// KVC1, KVC2 and KVC3 from above...

#define TIME_THIS(func,times) \
({\
mach_timebase_info_data_t info; \
mach_timebase_info(&info); \
uint64_t start = mach_absolute_time(); \
for(int i=0; i<(int)times; i++) \
func ; \
uint64_t duration = mach_absolute_time() - start; \
duration *= info.numer; \
duration /= info.denom; \
duration /= 1000000; \
NSLog(@"%i executions of line %i took %lld milliseconds", times, __LINE__, duration); \
});

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSMutableDictionary *dict=mkDict();
    NSMutableDictionary *dict2=mkDict();
    NSMutableDictionary *dict3=mkDict();

    TIME_THIS(KVC1(dict,1000),10);
    TIME_THIS(KVC2(dict2,1000),10);
    TIME_THIS(KVC3(dict3,1000),10);

    if([dict isEqualToDictionary:dict2])
        NSLog(@"And they are the same...");
    [pool drain];
    return 0;
}

结果如下:

10 executions of line 256 took 57 milliseconds
10 executions of line 257 took 7930 milliseconds
10 executions of line 258 took 46 milliseconds
And they are the same...

问题:为什么 OS X Snow Leopard / Lion 建议的使用 KeyPaths 的方法这么慢?如果将count 的大小增加到10,000 或更多,KVC2 会变得无限慢,而其他两种方法线性增加。

我做错了吗?是否有更好的习惯用法来访问字典中子集合的单个元素?

【问题讨论】:

    标签: objective-c nsdictionary key-value-coding


    【解决方案1】:

    KVC2(),你发送

    [dict insertValue:[NSString stringWithUTF8String:buf1] 
              atIndex:i
    inPropertyWithKey:@"array_key"]; 
    

    该方法的文档说明如下:

    如果方法insertIn&lt;Key&gt;:atIndex: 存在,则调用它。如果没有找到对应的符合脚本的 KVC 方法 (insertIn&lt;Key&gt;:atIndex:),则此方法会调用 mutableArrayValueForKey: 并改变结果。

    由于消息被发送到dictNSDictionary 的一个实例,没有-insertIn&lt;Key&gt;:atIndex: 方法,因此发送-mutableArrayValueForKey:。此方法的文档说明如下:

    返回值 一个可变数组代理,提供对 key 指定的有序对多关系的读写访问。

    讨论 添加到可变数组的对象变得与接收者相关,而从可变数组中删除的对象变得不相关。默认实现识别与 valueForKey: 相同的简单访问器方法和数组访问器方法,并遵循相同的直接实例变量访问策略,但始终返回可变集合代理对象,而不是 valueForKey: 将返回的不可变集合。

    所以发生的事情是 在每次迭代中:

    1. 代理可变数组被创建为原始数组的可变副本
    2. 一个对象被添加到代理数组中;
    3. 代理数组将相同的对象添加到原始数组中。

    如果您使用 Instruments 来分析您的程序,您会注意到大约 50% 的处理时间花费在 -[NSKeyValueSlowMutableArray insertObject:atIndex:] 上 — 我认为可以安全地假设 NSKeyValueSlowMutableArray 是代理数组,它的名称应该是其性能的线索。

    【讨论】:

    • +1 its name should be a clue of its performance. 好笑! :-)
    【解决方案2】:

    因为框架需要弄清楚如何在给定字符串访问路径的情况下获取元素:解析字符串、错误检查、创建更多字符串实例并释放它们。

    由于键值观察、绑定等原因,这是一种首选方式,因此 Cocoa 可以为您创造很多魔力。慢是相对的:它可能比直接访问慢,但它是否太慢了?只有分析一个真实的用例才能告诉你它是否变得太慢以及你是否需要优化。如果您在 UI 上使用 keypath 设置一些变量,那么您可能不会注意到速度不足,如果您尝试处理大量数据,那么 keypath 可能不是最佳解决方案。正如我所说:描述你的用例。

    您必须在代码的易用性、可访问 Cocoa KVC 提供的所有优点和速度之间做出妥协,只有当您将代码视为全局的一部分时,您才会知道答案。

    【讨论】:

      【解决方案3】:

      第一个和第三个实现对子元素有一个“静态”引用,在第三个实现中绝对不会第二次评估。访问子元素的第二个实现的可变性可能导致时间问题......并且 [NSDictionary insertValue: atIndex: forPropertyKey:] 在非静态评估的元素(NSMutableArray)中存在随机访问插入问题,也是一个 KVO可能会引发许多未知副作用的过程...在不使用 KVO 脚本 insertValue:atIndex:forPropertyWithKey: 的情况下再试一次,看看 keyPath 是否那么慢,我敢打赌它比其他的慢,但规模不同

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-26
        • 2018-03-23
        • 1970-01-01
        • 2010-11-02
        • 1970-01-01
        • 1970-01-01
        • 2011-05-26
        • 2019-02-23
        相关资源
        最近更新 更多