Protocol 作为 NSMutableDictionary 的 key

Tags
iOS
Date
May 10, 2021
Class 作为 NSMutableDictionary 的 key
上次讨论了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...