分享

为 NSArray/NSDictionary 优雅地过滤 nil 值 | 吴品诚的技术博客

 jerry_tom123 2021-09-29

作为一名 iOS 开发者,肯定知道 NSArray/NSDictionary 不能存储 nil 值,如果你试图往数组/字典中存储 nil,那么 App 也将毫不客气的为你闪退。

尽管在日常的编码中,我们都会小心翼翼的处理 nil,但是总会有纰漏,毕竟大部分数据都是从服务器下发的,我们很难彻底把控。作为一名码农,肯定是想着怎么偷懒的,既能自动规避 nil,又能够不影响现有代码,最好不用引入第三方方法。得益于 Objective-C 的 runtime 机制,我们可以很优雅地通过 Method Swizlling 来解决上述问题。

但是我们需要注意一点的是,我们不能直接对 NSArray/NSMutableArray、NSDictionary/NSMutableDictionary 这些类进行 method swizlling 操作,因为它们底层是通过 Class cluster 来实现的,我们需要对隐藏在它们背后的真实的类进行 method swizlling,否则没任何效果。

  1. #import <Foundation/Foundation.h>
  2. #import <objc/message.h>
  3. void safe_swizzle_method(Class originalClass, Class swizzledClass, SEL originalSelector, SEL swizzledSelector)
  4. {
  5. Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
  6. Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
  7. IMP originalIMP = method_getImplementation(originalMethod);
  8. IMP swizzledIMP = method_getImplementation(swizzledMethod);
  9. const char *originalType = method_getTypeEncoding(originalMethod);
  10. const char *swizzledType = method_getTypeEncoding(swizzledMethod);
  11. class_replaceMethod(originalClass, swizzledSelector, originalIMP, originalType);
  12. class_replaceMethod(originalClass, originalSelector, swizzledIMP, swizzledType);
  13. }
  14. #pragma mark - Array
  15. @interface XPSafeArray : NSObject
  16. @end
  17. @implementation XPSafeArray
  18. + (void)load {
  19. static dispatch_once_t onceToken;
  20. dispatch_once(&onceToken, ^{
  21. NSArray<NSString *> *array1 = @[
  22. NSStringFromSelector(@selector(objectAtIndex:)),
  23. NSStringFromSelector(@selector(objectAtIndexedSubscript:))
  24. ];
  25. for (NSString *str in array1) {
  26. safe_swizzle_method(NSClassFromString(@"__NSArrayI"), self,
  27. NSSelectorFromString(str),
  28. NSSelectorFromString([@"safe_" stringByAppendingString:str]));
  29. }
  30. NSArray<NSString *> *array2 = @[
  31. NSStringFromSelector(@selector(objectAtIndex:)),
  32. NSStringFromSelector(@selector(objectAtIndexedSubscript:)),
  33. NSStringFromSelector(@selector(insertObject:atIndex:)),
  34. NSStringFromSelector(@selector(setObject:atIndexedSubscript:)),
  35. NSStringFromSelector(@selector(insertObjects:atIndexes:))
  36. ];
  37. for (NSString *str in array2) {
  38. safe_swizzle_method(NSClassFromString(@"__NSArrayM"), self,
  39. NSSelectorFromString(str),
  40. NSSelectorFromString([@"safe_" stringByAppendingString:str]));
  41. }
  42. });
  43. }
  44. - (id)safe_objectAtIndex:(NSUInteger)index {
  45. NSUInteger count = [(NSArray*)self count];
  46. if (count == 0 || index >= count) {
  47. return nil;
  48. }
  49. return [self safe_objectAtIndex:index];
  50. }
  51. - (id)safe_objectAtIndexedSubscript:(NSUInteger)index {
  52. NSUInteger count = [(NSArray*)self count];
  53. if (count == 0 || index >= count) {
  54. return nil;
  55. }
  56. return [self safe_objectAtIndexedSubscript:index];
  57. }
  58. - (void)safe_insertObject:(id)anObject atIndex:(NSUInteger)index {
  59. if (anObject == nil) return;
  60. [self safe_insertObject:anObject atIndex:index];
  61. }
  62. - (void)safe_setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
  63. if (obj == nil) return;
  64. [self safe_setObject:obj atIndexedSubscript:idx];
  65. }
  66. - (void)safe_insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes {
  67. if (objects && objects.count == indexes.count) {
  68. [self safe_insertObjects:objects atIndexes:indexes];
  69. }
  70. }
  71. @end
  72. #pragma mark - Dictionary
  73. @interface XPSafeDictionary : NSDictionary
  74. @end
  75. @implementation XPSafeDictionary
  76. + (void)load {
  77. static dispatch_once_t onceToken;
  78. dispatch_once(&onceToken, ^{
  79. safe_swizzle_method(NSClassFromString(@"__NSPlaceholderDictionary"),
  80. self,
  81. @selector(initWithObjects:forKeys:count:),
  82. @selector(safe_initWithObjects:forKeys:count:));
  83. safe_swizzle_method(NSClassFromString(@"__NSDictionaryM"),
  84. self,
  85. @selector(setObject:forKey:),
  86. @selector(safe_setObject:forKey:));
  87. safe_swizzle_method(NSClassFromString(@"__NSDictionaryM"),
  88. self,
  89. @selector(removeObjectForKey:),
  90. @selector(safe_removeObjectForKey:));
  91. });
  92. }
  93. - (id)safe_initWithObjects:(id _Nonnull const [])objects forKeys:(id<NSCopying> _Nonnull const [])keys count:(NSUInteger)cnt {
  94. id safeObjects[cnt];
  95. id safeKeys[cnt];
  96. NSUInteger count = 0;
  97. for (NSUInteger idx=0; idx<cnt; idx++) {
  98. id key = keys[idx];
  99. id obj = objects[idx];
  100. if (!key) {
  101. continue;
  102. }
  103. if (!obj) {
  104. obj = [NSNull null]; // 可根据你项目需求,将`NSString`作为默认值
  105. }
  106. safeKeys[count] = key;
  107. safeObjects[count] = obj;
  108. count++;
  109. }
  110. return [self safe_initWithObjects:safeObjects forKeys:safeKeys count:count];
  111. }
  112. - (void)safe_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
  113. if (anObject && aKey) {
  114. [self safe_setObject:anObject forKey:aKey];
  115. }
  116. }
  117. - (void)safe_removeObjectForKey:(id)aKey {
  118. if (aKey) {
  119. [self safe_removeObjectForKey:aKey];
  120. }
  121. }
  122. @end

以上只给出部分实现,但是已基本满足日常使用,你可以自己根据自己的需求进行完善。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多