Giter VIP home page Giter VIP logo

sdmagichook's Introduction

SDMagicHook

中文文档 原理解析1 原理解析2 与Aspects异同

A safe and influence-restricted method hooking for both Objective-C and Swift.

Improvement

Classical method swizzling with method_exchangeImplementations is quite simple, but it has a lot of limitations and defects:

  • You have to add a new method in a class category everytime when you want to swizzle a method.
  • Different method implementation with same selector in different category will case method conflicts.
  • The method swizzling will affect all the instances of the target class,however in most cases it is not necessary but even has side effects.

Now SDMagicHook will solve the problems mentioned above.

Changes Log

2020.08.02 -- fix bug about KVO compatibility, release "1.2.5"

2020.05.01 -- release KVO-compatible version "1.2.4"

2020.03.09 -- KVO compatible

Usage

Example for hooking CALayer's setBackgroundColor: method to find the one who secretly changed the background color of the view:

[self.view.layer hookMethod:@selector(setBackgroundColor:) impBlock:^(CALayer *layer, CGColorRef color){
    [layer callOriginalMethodInBlock:^{
        [layer setBackgroundColor:color];
    }];
    NSLog(@"%@", [NSString stringWithFormat:@"AHA! Catch it! Here are the clues.\n\n%@", [NSThread callStackSymbols]]);
}];

Example for hooking UIButton's pointInside:withEvent: method to expand the hot area:

- (void)expand:(Boolean)yn {

    if (yn) {
        __weak typeof(self) weakSelf = self;
        _hookID = [_button hookMethod:@selector(pointInside:withEvent:) impBlock:^(UIView *v, CGPoint p, UIEvent *e){
            __block BOOL res = false;
            [v callOriginalMethodInBlock:^{
                res = [v pointInside:p withEvent:e];
            }];
            if (res) return YES;

            return [weakSelf pointCheck:p view:v];
        }];
    } else {
        [_button removeHook:@selector(pointInside:withEvent:) strId:_hookID];
    }
}

Example for hooking class method:

- (void)hookClassMethod {
    [[DemoVC3 class] hookMethod:@selector(testClassMethod) key:&testClassMethodTag impBlock:^(id cls){
        [cls callOriginalMethodInBlock:^{
            [[DemoVC3 class] testClassMethod];
        }];
        printf(">> hooked testClassMethod");
    }];
}

+ (void)testClassMethod {
    printf(">> %s", sel_getName(_cmd));
}

- (void)dealloc {
    [[DemoVC3 class] removeHook:@selector(testClassMethod) key:&testClassMethodTag];
}

Example for observe any instance's dealloc event.

[[NSObject new] addObjectDeallocCallbackBlock:^{
    printf("Will dealloc...");
}];

Example for hooking UIViewController's viewDidDisappear: method with Swift:

override func viewDidLoad() {
    super.viewDidLoad()
    let imp: @convention(block) (UIViewController, Bool) -> Void = { (vc, flag) in
        vc.callOriginalMethod {
            vc.viewDidDisappear(flag)
        }
        print(vc)
    }
    rootVC = navigationController?.children.first
    hookID = rootVC?.hookMethod(#selector(UIViewController.viewDidDisappear(_:)), impBlock: imp)
}

License

MIT License.

sdmagichook's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sdmagichook's Issues

swift 调用 请检查你是否在hook了 `pointInside:withEvent:` 方法之后忘记调用原始方法。

请检查你是否在hook了 <ButtonDropdown: 0x12a019350; baseClass = UIButton; frame = (20 9.5; 57 20); opaque = NO; layer = <CALayer: 0x2807c1fc0>>pointInside:withEvent: 方法之后忘记调用原始方法。

    //扩大点击响应面积
    private func expandBtn(){
        let imp: @convention(block) (UIView , CGPoint , UIEvent ) -> Bool = { (v,p,e) in
            v.callOriginalMethod {
                v.point(inside: p, with: e) //这里为何一定要调用一次?,不调用就会 请检查你是否在hook了 `pointInside:withEvent:` 方法之后忘记调用原始方法。
            }
            let r = v.bounds.inset(by: .init(top: 10, left: 10, bottom: 10, right: 30))
            return r.contains(p)
        }
        self.btn_buiding.hookMethod(#selector(UIButton.point(inside:with:)), impBlock: imp)
//        self.btn_floor.hookMethod(#selector(UIButton.point(inside:with:)), impBlock: imp)
    }

demo0 KVO crash

2020-04-02 16:32:15.932354+0800 SDHookDemo[43318:2582823] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x600000353240 of class CALayer was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x6000003480c0> (
<NSKeyValueObservance 0x600000db42d0: Observer: 0x600002737300, Key path: class, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x600000db4210>
)'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23e3dcce __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff50b3b9b2 objc_exception_throw + 48
2 Foundation 0x00007fff258e2f73 NSKVODeallocate + 569
3 QuartzCore 0x00007fff2b4d1795 _ZN2CA5Layer16free_transactionEPNS_11TransactionE + 623
4 QuartzCore 0x00007fff2b44e514 _ZN2CA11Transaction6commitEv + 942
5 QuartzCore 0x00007fff2b44ed81 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 79
6 CoreFoundation 0x00007fff23da1067 CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION + 23
7 CoreFoundation 0x00007fff23d9bb1e __CFRunLoopDoObservers + 430
8 CoreFoundation 0x00007fff23d9c06a __CFRunLoopRun + 1226
9 CoreFoundation 0x00007fff23d9b884 CFRunLoopRunSpecific + 404
10 GraphicsServices 0x00007fff38b5ac1a GSEventRunModal + 139
11 UIKitCore 0x00007fff48c19220 UIApplicationMain + 1605
12 SDHookDemo 0x000000010c1db86a main + 122
13 libdyld.dylib 0x00007fff519b910d start + 1
14 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

用于打点, 如何Hook所有继承某个类的实例函数,而不是仅仅某一个实例的函数

import SDMagicHook
extension AppDelegate {
    private func setAutoTracker(){
        let imp: @convention(block) (UIViewController, Bool) -> Void = { (vc, flag) in
            vc.callOriginalMethod {
                vc.viewWillAppear(flag)
            }
            print(vc.title ?? "" + "viewWillAppear")
        }
        UIViewController.hookMethod(#selector(UIViewController.viewWillAppear(_:)), impBlock: imp)
    }
}

这应该是hook的类方法
报错崩溃

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The Selector viewWillAppear: may be wrong, please check it!'

代码应该在哪里编写

请问hook代码应该写到哪里呢?难道是直接写到程序xcodeproj源代码里吗?

能不能在程序已经编译好的情况下,通过动态库注入的方法,实现hook?
如果通过动态库注入的方法,实现hook,那么能不能hook swift方法?
如果可以注入动态库,那这个动态库能不能直接用swift+sdmagichook编写?

Crash Issue

我初次运行该项目时发现 在点击 DemoVC0 CatchIt 按钮后, 在点击方法中 hook 了layer 的setBackgroundColor 方法, 再次返回home, 然后就Crash.

看了代码发现 在SDMagicHook.h第 148行代码, 调用了 SDNewClassManager的hasSetupKVO属性 判定是否添加了 kvo, 如果没有添加KVO 则添加, 查看了整个项目 都没有发现有移出 该KVO 的代码, 所以导致的Crash.

不知道作者设计hasSetupKVO是否有其他的考量,
在SDMagicHook.h第 249行, 同样加入了KVO 监听 依然没有移出监听操作,
上述只是我在学习时发现的.

iOS版本: 14.0
复现情况: 必现

@gsdios

SDMagicHook与Aspects的异同

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:方法传参即可,直观简便。

SDOrderedDict 这个类的命名有点不合适

看了一下 SDOrderedDict 这个类的实现,是用来保存多个 key 相同的 value 的。而 Ordered 这个单词是表示”有序“的意思,大部分编程语言或者其标准库中都有 OrderedSet、OrderedMap、OrderedDictionary 的概念,表示这个容器内的元素是按 Key 排序的。
这里的 SDOrderedDict 是一个可包含重复 key 的元素的容器,可以参考 C++ STL 的命名,叫做 "MultiDict" 会更合适一些。

iOS 14.5崩溃

///扩大点击响应面积,edge 正数值是缩小面积,扩大是负数

    func expandResponseArea(edge:UIEdgeInsets){
        let imp: @convention(block) (UIView , CGPoint , UIEvent ) -> Bool = { (v,p,e) in
            v.callOriginalMethod {
                v.point(inside: p, with: e) //必须调用一次原始方法
            }
            let r = v.bounds.inset(by: edge)
            return r.contains(p)
        }
        //劫持point(inside:with:
        self.hookMethod(#selector(UIButton.point(inside:with:)), impBlock: imp)  //Crash Here:Thread 1: EXC_BAD_ACCESS (code=2, address=0x215c5e880)
        
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.