diff --git a/Documents/README.zh-Hans.md b/Documents/README.zh-Hans.md index 5445e019..a836e539 100644 --- a/Documents/README.zh-Hans.md +++ b/Documents/README.zh-Hans.md @@ -19,7 +19,7 @@ class TestObject { // No need to inherit from NSObject. let obj = TestObject() -let token = try hookBefore(object: obj, selector: #selector(TestObject.testMethod)) { +let token = try ObjectHook(obj).hookBefore(#selector(TestObject.testMethod)) { print("Before executing `testMethod`") } @@ -39,7 +39,7 @@ class TestObject { let obj = TestObject() -let token = try hookAfter(object: obj, selector: #selector(TestObject.testMethod(_:)), closure: { obj, sel, parameter in +let token = try ObjectHook(obj).hookAfter(#selector(TestObject.testMethod(_:)), closure: { obj, sel, parameter in print("After executing `testMethod` with parameter: \(parameter)") } as @convention(block) ( // Using `@convention(block)` to declares a Swift closure as an Objective-C block AnyObject, // `obj` Instance @@ -65,7 +65,7 @@ class Math { let math = Math() -try hookInstead(object: math, selector: #selector(Math.double(_:)), closure: { original, obj, sel, number in +try ObjectHook(math).hook(#selector(Math.double(_:)), closure: { original, obj, sel, number in print("Before executing `double`") let originalResult = original(obj, sel, number) print("After executing `double`, got result \(originalResult)") diff --git a/Example/MacCocoapodsDynamicExample/ViewController.swift b/Example/MacCocoapodsDynamicExample/ViewController.swift index 1eaed699..ed46e72c 100644 --- a/Example/MacCocoapodsDynamicExample/ViewController.swift +++ b/Example/MacCocoapodsDynamicExample/ViewController.swift @@ -25,7 +25,7 @@ class ViewController: NSViewController { func hook() { // hook do { - try hookBefore(object: self, selector: #selector(NSViewController.viewDidAppear), closure: { viewController, _ in + try ObjectHook(self).hookBefore(#selector(NSViewController.viewDidAppear), closure: { viewController, _ in print("ViewController did appear") print("Title: \(viewController.title ?? "")") }) diff --git a/Example/MacCocoapodsStaticExample/ViewController.swift b/Example/MacCocoapodsStaticExample/ViewController.swift index e3cdcd33..1d59cacd 100644 --- a/Example/MacCocoapodsStaticExample/ViewController.swift +++ b/Example/MacCocoapodsStaticExample/ViewController.swift @@ -25,7 +25,7 @@ class ViewController: NSViewController { func hook() { // hook do { - try hookBefore(object: self, selector: #selector(NSViewController.viewDidAppear), closure: { viewController, _ in + try ObjectHook(self).hookBefore(#selector(NSViewController.viewDidAppear), closure: { viewController, _ in print("ViewController did appear") print("Title: \(viewController.title ?? "")") }) diff --git a/Example/SPMExample/ContentView.swift b/Example/SPMExample/ContentView.swift index 779151bc..3e79b28a 100644 --- a/Example/SPMExample/ContentView.swift +++ b/Example/SPMExample/ContentView.swift @@ -30,7 +30,7 @@ struct ContentView: View { let obj = MyObject() // hook do { - try hookBefore(object: obj, selector: #selector(MyObject.test), closure: { viewController, _ in + try ObjectHook(obj).hookBefore(#selector(MyObject.test), closure: { viewController, _ in print("hooked") }) } catch { diff --git a/Example/iOSCocoapodsDynamicExample/ViewController.swift b/Example/iOSCocoapodsDynamicExample/ViewController.swift index 71eb92cc..5846711d 100644 --- a/Example/iOSCocoapodsDynamicExample/ViewController.swift +++ b/Example/iOSCocoapodsDynamicExample/ViewController.swift @@ -20,7 +20,7 @@ class ViewController: UIViewController { func hook() { // hook do { - try hookBefore(object: self, selector: #selector(UIViewController.viewDidAppear(_:)), closure: { viewController, _ in + try ObjectHook(self).hookBefore(#selector(UIViewController.viewDidAppear(_:)), closure: { viewController, _ in print("ViewController did appear") print("Title: \(viewController.title ?? "")") }) diff --git a/Example/iOSCocoapodsStaticExample/ViewController.swift b/Example/iOSCocoapodsStaticExample/ViewController.swift index 73747178..3396d44c 100644 --- a/Example/iOSCocoapodsStaticExample/ViewController.swift +++ b/Example/iOSCocoapodsStaticExample/ViewController.swift @@ -20,7 +20,7 @@ class ViewController: UIViewController { func hook() { // hook do { - try hookBefore(object: self, selector: #selector(UIViewController.viewDidAppear(_:)), closure: { viewController, _ in + try ObjectHook(self).hookBefore(#selector(UIViewController.viewDidAppear(_:)), closure: { viewController, _ in print("ViewController did appear") print("Title: \(viewController.title ?? "")") }) diff --git a/README.md b/README.md index 5db1f375..ade191de 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ class TestObject { // No need to inherit from NSObject. let obj = TestObject() -let token = try hookBefore(object: obj, selector: #selector(TestObject.testMethod)) { +let token = try ObjectHook(obj).hookBefore(#selector(TestObject.testMethod)) { print("Before executing `testMethod`") } @@ -40,7 +40,7 @@ class TestObject { let obj = TestObject() -let token = try hookAfter(object: obj, selector: #selector(TestObject.testMethod(_:)), closure: { obj, sel, parameter in +let token = try ObjectHook(obj).hookAfter(#selector(TestObject.testMethod(_:)), closure: { obj, sel, parameter in print("After executing `testMethod` with parameter: \(parameter)") } as @convention(block) ( // Using `@convention(block)` to declares a Swift closure as an Objective-C block AnyObject, // `obj` Instance @@ -66,7 +66,7 @@ class Math { let math = Math() -try hookInstead(object: math, selector: #selector(Math.double(_:)), closure: { original, obj, sel, number in +try ObjectHook(math).hook(#selector(Math.double(_:)), closure: { original, obj, sel, number in print("Before executing `double`") let originalResult = original(obj, sel, number) print("After executing `double`, got result \(originalResult)") diff --git a/SwiftHook/Classes/Hook/NSObject+SelectorName.swift b/SwiftHook/Classes/Hook/NSObject+SelectorName.swift new file mode 100644 index 00000000..a4485070 --- /dev/null +++ b/SwiftHook/Classes/Hook/NSObject+SelectorName.swift @@ -0,0 +1,112 @@ +// +// NSObject+SelectorName.swift +// +// +// Created by Florian Zand on 06.05.25. +// + +import Foundation + +extension PartialKeyPath { + func getterName() throws -> String where Root: AnyObject { + guard let getterName = _kvcKeyPathString else { + throw SwiftHookError.nonObjcProperty + } + return getterName + } + + func setterName() throws -> String where Root: AnyObject { + guard let setterName = NSObject.setterName(for: try getterName(), _class: Root.self) else { + throw SwiftHookError.nonObjcProperty + } + return setterName + } + + func getterName() throws -> String where Root == T.Type, T: AnyObject { + guard let getterName = _kvcKeyPathString else { + throw SwiftHookError.nonObjcProperty + } + return getterName + } + + func setterName() throws -> String where Root == T.Type, T: AnyObject { + guard let setterName = NSObject.setterName(for: try getterName(), _class: T.self) else { + throw SwiftHookError.nonObjcProperty + } + return setterName + } +} + +fileprivate extension NSObject { + static func setterName(for getterName: String, _class: AnyClass) -> String? { + var names: [String] = [] + if getterName.hasPrefix("is") { + names.append("set\(getterName.dropFirst(2).uppercasedFirst()):") + } else if getterName.hasPrefix("get") { + names.append("set\(getterName.dropFirst(3).uppercasedFirst()):") + } + names.append("set\(getterName.uppercasedFirst()):") + for name in names { + if class_respondsToSelector(_class, NSSelectorFromString(name)) { + return name + } + } + + let getterSelector = Selector(getterName) + var currentClass: AnyClass? = _class + while let c = currentClass { + var propertyCount: UInt32 = 0 + guard let properties = class_copyPropertyList(c, &propertyCount) else { + currentClass = class_getSuperclass(c) + continue + } + defer { free(properties) } + + for i in 0.. String? { + var count: UInt32 = 0 + guard let attrs = property_copyAttributeList(self, &count) else { return nil } + defer { free(attrs) } + for i in 0.. String { + if isEmpty { return String(self) } + return prefix(1).uppercased() + dropFirst() + } +} diff --git a/SwiftHook/Classes/Public/ClassHook.swift b/SwiftHook/Classes/Public/ClassHook.swift new file mode 100644 index 00000000..7f62361d --- /dev/null +++ b/SwiftHook/Classes/Public/ClassHook.swift @@ -0,0 +1,506 @@ +// +// ClassHook.swift +// SwiftHook +// +// Created by Florian Zand on 16.05.25. +// + +import Foundation + +/// Hooks class methods. +public struct ClassHook { + let targetClass: AnyClass + + public init(_ targetClass: T.Type) { + self.targetClass = targetClass + } + + // MARK: - Before + + /** + Execute the closure before the execution of class's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hookBefore(#selector(MyObject.sum(_:_:)) { + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { + return try hookBefore(selector, closure: closure as Any) + } + + /** + Execute the closure before the execution of class's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with the object and the selector before the execution of class's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hookBefore(#selector(MyObject.sum(_:_:)) { obj, sel in + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: @escaping (_ class: T.Type, _ selector: Selector) -> Void) throws -> Token { + let closure = { obj, sel in + guard let obj = obj as? T.Type else { fatalError() } + closure(obj, sel) + } as @convention(block) (AnyObject, Selector) -> Void + return try hookBefore(selector, closure: closure as Any) + } + + /** + Execute the closure with the object and the selector before the execution of class's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: @escaping (_ `class`: T.Type, _ selector: Selector) -> Void) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with all parameters before the execution of class's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hookBefore(#selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in + print("hooked") + } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be `Void`. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: Any) throws -> Token { + guard let targetClass = object_getClass(targetClass) else { + throw SwiftHookError.internalError(file: #file, line: #line) + } + return try swiftHookSerialQueue.sync { + try parametersCheck(targetClass: targetClass, selector: selector, mode: .before, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: selector, mode: .before, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with all parameters before the execution of class's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be `Void`. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: Any) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + // MARK: - After + + /** + Execute the closure after the execution of class's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hookAfter(#selector(MyObject.sum(_:_:)) { + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { + return try hookAfter(selector, closure: closure as Any) + } + + /** + Execute the closure after the execution of class's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with the object and the selector after the execution of class's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hookAfter(#selector(MyObject.sum(_:_:)) { obj, sel in + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: @escaping (_ class: T.Type, _ selector: Selector) -> Void) throws -> Token { + let closure = { obj, sel in + guard let obj = obj as? T.Type else { fatalError() } + closure(obj, sel) + } as @convention(block) (AnyObject, Selector) -> Void + return try hookAfter(selector, closure: closure as Any) + } + + /** + Execute the closure with the object and the selector after the execution of class's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: @escaping (_ `class`: T.Type, _ selector: Selector) -> Void) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with all parameters after the execution of class's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hookAfter(#selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in + print("hooked") + } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be `Void`. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: Any) throws -> Token { + guard let targetClass = object_getClass(targetClass) else { + throw SwiftHookError.internalError(file: #file, line: #line) + } + return try swiftHookSerialQueue.sync { + try parametersCheck(targetClass: targetClass, selector: selector, mode: .after, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: selector, mode: .after, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with all parameters after the execution of class's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be `Void`. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: Any) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + // MARK: - Instead + + /** + Replace the implementation of class's method by the closure. + + Example usage: + + ``` + class MyObject { + @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassHook(MyObject.self).hook(#selector(MyObject.sum(_:_:)), closure: { original, obj, sel, number1, numebr2 in + return original(obj, sel, number1, numebr2) * 2 + } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Int ) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain `AnyObject` and `Selector` at the beginning).. + 2. The second parameter has to be `AnyObject` or your class (When it's your class. + 3. The third parameter has to be `Selector`. + 4. The rest parameters are the same as the method's. + 5. The return type has to be the same as the original method's. + 6. The keyword `@convention(block)` is necessary, + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hook(_ selector: Selector, closure: Any) throws -> Token { + guard let targetClass = object_getClass(targetClass) else { + throw SwiftHookError.internalError(file: #file, line: #line) + } + return try swiftHookSerialQueue.sync { + try parametersCheck(targetClass: targetClass, selector: selector, mode: .instead, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: selector, mode: .instead, hookClosure: closure as AnyObject) + } + } + + /** + Replace the implementation of class's method by the closure. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain `AnyObject` and `Selector` at the beginning).. + 2. The second parameter has to be `AnyObject` or your class (When it's your class. + 3. The third parameter has to be `Selector`. + 4. The rest parameters are the same as the method's. + 5. The return type has to be the same as the original method's. + 6. The keyword `@convention(block)` is necessary, + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hook(_ selector: String, closure: Any) throws -> Token { + try hook(NSSelectorFromString(selector), closure: closure) + } +} + +public extension ClassHook { + /** + Hooks before getting the specified property of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked before the property is get. It receives: + - `object`: The object. + - `value`: The value of the property to be get. + + Example usage: + ```swift + try ClassHook(MyObject.self).hookBefore(\.classProperty) { class_, value in + // hooks before. + } + ``` + */ + @discardableResult + func hookBefore(_ keyPath: KeyPath, closure: @escaping (_ class_: T.Type, _ value: Value)->()) throws -> Token { + try hookBefore(try keyPath.getterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T.Type else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks before setting the specified property of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked before the property is set. It receives: + - `object`: The object. + - `value`: The new value of the property to be set. + + Example usage: + ```swift + try ClassHook(MyObject.self).hookBefore(set \.classProperty) { class_, value in + // hooks before. + } + ``` + */ + @discardableResult + func hookBefore(set keyPath: WritableKeyPath, closure: @escaping (_ class_: T.Type, _ value: Value)->()) throws -> Token { + try hookBefore(try keyPath.setterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T.Type else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks after getting the specified property of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked after the property is read. It receives: + - `object`: The object. + - `value`: The current value of the property. + + Example usage: + ```swift + try ClassHook(MyObject.self).hookAfter(\.classProperty) { class_, value in + // hooks after. + } + ``` + */ + @discardableResult + func hookAfter(_ keyPath: KeyPath, closure: @escaping (_ class_: T.Type, _ value: Value)->()) throws -> Token { + try hookAfter(try keyPath.getterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T.Type else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks after setting the specified property of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked after the property is set. It receives: + - `object`: The object. + - `value`: The new value of the property. + + Example usage: + ```swift + try ClassHook(MyObject.self).hookAfter(set \.classProperty) { class_, value in + // hooks after. + } + ``` + */ + @discardableResult + func hookAfter(set keyPath: WritableKeyPath, closure: @escaping (_ class_: T.Type, _ value: Value)->()) throws -> Token { + try hookAfter(try keyPath.setterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T.Type else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks getting the specified property of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: A closure that is invoked whenever the property is read. It receives: + - `object`: The instance on which the property is being accessed. + - `original`: The value returned by the original getter. + - Returns: The value to return from the getter. This can be the original value or a modified one. + + Example usage: + ```swift + try ClassHook(MyObject.self).hook(\.classProperty) { class_, originalValue in + return original.uppercased() + } + ``` + */ + @discardableResult + func hook(_ keyPath: KeyPath, closure: @escaping (_ class_: T.Type, _ original: Value)->(Value)) throws -> Token { + try hook(try keyPath.getterName(), closure: { original, obj, sel in + if let value = original(obj, sel) as? Value, let obj = obj as? T.Type { + return closure(obj, value) + } + return original(obj, sel) + } as @convention(block) ((AnyObject, Selector) -> Any, + AnyObject, Selector) -> Any) + } + + /** + Hooks setting the specified property of the class. + + - Parameters: + - keyPath: The key path to the writable property to hook. + - closure: The handler that is invoked whenever the property is set. It receives: + - `object`: The instance on which the property is being set. + - `value`: The new value that is about to be written to the property. + - `original`: A block that invokes the original setter behavior. If the block isn't called, the property will not be updated. + + Example usage: + ```swift + try ClassHook(MyObject.self).hook(set \.classProperty) { class_, value, original in + if stringValue != "" { + // Sets the stringValue. + original(stringValue) + } + } + ``` + */ + @discardableResult + func hook(set keyPath: WritableKeyPath, closure: @escaping (_ class_: T.Type, _ value: Value, _ original: (Value)->())->()) throws -> Token { + try hook(try keyPath.setterName(), closure: { original, obj, sel, val in + if let val = val as? Value, let ob = obj as? T.Type { + let original: (Value)->() = { original(obj, sel, $0) } + closure(ob, val, original) + } else { + original(obj, sel, val) + } + } as @convention(block) ((AnyObject, Selector, Any) -> Void, + AnyObject, Selector, Any) -> Void) + } +} diff --git a/SwiftHook/Classes/Public/ClassInstanceHook.swift b/SwiftHook/Classes/Public/ClassInstanceHook.swift new file mode 100644 index 00000000..a2256b86 --- /dev/null +++ b/SwiftHook/Classes/Public/ClassInstanceHook.swift @@ -0,0 +1,606 @@ +// +// ClassInstanceHook.swift +// SwiftHook +// +// Created by Florian Zand on 16.05.25. +// + +import Foundation + + +/// Hooks methods of all instances of a class. +public struct ClassInstanceHook { + let targetClass: AnyClass + + public init(_ targetClass: T.Type) { + self.targetClass = targetClass + } + + // MARK: - Hook Before + + /** + Execute the closure before the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.sum(_:_:)) { + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookBefore(selector, closure: closure as Any) + } + + /** + Execute the closure before the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with the object and the selector before the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)) { obj, sel in + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + let closure = { obj, sel in + guard let obj = obj as? T else { fatalError() } + closure(obj, sel) + } as @convention(block) (AnyObject, Selector) -> Void + return try hookBefore(selector, closure: closure as Any) + } + + /** + Execute the closure with the object and the selector before the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with all parameters before the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in + print("hooked") + } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be AnyObject or your class (When it's your class). + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: Any) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: selector, mode: .before, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: selector, mode: .before, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with all parameters before the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be AnyObject or your class (When it's your class). + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: Any) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + // MARK: - Hook After + + /** + Execute the closure after the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.sum(_:_:)) { + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookAfter(selector, closure: closure as Any) + } + + /** + Execute the closure after the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with the object and the selector after the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)) { obj, sel in + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + let closure = { obj, sel in + guard let obj = obj as? T else { fatalError() } + closure(obj, sel) + } as @convention(block) (AnyObject, Selector) -> Void + return try hookAfter(selector, closure: closure as Any) + } + + /** + Execute the closure with the object and the selector after the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with all parameters after the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in + print("hooked", number1, number2) + } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class). + 2. The second parameter has to be Selector. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: Any) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: selector, mode: .after, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: selector, mode: .after, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with all parameters after the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class). + 2. The second parameter has to be Selector. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: Any) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + // MARK: - Hook + + /** + Replace the implementation of object's method by the closure. + + Example usage: + + ``` + class MyObject { + @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + try! ClassInstanceHook(MyObject.self).hook(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { original, obj, sel, number1, numebr2 in + return original(obj, sel, number1, numebr2) * 2 + } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Int ) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain Object and Selector at the beginning).. + 2. The second parameter has to be AnyObject or your class (When it's your class). + 3. The third parameter has to be `Selector`. + 4. The rest parameters are the same as the method's. + 5. The return type has to be the same as the original method's. + 6. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hook(_ selector: Selector, closure: Any) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: selector, mode: .instead, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: selector, mode: .instead, hookClosure: closure as AnyObject) + } + } + + /** + Replace the implementation of object's method by the closure. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain Object and Selector at the beginning).. + 2. The second parameter has to be AnyObject or your class (When it's your class). + 3. The third parameter has to be `Selector`. + 4. The rest parameters are the same as the method's. + 5. The return type has to be the same as the original method's. + 6. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + */ + @discardableResult + public func hook(_ selector: String, closure: Any) throws -> Token { + try hook(NSSelectorFromString(selector), closure: closure) + } +} + +// MARK: Hook Dealloc +public extension ClassInstanceHook where T: NSObject { + + /** + Execute the closure before the object deinit. + + Example usage: + + ``` + try! ClassInstanceHook(MyObject.self).hookDeallocBefore { + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + func hookDeallocBefore(closure: @escaping @convention(block) () -> Void) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .before, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with the object before the object deinit. + + Example usage: + + ``` + try! ClassInstanceHook(MyObject.self).hookDeallocBefore { object in + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + func hookDeallocBefore(closure: @escaping (_ object: T) -> Void) throws -> Token { + let closure = { obj in + guard let obj = obj as? T else { fatalError() } + closure(obj) + } as @convention(block) (NSObject) -> Void + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .before, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure after the object deinit. + + Example usage: + + ``` + try! ClassInstanceHook(MyObject.self).hookDeallocAfter { + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + func hookDeallocAfter(closure: @escaping @convention(block) () -> Void) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .after, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .after, hookClosure: closure as AnyObject) + } + } + + /** + Replace the implementation of object's deinit method by the closure. + + Example usage: + + ``` + try! ClassInstanceHook(MyObject.self).hookDealloc { original in + print("before release") + original() + print("after release") + } + ``` + + - Parameter closure: The hook closure with the original dealloc method as parameter. You have to call it to avoid memory leak. + - Returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + func hookDealloc(closure: @escaping @convention(block) (_ original: () -> Void) -> Void) throws -> Token { + try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .instead, closure: closure as AnyObject) + return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .instead, hookClosure: closure as AnyObject) + } + } +} + +// MARK: - Hook Property + +public extension ClassInstanceHook { + /** + Hooks before getting the specified property for all instances of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked before the property is get. It receives: + - `object`: The object. + - `value`: The value of the property to be get. + + Example usage: + ```swift + try ClassInstanceHook(NSTextField.self).hookBefore(all: \.stringValue) { textfield, stringValue in + // hooks before. + } + ``` + */ + @discardableResult + func hookBefore(_ keyPath: KeyPath, closure: @escaping (_ object: T, _ value: Value)->()) throws -> Token { + try hookBefore(try keyPath.getterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks before setting the specified property for all instances of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked before the property is set. It receives: + - `object`: The object. + - `value`: The new value of the property to be set. + + Example usage: + ```swift + try ClassInstanceHook(NSTextField.self).hookBefore(set: \.stringValue) { textfield, stringValue in + // hooks before + } + ``` + */ + @discardableResult + func hookBefore(set keyPath: WritableKeyPath, closure: @escaping (_ object: T, _ value: Value)->()) throws -> Token { + try hookBefore(try keyPath.setterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks after getting the specified property for all instances of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked after the property is read. It receives: + - `object`: The object. + - `value`: The current value of the property. + + Example usage: + ```swift + try ClassInstanceHook(NSTextField.self).hookAfter(\.stringValue) { textfield, stringValue in + // hooks after. + } + ``` + */ + @discardableResult + func hookAfter(_ keyPath: KeyPath, closure: @escaping (_ object: T, _ value: Value)->()) throws -> Token { + try hookAfter(try keyPath.getterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks after setting the specified property for all instances of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked after the property is set. It receives: + - `object`: The object. + - `value`: The new value of the property. + + Example usage: + ```swift + try ClassInstanceHook(NSTextField.self).hookAfter(set: \.stringValue) { textfield, stringValue in + // hooks after. + } + ``` + */ + @discardableResult + func hookAfter(set keyPath: WritableKeyPath, closure: @escaping (_ object: T, _ value: Value)->()) throws -> Token { + try hookAfter(try keyPath.setterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks getting the specified property for all instances of the class. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: A closure that is invoked whenever the property is read. It receives: + - `object`: The instance on which the property is being accessed. + - `original`: The value returned by the original getter. + - Returns: The value to return from the getter. This can be the original value or a modified one. + + Example usage: + ```swift + try ClassInstanceHook(NSTextField.self).hook(\.stringValue) { object, original in + return original.uppercased() + } + ``` + */ + @discardableResult + func hook(_ keyPath: KeyPath, closure: @escaping (_ object: T, _ original: Value)->(Value)) throws -> Token { + try hook(try keyPath.getterName(), closure: { original, obj, sel in + if let value = original(obj, sel) as? Value, let obj = obj as? T { + return closure(obj, value) + } + return original(obj, sel) + } as @convention(block) ((AnyObject, Selector) -> Any, + AnyObject, Selector) -> Any) + } + + /** + Hooks setting the specified property for all instances of the class. + + - Parameters: + - keyPath: The key path to the writable property to hook. + - closure: The handler that is invoked whenever the property is set. It receives: + - `object`: The instance on which the property is being set. + - `value`: The new value that is about to be written to the property. + - `original`: A block that invokes the original setter behavior. If the block isn't called, the property will not be updated. + + Example usage: + ```swift + try ClassInstanceHook(NSTextField.self).hook(set: \.stringValue) { textfield, stringValue, original in + if stringValue != "" { + // Sets the stringValue. + original(stringValue) + } + } + ``` + */ + @discardableResult + func hook(set keyPath: WritableKeyPath, closure: @escaping (_ object: T, _ value: Value, _ original: (Value)->())->()) throws -> Token { + try hook(try keyPath.setterName(), closure: { original, obj, sel, val in + if let val = val as? Value, let ob = obj as? T { + let original: (Value)->() = { original(obj, sel, $0) } + closure(ob, val, original) + } else { + original(obj, sel, val) + } + } as @convention(block) ((AnyObject, Selector, Any) -> Void, + AnyObject, Selector, Any) -> Void) + } +} diff --git a/SwiftHook/Classes/Public/HookAllInstances.swift b/SwiftHook/Classes/Public/HookAllInstances.swift deleted file mode 100644 index 2a1401d4..00000000 --- a/SwiftHook/Classes/Public/HookAllInstances.swift +++ /dev/null @@ -1,368 +0,0 @@ -// -// HookAllInstances.swift -// SwiftHook -// -// Created by Yanni Wang on 24/6/21. -// Copyright © 2021 Yanni. All rights reserved. -// - -import Foundation - -// MARK: - empty closure - -// before -/** - Execute the closure before the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { - print("hooked") - }) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookBefore(targetClass: AnyClass, selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try hookBefore(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// after -/** - Execute the closure after the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { - print("hooked") - }) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookAfter(targetClass: AnyClass, selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try hookAfter(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// MARK: - self and selector closure - -// before -/** - Execute the closure with the object and the selector before the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel in - print("hooked") - }) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookBefore(targetClass: T.Type, selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { - let closure = { obj, sel in - guard let obj = obj as? T else { fatalError() } - closure(obj, sel) - } as @convention(block) (AnyObject, Selector) -> Void - return try hookBefore(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// after -/** - Execute the closure with the object and the selector after the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel in - print("hooked") - }) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookAfter(targetClass: T.Type, selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { - let closure = { obj, sel in - guard let obj = obj as? T else { fatalError() } - closure(obj, sel) - } as @convention(block) (AnyObject, Selector) -> Void - return try hookAfter(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// MARK: - custom closure - -// before -/** - Execute the closure with all parameters before the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in - print("hooked") - } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. The following is a description of the closure - 1. The first parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 2. The second parameter has to be Selector. - 3. The rest parameters are the same as the method's. - 4. The return type has to be Void. - 5. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookBefore(targetClass: AnyClass, selector: Selector, closure: Any) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: selector, mode: .before, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: selector, mode: .before, hookClosure: closure as AnyObject) - } -} - -// after -/** - Execute the closure with all parameters after the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in - print("hooked") - } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. The following is a description of the closure - 1. The first parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 2. The second parameter has to be Selector. - 3. The rest parameters are the same as the method's. - 4. The return type has to be Void. - 5. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookAfter(targetClass: AnyClass, selector: Selector, closure: Any) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: selector, mode: .after, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: selector, mode: .after, hookClosure: closure as AnyObject) - } -} - -// instead -/** - Replace the implementation of object's method by the closure. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { original, obj, sel, number1, numebr2 in - // You may call the original method with some different parameters. You can even not call the original method. - return original(obj, sel, number1, numebr2) - } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Int ) - _ = MyObject().sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. The following is a description of the closure - 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain Object and Selector at the beginning).. - 2. The second parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 3. The third parameter has to be Selector. - 4. The rest parameters are the same as the method's. - 5. The return type has to be the same as the original method's. - 6. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookInstead(targetClass: AnyClass, selector: Selector, closure: Any) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: selector, mode: .instead, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: selector, mode: .instead, hookClosure: closure as AnyObject) - } -} - -// MARK: before deinit - -/** - Execute the closure before the object deinit. - - # Example - ``` - class MyObject: NSObject { - } - let token = try! hookDeallocBefore(targetClass: MyObject.self, closure: { - print("hooked") - }) - autoreleasepool { - let object = MyObject() - } - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocBefore(targetClass: NSObject.Type, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .before, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) - } -} - -/** - Execute the closure with the object before the object deinit. - - # Example - ``` - class MyObject: NSObject { - } - let token = try! hookDeallocBefore(targetClass: MyObject.self, closure: { obj in - print("hooked") - }) - autoreleasepool { - let object = MyObject() - } - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. - - **WARNING**: In the closure, do not assign the object to anywhere outside the closure. Do not keep the reference of the object. Because the object is going to be released. Otherwise it crashs on: - 1. `malloc: *** error for object: pointer being freed was not allocated` - 2. `Cannot form weak reference to instance of class xxx. It is possible that this object was over-released, or is in the process of deallocation.` - 3. `EXC_BAD_ACCESS`. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocBefore(targetClass: T.Type, closure: @escaping (_ object: T) -> Void) throws -> Token { - let closure = { obj in - guard let obj = obj as? T else { fatalError() } - closure(obj) - } as @convention(block) (NSObject) -> Void - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .before, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) - } -} - -// MARK: after deinit - -/** - Execute the closure after the object deinit. - - # Example - ``` - class MyObject: NSObject { - } - let token = try! hookDeallocAfter(targetClass: MyObject.self, closure: { - print("hooked") - }) - autoreleasepool { - let object = MyObject() - } - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocAfter(targetClass: NSObject.Type, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .after, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .after, hookClosure: closure as AnyObject) - } -} - -// MARK: replace deinit - -/** - Replace the implementation of object's deinit method by the closure. - - # Example - ``` - class MyObject: NSObject { - } - let token = try! hookDeallocInstead(targetClass: MyObject.self, closure: { original in - print("before release") - original() - print("after release") - }) - autoreleasepool { - let object = MyObject() - } - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. - - - parameter original: The original dealloc method. - - **WARNING**: Have to call original to avoid memory leak. - - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocInstead(targetClass: NSObject.Type, closure: @escaping @convention(block) (_ original: () -> Void) -> Void) throws -> Token { - try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(targetClass: targetClass, selector: deallocSelector, mode: .instead, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: deallocSelector, mode: .instead, hookClosure: closure as AnyObject) - } -} diff --git a/SwiftHook/Classes/Public/HookClassMethods.swift b/SwiftHook/Classes/Public/HookClassMethods.swift deleted file mode 100644 index 63cd30e5..00000000 --- a/SwiftHook/Classes/Public/HookClassMethods.swift +++ /dev/null @@ -1,247 +0,0 @@ -// -// HookClassMethods.swift -// SwiftHook -// -// Created by Yanni Wang on 24/6/21. -// Copyright © 2021 Yanni. All rights reserved. -// - -import Foundation - -// MARK: - empty closure - -// before -/** - Execute the closure before the execution of class's method. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { - print("hooked") - }) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodBefore(targetClass: AnyClass, selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try hookClassMethodBefore(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// after -/** - Execute the closure after the execution of class's method. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { - print("hooked") - }) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodAfter(targetClass: AnyClass, selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try hookClassMethodAfter(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// MARK: - self and selector closure - -// before -/** - Execute the closure with the object and the selector before the execution of class's method. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel in - print("hooked") - }) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodBefore(targetClass: T.Type, selector: Selector, closure: @escaping (_ class: T.Type, _ selector: Selector) -> Void) throws -> Token { - let closure = { obj, sel in - guard let obj = obj as? T.Type else { fatalError() } - closure(obj, sel) - } as @convention(block) (AnyObject, Selector) -> Void - return try hookClassMethodBefore(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// after -/** - Execute the closure with the object and the selector after the execution of class's method. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel in - print("hooked") - }) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodAfter(targetClass: T.Type, selector: Selector, closure: @escaping (_ class: T.Type, _ selector: Selector) -> Void) throws -> Token { - let closure = { obj, sel in - guard let obj = obj as? T.Type else { fatalError() } - closure(obj, sel) - } as @convention(block) (AnyObject, Selector) -> Void - return try hookClassMethodAfter(targetClass: targetClass, selector: selector, closure: closure as Any) -} - -// MARK: - custom closure - -// before -/** - Execute the closure with all parameters before the execution of class's method. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in - print("hooked") - } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. The following is a description of the closure - 1. The first parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 2. The second parameter has to be Selector. - 3. The rest parameters are the same as the method's. - 4. The return type has to be Void. - 5. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodBefore(targetClass: AnyClass, selector: Selector, closure: Any) throws -> Token { - guard let targetClass = object_getClass(targetClass) else { - throw SwiftHookError.internalError(file: #file, line: #line) - } - return try swiftHookSerialQueue.sync { - try parametersCheck(targetClass: targetClass, selector: selector, mode: .before, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: selector, mode: .before, hookClosure: closure as AnyObject) - } -} - -// after -/** - Execute the closure with all parameters after the execution of class's method. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodAfter(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in - print("hooked") - } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. The following is a description of the closure - 1. The first parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 2. The second parameter has to be Selector. - 3. The rest parameters are the same as the method's. - 4. The return type has to be Void. - 5. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodAfter(targetClass: AnyClass, selector: Selector, closure: Any) throws -> Token { - guard let targetClass = object_getClass(targetClass) else { - throw SwiftHookError.internalError(file: #file, line: #line) - } - return try swiftHookSerialQueue.sync { - try parametersCheck(targetClass: targetClass, selector: selector, mode: .after, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: selector, mode: .after, hookClosure: closure as AnyObject) - } -} - -// instead -/** - Replace the implementation of class's method by the closure. - - # Example - ``` - class MyObject { - @objc dynamic class func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let token = try! hookClassMethodInstead(targetClass: MyObject.self, selector: #selector(MyObject.sum(_:_:)), closure: { original, obj, sel, number1, numebr2 in - // You may call the original method with some different parameters. You can even not call the original method. - return original(obj, sel, number1, numebr2) - } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Int ) - _ = MyObject.sum(1, 2) - token.cancelHook() // cancel hook - ``` - - parameter targetClass: The class you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. The following is a description of the closure - 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain Object and Selector at the beginning).. - 2. The second parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 3. The third parameter has to be Selector. - 4. The rest parameters are the same as the method's. - 5. The return type has to be the same as the original method's. - 6. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookClassMethodInstead(targetClass: AnyClass, selector: Selector, closure: Any) throws -> Token { - guard let targetClass = object_getClass(targetClass) else { - throw SwiftHookError.internalError(file: #file, line: #line) - } - return try swiftHookSerialQueue.sync { - try parametersCheck(targetClass: targetClass, selector: selector, mode: .instead, closure: closure as AnyObject) - return try internalHook(targetClass: targetClass, selector: selector, mode: .instead, hookClosure: closure as AnyObject) - } -} diff --git a/SwiftHook/Classes/Public/HookCommon.swift b/SwiftHook/Classes/Public/HookCommon.swift index d913764f..5b430157 100644 --- a/SwiftHook/Classes/Public/HookCommon.swift +++ b/SwiftHook/Classes/Public/HookCommon.swift @@ -40,6 +40,8 @@ public enum SwiftHookError: Error { case hookInstanceOfNSTaggedPointerString // Unsupport to hook instance of NSTaggedPointerString. case hookKVOUnsupportedInstance // Unable to hook a instance which is not support KVO. + + case nonObjcProperty } // MARK: - Token diff --git a/SwiftHook/Classes/Public/HookSpecificInstance.swift b/SwiftHook/Classes/Public/HookSpecificInstance.swift deleted file mode 100644 index 9a3f8ecf..00000000 --- a/SwiftHook/Classes/Public/HookSpecificInstance.swift +++ /dev/null @@ -1,409 +0,0 @@ -// -// HookInstance.swift -// SwiftHook -// -// Created by Yanni Wang on 20/6/21. -// Copyright © 2021 Yanni. All rights reserved. -// - -import Foundation - -// MARK: - empty closure - -// before -/** - Execute the closure before the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookBefore(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { - print("hooked") - }) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookBefore(object: AnyObject, selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try hookBefore(object: object, selector: selector, closure: closure as Any) -} - -// after -/** - Execute the closure after the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookAfter(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { - print("hooked") - }) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookAfter(object: AnyObject, selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try hookAfter(object: object, selector: selector, closure: closure as Any) -} - -// MARK: - self and selector closure - -// before -/** - Execute the closure with the object and the selector before the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookBefore(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel in - print("hooked") - }) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookBefore(object: T, selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { - let closure = { obj, sel in - guard let obj = obj as? T else { fatalError() } - closure(obj, sel) - } as @convention(block) (AnyObject, Selector) -> Void - return try hookBefore(object: object, selector: selector, closure: closure as Any) -} - -// after -/** - Execute the closure with the object and the selector after the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookAfter(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel in - print("hooked") - }) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookAfter(object: T, selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { - let closure = { obj, sel in - guard let obj = obj as? T else { fatalError() } - closure(obj, sel) - } as @convention(block) (AnyObject, Selector) -> Void - return try hookAfter(object: object, selector: selector, closure: closure as Any) -} - -// MARK: - custom closure - -// before -/** - Execute the closure with all parameters before the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookBefore(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in - print("hooked") - } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. The following is a description of the closure - 1. The first parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 2. The second parameter has to be Selector. - 3. The rest parameters are the same as the method's. - 4. The return type has to be Void. - 5. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookBefore(object: AnyObject, selector: Selector, closure: Any) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(object: object, selector: selector, mode: .before, closure: closure as AnyObject) - return try internalHook(object: object, selector: selector, mode: .before, hookClosure: closure as AnyObject) - } -} - -// after -/** - Execute the closure with all parameters after the execution of object's method. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookAfter(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in - print("hooked") - } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. The following is a description of the closure - 1. The first parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 2. The second parameter has to be Selector. - 3. The rest parameters are the same as the method's. - 4. The return type has to be Void. - 5. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookAfter(object: AnyObject, selector: Selector, closure: Any) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(object: object, selector: selector, mode: .after, closure: closure as AnyObject) - return try internalHook(object: object, selector: selector, mode: .after, hookClosure: closure as AnyObject) - } -} - -// instead -/** - Replace the implementation of object's method by the closure. - - # Example - ``` - class MyObject { - @objc dynamic func sum(_ number1: Int, _ number2: Int) -> Int { - return number1 + number2 - } - } - let object = MyObject() - let token = try! hookInstead(object: object, selector: #selector(MyObject.sum(_:_:)), closure: { original, obj, sel, number1, numebr2 in - // You may call the original method with some different parameters. You can even not call the original method. - return original(obj, sel, number1, numebr2) - } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Int ) - _ = object.sum(1, 2) - token.cancelHook() // cancel hook -``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. The following is a description of the closure - 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain Object and Selector at the beginning). - 2. The second parameter has to be AnyObject or YOUR CLASS (When it's YOUR CLASS. The class has to inherits from NSObject, otherwise will build error with "XXX is not representable in Objective-C, so it cannot be used with '@convention(block)'"). - 3. The third parameter has to be Selector. - 4. The rest parameters are the same as the method's. - 5. The return type has to be the same as the original method's. - 6. The keyword `@convention(block)` is necessary - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookInstead(object: AnyObject, selector: Selector, closure: Any) throws -> Token { - return try swiftHookSerialQueue.sync { - try parametersCheck(object: object, selector: selector, mode: .instead, closure: closure as AnyObject) - return try internalHook(object: object, selector: selector, mode: .instead, hookClosure: closure as AnyObject) - } -} - -// MARK: before deinit -/** - Execute the closure before the object deinit. - - # Example - ``` - class MyObject: NSObject { - } - var token: Token? - autoreleasepool { - let object = MyObject() - token = try! hookDeallocBefore(object: object, closure: { - print("hooked") - }) - } - token?.cancelHook() // cancel hook - ``` - - parameter object: The object you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocBefore(object: NSObject, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(object: object, selector: deallocSelector, mode: .before, closure: closure as AnyObject) - return try internalHook(object: object, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) - } -} - -/** - Execute the closure with the object before the object deinit. - - # Example - ``` - class MyObject: NSObject { - } - var token: Token? - autoreleasepool { - let object = MyObject() - token = try! hookDeallocBefore(object: object, closure: { obj in - print("hooked") - }) - } - token?.cancelHook() // cancel hook - ``` - - parameter object: The object you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. - - **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - **WARNING**: In the closure, do not assign the object to anywhere outside the closure. Do not keep the reference of the object. Because the object is going to be released. Otherwise it crashs on: - 1. `malloc: *** error for object: pointer being freed was not allocated` - 2. `Cannot form weak reference to instance of class xxx. It is possible that this object was over-released, or is in the process of deallocation.` - 3. `EXC_BAD_ACCESS`. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocBefore(object: T, closure: @escaping (_ object: T) -> Void) throws -> Token { - let closure = { obj in - guard let obj = obj as? T else { fatalError() } - closure(obj) - } as @convention(block) (NSObject) -> Void - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(object: object, selector: deallocSelector, mode: .before, closure: closure as AnyObject) - return try internalHook(object: object, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) - } -} - -// MARK: after deinit - -/** - Execute the closure after the object deinit. - - # Example - ``` - class MyObject: NSObject { - } - var token: Token? - autoreleasepool { - let object = MyObject() - token = try! hookDeallocAfter(object: object, closure: { - print("hooked") - }) - } - token?.cancelHook() // cancel hook - ``` - - parameter object: The object you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocAfter(object: NSObject, closure: @escaping @convention(block) () -> Void) throws -> Token { - return try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(object: object, selector: deallocSelector, mode: .after, closure: closure as AnyObject) - return try internalHook(object: object, selector: deallocSelector, mode: .after, hookClosure: closure as AnyObject) - } -} - -/** - Execute the closure after the object deinit. This API doesn't use the runtime. Just add a "Associated Object" to the object. The object is the only one retained the "Associated Object". So after the object released, the "Associated Object" know this event. This API can work for both NSObject and pure Swift object. - - # Example - ``` - class MyObject: NSObject { - } - var token: Token? - autoreleasepool { - let object = MyObject() - token = hookDeallocAfterByTail(object: object, closure: { - print("hooked") - }) - } - token?.cancelHook() // cancel hook - ``` - - parameter object: The object you want to hook on. It doesn’t have to be inherited from NSObject. - - parameter closure: The hook closure. **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocAfterByTail(object: AnyObject, closure: @escaping @convention(block) () -> Void) -> Token { - return swiftHookSerialQueue.sync { - return hookDeallocAfterByDelegate(object: object, closure: closure as AnyObject) - } -} - -// MARK: replace deinit - -/** - Replace the implementation of object's deinit method by the closure. - - # Example - ``` - class MyObject: NSObject { - } - var token: Token? - autoreleasepool { - let object = MyObject() - token = try! hookDeallocInstead(object: object, closure: { original in - print("before release") - original() - print("after release") - }) - } - token?.cancelHook() // cancel hook - ``` - - parameter object: The object you want to hook on. It has to be inherited from NSObject. - - parameter closure: The hook closure. - - **WARNING**: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. - - - parameter original: The original dealloc method. - - **WARNING**: Have to call original to avoid memory leak. - - - returns: The token of this hook behavior. You may cancel this hook through this token. - */ -@discardableResult -public func hookDeallocInstead(object: NSObject, closure: @escaping @convention(block) (_ original: () -> Void) -> Void) throws -> Token { - try swiftHookSerialQueue.sync { () -> Token in - try parametersCheck(object: object, selector: deallocSelector, mode: .instead, closure: closure as AnyObject) - return try internalHook(object: object, selector: deallocSelector, mode: .instead, hookClosure: closure as AnyObject) - } -} diff --git a/SwiftHook/Classes/Public/ObjectHook.swift b/SwiftHook/Classes/Public/ObjectHook.swift new file mode 100644 index 00000000..7bfdf75a --- /dev/null +++ b/SwiftHook/Classes/Public/ObjectHook.swift @@ -0,0 +1,666 @@ +// +// ObjectHook.swift +// SwiftHook +// +// Created by Florian Zand on 16.05.25. +// + +import Foundation + +/// Hooks methods of an object. +public struct ObjectHook { + let object: T + + public init(_ object: T) { + self.object = object + } + + // MARK: - empty closure + + /** + Execute the closure before the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hookBefore(#selector(MyObject.sum(_:_:)) { + print("hooked") + + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookBefore(selector, closure: closure as Any) + } + + /** + Execute the closure before the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with the object and the selector before the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hookBefore(#selector(MyObject.sum(_:_:))) { obj, sel in + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + let closure = { obj, sel in + guard let obj = obj as? T else { fatalError() } + closure(obj, sel) + } as @convention(block) (AnyObject, Selector) -> Void + return try hookBefore(selector, closure: closure as Any) + } + + /** + Execute the closure with the object and the selector before the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure with all parameters before the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hookBefore(#selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in + print("hooked") + } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookBefore(_ selector: Selector, closure: Any) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(object: object, selector: selector, mode: .before, closure: closure as AnyObject) + return try internalHook(object: object, selector: selector, mode: .before, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with all parameters before the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookBefore(_ selector: String, closure: Any) throws -> Token { + try hookBefore(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure after the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hookAfter(#selector(MyObject.sum(_:_:))) { + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookAfter(selector, closure: closure as Any) + } + + /** + Execute the closure after the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: @escaping @convention(block) () -> Void) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + // MARK: - self and selector closure + + /** + Execute the closure with the object and the selector after the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hookAfter(#selector(MyObject.sum(_:_:))) { obj, sel in + print("hooked") + } + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + let closure = { obj, sel in + guard let obj = obj as? T else { fatalError() } + closure(obj, sel) + } as @convention(block) (AnyObject, Selector) -> Void + return try hookAfter(selector, closure: closure as Any) + } + + /** + Execute the closure with the object and the selector after the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: @escaping (_ object: T, _ selector: Selector) -> Void) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + // MARK: - custom closure + + + /** + Execute the closure with all parameters after the execution of object's method. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hookAfter(#selector(MyObject.sum(_:_:)), closure: { obj, sel, number1, number2 in + print("hooked") + } as @convention(block) (AnyObject, Selector, Int, Int) -> Void) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookAfter(_ selector: Selector, closure: Any) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(object: object, selector: selector, mode: .before, closure: closure as AnyObject) + return try internalHook(object: object, selector: selector, mode: .after, hookClosure: closure as AnyObject) + } + } + + /** + Execute the closure with all parameters after the execution of object's method. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be `AnyObject` or your class (When it's your class. + 2. The second parameter has to be `Selector`. + 3. The rest parameters are the same as the method's. + 4. The return type has to be Void. + 5. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookAfter(_ selector: String, closure: Any) throws -> Token { + try hookAfter(NSSelectorFromString(selector), closure: closure) + } + + /** + Replace the implementation of object's method by the closure. + + Example usage: + + ``` + class MyObject { + @objc func sum(_ number1: Int, _ number2: Int) -> Int { + return number1 + number2 + } + } + + let object = MyObject() + try! ObjectHook(object).hook(#selector(MyObject.sum(_:_:)), closure: { original, obj, sel, number1, numebr2 in + return original(obj, sel, number1, numebr2) * 2 + } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Int ) + ``` + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain `AnyObject` and `Selector` at the beginning). + 2. The second parameter has to be `AnyObject` or your class (When it's your class. + 3. The third parameter has to be `Selector`. + 4. The rest parameters are the same as the method's. + 5. The return type has to be the same as the original method's. + 6. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hook(_ selector: Selector, closure: Any) throws -> Token { + return try swiftHookSerialQueue.sync { + try parametersCheck(object: object, selector: selector, mode: .instead, closure: closure as AnyObject) + return try internalHook(object: object, selector: selector, mode: .instead, hookClosure: closure as AnyObject) + } + } + + /** + Replace the implementation of object's method by the closure. + + - parameter selector: The method you want to hook on. It has to be declared with the keywords `@objc` and `dynamic`. + - parameter closure: The hook closure as following: + 1. The first parameter has to be a closure. This closure means original method. The closure's parameters and return type are the same as the original method's (The parameters contain `AnyObject` and `Selector` at the beginning). + 2. The second parameter has to be `AnyObject` or your class (When it's your class. + 3. The third parameter has to be `Selector`. + 4. The rest parameters are the same as the method's. + 5. The return type has to be the same as the original method's. + 6. The keyword `@convention(block)` is necessary + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hook(_ selector: String, closure: Any) throws -> Token { + try hook(NSSelectorFromString(selector), closure: closure) + } + + /** + Execute the closure after the object deinit. + + Example usage: + + ``` + try! ObjectHook(object).hookDeallocAfter { + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookDeallocAfterByTail(closure: @escaping @convention(block) () -> Void) -> Token { + return swiftHookSerialQueue.sync { + return hookDeallocAfterByDelegate(object: object, closure: closure as AnyObject) + } + } +} + +extension ObjectHook where T: NSObject { + /** + Execute the closure with the object before the object deinit. + + Example usage: + + ``` + try! ObjectHook(object).hookDeallocBefore { obj in + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + - Note: In the closure, do not assign the object to anywhere outside the closure. Do not keep the reference of the object. Because the object is going to be released. + */ + @discardableResult + public func hookDeallocBefore(closure: @escaping (_ object: T) -> Void) throws -> Token { + let closure = { obj in + guard let obj = obj as? T else { fatalError() } + closure(obj) + } as @convention(block) (NSObject) -> Void + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(object: object, selector: deallocSelector, mode: .before, closure: closure as AnyObject) + return try internalHook(object: object, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) + } + } + + // MARK: before deinit + /** + Execute the closure before the object deinit. + + Example usage: + + ``` + try! ObjectHook(object).hookDeallocBefore { + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + func hookDeallocBefore(closure: @escaping @convention(block) () -> Void) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(object: object, selector: deallocSelector, mode: .before, closure: closure as AnyObject) + return try internalHook(object: object, selector: deallocSelector, mode: .before, hookClosure: closure as AnyObject) + } + } + + // MARK: after deinit + + /** + Execute the closure after the object deinit. + + Example usage: + + ``` + try! ObjectHook(object).hookDeallocAfter { + print("hooked") + } + ``` + + - parameter closure: The hook closure. + - returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookDeallocAfter(closure: @escaping @convention(block) () -> Void) throws -> Token { + return try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(object: object, selector: deallocSelector, mode: .after, closure: closure as AnyObject) + return try internalHook(object: object, selector: deallocSelector, mode: .after, hookClosure: closure as AnyObject) + } + } + + // MARK: replace deinit + + /** + Replace the implementation of object's deinit method by the closure. + + Example usage: + + ``` + try! ObjectHook(object).hookDealloc { original in + print("before release of object") + original() + print("after release of object") + } + ``` + + - Parameter closure: The hook closure with the original dealloc method as parameter. You have to call it to avoid memory leak. + - Returns: The token of this hook behavior. You may cancel this hook through this token. + + - Note: The object will retain the closure. So make sure that the closure doesn't retain the object in turn to avoid memory leak because of cycle retain. + */ + @discardableResult + public func hookDealloc(closure: @escaping @convention(block) (_ original: () -> Void) -> Void) throws -> Token { + try swiftHookSerialQueue.sync { () -> Token in + try parametersCheck(object: object, selector: deallocSelector, mode: .instead, closure: closure as AnyObject) + return try internalHook(object: object, selector: deallocSelector, mode: .instead, hookClosure: closure as AnyObject) + } + } +} + +public extension ObjectHook { + /** + Hooks before getting the specified property. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked before the property is get. It receives: + - `object`: The object. + - `value`: The value to be returned by the property. + + Example usage: + ```swift + try ObjectHook(textfield).hookBefore(\.stringValue) { textfield, stringValue in + // hooks before. + } + ``` + */ + @discardableResult + func hookBefore(_ keyPath: KeyPath, closure: @escaping (_ object: T,_ value: Value)->()) throws -> Token { + try hookBefore(try keyPath.getterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks before setting the specified property. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked before the property is set. It receives: + - `object`: The object. + - `value`: The new value to be set to the property. + + Example usage: + ```swift + try ObjectHook(textfield).hookBefore(set \.stringValue) { textfield, stringValue in + // hooks before. + } + ``` + */ + @discardableResult + func hookBefore(set keyPath: WritableKeyPath, closure: @escaping (_ object: T,_ value: Value)->()) throws -> Token { + try hookBefore(try keyPath.setterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks after getting the specified property. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked after the property is read. It receives: + - `object`: The object. + - `value`: The current value of the property. + + Example usage: + ```swift + try ObjectHook(textfield).hookAfter(\.stringValue) { textfield, stringValue in + // hooks after. + } + ``` + */ + @discardableResult + func hookAfter(_ keyPath: KeyPath, closure: @escaping (_ object: T,_ value: Value)->()) throws -> Token { + try hookAfter(try keyPath.getterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks after setting the specified property. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: The handler that is invoked after the property is set. It receives: + - `object`: The object. + - `value`: The new value of the property. + + Example usage: + ```swift + try ObjectHook(textfield).hookAfter(set \.stringValue) { textfield, stringValue in + // hooks after. + } + ``` + */ + @discardableResult + func hookAfter(set keyPath: WritableKeyPath, closure: @escaping (_ object: T,_ value: Value)->()) throws -> Token { + try hookAfter(try keyPath.setterName(), closure: { obj, sel, val in + guard let val = val as? Value, let obj = obj as? T else { return } + closure(obj, val) + } as @convention(block) (AnyObject, Selector, Any) -> Void ) + } + + /** + Hooks getting the specified property. + + - Parameters: + - keyPath: The key path to the property to hook. + - closure: A closure that is invoked whenever the property is read. It receives: + - `object`: The instance on which the property is being accessed. + - `original`: The value returned by the original getter. + - Returns: The value to return from the getter. This can be the original value or a modified one. + + Example usage: + ```swift + try ObjectHook(textfield).hook(\.stringValue) { object, originalValue in + return originalValue.uppercased() + } + ``` + */ + @discardableResult + func hook(_ keyPath: KeyPath, closure: @escaping (_ object: T, _ original: Value)->(Value)) throws -> Token { + try hook(try keyPath.getterName(), closure: { original, obj, sel in + if let value = original(obj, sel) as? Value, let obj = obj as? T { + return closure(obj, value) + } + return original(obj, sel) + } as @convention(block) ((AnyObject, Selector) -> Any, + AnyObject, Selector) -> Any) + } + + /** + Hooks setting the specified property. + + - Parameters: + - keyPath: The key path to the writable property to hook. + - closure: The handler that is invoked whenever the property is set. It receives: + - `object`: The instance on which the property is being set. + - `value`: The new value that is about to be written to the property. + - `original`: A block that invokes the original setter. If the block isn't called, the property will not be updated. + + Example usage: + ```swift + try ObjectHook(textfield).hook(set \.stringValue) { textfield, stringValue, original in + if stringValue != "" { + // Sets the stringValue. + original(stringValue) + } + } + ``` + */ + @discardableResult + func hook(set keyPath: WritableKeyPath, closure: @escaping (_ object: T, _ value: Value, _ original: (Value)->())->()) throws -> Token { + try hook(try keyPath.setterName(), closure: { original, obj, sel, val in + if let val = val as? Value, let ob = obj as? T { + let original: (Value)->() = { original(obj, sel, $0) } + closure(ob, val, original) + } else { + original(obj, sel, val) + } + } as @convention(block) ((AnyObject, Selector, Any) -> Void, + AnyObject, Selector, Any) -> Void) + } +} diff --git a/SwiftHook/Classes/PublicObjectiveCAPI/HookAllInstancesOC.swift b/SwiftHook/Classes/PublicObjectiveCAPI/HookAllInstancesOC.swift index dc5a2fc0..f2c03876 100644 --- a/SwiftHook/Classes/PublicObjectiveCAPI/HookAllInstancesOC.swift +++ b/SwiftHook/Classes/PublicObjectiveCAPI/HookAllInstancesOC.swift @@ -173,7 +173,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookBefore(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookBefore(targetClass: self, selector: selector, closure: closure)) + return OCToken(token: try ClassInstanceHook(self).hookBefore(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -215,7 +215,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookAfter(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookAfter(targetClass: self, selector: selector, closure: closure)) + return OCToken(token: try ClassInstanceHook(self).hookAfter(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -260,7 +260,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookInstead(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookInstead(targetClass: self, selector: selector, closure: closure)) + return OCToken(token: try ClassInstanceHook(self).hook(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -289,7 +289,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookDeallocBefore(closure: @escaping @convention(block) () -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocBefore(targetClass: self, closure: closure)) + return OCToken(token: try ClassInstanceHook(self).hookDeallocBefore(closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -322,7 +322,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookDeallocBefore(closureObj: @escaping @convention(block) (_ object: NSObject) -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocBefore(targetClass: self, closure: closureObj)) + return OCToken(token: try ClassInstanceHook(self).hookDeallocBefore(closure: closureObj)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -351,7 +351,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookDeallocAfter(closure: @escaping @convention(block) () -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocAfter(targetClass: self, closure: closure)) + return OCToken(token: try ClassInstanceHook(self).hookDeallocAfter(closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -385,7 +385,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookDeallocInstead(closure: @escaping @convention(block) (_ original: () -> Void) -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocInstead(targetClass: self, closure: closure)) + return OCToken(token: try ClassInstanceHook(self).hookDealloc(closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error diff --git a/SwiftHook/Classes/PublicObjectiveCAPI/HookClassMethodsOC.swift b/SwiftHook/Classes/PublicObjectiveCAPI/HookClassMethodsOC.swift index 43e8c4d9..b5495191 100644 --- a/SwiftHook/Classes/PublicObjectiveCAPI/HookClassMethodsOC.swift +++ b/SwiftHook/Classes/PublicObjectiveCAPI/HookClassMethodsOC.swift @@ -181,7 +181,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookClassMethodBefore(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookClassMethodBefore(targetClass: self, selector: selector, closure: closure)) + return OCToken(token: try ClassHook(self).hookBefore(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -223,7 +223,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookClassMethodAfter(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookClassMethodAfter(targetClass: self, selector: selector, closure: closure)) + return OCToken(token: try ClassHook(self).hookAfter(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -267,7 +267,7 @@ public extension NSObject { @discardableResult @objc class func sh_hookClassMethodInstead(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookClassMethodInstead(targetClass: self, selector: selector, closure: closure)) + return OCToken(token: try ClassHook(self).hook(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error diff --git a/SwiftHook/Classes/PublicObjectiveCAPI/HookCommonOC.swift b/SwiftHook/Classes/PublicObjectiveCAPI/HookCommonOC.swift index 9bb4131c..bdce1ec6 100644 --- a/SwiftHook/Classes/PublicObjectiveCAPI/HookCommonOC.swift +++ b/SwiftHook/Classes/PublicObjectiveCAPI/HookCommonOC.swift @@ -61,6 +61,10 @@ extension SwiftHookError { case .hookKVOUnsupportedInstance: code = 11 description = "Unable to hook a instance which is not support KVO." + case .nonObjcProperty: + // Won't be thrown as any key path to a property of a NSObject is an Objective-C property. + code = 12 + description = "The property isn't an Objective-C property." } return NSError.init(domain: "SwiftHook.SwiftHookError", code: code, userInfo: [NSLocalizedDescriptionKey: description]) diff --git a/SwiftHook/Classes/PublicObjectiveCAPI/HookSpecificInstanceOC.swift b/SwiftHook/Classes/PublicObjectiveCAPI/HookSpecificInstanceOC.swift index 885e2bb3..6a346e49 100644 --- a/SwiftHook/Classes/PublicObjectiveCAPI/HookSpecificInstanceOC.swift +++ b/SwiftHook/Classes/PublicObjectiveCAPI/HookSpecificInstanceOC.swift @@ -178,7 +178,7 @@ public extension NSObject { @discardableResult @objc func sh_hookBefore(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookBefore(object: self, selector: selector, closure: closure)) + return OCToken(token: try ObjectHook(self).hookBefore(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -221,7 +221,7 @@ public extension NSObject { @discardableResult @objc func sh_hookAfter(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookAfter(object: self, selector: selector, closure: closure)) + return OCToken(token: try ObjectHook(self).hookAfter(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -267,7 +267,7 @@ public extension NSObject { @discardableResult @objc func sh_hookInstead(selector: Selector, closure: Any) throws -> OCToken { do { - return OCToken(token: try hookInstead(object: self, selector: selector, closure: closure)) + return OCToken(token: try ObjectHook(self).hook(selector, closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -298,7 +298,7 @@ public extension NSObject { @discardableResult @objc func sh_hookDeallocBefore(closure: @escaping @convention(block) () -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocBefore(object: self, closure: closure)) + return OCToken(token: try ObjectHook(self).hookDeallocBefore(closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -334,7 +334,7 @@ public extension NSObject { @discardableResult @objc func sh_hookDeallocBefore(closureObj: @escaping @convention(block) (_ object: NSObject) -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocBefore(object: self, closure: closureObj)) + return OCToken(token: try ObjectHook(self).hookDeallocBefore(closure: closureObj)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -365,7 +365,7 @@ public extension NSObject { @discardableResult @objc func sh_hookDeallocAfter(closure: @escaping @convention(block) () -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocAfter(object: self, closure: closure)) + return OCToken(token: try ObjectHook(self).hookDeallocAfter(closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error @@ -393,7 +393,7 @@ public extension NSObject { */ @discardableResult @objc func sh_hookDeallocAfterByTail(closure: @escaping @convention(block) () -> Void) -> OCToken { - return OCToken(token: hookDeallocAfterByTail(object: self, closure: closure)) + return OCToken(token: ObjectHook(self).hookDeallocAfterByTail(closure: closure)) } // MARK: replace deinit @@ -425,7 +425,7 @@ public extension NSObject { @discardableResult @objc func sh_hookDeallocInstead(closure: @escaping @convention(block) (_ original: () -> Void) -> Void) throws -> OCToken { do { - return OCToken(token: try hookDeallocInstead(object: self, closure: closure)) + return OCToken(token: try ObjectHook(self).hookDealloc(closure: closure)) } catch { guard let swiftHookError = error as? SwiftHookError else { throw error diff --git a/SwiftHookTests/Advanced/Basic/AllInstancesInsteadTests.swift b/SwiftHookTests/Advanced/Basic/AllInstancesInsteadTests.swift index 5266847e..3489eb80 100644 --- a/SwiftHookTests/Advanced/Basic/AllInstancesInsteadTests.swift +++ b/SwiftHookTests/Advanced/Basic/AllInstancesInsteadTests.swift @@ -264,7 +264,7 @@ class AllInstancesInsteadTests: XCTestCase { func testChangeReturn() { do { - let token = try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.generateNumber(number:)), closure: { original, object, selector, number in + let token = try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.generateNumber(number:)), closure: { original, object, selector, number in XCTAssertEqual(number, 4) let number = original(object, selector, 5) XCTAssertEqual(number.intValue, 5) diff --git a/SwiftHookTests/Advanced/Basic/ClassMethodInsteadTests.swift b/SwiftHookTests/Advanced/Basic/ClassMethodInsteadTests.swift index d6d5d61c..ca21ef17 100644 --- a/SwiftHookTests/Advanced/Basic/ClassMethodInsteadTests.swift +++ b/SwiftHookTests/Advanced/Basic/ClassMethodInsteadTests.swift @@ -276,7 +276,7 @@ class ClassMethodInsteadTests: XCTestCase { func testChangeReturn() { do { - let token = try hookClassMethodInstead(targetClass: TestObject.self, selector: #selector(TestObject.classGenerateNumber(number:)), closure: { original, o, s, number in + let token = try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.classGenerateNumber(number:)), closure: { original, o, s, number in XCTAssertEqual(number, 4) let number = original(o, s, 5) XCTAssertEqual(number.intValue, 5) diff --git a/SwiftHookTests/Advanced/Basic/SingleInstancesInsteadTests.swift b/SwiftHookTests/Advanced/Basic/SingleInstancesInsteadTests.swift index 028b7fe3..843faad2 100644 --- a/SwiftHookTests/Advanced/Basic/SingleInstancesInsteadTests.swift +++ b/SwiftHookTests/Advanced/Basic/SingleInstancesInsteadTests.swift @@ -311,7 +311,7 @@ class SingleInstancesInsteadTests: XCTestCase { func testChangeReturn() { do { let object = TestObject() - let token = try hookInstead(object: object, selector: #selector(TestObject.generateNumber(number:)), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(TestObject.generateNumber(number:)), closure: { original, o, s, number in XCTAssertEqual(number, 4) let number = original(o, s, 5) XCTAssertEqual(number.intValue, 5) diff --git a/SwiftHookTests/Advanced/CompatibilityTests.swift b/SwiftHookTests/Advanced/CompatibilityTests.swift index 2e019c56..e49a4f35 100644 --- a/SwiftHookTests/Advanced/CompatibilityTests.swift +++ b/SwiftHookTests/Advanced/CompatibilityTests.swift @@ -38,7 +38,7 @@ class CompatibilityTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -79,7 +79,7 @@ class CompatibilityTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -125,7 +125,7 @@ class CompatibilityTests: XCTestCase { } XCTAssertTrue(try testGetObjectType(object: object) == .KVOed(mode: .normal)) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -166,7 +166,7 @@ class CompatibilityTests: XCTestCase { } XCTAssertTrue(try testGetObjectType(object: object) == .KVOed(mode: .normal)) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -227,7 +227,7 @@ class CompatibilityTests: XCTestCase { // SwiftHook let start = Int.random(in: Int.min ... Int.max) let end = Int.random(in: Int.min ... Int.max) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.property1), closure: { original, object, selector, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.property1), closure: { original, object, selector, number in order.append(start) original(object, selector, number) order.append(end) @@ -372,7 +372,7 @@ class CompatibilityTests: XCTestCase { } XCTAssertEqual(try testGetObjectType(object: object), .KVOed(mode: .normal)) - _ = try hookInstead(object: object, selector: #selector(setter: MyObject1.obj), closure: { original, object, selector, number in + _ = try ObjectHook(object).hook(#selector(setter: MyObject1.obj), closure: { original, object, selector, number in order.append(1) original(object, selector, number) order.append(3) @@ -392,7 +392,7 @@ class CompatibilityTests: XCTestCase { var order = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - _ = try hookInstead(object: object, selector: #selector(setter: MyObject1.obj), closure: { original, object, selector, number in + _ = try ObjectHook(object).hook(#selector(setter: MyObject1.obj), closure: { original, object, selector, number in order.append(1) original(object, selector, number) order.append(3) @@ -432,33 +432,33 @@ class CompatibilityTests: XCTestCase { } // before - try hookBefore(object: object, selector: deallocSelector) { + try ObjectHook(object).hookBefore(deallocSelector) { order.append(4) } - try hookDeallocBefore(object: object, closure: { + try ObjectHook(object).hookDeallocBefore(closure: { order.append(3) }) // instead - try hookInstead(object: object, selector: deallocSelector, closure: { original in + try ObjectHook(object).hook(deallocSelector, closure: { original in order.append(2) original() order.append(8) } as @convention(block) (() -> Void) -> Void) - try hookDeallocInstead(object: object, closure: { original in + try ObjectHook(object).hookDealloc(closure: { original in order.append(1) original() order.append(9) } as @convention(block) (() -> Void) -> Void) // after - try hookAfter(object: object, selector: deallocSelector) { + try ObjectHook(object).hookAfter(deallocSelector) { order.append(7) } - try hookDeallocAfter(object: object, closure: { + try ObjectHook(object).hookDeallocAfter(closure: { order.append(6) }) - hookDeallocAfterByTail(object: object, closure: { + ObjectHook(object).hookDeallocAfterByTail(closure: { order.append(5) }) XCTAssertTrue(try testGetObjectType(object: object) == .KVOed(mode: .swiftHook)) @@ -480,20 +480,20 @@ class CompatibilityTests: XCTestCase { XCTAssertTrue(try testGetObjectType(object: object) == .normal) // before - try hookBefore(object: object, selector: deallocSelector) { + try ObjectHook(object).hookBefore(deallocSelector) { order.append(4) } - try hookDeallocBefore(object: object, closure: { + try ObjectHook(object).hookDeallocBefore(closure: { order.append(3) }) // instead - try hookInstead(object: object, selector: deallocSelector, closure: { original in + try ObjectHook(object).hook(deallocSelector, closure: { original in order.append(2) original() order.append(8) } as @convention(block) (() -> Void) -> Void) - try hookDeallocInstead(object: object, closure: { original in + try ObjectHook(object).hookDealloc(closure: { original in order.append(1) original() order.append(9) @@ -505,13 +505,13 @@ class CompatibilityTests: XCTestCase { kvo.invalidate() // after - try hookAfter(object: object, selector: deallocSelector) { + try ObjectHook(object).hookAfter(deallocSelector) { order.append(7) } - try hookDeallocAfter(object: object, closure: { + try ObjectHook(object).hookDeallocAfter(closure: { order.append(6) }) - hookDeallocAfterByTail(object: object, closure: { + ObjectHook(object).hookDeallocAfterByTail(closure: { order.append(5) }) XCTAssertTrue(try testGetObjectType(object: object) == .KVOed(mode: .swiftHook)) @@ -533,33 +533,33 @@ class CompatibilityTests: XCTestCase { XCTAssertTrue(try testGetObjectType(object: object) == .normal) // before - try hookBefore(object: object, selector: deallocSelector) { + try ObjectHook(object).hookBefore(deallocSelector) { order.append(4) } - try hookDeallocBefore(object: object, closure: { + try ObjectHook(object).hookDeallocBefore(closure: { order.append(3) }) // instead - try hookInstead(object: object, selector: deallocSelector, closure: { original in + try ObjectHook(object).hook(deallocSelector, closure: { original in order.append(2) original() order.append(8) } as @convention(block) (() -> Void) -> Void) - try hookDeallocInstead(object: object, closure: { original in + try ObjectHook(object).hookDealloc(closure: { original in order.append(1) original() order.append(9) } as @convention(block) (() -> Void) -> Void) // after - try hookAfter(object: object, selector: deallocSelector) { + try ObjectHook(object).hookAfter(deallocSelector) { order.append(7) } - try hookDeallocAfter(object: object, closure: { + try ObjectHook(object).hookDeallocAfter(closure: { order.append(6) }) - hookDeallocAfterByTail(object: object, closure: { + ObjectHook(object).hookDeallocAfterByTail(closure: { order.append(5) }) @@ -588,7 +588,7 @@ class CompatibilityTests: XCTestCase { // var expectation = [Int]() // XCTAssertEqual(try testGetObjectType(object: object), .normal) // - // let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + // let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in // expectation.append(1) // original(o, s, number) // expectation.append(2) @@ -638,7 +638,7 @@ class CompatibilityTests: XCTestCase { } as @convention(block) (AspectInfo) -> Void) XCTAssertTrue(try testGetObjectType(object: object) == .others) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(4) @@ -682,7 +682,7 @@ class CompatibilityTests: XCTestCase { // } as @convention(block) (AspectInfo) -> Void) // XCTAssertTrue(try testGetObjectType(object: object) == .others) // - // let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + // let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in // expectation.append(1) // original(o, s, number) // expectation.append(4) @@ -720,7 +720,7 @@ class CompatibilityTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -764,7 +764,7 @@ class CompatibilityTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -807,7 +807,7 @@ class CompatibilityTests: XCTestCase { } } - let token1 = try hookInstead(targetClass: MyOCObject.self, selector: #selector(MyOCObject.myMethod), closure: { original, o, s in + let token1 = try ClassInstanceHook(MyOCObject.self).hook(#selector(MyOCObject.myMethod), closure: { original, o, s in original(o, s) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) guard let hookToken1 = token1 as? HookToken else { @@ -816,7 +816,7 @@ class CompatibilityTests: XCTestCase { } XCTAssertTrue(try internalCancelHook(token: hookToken1)!) - let token2 = try hookInstead(targetClass: MySwiftObject.self, selector: #selector(MySwiftObject.myMethod), closure: { original, o, s in + let token2 = try ClassInstanceHook(MySwiftObject.self).hook(#selector(MySwiftObject.myMethod), closure: { original, o, s in original(o, s) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) guard let hookToken2 = token2 as? HookToken else { @@ -825,7 +825,7 @@ class CompatibilityTests: XCTestCase { } XCTAssertTrue(try internalCancelHook(token: hookToken2)!) - let token3 = try hookInstead(targetClass: MyOCObject.self, selector: #selector(MyOCObject.myMethod), closure: { original, o, s in + let token3 = try ClassInstanceHook(MyOCObject.self).hook(#selector(MyOCObject.myMethod), closure: { original, o, s in original(o, s) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) let method3 = class_getInstanceMethod(MyOCObject.self, #selector(MyOCObject.myMethod))! @@ -837,7 +837,7 @@ class CompatibilityTests: XCTestCase { } XCTAssertFalse(try internalCancelHook(token: hookToken3)!) - let token4 = try hookInstead(targetClass: MySwiftObject.self, selector: #selector(MySwiftObject.myMethod), closure: { original, o, s in + let token4 = try ClassInstanceHook(MySwiftObject.self).hook(#selector(MySwiftObject.myMethod), closure: { original, o, s in original(o, s) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) let method4 = class_getInstanceMethod(MySwiftObject.self, #selector(MySwiftObject.myMethod))! diff --git a/SwiftHookTests/Advanced/MethodObjectAndSelectorTest.swift b/SwiftHookTests/Advanced/MethodObjectAndSelectorTest.swift index d5c96d53..ad0734e9 100644 --- a/SwiftHookTests/Advanced/MethodObjectAndSelectorTest.swift +++ b/SwiftHookTests/Advanced/MethodObjectAndSelectorTest.swift @@ -27,7 +27,7 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertTrue(obj === object) XCTAssertTrue(sel == selector) } as @convention(block) (NSObject, Selector) -> Void - let token = try hookBefore(targetClass: targetClass, selector: selector, closure: closure) + let token = try ClassInstanceHook(targetClass).hookBefore(selector, closure: closure) // test hook object.noArgsNoReturnFunc() @@ -49,7 +49,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.noArgsNoReturnFunc) let closure = {_, _ in } as @convention(block) (Selector, NSObject) -> Void - try hookBefore(targetClass: targetClass, selector: selector, closure: closure) + try ClassInstanceHook(targetClass).hookBefore(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `befor` and `after` mode. The parameters type of the hook closure have to be nil or `@:` or as the same as method's. The closure parameters type is `:@`. The method parameters type is `@:`. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -72,7 +72,7 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertTrue(obj === object) XCTAssertTrue(sel == selector) } as @convention(block) (NSObject, Selector) -> Void - let token = try hookAfter(targetClass: targetClass, selector: selector, closure: closure) + let token = try ClassInstanceHook(targetClass).hookAfter(selector, closure: closure) // test hook object.noArgsNoReturnFunc() @@ -94,7 +94,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.noArgsNoReturnFunc) let closure = {_, _ in } as @convention(block) (Selector, NSObject) -> Void - try hookAfter(targetClass: targetClass, selector: selector, closure: closure) + try ClassInstanceHook(targetClass).hookAfter(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `befor` and `after` mode. The parameters type of the hook closure have to be nil or `@:` or as the same as method's. The closure parameters type is `:@`. The method parameters type is `@:`. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -119,7 +119,7 @@ class MethodObjectAndSelectorTest: XCTestCase { original(obj, sel, closure) result.append(1) } as @convention(block) (@escaping (AnyObject, Selector, () -> Void) -> Void, AnyObject, Selector, () -> Void) -> Void - let token = try hookInstead(targetClass: targetClass, selector: selector, closure: closure) + let token = try ClassInstanceHook(targetClass).hook(selector, closure: closure) // test hook object.execute { @@ -143,7 +143,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.noArgsNoReturnFunc) let closure = { _, _, _ in } as @convention(block) ((Selector, NSObject) -> Void, Selector, NSObject) -> Void - try hookInstead(targetClass: targetClass, selector: selector, closure: closure) + try ClassInstanceHook(targetClass).hook(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `instead` mode. The parameters type of the original closure (the hook closure's first parameter) have to be the same as the method's. The original closure parameters type is `:@`. But the method parameters type is `@:`. They are not the same. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -170,13 +170,13 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertEqual(obj.number, 2) XCTAssertEqual(sel, #selector(ObjectiveCTestObject.noArgsNoReturnFunc)) } as @convention(block) (ObjectiveCTestObject, Selector) -> Void - let tokenBefore = try hookBefore(targetClass: targetClass, selector: selector, closure: closureBefore) + let tokenBefore = try ClassInstanceHook(targetClass).hookBefore(selector, closure: closureBefore) let closureAfter = {obj, sel in XCTAssertEqual(obj.number, 2) XCTAssertEqual(sel, #selector(ObjectiveCTestObject.noArgsNoReturnFunc)) } as @convention(block) (ObjectiveCTestObject, Selector) -> Void - let tokenAfter = try hookAfter(targetClass: targetClass, selector: selector, closure: closureAfter) + let tokenAfter = try ClassInstanceHook(targetClass).hookAfter(selector, closure: closureAfter) let closureInstead1 = {original, obj, sel in XCTAssertEqual(obj.number, 3) @@ -185,7 +185,7 @@ class MethodObjectAndSelectorTest: XCTestCase { object.number = 2 return original(object, #selector(ObjectiveCTestObject.noArgsNoReturnFunc)) } as @convention(block) ((ObjectiveCTestObject, Selector) -> String, ObjectiveCTestObject, Selector) -> String - let tokenInstead1 = try hookInstead(targetClass: targetClass, selector: selector, closure: closureInstead1) + let tokenInstead1 = try ClassInstanceHook(targetClass).hook(selector, closure: closureInstead1) let closureInstead2 = {original, obj, sel in XCTAssertEqual(obj.number, 4) @@ -194,7 +194,7 @@ class MethodObjectAndSelectorTest: XCTestCase { object.number = 3 return original(object, #selector(ObjectiveCTestObject.classNoArgsNoReturnFunc)) } as @convention(block) ((ObjectiveCTestObject, Selector) -> String, ObjectiveCTestObject, Selector) -> String - let tokenInstead2 = try hookInstead(targetClass: targetClass, selector: selector, closure: closureInstead2) + let tokenInstead2 = try ClassInstanceHook(targetClass).hook(selector, closure: closureInstead2) let closureInstead3 = {original, obj, sel in XCTAssertEqual(obj.number, 1) @@ -203,7 +203,7 @@ class MethodObjectAndSelectorTest: XCTestCase { object.number = 4 return original(object, #selector(ObjectiveCTestObject.sumFunc(withA:b:))) } as @convention(block) ((ObjectiveCTestObject, Selector) -> String, ObjectiveCTestObject, Selector) -> String - let tokenInstead3 = try hookInstead(targetClass: targetClass, selector: selector, closure: closureInstead3) + let tokenInstead3 = try ClassInstanceHook(targetClass).hook(selector, closure: closureInstead3) // test hook result = object.getSelfNumberPlusCMD() @@ -235,7 +235,7 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertTrue(obj === targetClass) XCTAssertTrue(sel == selector) } as @convention(block) (NSObject, Selector) -> Void - let token = try hookClassMethodBefore(targetClass: targetClass, selector: selector, closure: closure) + let token = try ClassHook(targetClass).hookBefore(selector, closure: closure) // test hook TestObject.classMethodNoArgsNoReturnFunc() @@ -257,7 +257,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.classMethodNoArgsNoReturnFunc) let closure = {_, _ in } as @convention(block) (Selector, NSObject) -> Void - try hookClassMethodBefore(targetClass: targetClass, selector: selector, closure: closure) + try ClassHook(targetClass).hookBefore(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `befor` and `after` mode. The parameters type of the hook closure have to be nil or `@:` or as the same as method's. The closure parameters type is `:@`. The method parameters type is `@:`. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -279,7 +279,7 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertTrue(obj === targetClass) XCTAssertTrue(sel == selector) } as @convention(block) (NSObject, Selector) -> Void - let token = try hookClassMethodAfter(targetClass: targetClass, selector: selector, closure: closure) + let token = try ClassHook(targetClass).hookAfter(selector, closure: closure) // test hook TestObject.classMethodNoArgsNoReturnFunc() @@ -301,7 +301,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.classMethodNoArgsNoReturnFunc) let closure = {_, _ in } as @convention(block) (Selector, NSObject) -> Void - try hookClassMethodAfter(targetClass: targetClass, selector: selector, closure: closure) + try ClassHook(targetClass).hookAfter(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `befor` and `after` mode. The parameters type of the hook closure have to be nil or `@:` or as the same as method's. The closure parameters type is `:@`. The method parameters type is `@:`. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -325,7 +325,7 @@ class MethodObjectAndSelectorTest: XCTestCase { original(obj, sel, closure) result.append(1) } as @convention(block) (@escaping (AnyObject, Selector, () -> Void) -> Void, AnyObject, Selector, () -> Void) -> Void - let token = try hookClassMethodInstead(targetClass: targetClass, selector: selector, closure: closure) + let token = try ClassHook(targetClass).hook(selector, closure: closure) // test hook TestObject.classMethodExecute { @@ -349,7 +349,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.classMethodNoArgsNoReturnFunc) let closure = { _, _, _ in } as @convention(block) ((Selector, NSObject) -> Void, Selector, NSObject) -> Void - try hookClassMethodInstead(targetClass: targetClass, selector: selector, closure: closure) + try ClassHook(targetClass).hook(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `instead` mode. The parameters type of the original closure (the hook closure's first parameter) have to be the same as the method's. The original closure parameters type is `:@`. But the method parameters type is `@:`. They are not the same. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -373,7 +373,7 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertTrue(obj === object) XCTAssertTrue(sel == selector) } as @convention(block) (NSObject, Selector) -> Void - let token = try hookBefore(object: object, selector: selector, closure: closure) + let token = try ObjectHook(object).hookBefore(selector, closure: closure) // test hook object.noArgsNoReturnFunc() @@ -394,7 +394,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.noArgsNoReturnFunc) let closure = {_, _ in } as @convention(block) (Selector, NSObject) -> Void - try hookBefore(object: TestObject(), selector: selector, closure: closure) + try ObjectHook(TestObject()).hookBefore(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `befor` and `after` mode. The parameters type of the hook closure have to be nil or `@:` or as the same as method's. The closure parameters type is `:@`. The method parameters type is `@:`. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -416,7 +416,7 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertTrue(obj === object) XCTAssertTrue(sel == selector) } as @convention(block) (NSObject, Selector) -> Void - let token = try hookAfter(object: object, selector: selector, closure: closure) + let token = try ObjectHook(object).hookAfter(selector, closure: closure) // test hook object.noArgsNoReturnFunc() @@ -437,7 +437,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.noArgsNoReturnFunc) let closure = {_, _ in } as @convention(block) (Selector, NSObject) -> Void - try hookAfter(object: TestObject(), selector: selector, closure: closure) + try ObjectHook(TestObject()).hookAfter(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `befor` and `after` mode. The parameters type of the hook closure have to be nil or `@:` or as the same as method's. The closure parameters type is `:@`. The method parameters type is `@:`. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -461,7 +461,7 @@ class MethodObjectAndSelectorTest: XCTestCase { original(obj, sel, closure) result.append(1) } as @convention(block) (@escaping (AnyObject, Selector, () -> Void) -> Void, AnyObject, Selector, () -> Void) -> Void - let token = try hookInstead(object: object, selector: selector, closure: closure) + let token = try ObjectHook(object).hook(selector, closure: closure) // test hook object.execute { @@ -484,7 +484,7 @@ class MethodObjectAndSelectorTest: XCTestCase { let selector = #selector(TestObject.noArgsNoReturnFunc) let closure = { _, _, _ in } as @convention(block) ((Selector, NSObject) -> Void, Selector, NSObject) -> Void - try hookInstead(object: TestObject(), selector: selector, closure: closure) + try ObjectHook(TestObject()).hook(selector, closure: closure) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { XCTAssertEqual(description, "For `instead` mode. The parameters type of the original closure (the hook closure's first parameter) have to be the same as the method's. The original closure parameters type is `:@`. But the method parameters type is `@:`. They are not the same. For more about Type Encodings: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html") @@ -510,13 +510,13 @@ class MethodObjectAndSelectorTest: XCTestCase { XCTAssertEqual(obj.number, 1) XCTAssertEqual(sel, #selector(ObjectiveCTestObject.getSelfNumberPlusCMD)) } as @convention(block) (ObjectiveCTestObject, Selector) -> Void - let tokenBefore = try hookBefore(object: object, selector: selector, closure: closureBefore) + let tokenBefore = try ObjectHook(object).hookBefore(selector, closure: closureBefore) let closureAfter = {obj, sel in XCTAssertEqual(obj.number, 3) XCTAssertEqual(sel, #selector(ObjectiveCTestObject.classNoArgsNoReturnFunc)) } as @convention(block) (ObjectiveCTestObject, Selector) -> Void - let tokenAfter = try hookAfter(object: object, selector: selector, closure: closureAfter) + let tokenAfter = try ObjectHook(object).hookAfter(selector, closure: closureAfter) let closure1 = {original, obj, sel in XCTAssertEqual(obj.number, 3) @@ -525,7 +525,7 @@ class MethodObjectAndSelectorTest: XCTestCase { object.number = 2 return original(object, #selector(ObjectiveCTestObject.noArgsNoReturnFunc)) } as @convention(block) ((ObjectiveCTestObject, Selector) -> String, ObjectiveCTestObject, Selector) -> String - let token1 = try hookInstead(object: object, selector: selector, closure: closure1) + let token1 = try ObjectHook(object).hook(selector, closure: closure1) let closure2 = {original, obj, sel in XCTAssertEqual(obj.number, 1) @@ -534,14 +534,14 @@ class MethodObjectAndSelectorTest: XCTestCase { object.number = 3 return original(object, #selector(ObjectiveCTestObject.classNoArgsNoReturnFunc)) } as @convention(block) ((ObjectiveCTestObject, Selector) -> String, ObjectiveCTestObject, Selector) -> String - let token2 = try hookInstead(object: object, selector: selector, closure: closure2) + let token2 = try ObjectHook(object).hook(selector, closure: closure2) let closure3 = {original, obj, sel in XCTAssertEqual(obj.number, 1) XCTAssertEqual(sel, #selector(ObjectiveCTestObject.getSelfNumberPlusCMD)) return original(obj, #selector(ObjectiveCTestObject.sumFunc(withA:b:))) } as @convention(block) ((ObjectiveCTestObject, Selector) -> String, ObjectiveCTestObject, Selector) -> String - let token3 = try hookInstead(object: object, selector: selector, closure: closure3) + let token3 = try ObjectHook(object).hook(selector, closure: closure3) // test hook result = object.getSelfNumberPlusCMD() @@ -567,15 +567,15 @@ class MethodObjectAndSelectorTest: XCTestCase { let obj = MyObject.init() var array = [Int]() - try hookBefore(object: obj, selector: #selector(MyObject.myMethod)) { + try ObjectHook(obj).hookBefore(#selector(MyObject.myMethod)) { array.append(2) } - try hookInstead(object: obj, selector: #selector(MyObject.myMethod), closure: {original, _, sel in + try ObjectHook(obj).hook(#selector(MyObject.myMethod), closure: {original, _, sel in array.append(1) original(NSURLRequest.init(), sel) array.append(4) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) - try hookAfter(object: obj, selector: #selector(MyObject.myMethod), closure: { + try ObjectHook(obj).hookAfter(#selector(MyObject.myMethod), closure: { array.append(3) }) obj.myMethod() diff --git a/SwiftHookTests/Advanced/OrderTests.swift b/SwiftHookTests/Advanced/OrderTests.swift index 4ce97ffe..fa5ba252 100644 --- a/SwiftHookTests/Advanced/OrderTests.swift +++ b/SwiftHookTests/Advanced/OrderTests.swift @@ -32,17 +32,17 @@ class OrderTests: XCTestCase { let urlAmazon = "https://www.amazon.com" let urlShopee = "https://www.shopee.com" - try hookBefore(targetClass: targetClass, selector: selector, closure: {_, _, url in + try ClassInstanceHook(targetClass).hookBefore(selector, closure: {_, _, url in order.append(2) XCTAssertEqual(url.absoluteString, urlApple) } as @convention(block) (AnyObject, Selector, URL) -> Void) - try hookAfter(targetClass: targetClass, selector: selector, closure: {_, _, url in + try ClassInstanceHook(targetClass).hookAfter(selector, closure: {_, _, url in order.append(5) XCTAssertEqual(url.absoluteString, urlApple) } as @convention(block) (AnyObject, Selector, URL) -> Void) - try hookInstead(targetClass: targetClass, selector: selector, closure: {original, o, s, url in + try ClassInstanceHook(targetClass).hook(selector, closure: {original, o, s, url in order.append(3) XCTAssertEqual(url.absoluteString, urlFacebook) let request = original(o, s, URL.init(string: urlApple)!) @@ -52,17 +52,17 @@ class OrderTests: XCTestCase { return newRequest } as @convention(block) ((AnyObject, Selector, URL) -> NSURLRequest, AnyObject, Selector, URL) -> NSURLRequest) - try hookBefore(object: object, selector: selector, closure: {_, _, url in + try ObjectHook(object).hookBefore(selector, closure: {_, _, url in order.append(0) XCTAssertEqual(url.absoluteString, urlFacebook) } as @convention(block) (AnyObject, Selector, URL) -> Void) - try hookAfter(object: object, selector: selector, closure: {_, _, url in + try ObjectHook(object).hookAfter(selector, closure: {_, _, url in order.append(7) XCTAssertEqual(url.absoluteString, urlFacebook) } as @convention(block) (AnyObject, Selector, URL) -> Void) - try hookInstead(object: object, selector: selector, closure: {original, o, s, url in + try ObjectHook(object).hook(selector, closure: {original, o, s, url in order.append(1) XCTAssertEqual(url.absoluteString, urlGoogle) let request = original(o, s, URL.init(string: urlFacebook)!) @@ -72,29 +72,29 @@ class OrderTests: XCTestCase { return newRequest } as @convention(block) ((AnyObject, Selector, URL) -> NSURLRequest, AnyObject, Selector, URL) -> NSURLRequest) - try hookDeallocBefore(targetClass: Request.self, closure: { + try ClassInstanceHook(Request.self).hookDeallocBefore(closure: { deallocOrder.append(2) }) - try hookDeallocAfter(targetClass: Request.self, closure: { + try ClassInstanceHook(Request.self).hookDeallocAfter(closure: { deallocOrder.append(6) }) - try hookDeallocInstead(targetClass: Request.self, closure: { original in + try ClassInstanceHook(Request.self).hookDealloc(closure: { original in deallocOrder.append(3) original() deallocOrder.append(5) }) - try hookDeallocBefore(object: object, closure: { + try ObjectHook(object).hookDeallocBefore(closure: { deallocOrder.append(0) }) - try hookDeallocAfter(object: object, closure: { + try ObjectHook(object).hookDeallocAfter(closure: { deallocOrder.append(8) }) - hookDeallocAfterByTail(object: object, closure: { + ObjectHook(object).hookDeallocAfterByTail(closure: { deallocOrder.append(4) }) @@ -162,13 +162,13 @@ class OrderTests: XCTestCase { switch hookMode { case 0: classBefore.append(randomNumber) - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) case 1: classInstead.append(randomNumber) - try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(order:)), closure: createInsteadHookClosure(randomNumber)) + try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.myMethod(order:)), closure: createInsteadHookClosure(randomNumber)) case 2: classAfter.append(randomNumber) - try hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) + try ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) default: XCTFail() } @@ -177,13 +177,13 @@ class OrderTests: XCTestCase { switch hookMode { case 0: objectBefore.append(randomNumber) - try hookBefore(object: object, selector: #selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) + try ObjectHook(object).hookBefore(#selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) case 1: objectInstead.append(randomNumber) - try hookInstead(object: object, selector: #selector(MyObject.myMethod(order:)), closure: createInsteadHookClosure(randomNumber)) + try ObjectHook(object).hook(#selector(MyObject.myMethod(order:)), closure: createInsteadHookClosure(randomNumber)) case 2: objectAfter.append(randomNumber) - try hookAfter(object: object, selector: #selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) + try ObjectHook(object).hookAfter(#selector(MyObject.myMethod(order:)), closure: createBeforeOrAfterHookClosure(randomNumber)) default: XCTFail() } @@ -217,21 +217,21 @@ class OrderTests: XCTestCase { var order = [Int]() - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod)) { + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod)) { order.append(8) } - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod)) { + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod)) { order.append(7) } - try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(6) original(obj, sel) order.append(11) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) - try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(5) original(obj, sel) order.append(12) @@ -251,17 +251,17 @@ class OrderTests: XCTestCase { order.append(4) } - try hookBefore(object: object, selector: #selector(MyObject.myMethod)) { + try ObjectHook(object).hookBefore(#selector(MyObject.myMethod)) { order.append(3) } - try hookInstead(object: object, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ObjectHook(object).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(2) original(obj, sel) order.append(15) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) - try hookInstead(object: object, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ObjectHook(object).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(1) original(obj, sel) order.append(16) @@ -295,17 +295,17 @@ class OrderTests: XCTestCase { order.append(6) } - try hookBefore(object: object, selector: #selector(MyObject.myMethod)) { + try ObjectHook(object).hookBefore(#selector(MyObject.myMethod)) { order.append(5) } - try hookInstead(object: object, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ObjectHook(object).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(2) original(obj, sel) order.append(15) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) - try hookInstead(object: object, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ObjectHook(object).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(1) original(obj, sel) order.append(16) @@ -315,27 +315,27 @@ class OrderTests: XCTestCase { order.append(10) } - try hookAfter(object: object, selector: #selector(MyObject.myMethod)) { + try ObjectHook(object).hookAfter(#selector(MyObject.myMethod)) { order.append(9) } let theClass: AnyClass = object_getClass(object)! - try hookBefore(targetClass: theClass, selector: #selector(MyObject.myMethod)) { + try ClassInstanceHook(theClass).hookBefore(#selector(MyObject.myMethod)) { order.append(8) } - try hookBefore(targetClass: theClass, selector: #selector(MyObject.myMethod)) { + try ClassInstanceHook(theClass).hookBefore(#selector(MyObject.myMethod)) { order.append(7) } - try hookInstead(targetClass: theClass, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ClassInstanceHook(theClass).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(4) original(obj, sel) order.append(13) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) - try hookInstead(targetClass: theClass, selector: #selector(MyObject.myMethod), closure: {original, obj, sel in + try ClassInstanceHook(theClass).hook(#selector(MyObject.myMethod), closure: {original, obj, sel in order.append(3) original(obj, sel) order.append(14) @@ -379,16 +379,16 @@ class OrderTests: XCTestCase { try hookBefore(object: object, selector: #selector(MyObject.myMethod1)) { order.append(1) } - try hookBefore(object: object, selector: #selector(MyObject.myMethod2)) { + try ObjectHook(object).hookBefore(#selector(MyObject.myMethod2)) { order.append(99) } - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod1), closure: { + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod1), closure: { order.append(4) }) - try hookBefore(targetClass: object_getClass(object)!, selector: #selector(MyObject.myMethod1), closure: { + try ClassInstanceHook(object_getClass(object)!).hookBefore(#selector(MyObject.myMethod1), closure: { order.append(3) }) - try hookBefore(targetClass: SuperObject.self, selector: #selector(MyObject.myMethod1), closure: { + try ClassInstanceHook(SuperObject.self).hookBefore(#selector(MyObject.myMethod1), closure: { order.append(5) }) object.myMethod1() diff --git a/SwiftHookTests/Advanced/ParametersCheckingTests.swift b/SwiftHookTests/Advanced/ParametersCheckingTests.swift index 1425c08e..c73189f2 100644 --- a/SwiftHookTests/Advanced/ParametersCheckingTests.swift +++ b/SwiftHookTests/Advanced/ParametersCheckingTests.swift @@ -18,7 +18,7 @@ class ParametersCheckingTests: XCTestCase { func testCanNotHookClassWithObjectAPI() { do { - try hookBefore(object: randomTestClass(), selector: randomSelector(), closure: { + try ObjectHook(randomTestClass()).hookBefore(randomSelector(), closure: { }) XCTFail() } catch SwiftHookError.hookClassWithObjectAPI { @@ -26,7 +26,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookAfter(object: randomTestClass(), selector: randomSelector(), closure: { + try ObjectHook(randomTestClass()).hookAfter(randomSelector(), closure: { }) XCTFail() } catch SwiftHookError.hookClassWithObjectAPI { @@ -34,7 +34,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(object: randomTestClass(), selector: randomSelector(), closure: { + try ObjectHook(randomTestClass()).hook(randomSelector(), closure: { }) XCTFail() } catch SwiftHookError.hookClassWithObjectAPI { @@ -45,7 +45,7 @@ class ParametersCheckingTests: XCTestCase { func testUnsupportHookPureSwiftObjectDealloc() { do { - try hookBefore(object: TestObject(), selector: deallocSelector, closure: { + try ObjectHook(TestObject()).hookBefore(deallocSelector, closure: { }) XCTFail() } catch SwiftHookError.pureSwiftObjectDealloc { @@ -54,7 +54,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookAfter(object: TestObject(), selector: deallocSelector, closure: { + try ObjectHook(TestObject()).hookAfter(deallocSelector, closure: { }) XCTFail() } catch SwiftHookError.pureSwiftObjectDealloc { @@ -62,7 +62,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(object: TestObject(), selector: deallocSelector, closure: { + try ObjectHook(TestObject()).hook(deallocSelector, closure: { }) XCTFail() } catch SwiftHookError.pureSwiftObjectDealloc { @@ -70,7 +70,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookBefore(targetClass: TestObject.self, selector: deallocSelector, closure: { + try ClassInstanceHook(TestObject.self).hookBefore(deallocSelector, closure: { }) XCTFail() } catch SwiftHookError.pureSwiftObjectDealloc { @@ -78,7 +78,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookAfter(targetClass: TestObject.self, selector: deallocSelector, closure: { + try ClassInstanceHook(TestObject.self).hookAfter(deallocSelector, closure: { }) XCTFail() } catch SwiftHookError.pureSwiftObjectDealloc { @@ -86,7 +86,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(targetClass: TestObject.self, selector: deallocSelector, closure: { + try ClassInstanceHook(TestObject.self).hook(deallocSelector, closure: { }) XCTFail() } catch SwiftHookError.pureSwiftObjectDealloc { @@ -97,21 +97,21 @@ class ParametersCheckingTests: XCTestCase { func testNoRespondSelector() { do { - try hookBefore(targetClass: randomTestClass(), selector: #selector(NSArray.object(at:)), closure: {}) + try ClassInstanceHook(randomTestClass()).hookBefore(#selector(NSArray.object(at:)), closure: {}) XCTFail() } catch SwiftHookError.noRespondSelector { } catch { XCTAssertNil(error) } do { - try hookClassMethodAfter(targetClass: TestObject.self, selector: #selector(TestObject.noArgsNoReturnFunc), closure: {}) + try ClassHook(TestObject.self).hookAfter(#selector(TestObject.noArgsNoReturnFunc), closure: {}) XCTFail() } catch SwiftHookError.noRespondSelector { } catch { XCTAssertNil(error) } do { - try hookInstead(object: TestObject(), selector: #selector(TestObject.classMethodNoArgsNoReturnFunc), closure: {}) + try ObjectHook(TestObject()).hook(#selector(TestObject.classMethodNoArgsNoReturnFunc), closure: {}) XCTFail() } catch SwiftHookError.noRespondSelector { } catch { @@ -121,21 +121,21 @@ class ParametersCheckingTests: XCTestCase { func testMissingSignature() { do { - try hookBefore(targetClass: randomTestClass(), selector: #selector(TestObject.noArgsNoReturnFunc), closure: NSObject()) + try ClassHook(randomTestClass()).hookBefore(#selector(TestObject.noArgsNoReturnFunc), closure: NSObject()) XCTFail() } catch SwiftHookError.wrongTypeForHookClosure { } catch { XCTAssertNil(error) } do { - try hookClassMethodAfter(targetClass: TestObject.self, selector: #selector(TestObject.classMethodNoArgsNoReturnFunc), closure: 1) + try ClassHook(TestObject.self).hookAfter(#selector(TestObject.classMethodNoArgsNoReturnFunc), closure: 1) XCTFail() } catch SwiftHookError.wrongTypeForHookClosure { } catch { XCTAssertNil(error) } do { - try hookInstead(object: TestObject(), selector: #selector(TestObject.noArgsNoReturnFunc), closure: {} as AnyObject) + try ObjectHook(TestObject()).hook(#selector(TestObject.noArgsNoReturnFunc), closure: {} as AnyObject) XCTFail() } catch SwiftHookError.wrongTypeForHookClosure { } catch { @@ -145,7 +145,7 @@ class ParametersCheckingTests: XCTestCase { func testIncompatibleClosureSignature() { do { - try hookBefore(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { _, _ in + try ClassInstanceHook(TestObject.self).hookBefore(#selector(TestObject.sumFunc(a:b:)), closure: { _, _ in return 1 } as @convention(block) (Int, Int) -> Int as AnyObject) XCTFail() @@ -155,7 +155,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookAfter(object: TestObject(), selector: #selector(TestObject.sumFunc(a:b:)), closure: { _, _ in + try ObjectHook(TestObject()).hookAfter(#selector(TestObject.sumFunc(a:b:)), closure: { _, _ in } as @convention(block) (Int, Double) -> Void as AnyObject) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { @@ -164,7 +164,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookAfter(object: TestObject(), selector: #selector(TestObject.testStructSignature(point:rect:)), closure: ({_, _ in + try ObjectHook(TestObject()).hookAfter(#selector(TestObject.testStructSignature(point:rect:)), closure: ({_, _ in } as @convention(block) (CGPoint, Double) -> Void) as AnyObject) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { @@ -173,7 +173,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { _, _ in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { _, _ in } as @convention(block) (Int, Int) -> Void as AnyObject) XCTFail() } catch SwiftHookError.incompatibleClosureSignature(description: let description) { @@ -187,7 +187,7 @@ class ParametersCheckingTests: XCTestCase { for selector in blacklistSelectors { do { let object = ObjectiveCTestObject() - try hookBefore(object: object, selector: selector) { + try ObjectHook(object).hookBefore(selector) { } XCTFail() } catch SwiftHookError.blacklist { @@ -196,7 +196,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookBefore(targetClass: ObjectiveCTestObject.self, selector: selector) { + try ClassInstanceHook(ObjectiveCTestObject.self).hookBefore(selector) { } XCTFail() } catch SwiftHookError.blacklist { @@ -208,7 +208,7 @@ class ParametersCheckingTests: XCTestCase { func test_Hook_Instead_Original_Closure() { do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, a, b in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, a, b in let result = original(a, b) return Int(result) } as @convention(block) ((Int, Int) -> Double, Int, Int) -> Int as AnyObject) @@ -219,7 +219,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, _, b in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, _, b in let result = original(NSObject.init(), b) return Int(result) } as @convention(block) ((NSObject, Int) -> Int, Int, Int) -> Int as AnyObject) @@ -230,7 +230,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, a, b in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, a, b in let result = original(a, b, 100) return Int(result) } as @convention(block) ((Int, Int, Int) -> Int, Int, Int) -> Int as AnyObject) @@ -241,7 +241,7 @@ class ParametersCheckingTests: XCTestCase { XCTAssertNil(error) } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, a, _ in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, a, _ in let result = original(a) return Int(result) } as @convention(block) ((Int) -> Int, Int, Int) -> Int as AnyObject) @@ -253,7 +253,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, o, s, a, b in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, o, s, a, b in let result = original(o, s, a, b) return Int(result) } as @convention(block) ((AnyObject, Selector, Int, Int) -> Double, AnyObject, Selector, Int, Int) -> Int as AnyObject) @@ -265,7 +265,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, o, s, a, b in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, o, s, a, b in let result = original(o, s, a, b) return Double(result) } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Int) -> Double as AnyObject) @@ -277,7 +277,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: TestObject.self, selector: #selector(TestObject.sumFunc(a:b:)), closure: { original, o, s, a, b in + try ClassInstanceHook(TestObject.self).hook(#selector(TestObject.sumFunc(a:b:)), closure: { original, o, s, a, b in let result = original(o, s, a, Int(b)) return Int(result) } as @convention(block) ((AnyObject, Selector, Int, Int) -> Int, AnyObject, Selector, Int, Double) -> Int as AnyObject) @@ -291,7 +291,7 @@ class ParametersCheckingTests: XCTestCase { func test_Hook_Dealloc() { do { - try hookBefore(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { + try ClassInstanceHook(ObjectiveCTestObject.self).hookBefore(deallocSelector, closure: { return 1 } as @convention(block) () -> Int as AnyObject) XCTFail() @@ -302,7 +302,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookBefore(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { _, _ in + try ClassInstanceHook(ObjectiveCTestObject.self).hookBefore(deallocSelector, closure: { _, _ in } as @convention(block) (String, Selector) -> Void as AnyObject) XCTFail() @@ -313,7 +313,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookAfter(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { _ in + try ClassInstanceHook(ObjectiveCTestObject.self).hookAfter(deallocSelector, closure: { _ in } as @convention(block) (ObjectiveCTestObject) -> Void as AnyObject) XCTFail() @@ -324,7 +324,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { original, o, s in + try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { original, o, s in original(o, s) } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void as AnyObject) XCTFail() @@ -335,7 +335,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { _ in + try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { _ in } as @convention(block) (AnyObject) -> Void as AnyObject) XCTFail() @@ -346,7 +346,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { original in + try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { original in _ = original() } as @convention(block) (() -> CGPoint) -> Void as AnyObject) XCTFail() @@ -357,7 +357,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { original in + try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { original in original(1, 1.1) } as @convention(block) ((Int, Double) -> Void) -> Void as AnyObject) XCTFail() @@ -368,7 +368,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { original in + try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { original in original() return "lol" } as @convention(block) (() -> Void) -> String as AnyObject) @@ -380,7 +380,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { original, _ in + try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { original, _ in original() } as @convention(block) (() -> Void, URL) -> Void as AnyObject) XCTFail() @@ -419,7 +419,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod1), closure: { _, _, _ in + try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.myMethod1), closure: { _, _, _ in return EmptyStruct.init() } as @convention(block) ((NSObject, Selector) -> EmptyStruct, NSObject, Selector) -> EmptyStruct) XCTFail() @@ -429,7 +429,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod2), closure: { + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod2), closure: { }) XCTFail() @@ -439,7 +439,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookAfter(object: MyObject(), selector: #selector(MyObject.myMethod3), closure: { + try ObjectHook(MyObject()).hookAfter(#selector(MyObject.myMethod3), closure: { }) XCTFail() @@ -449,7 +449,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod4), closure: { _, _, _ in + try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.myMethod4), closure: { _, _, _ in return InternalEmptyStruct.init() } as @convention(block) ((NSObject, Selector) -> InternalEmptyStruct, NSObject, Selector) -> InternalEmptyStruct) XCTFail() @@ -459,7 +459,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod5), closure: { + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod5), closure: { }) XCTFail() @@ -469,7 +469,7 @@ class ParametersCheckingTests: XCTestCase { } do { - try hookAfter(object: MyObject(), selector: #selector(MyObject.myMethod6), closure: { + try ObjectHook(MyObject()).hookAfter(#selector(MyObject.myMethod6), closure: { }) XCTFail() diff --git a/SwiftHookTests/Advanced/SpecialMethodTests.swift b/SwiftHookTests/Advanced/SpecialMethodTests.swift index c1ba8fd7..2137dc10 100644 --- a/SwiftHookTests/Advanced/SpecialMethodTests.swift +++ b/SwiftHookTests/Advanced/SpecialMethodTests.swift @@ -22,33 +22,33 @@ class SpecialMethodTests: XCTestCase { XCTAssertTrue(try testGetObjectType(object: object) == .normal) // before - try hookBefore(object: object, selector: deallocSelector) { + try ObjectHook(object).hookBefore(deallocSelector) { executed.append(-1) } - try hookDeallocBefore(object: object, closure: { + try ObjectHook(object).hookDeallocBefore(closure: { executed.append(-2) }) // instead - try hookInstead(object: object, selector: deallocSelector, closure: { original in + try ObjectHook(object).hook(deallocSelector, closure: { original in executed.append(-3) original() executed.append(3) } as @convention(block) (() -> Void) -> Void) - try hookDeallocInstead(object: object, closure: { original in + try ObjectHook(object).hookDealloc(closure: { original in executed.append(-4) original() executed.append(4) } as @convention(block) (() -> Void) -> Void) // after - try hookAfter(object: object, selector: deallocSelector) { + try ObjectHook(object).hookAfter(deallocSelector) { executed.append(1) } - try hookDeallocAfter(object: object, closure: { + try ObjectHook(object).hookDeallocAfter(closure: { executed.append(2) }) - hookDeallocAfterByTail(object: object, closure: { + ObjectHook(object).hookDeallocAfterByTail(closure: { executed.append(5) }) XCTAssertTrue(try testGetObjectType(object: object) == .KVOed(mode: .swiftHook)) @@ -71,7 +71,7 @@ class SpecialMethodTests: XCTestCase { } XCTAssertTrue(try testGetObjectType(object: object) == .normal) - hookDeallocAfterByTail(object: object, closure: { + ObjectHook(object).hookDeallocAfterByTail(closure: { executed.append(2) }) XCTAssertTrue(try testGetObjectType(object: object) == .normal) @@ -96,23 +96,23 @@ class SpecialMethodTests: XCTestCase { _ = ObjectiveCTestObject() // This will not trigger hook because it will release immediately. Maybe compiler optimization. // before - let token1 = try hookBefore(targetClass: ObjectiveCTestObject.self, selector: deallocSelector) { + let token1 = try ClassInstanceHook(ObjectiveCTestObject.self).hookBefore(deallocSelector) { executed.append(-1) } tokens.append(token1) - let token2 = try hookDeallocBefore(targetClass: ObjectiveCTestObject.self, closure: { + let token2 = try ClassInstanceHook(ObjectiveCTestObject.self).hookDeallocBefore(closure: { executed.append(-2) }) tokens.append(token2) // instead - let token3 = try hookInstead(targetClass: ObjectiveCTestObject.self, selector: deallocSelector, closure: { original in + let token3 = try ClassInstanceHook(ObjectiveCTestObject.self).hook(deallocSelector, closure: { original in executed.append(-3) original() executed.append(3) } as @convention(block) (() -> Void) -> Void) tokens.append(token3) - let token4 = try hookDeallocInstead(targetClass: ObjectiveCTestObject.self, closure: { original in + let token4 = try ClassInstanceHook(ObjectiveCTestObject.self).hookDealloc(closure: { original in executed.append(-4) original() executed.append(4) @@ -120,11 +120,11 @@ class SpecialMethodTests: XCTestCase { tokens.append(token4) // after - let token5 = try hookAfter(targetClass: ObjectiveCTestObject.self, selector: deallocSelector) { + let token5 = try ClassInstanceHook(ObjectiveCTestObject.self).hookAfter(deallocSelector) { executed.append(1) } tokens.append(token5) - let token6 = try hookDeallocAfter(targetClass: ObjectiveCTestObject.self, closure: { + let token6 = try ClassInstanceHook(ObjectiveCTestObject.self).hookDeallocAfter(closure: { executed.append(2) }) tokens.append(token6) diff --git a/SwiftHookTests/Advanced/StructTests.swift b/SwiftHookTests/Advanced/StructTests.swift index 08a14b7e..da976ee0 100644 --- a/SwiftHookTests/Advanced/StructTests.swift +++ b/SwiftHookTests/Advanced/StructTests.swift @@ -19,12 +19,12 @@ class StructTests: XCTestCase { } do { - let token1 = try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.point(inside:with:)), closure: {_, _, point, event in + let token1 = try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.point(inside:with:)), closure: {_, _, point, event in XCTAssertEqual(point, CGPoint.init(x: 11, y: 22)) XCTAssertNil(event) } as @convention(block)(AnyObject, Selector, CGPoint, UIEvent?) -> Void) - let token2 = try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.point(inside:with:)), closure: {original, object, selector, point, event in + let token2 = try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.point(inside:with:)), closure: {original, object, selector, point, event in XCTAssertEqual(point, CGPoint.init(x: 11, y: 22)) XCTAssertNil(event) let result = original(object, selector, point, event) @@ -51,7 +51,7 @@ class StructTests: XCTestCase { var pointerForTesting: UInt8 = 0 withUnsafeMutablePointer(to: &pointerForTesting) { pointer in do { - let token1 = try hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.doublePoint(theStruct:)), closure: {_, _, s in + let token1 = try ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.doublePoint(theStruct:)), closure: {_, _, s in XCTAssertEqual(s.i, 11) XCTAssertEqual(s.p, CGPoint.init(x: 22, y: 33)) XCTAssertEqual(s.frame, CGRect.init(x: 44, y: 55, width: 66, height: 77)) @@ -60,7 +60,7 @@ class StructTests: XCTestCase { } as @convention(block)(AnyObject, Selector, ComplexityStruct) -> Void) - let token2 = try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.doublePoint(theStruct:)), closure: {original, object, selector, s in + let token2 = try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.doublePoint(theStruct:)), closure: {original, object, selector, s in XCTAssertEqual(s.i, 11) XCTAssertEqual(s.p, CGPoint.init(x: 22, y: 33)) XCTAssertEqual(s.frame, CGRect.init(x: 44, y: 55, width: 66, height: 77)) @@ -107,11 +107,11 @@ class StructTests: XCTestCase { } do { - let token1 = try hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.doublePoint(theStruct:)), closure: {_, _, s in + let token1 = try ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.doublePoint(theStruct:)), closure: {_, _, s in XCTAssertEqual(s.frame1, CGRect.init(x: 1, y: 2, width: 3, height: 4)) } as @convention(block)(AnyObject, Selector, BigStruct) -> Void) - let token2 = try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.doublePoint(theStruct:)), closure: {original, object, selector, s in + let token2 = try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.doublePoint(theStruct:)), closure: {original, object, selector, s in XCTAssertEqual(s.frame1, CGRect.init(x: 1, y: 2, width: 3, height: 4)) var result = original(object, selector, s) XCTAssertEqual(result.frame1, CGRect.init(x: 2, y: 4, width: 6, height: 8)) diff --git a/SwiftHookTests/Advanced/ThreadSafetyTests.swift b/SwiftHookTests/Advanced/ThreadSafetyTests.swift index 52f06830..595d34d9 100644 --- a/SwiftHookTests/Advanced/ThreadSafetyTests.swift +++ b/SwiftHookTests/Advanced/ThreadSafetyTests.swift @@ -26,7 +26,7 @@ class ThreadSafetyTests: XCTestCase { do { let targetClass: AnyClass = objc_allocateClassPair(TestObject.self, "ThreadSafetyTests_\(index)", 0)! objc_registerClassPair(targetClass) - let token = try hookAfter(targetClass: targetClass, selector: #selector(TestObject.noArgsNoReturnFunc)) {} + let token = try ClassInstanceHook(targetClass).hookAfter(#selector(TestObject.noArgsNoReturnFunc)) {} guard let hookToken = token as? HookToken else { XCTFail() return @@ -43,7 +43,7 @@ class ThreadSafetyTests: XCTestCase { DispatchQueue.concurrentPerform(iterations: 1000) { _ in do { _ = try autoreleasepool { - try hookInstead(object: TestObject(), selector: #selector(TestObject.noArgsNoReturnFunc), closure: { _, _, _ in + try ObjectHook(TestObject()).hook(#selector(TestObject.noArgsNoReturnFunc), closure: { _, _, _ in } as @convention(block) ((AnyObject, Selector) -> Void, AnyObject, Selector) -> Void) } } catch { @@ -85,7 +85,7 @@ class ThreadSafetyTests: XCTestCase { for _ in 0 ... 1000 { let object = randomTestObject() objects.append(object) - tokens.append(hookDeallocAfterByTail(object: object, closure: { + tokens.append(ObjectHook(object).hookDeallocAfterByTail(closure: { })) } DispatchQueue.concurrentPerform(iterations: 1000) { index in @@ -126,17 +126,17 @@ class ThreadSafetyTests: XCTestCase { } MyObject.result = 0 - try hookAfter(object: object, selector: #selector(MyObject.plus1), closure: { _, _ in + try ObjectHook(object).hookAfter(#selector(MyObject.plus1), closure: { _, _ in MyObject.serialQueue.async { MyObject.result += 1 } } as @convention(block) (AnyObject, Selector) -> Void) - try hookBefore(object: object, selector: #selector(MyObject.plus1), closure: { _, _ in + try ObjectHook(object).hookBefore(#selector(MyObject.plus1), closure: { _, _ in MyObject.serialQueue.async { MyObject.result += 1 } } as @convention(block) (AnyObject, Selector) -> Void) - try hookInstead(object: object, selector: #selector(MyObject.plus1), closure: { original, obj, selector in + try ObjectHook(object).hook(#selector(MyObject.plus1), closure: { original, obj, selector in MyObject.serialQueue.async { MyObject.result += 1 } @@ -183,17 +183,17 @@ class ThreadSafetyTests: XCTestCase { } MyObject.result = 0 - try hookAfter(object: object, selector: #selector(MyObject.plus1), closure: { _, _ in + try ObjectHook(object).hookAfter(#selector(MyObject.plus1), closure: { _, _ in MyObject.serialQueue.async { MyObject.result += 1 } } as @convention(block) (AnyObject, Selector) -> Void) - try hookBefore(object: object, selector: #selector(MyObject.plus1), closure: { _, _ in + try ObjectHook(object).hookBefore(#selector(MyObject.plus1), closure: { _, _ in MyObject.serialQueue.async { MyObject.result += 1 } } as @convention(block) (AnyObject, Selector) -> Void) - try hookInstead(object: object, selector: #selector(MyObject.plus1), closure: { original, obj, selector in + try ObjectHook(object).hook(#selector(MyObject.plus1), closure: { original, obj, selector in MyObject.serialQueue.async { MyObject.result += 1 } diff --git a/SwiftHookTests/Advanced/VariableCaptureTests.swift b/SwiftHookTests/Advanced/VariableCaptureTests.swift index bbc7ad2a..6665aebc 100644 --- a/SwiftHookTests/Advanced/VariableCaptureTests.swift +++ b/SwiftHookTests/Advanced/VariableCaptureTests.swift @@ -20,7 +20,7 @@ class VariableCaptureTests: XCTestCase { let object = MyObject() let addNumber = 3 - let token = try hookInstead(object: object, selector: #selector(MyObject.double(number:)), closure: {original, o, s, number in + let token = try ObjectHook(object).hook(#selector(MyObject.double(number:)), closure: {original, o, s, number in let result = original(o, s, number) XCTAssertEqual(result, 22) return result + addNumber diff --git a/SwiftHookTests/AspectsTests/AspectsSwiftTests.swift b/SwiftHookTests/AspectsTests/AspectsSwiftTests.swift index 17272170..dbca7016 100644 --- a/SwiftHookTests/AspectsTests/AspectsSwiftTests.swift +++ b/SwiftHookTests/AspectsTests/AspectsSwiftTests.swift @@ -101,7 +101,7 @@ class AspectsSwiftTests: XCTestCase { let object = ObjectiveCTestObject() var expectation = [Int]() - let token = try hookInstead(object: object, selector: #selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: ObjectiveCTestObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(2) diff --git a/SwiftHookTests/Components/HookContextTests.swift b/SwiftHookTests/Components/HookContextTests.swift index 343b66b5..9ddefc6c 100644 --- a/SwiftHookTests/Components/HookContextTests.swift +++ b/SwiftHookTests/Components/HookContextTests.swift @@ -142,7 +142,7 @@ class HookContextTests: XCTestCase { let countBefore = debug_getNormalClassHookContextsCount() // hook - let token: HookToken! = try hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.myMethod), closure: { + let token: HookToken! = try ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.myMethod), closure: { }) as? HookToken @@ -170,7 +170,7 @@ class HookContextTests: XCTestCase { var token1: HookToken! try autoreleasepool { let object = MyObject.init() - token1 = try hookAfter(object: object, selector: #selector(MyObject.myMethod), closure: { + token1 = try ObjectHook(object).hookAfter(#selector(MyObject.myMethod), closure: { }) as? HookToken XCTAssertEqual(debug_getinstancewHookContextsCount(), countBefore + 1) } @@ -184,7 +184,7 @@ class HookContextTests: XCTestCase { var token2: HookToken! try autoreleasepool { let object = MyObject.init() - token2 = try hookAfter(object: object, selector: #selector(MyObject.myMethod), closure: { + token2 = try ObjectHook(object).hookAfter(#selector(MyObject.myMethod), closure: { }) as? HookToken XCTAssertEqual(debug_getinstancewHookContextsCount(), countBefore + 1) } @@ -204,7 +204,7 @@ class HookContextTests: XCTestCase { let countBefore = debug_getinstancewHookContextsCount() let object1 = MyObject.init() - let token1: HookToken! = try hookAfter(object: object1, selector: #selector(MyObject.myMethod), closure: { + let token1: HookToken! = try ObjectHook(object1).hookAfter(#selector(MyObject.myMethod), closure: { }) as? HookToken // check @@ -222,7 +222,7 @@ class HookContextTests: XCTestCase { XCTAssertEqual(debug_getinstancewHookContextsCount(), countBefore + 1) let object2 = MyObject.init() - let token2: HookToken! = try hookAfter(object: object2, selector: #selector(MyObject.myMethod), closure: { + let token2: HookToken! = try ObjectHook(object2).hookAfter(#selector(MyObject.myMethod), closure: { }) as? HookToken // check diff --git a/SwiftHookTests/Components/KVOWrapperTests.swift b/SwiftHookTests/Components/KVOWrapperTests.swift index 5c2eb2bc..bba70d60 100644 --- a/SwiftHookTests/Components/KVOWrapperTests.swift +++ b/SwiftHookTests/Components/KVOWrapperTests.swift @@ -98,7 +98,7 @@ class KVOWrapperTests: XCTestCase { XCTAssertEqual(expectation, [random1]) expectation.removeAll() - try hookBefore(object: object, selector: #selector(setter: SwiftObject.number), closure: { + try ObjectHook(object).hookBefore(#selector(setter: SwiftObject.number), closure: { expectation.append(random3) }) XCTAssertTrue(try isKVOed(object: object)) @@ -125,7 +125,7 @@ class KVOWrapperTests: XCTestCase { let newClassName = "Test" + NSStringFromClass(baseClass) let newClass: AnyClass = objc_allocateClassPair(baseClass, newClassName, 0)! objc_registerClassPair(newClass) - try hookInstead(targetClass: newClass, selector: NSSelectorFromString("class"), closure: { original, obj, select in + try ClassInstanceHook(newClass).hook(NSSelectorFromString("class"), closure: { original, obj, select in XCTAssertTrue(original(obj, select) == newClass) return baseClass } as @convention(block) ((NSObject, Selector) -> AnyClass, NSObject, Selector) -> AnyClass) @@ -133,7 +133,7 @@ class KVOWrapperTests: XCTestCase { XCTAssertFalse(try isKVOed(object: object)) XCTAssertTrue(sht_getClass(object) == baseClass) - try hookBefore(object: object, selector: #selector(setter: SwiftObject.number), closure: { + try ObjectHook(object).hookBefore(#selector(setter: SwiftObject.number), closure: { expectation.append(random1) }) XCTAssertTrue(try isKVOed(object: object)) @@ -157,7 +157,7 @@ class KVOWrapperTests: XCTestCase { let random1 = Int.random(in: Int.min ... Int.max) let random2 = Int.random(in: Int.min ... Int.max) - try hookBefore(object: object, selector: #selector(setter: SwiftObject.number), closure: { + try ObjectHook(object).hookBefore(#selector(setter: SwiftObject.number), closure: { expectation.append(random1) }) XCTAssertTrue(try isKVOed(object: object)) @@ -287,7 +287,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -331,7 +331,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.Number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.Number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -383,7 +383,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -435,7 +435,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.Number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.Number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -479,7 +479,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.___number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.___number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -523,7 +523,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.___Number), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.___Number), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -567,7 +567,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: MyObject.swiftHookPrivateProperty), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: MyObject.swiftHookPrivateProperty), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -611,7 +611,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: NSKVONotifying_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: NSKVONotifying_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -652,7 +652,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: NSKVONotifying_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: NSKVONotifying_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -696,7 +696,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: SwiftHook_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: SwiftHook_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) @@ -737,7 +737,7 @@ class KVOWrapperTests: XCTestCase { var expectation = [Int]() XCTAssertEqual(try testGetObjectType(object: object), .normal) - let token = try hookInstead(object: object, selector: #selector(setter: SwiftHook_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in + let token = try ObjectHook(object).hook(#selector(setter: SwiftHook_MyClass.swiftHookPrivateProperty), closure: { original, o, s, number in expectation.append(1) original(o, s, number) expectation.append(3) diff --git a/SwiftHookTests/Components/OverrideSuperMethodTests.swift b/SwiftHookTests/Components/OverrideSuperMethodTests.swift index b2cfbca3..b17801f0 100644 --- a/SwiftHookTests/Components/OverrideSuperMethodTests.swift +++ b/SwiftHookTests/Components/OverrideSuperMethodTests.swift @@ -177,7 +177,7 @@ class OverrideSuperMethodTests: XCTestCase { try autoreleasepool { // hook let object = MyObject.init() - token = try hookAfter(object: object, selector: #selector(MySuperObject.myMethod), closure: { + token = try ObjectHook(object).hookAfter(#selector(MySuperObject.myMethod), closure: { }) as? HookToken dynamicClass = object_getClass(object) @@ -215,7 +215,7 @@ class OverrideSuperMethodTests: XCTestCase { XCTAssertNil(getMethodWithoutSearchingSuperClasses(targetClass: MyObject.self, selector: #selector(MySuperObject.myMethod))) // hook - let token: HookToken! = try hookAfter(targetClass: MyObject.self, selector: #selector(MySuperObject.myMethod), closure: { + let token: HookToken! = try ClassInstanceHook(MyObject.self).hookAfter(#selector(MySuperObject.myMethod), closure: { }) as? HookToken diff --git a/SwiftHookTests/SwiftAPITests/HookAllInstancesTests.swift b/SwiftHookTests/SwiftAPITests/HookAllInstancesTests.swift index ba0ae65f..3ed9c950 100644 --- a/SwiftHookTests/SwiftAPITests/HookAllInstancesTests.swift +++ b/SwiftHookTests/SwiftAPITests/HookAllInstancesTests.swift @@ -151,7 +151,7 @@ class HookAllInstancesTests: XCTestCase { XCTAssertFalse(obj.run) XCTAssertFalse(run) - try hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel in + try ClassInstanceHook(MyObject.self).hookBefore(#selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel in XCTAssertTrue(obj! === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) @@ -186,7 +186,7 @@ class HookAllInstancesTests: XCTestCase { XCTAssertFalse(obj.run) XCTAssertFalse(run) - try hookAfter(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel, number, url in + try ClassInstanceHook(MyObject.self).hookAfter(#selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel, number, url in XCTAssertTrue(obj! === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) XCTAssertEqual(number, 77) @@ -223,7 +223,7 @@ class HookAllInstancesTests: XCTestCase { XCTAssertFalse(obj.run) XCTAssertFalse(run) - try hookInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(number:url:)), closure: { [weak obj] original, object, sel, number, url in + try ClassInstanceHook(MyObject.self).hook(#selector(MyObject.myMethod(number:url:)), closure: { [weak obj] original, object, sel, number, url in XCTAssertTrue(obj! === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) XCTAssertEqual(number, 77) @@ -372,7 +372,7 @@ class HookAllInstancesTests: XCTestCase { XCTAssertNotNil(reference) XCTAssertEqual(run, false) XCTAssertFalse(MyObject.isReleased) - try hookDeallocInstead(targetClass: MyObject.self, closure: { original in + try ClassInstanceHook(MyObject.self).hookDealloc(closure: { original in XCTAssertNil(reference) XCTAssertEqual(run, false) XCTAssertFalse(MyObject.isReleased) diff --git a/SwiftHookTests/SwiftAPITests/HookClassMethodsTests.swift b/SwiftHookTests/SwiftAPITests/HookClassMethodsTests.swift index a59599b0..c3e0e847 100644 --- a/SwiftHookTests/SwiftAPITests/HookClassMethodsTests.swift +++ b/SwiftHookTests/SwiftAPITests/HookClassMethodsTests.swift @@ -146,7 +146,7 @@ class HookClassMethodsTests: XCTestCase { XCTAssertFalse(MyObject.run) XCTAssertFalse(run) - try hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(number:url:)), closure: { object, sel in + try ClassHook(MyObject.self).hookBefore(#selector(MyObject.myMethod(number:url:)), closure: { object, sel in XCTAssertTrue(MyObject.self === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) @@ -180,7 +180,7 @@ class HookClassMethodsTests: XCTestCase { XCTAssertFalse(MyObject.run) XCTAssertFalse(run) - try hookClassMethodAfter(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(number:url:)), closure: { object, sel, number, url in + try ClassHook(MyObject.self).hookAfter(#selector(MyObject.myMethod(number:url:)), closure: { object, sel, number, url in XCTAssertTrue(MyObject.self === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) XCTAssertEqual(number, 77) @@ -216,7 +216,7 @@ class HookClassMethodsTests: XCTestCase { XCTAssertFalse(MyObject.run) XCTAssertFalse(run) - try hookClassMethodInstead(targetClass: MyObject.self, selector: #selector(MyObject.myMethod(number:url:)), closure: { original, object, sel, number, url in + try ClassHook(MyObject.self).hook(#selector(MyObject.myMethod(number:url:)), closure: { original, object, sel, number, url in XCTAssertTrue(MyObject.self === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) XCTAssertEqual(number, 77) diff --git a/SwiftHookTests/SwiftAPITests/HookSpecificInstanceTests.swift b/SwiftHookTests/SwiftAPITests/HookSpecificInstanceTests.swift index 62dd8d05..42498ca5 100644 --- a/SwiftHookTests/SwiftAPITests/HookSpecificInstanceTests.swift +++ b/SwiftHookTests/SwiftAPITests/HookSpecificInstanceTests.swift @@ -151,7 +151,7 @@ class HookSpecificInstanceTests: XCTestCase { XCTAssertFalse(obj.run) XCTAssertFalse(run) - try hookBefore(object: obj, selector: #selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel in + try ObjectHook(obj).hookBefore(#selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel in XCTAssertTrue(obj! === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) @@ -186,7 +186,7 @@ class HookSpecificInstanceTests: XCTestCase { XCTAssertFalse(obj.run) XCTAssertFalse(run) - try hookAfter(object: obj, selector: #selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel, number, url in + try ObjectHook(obj).hookAfter(#selector(MyObject.myMethod(number:url:)), closure: { [weak obj] object, sel, number, url in XCTAssertTrue(obj! === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) XCTAssertEqual(number, 77) @@ -223,7 +223,7 @@ class HookSpecificInstanceTests: XCTestCase { XCTAssertFalse(obj.run) XCTAssertFalse(run) - try hookInstead(object: obj, selector: #selector(MyObject.myMethod(number:url:)), closure: { [weak obj] original, object, sel, number, url in + try ObjectHook(obj).hook(#selector(MyObject.myMethod(number:url:)), closure: { [weak obj] original, object, sel, number, url in XCTAssertTrue(obj! === object) XCTAssertEqual(sel, #selector(MyObject.myMethod)) XCTAssertEqual(number, 77) @@ -404,7 +404,7 @@ class HookSpecificInstanceTests: XCTestCase { XCTAssertNotNil(reference) XCTAssertEqual(run, false) XCTAssertFalse(MyObject.isReleased) - try hookDeallocInstead(object: obj, closure: { original in + try ObjectHook(obj).hookDealloc(closure: { original in XCTAssertNil(reference) XCTAssertEqual(run, false) XCTAssertFalse(MyObject.isReleased)