iOS - Runtime Method Swizzling(下)

简介: Runtime合集 iOS - Runtime基础
+关注继续查看

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存在的问题


相关文章
|
4天前
|
iOS开发
iOS开发 GET、POST请求方法:NSURLSession篇
iOS开发 GET、POST请求方法:NSURLSession篇
6 0
|
3月前
|
开发框架 Dart 开发工具
使用Flutter开发一套可同时运行在Android和iOS平台的代码
Flutter是一种跨平台移动应用开发框架,它允许开发者使用单一代码库构建高性能、美观且可在多个平台上运行的应用程序。本文将介绍如何使用Flutter开发一套同时适用于Android和iOS平台的代码。
|
3月前
|
iOS开发
iOS开发中使用第三方静态字体
iOS开发中使用第三方静态字体
|
iOS开发
iOS - Runtime Swizzling 源码剖析
Runtime源码下载 源码位于objc-class-old.m
|
iOS开发
iOS - Runtime Method Swizzling(中)
Runtime合集 iOS - Runtime基础
|
iOS开发
iOS - Runtime Method Swizzling(上)
Runtime合集 iOS - Runtime基础
iOS - Runtime Method Swizzling(上)
|
API iOS开发
iOS底层原理:Method Swizzling原理和注意事项(一)
Method Swizzling的含义是方法交换,其核心内容是使用runtime api在运行时将一个方法的实现替换成另一个方法的实现。我们利用它可以替换系统或者我们自定义类的方法实现,进而达到我们的特殊目的,这就是我们常说的iOS黑魔法。
iOS底层原理:Method Swizzling原理和注意事项(一)
|
API iOS开发
iOS底层原理:Method Swizzling原理和注意事项(二)
Method Swizzling的含义是方法交换,其核心内容是使用runtime api在运行时将一个方法的实现替换成另一个方法的实现。我们利用它可以替换系统或者我们自定义类的方法实现,进而达到我们的特殊目的,这就是我们常说的iOS黑魔法。
iOS底层原理:Method Swizzling原理和注意事项(二)
|
iOS开发 Swift
【iOS开发】扯淡 Method Swizzling
写在前面 关于 Method Swizzling 这个东西,已经有很多高人写了详细的文章来介绍,我就不再班门弄斧,往深了说了。 而且不作延伸的话,这项技术本身也没有复杂到要长文论述的地步。
924 0
相关产品
云迁移中心
推荐文章
更多