Last active
December 2, 2020 01:56
-
-
Save steipete/5bd38c1dc9a517ad414c043fbcc8435d to your computer and use it in GitHub Desktop.
Case Insensitive NSDictionary subclass. This seems like a lost art, so I'm sharing it here. License: MIT, http://pspdfkit.com/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Higher-order functions for `NSDictionary`. | |
@interface NSDictionary <KeyType, ObjectType> (PSPDFFoundation) | |
/// Converts the current dictionary into a case insensitive one. | |
@property (nonatomic, readonly) NSDictionary<NSString *, ObjectType> *pst_caseInsensitiveDictionary; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "NSDictionary+CaseInsensitive.h" | |
#include <vector> | |
#define PSPDF_ALLOW_CAST_QUALIFIERS(expression) _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wcast-qual\"") expression _Pragma("clang diagnostic pop") | |
@implementation NSDictionary (PSPDFFoundation) | |
static Boolean PSPDFCaseInsensitiveEqualCallback(const void *a, const void *b) { | |
id objA = (__bridge id)a, objB = (__bridge id)b; | |
Boolean ret = FALSE; | |
if ([objA isKindOfClass:NSString.class] && [objB isKindOfClass:NSString.class]) { | |
ret = ([objA compare:objB options:NSCaseInsensitiveSearch | NSLiteralSearch] == NSOrderedSame); | |
} else { | |
ret = (Boolean)[objA isEqual:objB]; | |
} | |
return ret; | |
} | |
static CFHashCode PSPDFCaseInsensitiveHashCallback(const void *value) { | |
id obj = (__bridge id)value; | |
NSObject *objToHash = PSPDFCast(obj, NSString).lowercaseString ?: obj; | |
return objToHash.hash; | |
} | |
- (NSDictionary *)pst_caseInsensitiveDictionary { | |
std::vector<void *> keys, values; | |
let count = CFDictionaryGetCount((CFDictionaryRef)self); | |
if (count) { | |
keys.reserve(count); | |
values.reserve(count); | |
PSPDF_ALLOW_CAST_QUALIFIERS(CFDictionaryGetKeysAndValues((CFDictionaryRef)self, (const void **)keys.data(), (const void **)values.data());) | |
} | |
CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks; | |
keyCallbacks.equal = PSPDFCaseInsensitiveEqualCallback; | |
keyCallbacks.hash = PSPDFCaseInsensitiveHashCallback; | |
PSPDF_ALLOW_CAST_QUALIFIERS(CFDictionaryRef caseInsensitiveDict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys.data(), (const void **)values.data(), count, &keyCallbacks, &kCFTypeDictionaryValueCallBacks);) | |
return (NSDictionary *)CFBridgingRelease(caseInsensitiveDict); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This works with Swift, as long as NSDictionary is retained:
To do this in all-Swift, use a different strategy:
https://stackoverflow.com/questions/33182260/case-insensitive-dictionary-in-swift
Bonus: Apple uses this in
NSHTTPURLResponse.allHeaderFields
and it might be surprising that this doesn't work in Swift the same way.For the
let
macro please see https://pspdfkit.com/blog/2017/even-swiftier-objective-c/#var-and-let