上次讨论了
Class 作为 NSMutableDictionary 的 key的情况,这次来看看
Protocol作为NSMutableDictionary的key,会怎样。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。问题也不大,只是对于这种底层框架,还是需要尽可能优化性能。
于是改用
CoreFoudation的CFDictionary。写个简单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.2的iPhone 8,对比耗时,以及观察调用堆栈。总耗时
CFMutableDictionaryRef大概是NSMutableDictionary的1/25,效果可观。原因如下- 不需要
NSString的转换字符串操作
- 不需要对
key的copy操作,甚至没有对key的retain操作(keyCallBacks为NULL)
当然了,这里有个前提是,这个
Protocol *的对象,在运行时里是唯一生成不变的。所以没必要转换为NSString。
Loading Comments...