作为一名 iOS 开发者,肯定知道 NSArray/NSDictionary 不能存储 nil 值,如果你试图往数组/字典中存储 nil,那么 App 也将毫不客气的为你闪退。
尽管在日常的编码中,我们都会小心翼翼的处理 nil,但是总会有纰漏,毕竟大部分数据都是从服务器下发的,我们很难彻底把控。作为一名码农,肯定是想着怎么偷懒的,既能自动规避 nil,又能够不影响现有代码,最好不用引入第三方方法。得益于 Objective-C 的 runtime 机制,我们可以很优雅地通过 Method Swizlling 来解决上述问题。
但是我们需要注意一点的是,我们不能直接对 NSArray/NSMutableArray、NSDictionary/NSMutableDictionary 这些类进行 method swizlling 操作,因为它们底层是通过 Class cluster 来实现的,我们需要对隐藏在它们背后的真实的类进行 method swizlling,否则没任何效果。
#import <Foundation/Foundation.h> #import <objc/message.h>
void safe_swizzle_method(Class originalClass, Class swizzledClass, SEL originalSelector, SEL swizzledSelector) { Method originalMethod = class_getInstanceMethod(originalClass, originalSelector); Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
IMP originalIMP = method_getImplementation(originalMethod); IMP swizzledIMP = method_getImplementation(swizzledMethod); const char *originalType = method_getTypeEncoding(originalMethod); const char *swizzledType = method_getTypeEncoding(swizzledMethod);
class_replaceMethod(originalClass, swizzledSelector, originalIMP, originalType); class_replaceMethod(originalClass, originalSelector, swizzledIMP, swizzledType); }
#pragma mark - Array
@interface XPSafeArray : NSObject
@end
@implementation XPSafeArray
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSArray<NSString *> *array1 = @[ NSStringFromSelector(@selector(objectAtIndex:)), NSStringFromSelector(@selector(objectAtIndexedSubscript:)) ]; for (NSString *str in array1) { safe_swizzle_method(NSClassFromString(@"__NSArrayI"), self, NSSelectorFromString(str), NSSelectorFromString([@"safe_" stringByAppendingString:str])); }
NSArray<NSString *> *array2 = @[ NSStringFromSelector(@selector(objectAtIndex:)), NSStringFromSelector(@selector(objectAtIndexedSubscript:)), NSStringFromSelector(@selector(insertObject:atIndex:)), NSStringFromSelector(@selector(setObject:atIndexedSubscript:)), NSStringFromSelector(@selector(insertObjects:atIndexes:)) ]; for (NSString *str in array2) { safe_swizzle_method(NSClassFromString(@"__NSArrayM"), self, NSSelectorFromString(str), NSSelectorFromString([@"safe_" stringByAppendingString:str])); } }); }
- (id)safe_objectAtIndex:(NSUInteger)index { NSUInteger count = [(NSArray*)self count]; if (count == 0 || index >= count) { return nil; } return [self safe_objectAtIndex:index]; }
- (id)safe_objectAtIndexedSubscript:(NSUInteger)index { NSUInteger count = [(NSArray*)self count]; if (count == 0 || index >= count) { return nil; } return [self safe_objectAtIndexedSubscript:index]; }
- (void)safe_insertObject:(id)anObject atIndex:(NSUInteger)index { if (anObject == nil) return; [self safe_insertObject:anObject atIndex:index]; }
- (void)safe_setObject:(id)obj atIndexedSubscript:(NSUInteger)idx { if (obj == nil) return; [self safe_setObject:obj atIndexedSubscript:idx]; }
- (void)safe_insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes { if (objects && objects.count == indexes.count) { [self safe_insertObjects:objects atIndexes:indexes]; } }
@end
#pragma mark - Dictionary
@interface XPSafeDictionary : NSDictionary
@end
@implementation XPSafeDictionary
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ safe_swizzle_method(NSClassFromString(@"__NSPlaceholderDictionary"), self, @selector(initWithObjects:forKeys:count:), @selector(safe_initWithObjects:forKeys:count:));
safe_swizzle_method(NSClassFromString(@"__NSDictionaryM"), self, @selector(setObject:forKey:), @selector(safe_setObject:forKey:));
safe_swizzle_method(NSClassFromString(@"__NSDictionaryM"), self, @selector(removeObjectForKey:), @selector(safe_removeObjectForKey:)); }); }
- (id)safe_initWithObjects:(id _Nonnull const [])objects forKeys:(id<NSCopying> _Nonnull const [])keys count:(NSUInteger)cnt { id safeObjects[cnt]; id safeKeys[cnt]; NSUInteger count = 0;
for (NSUInteger idx=0; idx<cnt; idx++) { id key = keys[idx]; id obj = objects[idx];
if (!key) { continue; } if (!obj) { obj = [NSNull null]; // 可根据你项目需求,将`NSString`作为默认值 }
safeKeys[count] = key; safeObjects[count] = obj; count++; } return [self safe_initWithObjects:safeObjects forKeys:safeKeys count:count]; }
- (void)safe_setObject:(id)anObject forKey:(id<NSCopying>)aKey { if (anObject && aKey) { [self safe_setObject:anObject forKey:aKey]; } }
- (void)safe_removeObjectForKey:(id)aKey { if (aKey) { [self safe_removeObjectForKey:aKey]; } }
@end
以上只给出部分实现,但是已基本满足日常使用,你可以自己根据自己的需求进行完善。
|