Say you have an ObjC class shown blow:
@interface MCADictionaryHolder : NSObject
@property (nonatomic) NSDictionary<NSString *, id> * _Nonnull objects;
- (void)randomise:(NSInteger)upperBound;
- (id _Nullable)itemForKey:(NSString * _Nonnull)key;
@end
@implementation MCADictionaryHolder
- (instancetype)init
{
self = [super init];
if (self) {
self.objects = [[NSDictionary alloc] init];
}
return self;
}
-(void)randomise:(NSInteger)upperBound {
NSMutableDictionary * d = [[NSMutableDictionary alloc] initWithCapacity:upperBound];
for (NSInteger i = 0; i < upperBound; i++) {
NSString *inStr = [@(i) stringValue];
[d setObject:inStr forKey:inStr];
}
self.objects = [[NSDictionary alloc] initWithDictionary:d];
}
-(id)itemForKey:(NSString *)key {
id value = [self.objects objectForKey:key];
return value;
}
@end
Say you launching a performance test similar to shown below:
class NSDictionaryToDictionaryBridgingTests: LogicTestCase {
func test_default() {
let bound = 2000
let keys = (0 ..< bound).map { "\($0)" }
var mutableDict: [String: Any] = [:]
for key in keys {
mutableDict[key] = key
}
let dict = mutableDict
let holder = MCADictionaryHolder()
holder.randomise(bound)
benchmark("Access NSDictionary via Swift API") {
for key in keys {
let value = holder.objects[key]
_ = value
}
}
benchmark("Access NSDictionary via NSDictionary API") {
let nsDict = holder.objects as NSDictionary
for key in keys {
let value = nsDict.object(forKey: key)
_ = value
}
}
benchmark("Access NSDictionary via dedicated method") {
for key in keys {
let value = holder.item(forKey: key)
_ = value
}
}
benchmark("Access to Swift Dictionary via Swift API") {
for key in keys {
let value = dict[key]
_ = value
}
}
}
}
Then performance test will show results similar to shown below:
Access NSDictionary via Swift API:
.......... 1103.655ms ± 9.358ms (mean ± SD)
Access NSDictionary via NSDictionary API:
.......... 0.263ms ± 0.001ms (mean ± SD)
Access NSDictionary via dedicated method:
.......... 0.335ms ± 0.002ms (mean ± SD)
Access to Swift Dictionary via Swift API:
.......... 0.174ms ± 0.001ms (mean ± SD)
From results you can see that:
- Accessing Swift Dictionary via Swift API gives a fastest result.
- Accessing NSDictionary via NSDictionary API on Swift side a bit slower, but acceptable.
Thus, no need to create a dedicated method to perform operations on NSDictionary
, just cast [AnyHashable: Any]
to NSDictionary
and perform needed operations.
Update
In some cases it is worth to access ObjC via convenience method or property to minimise cost of crossing border between ObjC <-> Swift.
Say you have an ObjC extension shown below:
@interface NSAppearance (MCA)
-(BOOL)mca_isDark;
@end
-(BOOL)mca_isDark {
if ([self.name isEqualToString:NSAppearanceNameDarkAqua]) {
return true;
}
if ([self.name isEqualToString:NSAppearanceNameVibrantDark]) {
return true;
}
if ([self.name isEqualToString:NSAppearanceNameAccessibilityHighContrastDarkAqua]) {
return true;
}
if ([self.name isEqualToString:NSAppearanceNameAccessibilityHighContrastVibrantDark]) {
return true;
}
return false;
}
@end
Say you launching a performance test similar to shown below:
class NSStringComparisonTests: LogicTestCase {
func isDarkUsingSwiftAPI(_ a: NSAppearance) -> Bool {
switch a.name {
case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
return true
default:
return false
}
}
func isDarkUsingObjCAPI(_ a: NSAppearance) -> Bool {
let nsName = a.name.rawValue as NSString
if nsName.isEqual(to: NSAppearance.Name.darkAqua) {
return true
}
if nsName.isEqual(to: NSAppearance.Name.vibrantDark) {
return true
}
if nsName.isEqual(to: NSAppearance.Name.accessibilityHighContrastDarkAqua) {
return true
}
if nsName.isEqual(to: NSAppearance.Name.accessibilityHighContrastVibrantDark) {
return true
}
return false
}
func test_default() {
let appearance = NSAppearance.current!
let numIterations = 1000000
benchmark("Compare using Swift API", numberOfIterations: numIterations) {
let value = isDarkUsingSwiftAPI(appearance)
_ = value
}
benchmark("Compare using ObjC API", numberOfIterations: numIterations) {
let value = isDarkUsingObjCAPI(appearance)
_ = value
}
benchmark("Compare using ObjC convenience property", numberOfIterations: numIterations) {
let value = appearance.mca_isDark()
_ = value
}
}
}
Then performance test will show results similar to shown below:
Compare using Swift API:
.......... 813.347ms ± 7.560ms (mean ± SD)
Compare using ObjC API:
.......... 534.337ms ± 1.065ms (mean ± SD)
Compare using ObjC convenience property:
.......... 142.729ms ± 0.197ms (mean ± SD)
From results, you can see that getting information from ObjC world via convenience method is the fastest solution.