
轉(zhuǎn)載當(dāng) NSDictionary 遇見(jiàn) nil
相信用 Objective-C 開(kāi)發(fā) iOS 應(yīng)用的人對(duì)下面的 crash 不會(huì)陌生:
*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]
*** setObjectForKey: key cannot be nil
*** setObjectForKey: object cannot be nil
Objective-C 里的 NSDictionary 是不支持 nil 作為 key 或者 value 的。但是總會(huì)有一些地方會(huì)偶然往 NSDictionary 里插入 nil value.
我們希望 NSDictionary 用起來(lái)是這樣的:
插入 nil 的時(shí)候不會(huì) crash
插入 nil 以后它對(duì)應(yīng)的 key 的確存在,且能取到值(NSNull)
被 serialize 成 JSON 的時(shí)候,被轉(zhuǎn)成 null
讓 NSNull 更接近 nil,可以吃任何方法不 crash
具體見(jiàn)代碼
//
// NSDictionary+NilSafe.m
// NSDictionary-NilSafe
//
// Created by Allen Hsu on 6/22/16.
// Copyright ? 2016 Glow Inc. All rights reserved.
//
#import <objc/runtime.h>
#import "NSDictionary+NilSafe.h"
@implementation NSObject (Swizzling)
+ (BOOL)gl_swizzleMethod:(SEL)origSel withMethod:(SEL)altSel {
Method origMethod = class_getInstanceMethod(self, origSel);
Method altMethod = class_getInstanceMethod(self, altSel);
if (!origMethod || !altMethod) {
return NO;
}
class_addMethod(self,
origSel,
class_getMethodImplementation(self, origSel),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel,
class_getMethodImplementation(self, altSel),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origSel),
class_getInstanceMethod(self, altSel));
return YES;
}
+ (BOOL)gl_swizzleClassMethod:(SEL)origSel withMethod:(SEL)altSel {
return [object_getClass((id)self) gl_swizzleMethod:origSel withMethod:altSel];
}
@end
@implementation NSDictionary (NilSafe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self gl_swizzleMethod:@selector(initWithObjects:forKeys:count:) withMethod:@selector(gl_initWithObjects:forKeys:count:)];
[self gl_swizzleClassMethod:@selector(dictionaryWithObjects:forKeys:count:) withMethod:@selector(gl_dictionaryWithObjects:forKeys:count:)];
});
}
+ (instancetype)gl_dictionaryWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {
id safeObjects[cnt];
id safeKeys[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt; i++) {
id key = keys[I];
id obj = objects[I];
if (!key) {
continue;
}
if (!obj) {
obj = [NSNull null];
}
safeKeys[j] = key;
safeObjects[j] = obj;
j++;
}
return [self gl_dictionaryWithObjects:safeObjects forKeys:safeKeys count:j];
}
- (instancetype)gl_initWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {
id safeObjects[cnt];
id safeKeys[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt; i++) {
id key = keys[I];
id obj = objects[I];
if (!key) {
continue;
}
if (!obj) {
obj = [NSNull null];
}
safeKeys[j] = key;
safeObjects[j] = obj;
j++;
}
return [self gl_initWithObjects:safeObjects forKeys:safeKeys count:j];
}
@end
@implementation NSMutableDictionary (NilSafe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = NSClassFromString(@"__NSDictionaryM");
[class gl_swizzleMethod:@selector(setObject:forKey:) withMethod:@selector(gl_setObject:forKey:)];
[class gl_swizzleMethod:@selector(setObject:forKeyedSubscript:) withMethod:@selector(gl_setObject:forKeyedSubscript:)];
});
}
- (void)gl_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
if (!aKey) {
return;
}
if (!anObject) {
anObject = [NSNull null];
}
[self gl_setObject:anObject forKey:aKey];
}
- (void)gl_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
if (!key) {
return;
}
if (!obj) {
obj = [NSNull null];
}
[self gl_setObject:obj forKeyedSubscript:key];
}
@end
@implementation NSNull (NilSafe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self gl_swizzleMethod:@selector(methodSignatureForSelector:) withMethod:@selector(gl_methodSignatureForSelector:)];
[self gl_swizzleMethod:@selector(forwardInvocation:) withMethod:@selector(gl_forwardInvocation:)];
});
}
- (NSMethodSignature *)gl_methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [self gl_methodSignatureForSelector:aSelector];
if (sig) {
return sig;
}
return [NSMethodSignature signatureWithObjCTypes:@encode(void)];
}
- (void)gl_forwardInvocation:(NSInvocation *)anInvocation {
NSUInteger returnLength = [[anInvocation methodSignature] methodReturnLength];
if (!returnLength) {
// nothing to do
return;
}
// set return value to all zero bits
char buffer[returnLength];
memset(buffer, 0, returnLength);
[anInvocation setReturnValue:buffer];
}
@end