MessageThrottle icon indicating copy to clipboard operation
MessageThrottle copied to clipboard

MTEngine.defaultEngine.classHooked 只有add,没有remove操作,和神策集成发生崩溃

Open ZhangTonghai opened this issue 4 years ago • 5 comments

我的工程中集成了神策统计,神策在处理统一的点击事件捕获是,使用了和MessageThrottle类似的生成一个形如EditViewController_6_XXXX 虚拟子类的操作,此处的数字会递增,反复进入同一个页面,可能会生成EditViewController_8_XXXX、EditViewController_9_XXXX 这种情况。

在 mt_overrideMethod 这个方法中,[MTEngine.defaultEngine.classHooked addObject:cls]添加的cls实际是神策统计生成的虚拟子类。 由于没有remove的操作,当下一次限流方法再次执行时,下面的代码在进行isSubclassOfClass判断是会发生崩溃,因为classHooked里的存放是EditViewController_8_XXXX,而传入的是EditViewController_9_XXXX。

  // check if subclass has hooked!
    for (Class clsHooked in MTEngine.defaultEngine.classHooked) {
        
        if (clsHooked != cls && [clsHooked isSubclassOfClass:cls]) {
            NSLog(@"Sorry: %@ used to be applied, can't apply it's super class %@!", NSStringFromClass(cls), NSStringFromClass(cls));
            return NO;
        }
    }

ZhangTonghai avatar Dec 02 '20 09:12 ZhangTonghai

@ZhangTonghai 没太看懂 crash 的原因,是 EditViewController_8_XXXX 这个类已经不存在了么?麻烦能提供个能复现的 demo 么?

yulingtianxia avatar Dec 02 '20 14:12 yulingtianxia

手误关闭了issue,叹气!!

ZhangTonghai avatar Dec 02 '20 15:12 ZhangTonghai

Demo

复现步骤: 1、运行后点击第一个页面红色cell,进入第二个vc 2、返回第一个vc并重复步骤1 3、重复步骤2 ,出现崩溃,如下图 azF3YEtRXUTcgLC

神策业务的大致流程是这样的:

- (void)sensorsdata_setDelegate:(id <UICollectionViewDelegate>)delegate(UIScrollView+AutoTrack.m)

+ (void)hookDidSelectMethodWithDelegate:(id)delegate(SADelegateProxy.m)

+ (NSString *)generateSensorsClassName:(id)obj(SADelegateProxy.m)

...

希望提供的资料有用。

ZhangTonghai avatar Dec 02 '20 16:12 ZhangTonghai

补充: 在设置collectionView的delegate之前调用mt_limitSelector则不会有问题,所以我还没断定问题在这边还是神策方面,忘指点。

ZhangTonghai avatar Dec 02 '20 16:12 ZhangTonghai

Crash 原因是 MessageThrottle 这边直接使用了神策动态创建的子类,然后下次神策创建新的子类时,会销毁旧创建的子类,导致下次 MessageThrottle 访问到不存在的类而 crash。神策使用 objc_disposeClassPair() 函数销毁创建的类时,一旦这个类存在实例,或存在其子类的实例,那么就会发生问题。因为神策在销毁子类之前并没有按照苹果官网文档的要求进行检查(是否存在实例或子类),而是暴力销毁,所以发生在神策之后的 Hook 操作都会受此影响,此问题需要神策来修复:

/** 
 * Destroy a class and its associated metaclass. 
 * 
 * @param cls The class to be destroyed. It must have been allocated with 
 *  \c objc_allocateClassPair
 * 
 * @warning Do not call if instances of this class or a subclass exist.
 */
OBJC_EXPORT void
objc_disposeClassPair(Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

『在设置collectionView的delegate之前调用mt_limitSelector则不会有问题』,是因为 MessageThrottle 先创建了子类并持有,神策后创建的子类并销毁,这样就影响不到 MessageThrottle 了。其实神策反复创建有计数功能的子类并逐个销毁的做法有一定风险,如果换成用属性等来计数可能会更好。

yulingtianxia avatar Dec 04 '20 14:12 yulingtianxia