上篇文章描述了对同类/类别中通过Method Swizzling实现Hook的方案。而在日常实际工作中,场景远比预想要复杂的多。MVC的设计模式意味着view中并不掺杂model的逻辑,并且view与model原则上是没有任何牵连的,如果想通过model的变化去控制view更新,苹果给出的解决方案有委托、通知、设置API等等,由Controller统一去统筹控制。
例如UITableView、UICollectionView等。
这类控件的展示与数据源的绑定关系体现在了委托代理、数据源代理上。而当我们想要Hook这些回调或代理方法时,同类/类别的Method Swizzling就只能望洋兴叹了,因为要拦截的方法的实现位置,往往并不是自身。
这时就体现出我们本章讨论内容的重要性了----跨类Method Swizzling。
话不多说,先上个图:
跨类Hook原理.png
相信图片已经将原理描述的非常清楚了,如果再有看不明白的。。。。那就再看一遍~
接下来是代码实现部分:
抛砖引玉,期待各位大神更优雅的实现。
//跨类交换方法
+ (void)exchangeImpWithOriginalClass:(Class)oriCls swizzledClass:(Class)swiCls originalSel:(SEL)oriSel swizzledSel:(SEL)swiSel tmpSel:(SEL)tmpSel msgForwardSel:(SEL)msgForwardSel
{
//增加原始方法
Method originalMethod = class_getInstanceMethod(oriCls, oriSel);
BOOL didAddOriMethod = class_addMethod(oriCls, oriSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
if (didAddOriMethod) {
originalMethod = class_getInstanceMethod(oriCls, oriSel);
SEL handleSel = msgForwardSel;
IMP handleIMP = class_getMethodImplementation(self, handleSel);
method_setImplementation(originalMethod, handleIMP);
}
//增加临时中转方法
class_addMethod(oriCls, tmpSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
Method swizzledMethod = class_getInstanceMethod(swiCls, swiSel);
class_replaceMethod(oriCls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(originalMethod));
}
代码对照着上图一起看,相信已经非常清晰了,不再赘述。
不明白msgForwardSel的作用的,可以看下我的上篇文章
接下来重点强调一下需要注意的点:
1.当我们在swiIMP中插入Hook后需要操作的代码时,一定要注意,此时self为oriCls实例而并非当前Hook类的实例。所以使用一些方法时一定要注意,可能造成欺骗过了编译器而形成了运行时crash。
2.在swiIMP中执行[self tmpSel]时,是安全的,这是由于我们创建tmpSel,是创建在了oriCls中,所以这里请放心使用。
3.tmpSel方法的本质只是提供一个交换时的临时宿主,所以该方法实现中的代码永远不会执行,可以不写或随意写一个return nil即可。
到了举例的阶段,一切的理论都要配合在实际场景中才显得更好理解:
例如:我们需要在获取UITableView有多少组,并且打印出来。
创建一个UITableView的类别,将下列代码粘贴进去
+ (void)load
{
//第一步:同类/类别交换setDataSource:方法。主要是用来获取tableView的DataSource实现实例
[self exchangeImpWithClass:self originalSel:@selector(setDataSource:) swizzledSel:@selector(hook_setDataSource:)];
}
- (void)hook_setDataSource:(id<UITableViewDataSource>)dataSource
{
//第二步:通过跨类交换numberOfSectionsInTableView:方法,来监听方法触发动作
[UITableView exchangeImpWithOriginalClass:[dataSource class] swizzledClass:[self class] originalSel:@selector(numberOfSectionsInTableView:) swizzledSel:@selector(hook_numberOfSectionsInTableView:) tmpSel:@selector(tmp_numberOfSectionsInTableView:) msgForwardSel:@selector(msgForward_numberOfSectionsInTableView:)];
[self hook_setDataSource:dataSource];
}
- (NSInteger)hook_numberOfSectionsInTableView:(UITableView *)tableView
{
//插入逻辑代码
NSInteger sec = [self tmp_numberOfSectionsInTableView:tableView];
NSLog(@"拦截到的组数为:%ld", (long)sec);
return sec;
}
- (NSInteger)tmp_numberOfSectionsInTableView:(UITableView *)tableView
{
return 0;
}
- (void)msgForward_numberOfSectionsInTableView:(UITableView *)tableView
{
//如果原类中,未实现numberOfSectionsInTableView: 则此方法会执行
NSLog(@"%s", __FUNCTION__);
}
#pragma mark - Swizzled
+ (void)exchangeImpWithClass:(Class)cls originalSel:(SEL)originalSel swizzledSel:(SEL)swizzledSel
{
Method originalMethod = class_getInstanceMethod(cls, originalSel);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);
BOOL didAddMethod = class_addMethod(cls,
originalSel,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
if (didAddMethod) {
//如果add成功,说明原始类并没有实现此方法的imp,为避免调用时执行消息转发,此处做统一处理
originalMethod = class_getInstanceMethod(cls, originalSel);
SEL handleSel = @selector(msgForwardHandle);
IMP handleIMP = class_getMethodImplementation(self, handleSel);
method_setImplementation(originalMethod, handleIMP);
}
method_exchangeImplementations(originalMethod, swizzledMethod);
}
//跨类交换方法
+ (void)exchangeImpWithOriginalClass:(Class)oriCls swizzledClass:(Class)swiCls originalSel:(SEL)oriSel swizzledSel:(SEL)swiSel tmpSel:(SEL)tmpSel msgForwardSel:(SEL)msgForwardSel
{
//增加原始方法
Method originalMethod = class_getInstanceMethod(oriCls, oriSel);
BOOL didAddOriMethod = class_addMethod(oriCls, oriSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
if (didAddOriMethod) {
originalMethod = class_getInstanceMethod(oriCls, oriSel);
SEL handleSel = msgForwardSel;
IMP handleIMP = class_getMethodImplementation(self, handleSel);
method_setImplementation(originalMethod, handleIMP);
}
//增加临时中转方法
class_addMethod(oriCls, tmpSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
Method swizzledMethod = class_getInstanceMethod(swiCls, swiSel);
class_replaceMethod(oriCls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(originalMethod));
}
- (void)msgForwardHandle
{
/**
备用方法,防止原类中没有实现需要交换的方法,导致交换后执行消息转发最终没有处理导致crash。
后续可以在做底层安全时,做到相应处理类中。
*/
NSLog(@"%s ", __FUNCTION__);
}
执行结果:
2017-06-23 16:38:33.579 HookDemo[22159:4362685] 拦截到的组数为:5
通过上述方案,可以实现非常强大的功能,例如通过类别方式实现UITableView/UICollectionView的刷新加载功能及自动显示隐藏空视图功能。
如有时间,我会再后续文章中会写下该模块及开源相关组件代码。
网友评论