Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Documents/README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ let token = try hookBefore(object: obj, selector: #selector(TestObject.testMetho
}

obj.testMethod()
token.cancelHook() // cancel the hook
token.revert() // cancel the hook
```

2. hook某个实例的某个方法,在目标方法执行之后调用 hook 闭包,并且获取方法的参数。
Expand All @@ -49,7 +49,7 @@ let token = try hookAfter(object: obj, selector: #selector(TestObject.testMethod
)

obj.testMethod("ABC")
token.cancelHook() // cancel the hook
token.revert() // cancel the hook
```

3. hook某个实例的某个方法,用 hook 闭包完全取代目标方法。
Expand Down Expand Up @@ -100,7 +100,7 @@ let token = try hookBefore(targetClass: TestObject.self, selector: #selector(Tes

let obj = TestObject()
obj.testMethod()
token.cancelHook() // cancel the hook
token.revert() // cancel the hook
```

5. hook某个类的某个类方法或静态方法,在目标方法执行之前调用 hook 闭包。
Expand Down
24 changes: 11 additions & 13 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions PerformanceTests/PerformanceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ - (void)testHookBeforeForAllInstance {
NSTimeInterval aspectsTime = [self executionTimeWithObject:testObject];
XCTAssertTrue([aspectsToken remove]);

OCToken *swiftHookToken = [TestObject sh_hookBeforeSelector:@selector(emptyMethod) error:&error closure:^{
HookToken *swiftHookToken = [TestObject sh_hookBeforeSelector:@selector(emptyMethod) error:&error closure:^{
}];
XCTAssertNil(error);

NSTimeInterval swiftHookTime = [self executionTimeWithObject:testObject];
[swiftHookToken cancelHook];
[swiftHookToken revert];

[self log:@"Hook with Befre mode for all instances" nonHookTime: nonHookTime aspectsTime: aspectsTime swiftHookTime: swiftHookTime];
XCTAssert(aspectsTime / swiftHookTime > 10 && aspectsTime / swiftHookTime < 30);
Expand All @@ -64,13 +64,13 @@ - (void)testHookInsteadForAllInstance {
NSTimeInterval aspectsTime = [self executionTimeWithObject:testObject];
XCTAssertTrue([aspectsToken remove]);

OCToken *swiftHookToken = [TestObject sh_hookInsteadWithSelector:@selector(emptyMethod) closure:^(void(^original)(NSObject *, SEL), NSObject *object, SEL selector){
HookToken *swiftHookToken = [TestObject sh_hookInsteadWithSelector:@selector(emptyMethod) closure:^(void(^original)(NSObject *, SEL), NSObject *object, SEL selector){
original(object, selector);
} error:&error];
XCTAssertNil(error);

NSTimeInterval swiftHookTime = [self executionTimeWithObject:testObject];
[swiftHookToken cancelHook];
[swiftHookToken revert];

[self log:@"Hook with Instead mode for all instances" nonHookTime: nonHookTime aspectsTime: aspectsTime swiftHookTime: swiftHookTime];
XCTAssert(aspectsTime / swiftHookTime > 3 && aspectsTime / swiftHookTime < 9);
Expand All @@ -88,12 +88,12 @@ - (void)testHookAfterForSingleInstance {
NSTimeInterval aspectsTime = [self executionTimeWithObject:testObject];
XCTAssertTrue([aspectsToken remove]);

OCToken *swiftHookToken = [testObject sh_hookAfterSelector:@selector(emptyMethod) error:&error closure:^{
HookToken *swiftHookToken = [testObject sh_hookAfterSelector:@selector(emptyMethod) error:&error closure:^{
}];
XCTAssertNil(error);

NSTimeInterval swiftHookTime = [self executionTimeWithObject:testObject];
[swiftHookToken cancelHook];
[swiftHookToken revert];

[self log:@"Hook with After mode for single instances" nonHookTime: nonHookTime aspectsTime: aspectsTime swiftHookTime: swiftHookTime];
XCTAssert(aspectsTime / swiftHookTime > 3 && aspectsTime / swiftHookTime < 9);
Expand All @@ -112,13 +112,13 @@ - (void)testHookInsteadForSingleInstance {
NSTimeInterval aspectsTime = [self executionTimeWithObject:testObject];
XCTAssertTrue([aspectsToken remove]);

OCToken *swiftHookToken = [testObject sh_hookInsteadWithSelector:@selector(emptyMethod) closure:^(void(^original)(NSObject *, SEL), NSObject *object, SEL selector){
HookToken *swiftHookToken = [testObject sh_hookInsteadWithSelector:@selector(emptyMethod) closure:^(void(^original)(NSObject *, SEL), NSObject *object, SEL selector){
original(object, selector);
} error:&error];
XCTAssertNil(error);

NSTimeInterval swiftHookTime = [self executionTimeWithObject:testObject];
[swiftHookToken cancelHook];
[swiftHookToken revert];

[self log:@"Hook with Instead mode for single instances" nonHookTime: nonHookTime aspectsTime: aspectsTime swiftHookTime: swiftHookTime];
XCTAssert(aspectsTime / swiftHookTime > 1.5 && aspectsTime / swiftHookTime < 3.5);
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let token = try hookBefore(object: obj, selector: #selector(TestObject.testMetho
}

obj.testMethod()
token.cancelHook() // cancel the hook
token.revert() // cancel the hook
```

2. Call the hook closure **after** executing **specified instance**'s method and get the parameters.
Expand All @@ -50,7 +50,7 @@ let token = try hookAfter(object: obj, selector: #selector(TestObject.testMethod
)

obj.testMethod("ABC")
token.cancelHook() // cancel the hook
token.revert() // cancel the hook
```

3. Totally override the mehtod for **specified instance**.
Expand Down Expand Up @@ -100,7 +100,7 @@ let token = try hookBefore(targetClass: TestObject.self, selector: #selector(Tes

let obj = TestObject()
obj.testMethod()
token.cancelHook() // cancel the hook
token.revert() // cancel the hook
```

5. Call the hook closure **before** executing the **class method**.
Expand Down
42 changes: 4 additions & 38 deletions SwiftHook/Classes/Hook/HookDeallocAfterDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,8 @@

import Foundation

private var associatedDelegateHandle: UInt8 = 0

private class HookDeallocAfterDelegate {

var hookClosures = [AnyObject]()

deinit {
for item in hookClosures.reversed() {
unsafeBitCast(item, to: (@convention(block) () -> Void).self)()
}
}
}

private struct HookDeallocAfterToken: Token {

weak var delegate: HookDeallocAfterDelegate?
weak var closure: AnyObject?

init(delegate: HookDeallocAfterDelegate, closure: AnyObject) {
self.delegate = delegate
self.closure = closure
}

func cancelHook() {
delegate?.hookClosures.removeAll(where: {(closure) -> Bool in
return closure === self.closure
})
}
}

func hookDeallocAfterByDelegate(object: AnyObject, closure: AnyObject) -> Token {
var delegate: HookDeallocAfterDelegate! = objc_getAssociatedObject(object, &associatedDelegateHandle) as? HookDeallocAfterDelegate
if delegate == nil {
delegate = HookDeallocAfterDelegate()
objc_setAssociatedObject(object, &associatedDelegateHandle, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
delegate.hookClosures.append(closure)
return HookDeallocAfterToken.init(delegate: delegate, closure: closure)
func hookDeallocAfterByDelegate(object: AnyObject, closure: AnyObject) -> HookToken {
let token = HookToken(deallocAfter: object, hookClosure: closure)
_ = token.apply()
return token
}
76 changes: 39 additions & 37 deletions SwiftHook/Classes/Hook/HookInternal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,51 @@

import Foundation

enum HookMode {
@objc public enum HookMode: Int {
case before
case after
case instead
}

func internalHook(targetClass: AnyClass, selector: Selector, mode: HookMode, hookClosure: AnyObject) throws -> HookToken {
let hookContext = try getHookContext(targetClass: targetClass, selector: selector, isSpecifiedInstance: false)
try hookContext.append(hookClosure: hookClosure, mode: mode)
return HookToken(hookContext: hookContext, hookClosure: hookClosure, mode: mode)
let token = try HookToken(targetClass: targetClass, selector: selector, mode: mode, hookClosure: hookClosure)
try internalApplyHook(token: token)
return token
}

func internalHook(object: AnyObject, selector: Selector, mode: HookMode, hookClosure: AnyObject) throws -> HookToken {
let targetClass: AnyClass
if let object = object as? NSObject {
guard try isSupportedKVO(object: object) else {
throw SwiftHookError.hookKVOUnsupportedInstance
}
// use KVO for specified instance hook
try wrapKVOIfNeeded(object: object, selector: selector)
guard let KVOedClass = object_getClass(object) else {
throw SwiftHookError.internalError(file: #file, line: #line)
}
targetClass = KVOedClass
} else {
// create dynamic class for specified instance hook
guard let baseClass = object_getClass(object) else {
throw SwiftHookError.internalError(file: #file, line: #line)
let token = try HookToken(object: object, selector: selector, mode: mode, hookClosure: hookClosure)
try internalApplyHook(token: token)
return token
}

func internalApplyHook(token: HookToken) throws {
if let targetClass = token.targetClass {
let hookContext = try getHookContext(targetClass: targetClass, selector: token.selector, isSpecifiedInstance: false)
try hookContext.append(hookClosure: token.hookClosure, mode: token.mode)
token.hookContext = hookContext
} else if let object = token.hookObject {
let targetClass: AnyClass
if let object = object as? NSObject {
guard try isSupportedKVO(object: object) else {
throw SwiftHookError.hookKVOUnsupportedInstance
}
// use KVO for specified instance hook
try wrapKVOIfNeeded(object: object, selector: token.selector)
guard let KVOedClass = object_getClass(object) else {
throw SwiftHookError.internalError(file: #file, line: #line)
}
targetClass = KVOedClass
} else {
// create dynamic class for specified instance hook
targetClass = try wrapDynamicClass(object: object)
}
targetClass = isDynamicClass(targetClass: baseClass) ? baseClass : try wrapDynamicClass(object: object)
// hook
let hookContext = try getHookContext(targetClass: targetClass, selector: token.selector, isSpecifiedInstance: true)
// set hook closure
try appendHookClosure(object: object, selector: token.selector, hookClosure: token.hookClosure, mode: token.mode)
token.hookContext = hookContext
}
// hook
let hookContext = try getHookContext(targetClass: targetClass, selector: selector, isSpecifiedInstance: true)
var token = HookToken(hookContext: hookContext, hookClosure: hookClosure, mode: mode)
token.hookObject = object
// set hook closure
try appendHookClosure(object: object, selector: selector, hookClosure: hookClosure, mode: mode)
return token
}

/**
Expand All @@ -58,7 +65,7 @@ func internalHook(object: AnyObject, selector: Selector, mode: HookMode, hookClo

# Case 2: Hook all instance or hook class method.
Try to change the Method's IMP from hooked to original and released context.
But it's dangerous when the current IMP is not previous hooked IMP. In this case. cancelHook() still works fine but the context will not be released.
But it's dangerous when the current IMP is not previous hooked IMP. In this case. revert() still works fine but the context will not be released.
1. Return true if the context will be released.
2. Return false if the context will not be released.
3. Returen nil means some issues like token already canceled.
Expand All @@ -79,11 +86,8 @@ func internalCancelHook(token: HookToken) throws -> Bool? {
// The object has been deinit.
return nil
}
guard let hookClosure = token.hookClosure else {
// Token has been canceled.
return nil
}
try removeHookClosure(object: hookObject, selector: hookContext.selector, hookClosure: hookClosure, mode: token.mode)
try removeHookClosure(object: hookObject, selector: hookContext.selector, hookClosure: token.hookClosure, mode: token.mode)
token.hookContext = nil

guard object_getClass(hookObject) == hookContext.targetClass else {
// The class is changed after hooking by SwiftHook.
Expand All @@ -106,10 +110,8 @@ func internalCancelHook(token: HookToken) throws -> Bool? {
return true
} else {
// This hook is for all instance or class method
guard let hookClosure = token.hookClosure else {
throw SwiftHookError.internalError(file: #file, line: #line)
}
try hookContext.remove(hookClosure: hookClosure, mode: token.mode)
try hookContext.remove(hookClosure: token.hookClosure, mode: token.mode)
token.hookContext = nil
guard !(try isIMPChanged(hookContext: hookContext)) else {
// The IMP is changed after hooking by SwiftHook.
return false
Expand Down
Loading