Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active December 2, 2020 01:56
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steipete/5bd38c1dc9a517ad414c043fbcc8435d to your computer and use it in GitHub Desktop.
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/
/// 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
#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
@steipete
Copy link
Author

steipete commented Nov 25, 2020

This works with Swift, as long as NSDictionary is retained:

let dict = ["A": "one", "b": "two"]
let csdict = ((dict as NSDictionary).caseInsensitive as NSDictionary)
let csdicts = (dict as NSDictionary).caseInsensitive
let test = dict["a"]    // nil
let test2 = csdict["a"] // "one"
let test3 = csdicts["a"] // nil (the cast/copy to Swift loses the custom CFDictionary callbacks)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment