[iOS] MessageThrottle源码分析

2020-04-22  本文已影响0人  木小易Ying

这个库就是限流的那个库,他能限制一个方法的调用,在一段时间内无论你调几次都只执行一次~ 地址:https://github.com/yulingtianxia/MessageThrottle

使用也挺简单的:

Stub *s = [Stub new];
// You can also assign `Stub.class` or `mt_metaClass(Stub.class)` to `target` argument.
MTRule *rule = [[MTRule alloc] initWithTarget:s selector:@selector(foo:) durationThreshold:0.01];
rule.mode = MTModePerformLast; // Or `MTModePerformFirstly`, ect
[rule apply];

所以其实限流就是在创建一个rule然后apply的时候做的~

所以先来看看apply:

- (BOOL)apply
{
    return [MTEngine.defaultEngine applyRule:self];
}

这里可以看到一个MTEngine.defaultEngine单例~ 先看看它是啥~

@interface MTEngine ()

@property (nonatomic) NSMapTable<id, NSMutableSet<NSString *> *> *targetSELs;
@property (nonatomic) NSMutableSet<Class> *classHooked;

- (void)discardRule:(MTRule *)rule whenTargetDealloc:(MTDealloc *)mtDealloc;

@end

@implementation MTEngine

static pthread_mutex_t mutex;
NSString * const kMTPersistentRulesKey = @"kMTPersistentRulesKey";

这里MTEngine是单例,它有两个用于存储限制什么对象的什么方法调用频次的属性,一个是NSMapTable类型的targetSELs,一个是NSMutableSet类型的classHooked。这俩我们可以先不看,只看持久化的部分。

+ (void)load
{
    NSArray<NSData *> *array = [NSUserDefaults.standardUserDefaults objectForKey:kMTPersistentRulesKey];
    for (NSData *data in array) {
        if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
            NSError *error = nil;
            MTRule *rule = [NSKeyedUnarchiver unarchivedObjectOfClass:MTRule.class fromData:data error:&error];
            if (error) {
                NSLog(@"%@", error.localizedDescription);
            }
            else {
                [rule apply];
            }
        } else {
            @try {
                MTRule *rule = [NSKeyedUnarchiver unarchiveObjectWithData:data];
                [rule apply];
            } @catch (NSException *exception) {
                NSLog(@"%@", exception.description);
            }
        }
    }
}

在load的时候,也就是类被加载的时候,他就会到NSUserDefaults.standardUserDefaults里面找kMTPersistentRulesKey的值,然后解包为MTRule以后依次apply。

并且其实这个类init的时候就注册了监听,当监听到app kill的时候去保存当前的rules:

- (instancetype)init
{
    self = [super init];
    if (self) {
        _targetSELs = [NSMapTable weakToStrongObjectsMapTable];
        _classHooked = [NSMutableSet set];
        pthread_mutex_init(&mutex, NULL);
        NSNotificationName name = nil;
#if TARGET_OS_IOS || TARGET_OS_TV
        name = UIApplicationWillTerminateNotification;
#elif TARGET_OS_OSX
        name = NSApplicationWillTerminateNotification;
#endif
        if (name.length > 0) {
            [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleAppWillTerminateNotification:) name:name object:nil];
        }
    }
    return self;
}

- (void)handleAppWillTerminateNotification:(NSNotification *)notification
{
    if (@available(macOS 10.11, *)) {
        [self savePersistentRules];
    }
}

- (void)savePersistentRules
{
    NSMutableArray<NSData *> *array = [NSMutableArray array];
    for (MTRule *rule in self.allRules) {
        if (rule.isPersistent) {
            NSData *data;
            if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
                NSError *error = nil;
                data = [NSKeyedArchiver archivedDataWithRootObject:rule requiringSecureCoding:YES error:&error];
                if (error) {
                    NSLog(@"%@", error.localizedDescription);
                }
            } else {
                data = [NSKeyedArchiver archivedDataWithRootObject:rule];
            }
            if (data) {
                [array addObject:data];
            }
        }
    }
    [NSUserDefaults.standardUserDefaults setObject:array forKey:kMTPersistentRulesKey];
}

那么这里为什么MTRule是可以被解包的呢?

@interface MTRule () <NSSecureCoding>

#pragma mark NSSecureCoding

+ (BOOL)supportsSecureCoding
{
    return YES;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    if (mt_object_isClass(self.target)) {
        Class cls = self.target;
        NSString *classKey = @"target";
        if (class_isMetaClass(cls)) {
            classKey = @"meta_target";
        }
        [aCoder encodeObject:NSStringFromClass(cls) forKey:classKey];
        [aCoder encodeObject:NSStringFromSelector(self.selector) forKey:@"selector"];
        [aCoder encodeDouble:self.durationThreshold forKey:@"durationThreshold"];
        [aCoder encodeObject:@(self.mode) forKey:@"mode"];
        [aCoder encodeDouble:self.lastTimeRequest forKey:@"lastTimeRequest"];
        [aCoder encodeBool:self.isPersistent forKey:@"persistent"];
        [aCoder encodeObject:NSStringFromSelector(self.aliasSelector) forKey:@"aliasSelector"];
    }
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    id target = NSClassFromString([aDecoder decodeObjectOfClass:NSString.class forKey:@"target"]);
    if (!target) {
        target = NSClassFromString([aDecoder decodeObjectOfClass:NSString.class forKey:@"meta_target"]);
        target = mt_metaClass(target);
    }
    if (target) {
        SEL selector = NSSelectorFromString([aDecoder decodeObjectOfClass:NSString.class forKey:@"selector"]);
        NSTimeInterval durationThreshold = [aDecoder decodeDoubleForKey:@"durationThreshold"];
        MTPerformMode mode = [[aDecoder decodeObjectForKey:@"mode"] unsignedIntegerValue];
        NSTimeInterval lastTimeRequest = [aDecoder decodeDoubleForKey:@"lastTimeRequest"];
        BOOL persistent = [aDecoder decodeBoolForKey:@"persistent"];
        NSString *aliasSelector = [aDecoder decodeObjectOfClass:NSString.class forKey:@"aliasSelector"];
        
        self = [self initWithTarget:target selector:selector durationThreshold:durationThreshold];
        self.mode = mode;
        self.lastTimeRequest = lastTimeRequest;
        self.persistent = persistent;
        self.aliasSelector = NSSelectorFromString(aliasSelector);
        return self;
    }
    return nil;
}

使用NSCoding来读写用户数据文件的问题在于,把全部的类编码到一个文件里,也就间接地给了这个文件访问你APP里面实例类的权限。

虽然你不能在一个NSCoded文件里(至少在iOS中的)存储可执行代码,但是一名黑客可以使用特制地文件骗过你的APP进入到实例化类中,这是你从没打算做的,或者是你想要在另一个不同的上下文时才做的。尽管以这种方式造成实际性的破坏很难,但是无疑会导致用户的APP崩溃掉或者数据丢失。

在iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。

因为MTRule实现了NSSecureCoding的协议,并且注意哦,只有if (mt_object_isClass(self.target))的时候,才会encode,也就是只对类对象的rule会持久化。


那么MTEnginetargetSELs的结构是什么样子呢?我们可以看看它是怎么把所有rule都拿出来的,也就是知道它存入的结构了~

- (NSArray<MTRule *> *)allRules
{
    pthread_mutex_lock(&mutex);
    NSMutableArray *rules = [NSMutableArray array];
    for (id target in [[self.targetSELs keyEnumerator] allObjects]) {
        NSMutableSet *selectors = [self.targetSELs objectForKey:target];
        for (NSString *selectorName in selectors) {
            MTDealloc *mtDealloc = objc_getAssociatedObject(target, NSSelectorFromString(selectorName));
            if (mtDealloc.rule) {
                [rules addObject:mtDealloc.rule];
            }
        }
    }
    pthread_mutex_unlock(&mutex);
    return [rules copy];
}

所以其实targetSELs这个map是以target对象为key,value是一个set,这个set存了所有这个对象限流的方法。

而实际的rule,会被包装为MTDealloc通过关联对象保存给对象,关联对象的key就是限流方法名。

这点可以在看applyRule的时候再验证这个推测~


MTEngineapplyRule是酱紫的:

- (BOOL)applyRule:(MTRule *)rule
{
    pthread_mutex_lock(&mutex);
    MTDealloc *mtDealloc = [rule mt_deallocObject];
    [mtDealloc lock];
    BOOL shouldApply = YES;
    if (mt_checkRuleValid(rule)) {
        ……
        shouldApply = shouldApply && mt_overrideMethod(rule);
        if (shouldApply) {
            [self addSelector:rule.selector onTarget:rule.target];
            rule.active = YES;
        }
    }
    else {
        shouldApply = NO;
        NSLog(@"Sorry: invalid rule.");
    }
    [mtDealloc unlock];
    if (!shouldApply) {
        objc_setAssociatedObject(rule.target, rule.selector, nil, OBJC_ASSOCIATION_RETAIN);
    }
    pthread_mutex_unlock(&mutex);
    return shouldApply;
}

会先生成一个记录rule的MTDealloc对象,然后check是不是应该apply rule,首先这个rule应该是合法的(mt_checkRuleValid做的事情就是check target & selector不为空,durationThreshold>0,方法不是forwardInvocation,并且对象不是MTRule以及MTEngine类)。

如果是不合法,那么就给target对象设置关联对象,key是selector,value是nil。

那么如果是valid rule会做什么呢?

for (id target in [[self.targetSELs keyEnumerator] allObjects]) {
    NSMutableSet *selectors = [self.targetSELs objectForKey:target];
    NSString *selectorName = NSStringFromSelector(rule.selector);
    // 遍历targetSELs的key,拿对应已经加过rule的selectors,如果包含要加的selector,则比对是不是target一致,一致则跳过
    if ([selectors containsObject:selectorName]) {
        if (target == rule.target) {
            shouldApply = NO;
            continue;
        }

        // 如果有加过同名selector,但是target不一致
        // 并且两者都是Class
        if (mt_object_isClass(rule.target) && mt_object_isClass(target)) {
            Class clsA = rule.target;
            Class clsB = target;
            // 如果两者是subclass的关系,就不应该apply
            shouldApply = !([clsA isSubclassOfClass:clsB] || [clsB isSubclassOfClass:clsA]);
            // inheritance relationship
            if (!shouldApply) {
                NSLog(@"Sorry: %@ already apply rule in %@. A message can only have one rule per class hierarchy.", selectorName, NSStringFromClass(clsB));
                break;
            }
        }
        // 如果target是class,并且是想要应用的rule.target的class,并且selector同名,那么也不应该apply
        else if (mt_object_isClass(target) && target == object_getClass(rule.target)) {
            shouldApply = NO;
            NSLog(@"Sorry: %@ already apply rule in target's Class(%@).", selectorName, target);
            break;
        }
    }
}

// 如果到这里shouldApply为YES,会执行mt_overrideMethod
shouldApply = shouldApply && mt_overrideMethod(rule);

// 如果仍旧应该apply,把selector和target记录到自己的targetSELs里面,并把rule active
if (shouldApply) {
    [self addSelector:rule.selector onTarget:rule.target];
    rule.active = YES;
}

上面的判断是循环target,然后看对应的selectors是不是包含想要加的selector名字,这里为什么不直接通过rule.target为key从self. targetSELs取对应的set,然后遍历看有木有同名selector即可??

那么mt_overrideMethod里面做了什么呢?

static BOOL mt_overrideMethod(MTRule *rule)
{
    id target = rule.target;
    SEL selector = rule.selector;
    // 加了_mt_前缀的方法
    SEL aliasSelector = rule.aliasSelector;
    Class cls;
    Class statedClass = [target class];
    Class baseClass = object_getClass(target);
    NSString *className = NSStringFromClass(baseClass);
    
    // 已经被MT Hook过了
    if ([className hasPrefix:MTSubclassPrefix]) {
        cls = baseClass;
    }
    // target是class,不是实例对象
    else if (mt_object_isClass(target)) {
        cls = target;
    }
    // class方法返回和object_getClass不一致,以object_getClass为准,因为可能被其他库hook过
    else if (statedClass != baseClass) {
        cls = baseClass;
    }
    // 正常走下面,创建一个MTSubclassPrefix前缀的子类,让它的父类指向原来的target的class
    else {
        const char *subclassName = [MTSubclassPrefix stringByAppendingString:className].UTF8String;
        Class subclass = objc_getClass(subclassName);
        
        if (subclass == nil) {
            subclass = objc_allocateClassPair(baseClass, subclassName, 0);
            if (subclass == nil) {
                NSLog(@"objc_allocateClassPair failed to allocate class %s.", subclassName);
                return NO;
            }

            // 让subclass的class方法返回原类,以及subclass的class的class方法也返回原类
            mt_hookedGetClass(subclass, statedClass);
            mt_hookedGetClass(object_getClass(subclass), statedClass);
            objc_registerClassPair(subclass);
        }

        // 让target的class是subclass,也就是MTSubclassPrefix前缀的子类
        object_setClass(target, subclass);
        cls = subclass;
    }
    
    // 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;
        }
    }
    
    // 设置MTDealloc的class为新建的子类
    [rule mt_deallocObject].cls = cls;
    
    // 如果原类覆写过forwardInvocation方法,且这个方法不是我们hook的mt_forwardInvocation方法,就把他放到MTForwardInvocationSelectorName这个method里面保存起来,并用mt_forwardInvocation替代原版的方法。
    if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)mt_forwardInvocation) {
        IMP originalImplementation = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)mt_forwardInvocation, "v@:@");
        if (originalImplementation) {
            class_addMethod(cls, NSSelectorFromString(MTForwardInvocationSelectorName), originalImplementation, "v@:@");
        }
    }
    
    // superCls就是原有target的类
    Class superCls = class_getSuperclass(cls);
    // 子类的target method
    Method targetMethod = class_getInstanceMethod(cls, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);

    // 如果这个targetMethodIMP不是消息转发方法
    if (!mt_isMsgForwardIMP(targetMethodIMP)) {
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        Method targetAliasMethod = class_getInstanceMethod(cls, aliasSelector);
        Method targetAliasMethodSuper = class_getInstanceMethod(superCls, aliasSelector);

        // 把原有的method转给aliasSelector
        if (![cls instancesRespondToSelector:aliasSelector] || targetAliasMethod == targetAliasMethodSuper) {
            __unused BOOL addedAlias = class_addMethod(cls, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), cls);
        }

        // 替换新建子类的selector为mt_getMsgForwardIMP方法,然后记录一下这个类已经被hook过了
        class_replaceMethod(cls, selector, mt_getMsgForwardIMP(statedClass, selector), typeEncoding);
        [MTEngine.defaultEngine.classHooked addObject:cls];
    }
    
    return YES;
}

所以其实上面这段做了:

  1. 创建MTSubclassPrefix = @"_MessageThrottle_"前缀的子类,让它的父类指向原来target的class
  2. 替换子类的forwardInvocation方法为mt_forwardInvocation,然后把原来的forwardInvocation方法存到MTForwardInvocationSelectorName里面
  3. 把原有的selector方法存入aliasSelector里
  4. mt_getMsgForwardIMP替代原有的selector

我用MTDemo跑了一下,打印了加throttle前后的class方法列表:

2020-04-22 08:00:23.214945+0800 MTDemo[4987:1268506] 方法名:foo:,参数个数:3,编码方式:v24@0:8@16
2020-04-22 08:00:23.215030+0800 MTDemo[4987:1268506] 方法名:.cxx_destruct,参数个数:2,编码方式:v16@0:8
2020-04-22 08:00:23.215072+0800 MTDemo[4987:1268506] 方法名:bar,参数个数:2,编码方式:@16@0:8
2020-04-22 08:00:23.215116+0800 MTDemo[4987:1268506] 方法名:setBar:,参数个数:3,编码方式:v24@0:8@16
2020-04-22 08:00:23.215177+0800 MTDemo[4987:1268506] ===========


2020-04-22 08:00:23.215704+0800 MTDemo[4987:1268506] 方法名:foo:,参数个数:3,编码方式:v24@0:8@16
2020-04-22 08:00:23.215763+0800 MTDemo[4987:1268506] 方法名:__mt_foo:,参数个数:3,编码方式:v24@0:8@16
2020-04-22 08:00:23.215800+0800 MTDemo[4987:1268506] 方法名:forwardInvocation:,参数个数:3,编码方式:v@:@
2020-04-22 08:00:23.215834+0800 MTDemo[4987:1268506] 方法名:class,参数个数:2,编码方式:#16@0:8

注意这里加了throttle以后不能用[obj class]去那方法列表,否则和之前是一样的,因为它的代码改了新建子类的class方法返回值,所以要用object_getClass拿到真正的class。


mt_getMsgForwardIMP返回的就是一个_objc_msgForward,所以当我们调用target被限流的selector的时候,其实是先转发给了mt_forwardInvocation,然后如果应该相应就执行mt_handleInvocation,如果不应该就转发给它原始的forward。

static void mt_forwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{
    MTDealloc *mtDealloc = nil;
    // 如果target不是class
    if (!mt_object_isClass(invocation.target)) {
        mtDealloc = objc_getAssociatedObject(invocation.target, invocation.selector);
    }
    // 如果target是class
    else {
        mtDealloc = objc_getAssociatedObject(object_getClass(invocation.target), invocation.selector);
    }
    
    BOOL respondsToAlias = YES;
    Class cls = object_getClass(invocation.target);
    
    do {
        // 如果类自己的关联对象上面没有拿到mtDealloc,就找它的class拿
        if (!mtDealloc.rule) {
            mtDealloc = objc_getAssociatedObject(cls, invocation.selector);
        }
        // target的class是新建的子类,这里看子类是不是响应aliasSelector
        if ((respondsToAlias = [cls instancesRespondToSelector:mtDealloc.rule.aliasSelector])) {
            break;
        }
        mtDealloc = nil;
    }
    // 如果当前类不响应aliasSelector就一直上溯找superclass,看父类是不是响应
    while (!respondsToAlias && (cls = class_getSuperclass(cls)));
    
    [mtDealloc lock];
    
    // 如果始终不响应,那么这个selector就转给最原始的forward方法
    if (!respondsToAlias) {
        mt_executeOrigForwardInvocation(assignSlf, selector, invocation);
    }
    else {
        // 如果响应,则自己去判断是不是满足了阈值要求,满足了才invoke
        mt_handleInvocation(invocation, mtDealloc.rule);
    }
    
    [mtDealloc unlock];
}

然后看下handle是如何做的,其实就是看是不是满足时间条件,如果满足就调用rule.aliasSelector:

static void mt_handleInvocation(NSInvocation *invocation, MTRule *rule)
{
    NSCParameterAssert(invocation);
    NSCParameterAssert(rule);
    
    // rule如果不isActive就直接invoke
    if (!rule.isActive) {
        [invocation invoke];
        return;
    }
    
    // 如果rule.durationThreshold小于等于0,或者mt_invokeFilterBlock返回yes,就调用rule.aliasSelector
    if (rule.durationThreshold <= 0 || mt_invokeFilterBlock(rule, invocation)) {
        invocation.selector = rule.aliasSelector;
        [invocation invoke];
        return;
    }
    
    NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
    now += MTEngine.defaultEngine.correctionForSystemTime;
    
    switch (rule.mode) {
        case MTPerformModeFirstly: {
            if (now - rule.lastTimeRequest > rule.durationThreshold) {
                // 如果当前-上次调用>时间阈值,则立刻调用
                invocation.selector = rule.aliasSelector;
                [invocation invoke];
                rule.lastTimeRequest = now;
                dispatch_async(rule.messageQueue, ^{
                    // May switch from other modes, set nil just in case.
                    rule.lastInvocation = nil;
                });
            }
            break;
        }
        case MTPerformModeLast: {
            invocation.selector = rule.aliasSelector;
            [invocation retainArguments];
            dispatch_async(rule.messageQueue, ^{
                rule.lastInvocation = invocation;
                // 如果当前-上次调用>时间阈值,dispatch_after在rule.durationThreshold时间之后调用
                if (now - rule.lastTimeRequest > rule.durationThreshold) {
                    rule.lastTimeRequest = now;
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(rule.durationThreshold * NSEC_PER_SEC)), rule.messageQueue, ^{
                        if (!rule.isActive) {
                            rule.lastInvocation.selector = rule.selector;
                        }
                        [rule.lastInvocation invoke];
                        rule.lastInvocation = nil;
                    });
                }
            });
            break;
        }
        case MTPerformModeDebounce: {
            invocation.selector = rule.aliasSelector;
            [invocation retainArguments];
            dispatch_async(rule.messageQueue, ^{
                rule.lastInvocation = invocation;
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(rule.durationThreshold * NSEC_PER_SEC)), rule.messageQueue, ^{
                    if (rule.lastInvocation == invocation) {
                        if (!rule.isActive) {
                            rule.lastInvocation.selector = rule.selector;
                        }
                        [rule.lastInvocation invoke];
                        rule.lastInvocation = nil;
                    }
                });
            });
            break;
        }
    }
}

上面有两个地方觉得比较奇怪:


那么卸载rule的时候做了啥呢?其实就是让target的类还原为之前的类,而非加了MT前缀的子类,并且通过mt_revertHook方法把selector以及消息转发都还原了~

- (BOOL)discardRule:(MTRule *)rule
{
    pthread_mutex_lock(&mutex);
    MTDealloc *mtDealloc = [rule mt_deallocObject];
    [mtDealloc lock];
    BOOL shouldDiscard = NO;
    if (mt_checkRuleValid(rule)) {
        [self removeSelector:rule.selector onTarget:rule.target];
        shouldDiscard = mt_recoverMethod(rule.target, rule.selector, rule.aliasSelector);
        rule.active = NO;
    }
    [mtDealloc unlock];
    pthread_mutex_unlock(&mutex);
    return shouldDiscard;
}

主要是这个方法,并且设置了active,但这里其实有一个问题诶,如果shouldDiscard是NO,那么就不应该被丢弃,那么就不应该还原,这个时候如果就把rule.active设置为NO了,但又没还原selector方法,不就可能死循环了么?

mt_recoverMethod做了神马呢?

static BOOL mt_recoverMethod(id target, SEL selector, SEL aliasSelector)
{
    Class cls;
    if (mt_object_isClass(target)) {
        cls = target;
        if ([MTEngine.defaultEngine containsSelector:selector onTargetsOfClass:cls]) {
            return NO;
        }
    }
    else {
        MTDealloc *mtDealloc = objc_getAssociatedObject(target, selector);
        // get class when apply rule on target.
        cls = mtDealloc.cls;
        // target current real class name
        NSString *className = NSStringFromClass(object_getClass(target));
        if ([className hasPrefix:MTSubclassPrefix]) {
            Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:MTSubclassPrefix withString:@""]);
            NSCAssert(originalClass != nil, @"Original class must exist");
            if (originalClass) {
                object_setClass(target, originalClass);
            }
        }
        if ([MTEngine.defaultEngine containsSelector:selector onTarget:cls] ||
            [MTEngine.defaultEngine containsSelector:selector onTargetsOfClass:cls]) {
            return NO;
        }
    }
    mt_revertHook(cls, selector, aliasSelector);
    return YES;
}

这个库我们用的时候有遇到的crash,感觉其实有点问题,就是改了太多runtime的了,因为我们的crash我尝试想自己实现一个不hook的,然后就更深刻的体会到了这个库超方便虽然我还没想好咋写,膜拜灰常厉害的作者~

上一篇 下一篇

猜你喜欢

热点阅读