SDMagicHook icon indicating copy to clipboard operation
SDMagicHook copied to clipboard

SDMagicHook与Aspects的异同

Open gsdios opened this issue 4 years ago • 3 comments

aspects和SDMagicHook基本思路都是基于类似kvo的isa替换,但是从api设计以及实现上也有明显的区别,我们通过以下示例简要介绍下:

1.解决了Aspects未能解决的KVO兼容问题,详见 https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247486231&idx=1&sn=1c6584e9dcc3edf71c42cf396bcab051&chksm=e9d0c0f5dea749e34bf23de8259cbc7c868d3c8a6fc56c4366412dfb03eac8f037ee1d8668a1&token=1383088962&lang=zh_CN#rd

2.设计实现了一套更为高效灵活的API,举例如下 假设有这样一个自定义类Test,在其内部定义了一个求和的方法,接收四个int类型的参数。


@implementation Test

- (int)sumWithA:(int)a b:(int)b c:(int)c d:(int)d {
    return a + b + c + d;
}

@end

现在要求将四个参数分别平方然后再求和。

使用aspects实现如下:

    Test *testObj = [Test new];
    [testObj aspect_hookSelector:@selector(sumWithA:b:c:d:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info, int a, int b, int c, int d) {
        int aa = a * a;
        int bb = b * b;
        int cc = c * c;
        int dd = d * d;
        [info.originalInvocation setArgument:&aa atIndex:2];
        [info.originalInvocation setArgument:&bb atIndex:3];
        [info.originalInvocation setArgument:&cc atIndex:4];
        [info.originalInvocation setArgument:&dd atIndex:5];
    } error:NULL];

    int sum = [testObj sumWithA:1 b:2 c:3 d:4];
    NSLog(@">>>> %d", sum); // >>>> 30

使用SDMagicHook实现如下:

    Test *testObj = [Test new];
    [testObj hookMethod:@selector(sumWithA:b:c:d:) impBlock:^(typeof(testObj) this, int a, int b, int c, int d) {
        __block int res;
        [this callOriginalMethodInBlock:^{
            res = [this sumWithA:a * a b:b * b c:c * c d:d * d];
        }];
        return res;
    }];

    int sum = [testObj sumWithA:1 b:2 c:3 d:4];
    NSLog(@">>>> %d", sum); // >>>> 30

由以上demo可以看出: 1.aspects使用AspectOptions来决定自定义方法和原始方法的执行顺序;SDMagicHook使用callOriginalMethodInBlock来调用原始方法,可以将原始方法放在自定义逻辑的前、中、后任意位置执行,更加灵活方便。

2.aspects将原始方法封装在NSInvocation里面,如果想要修改sumWithA:b:c:d:的参数值需要调用setArgument:atIndex:方法来实现,api不够简洁友好;SDMagicHook只需在callOriginalMethodInBlock的block参数内部直接调用原始的sumWithA:b:c:d:方法传参即可,直观简便。

gsdios avatar Mar 03 '20 13:03 gsdios

1.Aspects在你说的那三个问题中是不存在的。实验了一下这三种情况,KVO和Aspects可以工作良好。虽然不知道readme是因为忘记更新了,还是说有其他的问题存在(不是这其中的三个问题)

2.还有就是文章说到KVO是获取原类的。那解释第三个问题是因为isa被重新覆盖,那第一个同样的也会有isa被覆盖。KVO应该是获取isa对应的类,而不是class方法对应的原类。所以第三个问题应该是因为类继承关系混乱了,因为KVO的类只会生成一次。KVO__A----->KVO__A__Custom----->KVO__A--->A,猜测类继承冲突,系统直接是KVO__A--->A,对象a是KVO__A的实例,所以失效 1.先调用 custom-KVO 再调用 native-KVO,native-KVO 和 custom-KVO 都运行正常 2.先调用 native-KVO 再调用 custom-KVO,custom-KVO 运行正常,native-KVO 会 crash 3.先调用 native-KVO 再调用 custom-KVO 再调用 native-KVO,native-KVO 运行正常,custom-KVO 失效,无 crash

Suerous avatar Aug 09 '20 10:08 Suerous

1.Aspects在你说的那三个问题中是不存在的。实验了一下这三种情况,KVO和Aspects可以工作良好。虽然不知道readme是因为忘记更新了,还是说有其他的问题存在(不是这其中的三个问题)

2.还有就是文章说到KVO是获取原类的。那解释第三个问题是因为isa被重新覆盖,那第一个同样的也会有isa被覆盖。KVO应该是获取isa对应的类,而不是class方法对应的原类。所以第三个问题应该是因为类继承关系混乱了,因为KVO的类只会生成一次。KVO__A----->KVO__A__Custom----->KVO__A--->A,猜测类继承冲突,系统直接是KVO__A--->A,对象a是KVO__A的实例,所以失效 1.先调用 custom-KVO 再调用 native-KVO,native-KVO 和 custom-KVO 都运行正常 2.先调用 native-KVO 再调用 custom-KVO,custom-KVO 运行正常,native-KVO 会 crash 3.先调用 native-KVO 再调用 custom-KVO 再调用 native-KVO,native-KVO 运行正常,custom-KVO 失效,无 crash

//
//  AspectsViewController.m
//  AspectsDemo
//
//  Created by Peter Steinberger on 05/05/14.
//  Copyright (c) 2014 PSPDFKit GmbH. All rights reserved.
//

#import "AspectsViewController.h"
#import "Aspects.h"

@implementation AspectsViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
    [self.view aspect_hookSelector:@selector(setFrame:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, CGRect frame){
        NSLog(@"%@", [NSValue valueWithCGRect:frame]);
    } error:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@", change);
}

@end
  1. 试下在Aspects的demo里面这样分别用系统的kov和aspects监听下setFrame方法会不会crash,认真跑下代码试下然后给下最新的结论
  2. 文章中出现过的词是原类(即原来的类)而非 元类(meta class),先把这两个概念搞清楚。具体什么问题导致不兼容可以先把demo运行起来跑一下看下实际情况再说。文章上已经讲的的很详细,对照着run一下?

@Suerous

gsdios avatar Aug 09 '20 17:08 gsdios

1.Aspects在你说的那三个问题中是不存在的。实验了一下这三种情况,KVO和Aspects可以工作良好。虽然不知道readme是因为忘记更新了,还是说有其他的问题存在(不是这其中的三个问题) 2.还有就是文章说到KVO是获取原类的。那解释第三个问题是因为isa被重新覆盖,那第一个同样的也会有isa被覆盖。KVO应该是获取isa对应的类,而不是class方法对应的原类。所以第三个问题应该是因为类继承关系混乱了,因为KVO的类只会生成一次。KVO__A----->KVO__A__Custom----->KVO__A--->A,猜测类继承冲突,系统直接是KVO__A--->A,对象a是KVO__A的实例,所以失效 1.先调用 custom-KVO 再调用 native-KVO,native-KVO 和 custom-KVO 都运行正常 2.先调用 native-KVO 再调用 custom-KVO,custom-KVO 运行正常,native-KVO 会 crash 3.先调用 native-KVO 再调用 custom-KVO 再调用 native-KVO,native-KVO 运行正常,custom-KVO 失效,无 crash

//
//  AspectsViewController.m
//  AspectsDemo
//
//  Created by Peter Steinberger on 05/05/14.
//  Copyright (c) 2014 PSPDFKit GmbH. All rights reserved.
//

#import "AspectsViewController.h"
#import "Aspects.h"

@implementation AspectsViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
    [self.view aspect_hookSelector:@selector(setFrame:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, CGRect frame){
        NSLog(@"%@", [NSValue valueWithCGRect:frame]);
    } error:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@", change);
}

@end
  1. 试下在Aspects的demo里面这样分别用系统的kov和aspects监听下setFrame方法会不会crash,认真跑下代码试下然后给下最新的结论
  2. 文章中出现过的词是原类(即原来的类)而非 元类(meta class),先把这两个概念搞清楚。具体什么问题导致不兼容可以先把demo运行起来跑一下看下实际情况再说。文章上已经讲的的很详细,对照着run一下?

@Suerous

1.Aspects在你说的那三个问题中是不存在的。实验了一下这三种情况,KVO和Aspects可以工作良好。虽然不知道readme是因为忘记更新了,还是说有其他的问题存在(不是这其中的三个问题) 2.还有就是文章说到KVO是获取原类的。那解释第三个问题是因为isa被重新覆盖,那第一个同样的也会有isa被覆盖。KVO应该是获取isa对应的类,而不是class方法对应的原类。所以第三个问题应该是因为类继承关系混乱了,因为KVO的类只会生成一次。KVO__A----->KVO__A__Custom----->KVO__A--->A,猜测类继承冲突,系统直接是KVO__A--->A,对象a是KVO__A的实例,所以失效 1.先调用 custom-KVO 再调用 native-KVO,native-KVO 和 custom-KVO 都运行正常 2.先调用 native-KVO 再调用 custom-KVO,custom-KVO 运行正常,native-KVO 会 crash 3.先调用 native-KVO 再调用 custom-KVO 再调用 native-KVO,native-KVO 运行正常,custom-KVO 失效,无 crash

//
//  AspectsViewController.m
//  AspectsDemo
//
//  Created by Peter Steinberger on 05/05/14.
//  Copyright (c) 2014 PSPDFKit GmbH. All rights reserved.
//

#import "AspectsViewController.h"
#import "Aspects.h"

@implementation AspectsViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
    [self.view aspect_hookSelector:@selector(setFrame:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, CGRect frame){
        NSLog(@"%@", [NSValue valueWithCGRect:frame]);
    } error:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@", change);
}

@end
  1. 试下在Aspects的demo里面这样分别用系统的kov和aspects监听下setFrame方法会不会crash,认真跑下代码试下然后给下最新的结论
  2. 文章中出现过的词是原类(即原来的类)而非 元类(meta class),先把这两个概念搞清楚。具体什么问题导致不兼容可以先把demo运行起来跑一下看下实际情况再说。文章上已经讲的的很详细,对照着run一下?

@Suerous

1.按照你跑的代码确实是崩溃了。所以我也在问是不是其他的情况,我看你的demo写的num属性。所以我是用的name属性测试的这三种情况,是运行成功的。 [aspectsController addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; aspectsController.name = @"1"; [AspectsViewController aspect_hookSelector:@selector(setName:) withOptions:0 usingBlock:^(id info, NSString *name) { NSLog(@"2aspect hook----%@", name); } error:nil];

2.我并没有说meta class的问题,特定对象的hook和meta class也扯不上啊,我说的就是你文章说的原类(原来的类)。我还特地写了类的继承关系。我说的是KVO是获取对象的isa生成子类的,而不是原来的类。所以我才写了类的继承关系。现在我大概明白了,nativeKVO是先看class方法类对象有没有被创建过,没有就通过对象的isa生成子类,有的话,直接用。这样情况1就能说通了,不会和情况3解释有冲突了。我一直以为你说的是KVO一直按class方法(原来的类)去获取/创建子类,这样情况1就说不通了。这就是我没看懂过来问这个原类的问题

Suerous avatar Aug 11 '20 16:08 Suerous