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
iOS黑魔法 - Method Swizzling
runtime中的交换方法method_exchangeImplementations存在的问题