Protocol 作为 NSMutableDictionary 的 key

Tags
iOS
Date
May 10, 2021
上次讨论了Class 作为 NSMutableDictionarykey的情况,
 
这次来看看Protocol作为NSMutableDictionarykey,会怎样。
Protocol *protocol = @protocol(UIScrollViewDelegate);

NSLog(@"class %@", NSStringFromClass([(id)protocol class]));
NSLog(@"isKindOfClass %d", [(id)protocol isKindOfClass:[NSObject class]]);
NSLog(@"conformsToProtocol %d", [(id)protocol conformsToProtocol:@protocol(NSCopying)]);

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"" forKey:protocol];
很遗憾,尽管Protocol *也是个NSObject对象,但并不遵循NSCopying协议。所以就发生了崩溃。
class Protocol
isKindOfClass 1
conformsToProtocol 0
-[Protocol copyWithZone:]: unrecognized selector sent to instance 0x7fff8a490608
 
当然了,通过NSStringFromProtocol,我们还是可以达到目的。
iOS 使用 Protocol 作为组件化解耦与通信的方式,大部分用的就是这种NSMutableDictionary+NSStringFromProtocol的方案。比如BeeHive。
问题也不大,只是对于这种底层框架,还是需要尽可能优化性能。
 
于是改用CoreFoudationCFDictionary。写个简单DEMO,对比一下性能。
自定义3个Protocol
@protocol DWJProtocol0 <NSObject>
@end
@protocol DWJProtocol1 <NSObject>
@end
@protocol DWJProtocol2 <NSObject>
@end
NSMutableDictionary进入3次写入,并读取30万次,统计读取耗时。
- (void)testNSDicionary
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@"" forKey:NSStringFromProtocol(@protocol(DWJProtocol0))];
    [dict setObject:@"" forKey:NSStringFromProtocol(@protocol(DWJProtocol1))];
    [dict setObject:@"" forKey:NSStringFromProtocol(@protocol(DWJProtocol2))];
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 100000; i++) {
        NSString *object0 = [dict objectForKey:NSStringFromProtocol(@protocol(DWJProtocol0))];
        NSString *object1 = [dict objectForKey:NSStringFromProtocol(@protocol(DWJProtocol1))];
        NSString *object2 = [dict objectForKey:NSStringFromProtocol(@protocol(DWJProtocol2))];
    }
    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"%@", @((endTime - startTime) * 1000));
}
同样对CFMutableDictionaryRef进入3次写入,并读取30万次,统计读取耗时。
- (void)testCFDictionary
{
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict, (__bridge const void *)(@protocol(DWJProtocol0)), (__bridge const void *)(@""));
    CFDictionarySetValue(dict, (__bridge const void *)(@protocol(DWJProtocol1)), (__bridge const void *)(@""));
    CFDictionarySetValue(dict, (__bridge const void *)(@protocol(DWJProtocol2)), (__bridge const void *)(@""));
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 100000; i++) {
        NSString *object0 = CFDictionaryGetValue(dict, (__bridge const void *)(@protocol(DWJProtocol0)));
        NSString *object1 = CFDictionaryGetValue(dict, (__bridge const void *)(@protocol(DWJProtocol1)));
        NSString *object2 = CFDictionaryGetValue(dict, (__bridge const void *)(@protocol(DWJProtocol2)));
    }
    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"%@", @((endTime - startTime) * 1000));
    CFRelease(dict);
}
直接跑Time Profiler,使用Debug模式运行在iOS 14.4.2iPhone 8,对比耗时,以及观察调用堆栈。
notion image
总耗时CFMutableDictionaryRef大概是NSMutableDictionary的1/25,效果可观。原因如下
  1. 不需要NSString的转换字符串操作
  1. 不需要对keycopy操作,甚至没有对keyretain操作(keyCallBacksNULL
 
当然了,这里有个前提是,这个Protocol *的对象,在运行时里是唯一生成不变的。所以没必要转换为NSString。

Loading Comments...