4.4 TableView、CollectionView 异常加载占位图
为 TableView 建立一个 Category,Category 中添加刷新回调 block 属性、占位图 View 属性。
在分类中实现一个自定义的 xxx_reloadData 方法,在其中添加判断是否为空,以及加载占位图、隐藏占位图的相关代码。
利用 Method Swizzling 将 reloadData 方法和 xxx_reloadData 进行方法交换。
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface UITableView (ReloadDataSwizzling) @property (nonatomic, assign) BOOL firstReload; @property (nonatomic, strong) UIView *placeholderView; @property (nonatomic, copy) void(^reloadBlock)(void); @end /*--------------------------------------*/ #import "UITableView+ReloadDataSwizzling.h" #import "XXXPlaceholderView.h" #import <objc/runtime.h> @implementation UITableView (ReloadDataSwizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(reloadData); SEL swizzledSelector = @selector(xxx_reloadData); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)xxx_reloadData { if (!self.firstReload) { [self checkEmpty]; } self.firstReload = NO; [self xxx_reloadData]; } - (void)checkEmpty { BOOL isEmpty = YES; // 判空 flag 标示 id <UITableViewDataSource> dataSource = self.dataSource; NSInteger sections = 1; // 默认TableView 只有一组 if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { sections = [dataSource numberOfSectionsInTableView:self] - 1; // 获取当前TableView 组数 } for (NSInteger i = 0; i <= sections; i++) { NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i]; // 获取当前TableView各组行数 if (rows) { isEmpty = NO; // 若行数存在,不为空 } } if (isEmpty) { // 若为空,加载占位图 if (!self.placeholderView) { // 若未自定义,加载默认占位图 [self makeDefaultPlaceholderView]; } self.placeholderView.hidden = NO; [self addSubview:self.placeholderView]; } else { // 不为空,隐藏占位图 self.placeholderView.hidden = YES; } } - (void)makeDefaultPlaceholderView { self.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); XXXPlaceholderView *placeholderView = [[XXXPlaceholderView alloc] initWithFrame:self.bounds]; __weak typeof(self) weakSelf = self; [placeholderView setReloadClickBlock:^{ if (weakSelf.reloadBlock) { weakSelf.reloadBlock(); } }]; self.placeholderView = placeholderView; } - (BOOL)firstReload { return [objc_getAssociatedObject(self, @selector(firstReload)) boolValue]; } - (void)setFirstReload:(BOOL)firstReload { objc_setAssociatedObject(self, @selector(firstReload), @(firstReload), OBJC_ASSOCIATION_ASSIGN); } - (UIView *)placeholderView { return objc_getAssociatedObject(self, @selector(placeholderView)); } - (void)setPlaceholderView:(UIView *)placeholderView { objc_setAssociatedObject(self, @selector(placeholderView), placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void (^)(void))reloadBlock { return objc_getAssociatedObject(self, @selector(reloadBlock)); } - (void)setReloadBlock:(void (^)(void))reloadBlock { objc_setAssociatedObject(self, @selector(reloadBlock), reloadBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end
4.5 APM(应用性能管理)、防止程序崩溃
通过 Method Swizzling 替换 NSURLConnection , NSURLSession 相关的原始实现(例如 NSURLConnection 的构造方法和 start 方法),在实现中加入网络性能埋点行为,然后调用原始实现。从而来监控网络。
防止程序崩溃,可以通过 Method Swizzling 拦截容易造成崩溃的系统方法,然后在替换方法捕获异常类型 NSException ,再对异常进行处理。最常见的例子就是拦截 arrayWithObjects:count: 方法避免数组越界
@implementation NSArray (ArrayBounds) + (void)load{ [super load]; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //方法交换只要一次就好 //替换 objectAtIndex NSString *tmpStr = @"objectAtIndex:"; NSString *tmpFirstStr = @"safe_ZeroObjectAtIndex:"; NSString *tmpThreeStr = @"safe_objectAtIndex:"; NSString *tmpSecondStr = @"safe_singleObjectAtIndex:"; // 替换 objectAtIndexedSubscript NSString *tmpSubscriptStr = @"objectAtIndexedSubscript:"; NSString *tmpSecondSubscriptStr = @"safe_objectAtIndexedSubscript:"; [HIHookUtility swizzlingInClass:NSClassFromString(@"__NSArray0") originalSelector:NSSelectorFromString(tmpStr) swizzledSelector:NSSelectorFromString(tmpFirstStr)]; [HIHookUtility swizzlingInClass:NSClassFromString(@"__NSSingleObjectArrayI") originalSelector:NSSelectorFromString(tmpStr) swizzledSelector:NSSelectorFromString(tmpSecondStr)]; [HIHookUtility swizzlingInClass:NSClassFromString(@"__NSArrayI") originalSelector:NSSelectorFromString(tmpStr) swizzledSelector:NSSelectorFromString(tmpThreeStr)]; [HIHookUtility swizzlingInClass:NSClassFromString(@"__NSArrayI") originalSelector:NSSelectorFromString(tmpSubscriptStr) swizzledSelector:NSSelectorFromString(tmpSecondSubscriptStr)]; }); } #pragma mark --- implement method /** 取出NSArray 第index个 值 对应 __NSArrayI @param index 索引 index @return 返回值 */ - (id)safe_objectAtIndex:(NSUInteger)index { if (index > self.count - 1 || !self.count){ @try { return [self safe_objectAtIndex:index]; } @catch (NSException *exception) { //__throwOutException 抛出异常 NSAssert(NO, @"---index越界!!!"); NSLog(@"exception: %@", exception.reason); return nil; } @finally { } }else{ return [self safe_objectAtIndex:index]; } }
参考
『Runtime』详解(二)Method Swizzling
runtime中的交换方法method_exchangeImplementations存在的问题