Skip to content

Commit a5a1ac0

Browse files
committed
Refactoring in ObjectHookStrategy 1/
1 parent 795fa34 commit a5a1ac0

File tree

3 files changed

+102
-45
lines changed

3 files changed

+102
-45
lines changed

Sources/InterposeKit/Hooks/HookStrategy/ClassHookStrategy/ClassHookStrategy.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation
1+
import ObjectiveC
22

33
internal final class ClassHookStrategy: HookStrategy {
44

@@ -23,6 +23,7 @@ internal final class ClassHookStrategy: HookStrategy {
2323
internal let `class`: AnyClass
2424
internal var scope: HookScope { .class }
2525
internal let selector: Selector
26+
2627
private let makeHookIMP: () -> IMP
2728

2829
// ============================================================================ //

Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import Foundation
21
import ITKSuperBuilder
2+
import ObjectiveC
33

4-
final class ObjectHookStrategy: HookStrategy {
4+
internal final class ObjectHookStrategy: HookStrategy {
55

6-
init(
6+
// ============================================================================ //
7+
// MARK: Initialization
8+
// ============================================================================ //
9+
10+
internal init(
711
object: NSObject,
812
selector: Selector,
913
makeHookIMP: @escaping () -> IMP
@@ -14,23 +18,34 @@ final class ObjectHookStrategy: HookStrategy {
1418
self.makeHookIMP = makeHookIMP
1519
}
1620

17-
let `class`: AnyClass
18-
let object: NSObject
19-
var scope: HookScope { .object(self.object) }
20-
let selector: Selector
21+
// ============================================================================ //
22+
// MARK: Configuration
23+
// ============================================================================ //
24+
25+
internal let `class`: AnyClass
26+
internal let object: NSObject
27+
internal var scope: HookScope { .object(self.object) }
28+
internal let selector: Selector
2129

2230
private let makeHookIMP: () -> IMP
23-
private(set) var appliedHookIMP: IMP?
24-
private(set) var storedOriginalIMP: IMP?
31+
32+
// ============================================================================ //
33+
// MARK: Implementations & Handle
34+
// ============================================================================ //
35+
36+
private(set) internal var appliedHookIMP: IMP?
37+
private(set) internal var storedOriginalIMP: IMP?
2538

2639
private lazy var handle = ObjectHookHandle(
2740
getOriginalIMP: { [weak self] in self?.storedOriginalIMP },
2841
setOriginalIMP: { [weak self] in self?.storedOriginalIMP = $0 }
2942
)
43+
44+
// ============================================================================ //
45+
// MARK: Validation
46+
// ============================================================================ //
3047

31-
/// Subclass that we create on the fly
32-
33-
func validate() throws {
48+
internal func validate() throws {
3449
guard class_getInstanceMethod(self.class, self.selector) != nil else {
3550
throw InterposeError.methodNotFound(
3651
class: self.class,
@@ -55,59 +70,77 @@ final class ObjectHookStrategy: HookStrategy {
5570
}
5671
}
5772

58-
func replaceImplementation() throws {
73+
// ============================================================================ //
74+
// MARK: Installing Implementation
75+
// ============================================================================ //
76+
77+
internal func replaceImplementation() throws {
5978
let hookIMP = self.makeHookIMP()
60-
self.appliedHookIMP = hookIMP
61-
ObjectHookRegistry.register(self.handle, for: hookIMP)
6279

80+
// Fetch the method, whose implementation we want to replace.
6381
guard let method = class_getInstanceMethod(self.class, self.selector) else {
64-
throw InterposeError.methodNotFound(class: self.class, selector: self.selector)
82+
throw InterposeError.methodNotFound(
83+
class: self.class,
84+
selector: self.selector
85+
)
6586
}
6687

67-
// The implementation of the call that is hooked must exist.
88+
// Ensure that the method has an associated implementation.
6889
guard self.lookUpIMP() != nil else {
6990
throw InterposeError.implementationNotFound(
7091
class: self.class,
7192
selector: self.selector
7293
)
7394
}
7495

75-
// Check if there's an existing subclass we can reuse.
76-
// Create one at runtime if there is none.
77-
let dynamicSubclass: AnyClass = try ObjectSubclassManager.ensureSubclassInstalled(for: self.object)
96+
// Retrieve a ready-to-use dynamic subclass. It might be reused if the object already
97+
// has one installed or a newly created one.
98+
let subclass: AnyClass = try ObjectSubclassManager.ensureSubclassInstalled(for: self.object)
7899

79-
// This function searches superclasses for implementations
80-
let classImplementsMethod = class_implementsInstanceMethod(dynamicSubclass, self.selector)
81-
let encoding = method_getTypeEncoding(method)
82-
83-
// If the subclass is empty, we create a super trampoline first.
84-
// If a hook already exists, we must skip this.
85-
if !classImplementsMethod {
100+
// If the dynamic subclass does not implement the method directly, we create a super
101+
// trampoline first. Otherwise, when a hook for that method has already been applied
102+
// (and potentially reverted), we skip this step.
103+
if !class_implementsInstanceMethod(subclass, self.selector) {
86104
do {
87-
try ITKSuperBuilder.addSuperInstanceMethod(to: dynamicSubclass, selector: self.selector)
88-
let imp = class_getMethodImplementation(dynamicSubclass, self.selector)!
89-
Interpose.log("Added super trampoline for -[\(dynamicSubclass) \(self.selector)] IMP: \(imp)")
105+
try ITKSuperBuilder.addSuperInstanceMethod(
106+
to: subclass,
107+
selector: self.selector
108+
)
109+
110+
Interpose.log({
111+
var message = "Added super trampoline for -[\(subclass) \(self.selector)]"
112+
if let imp = class_getMethodImplementation(subclass, self.selector) {
113+
message += " IMP: \(imp)"
114+
}
115+
return message
116+
}())
90117
} catch {
91-
// Interpose.log("Failed to add super implementation to -[\(dynamicClass).\(selector)]: \(error)")
92-
throw InterposeError.unknownError(String(describing: error))
118+
throw InterposeError.failedToAddSuperTrampoline(
119+
class: subclass,
120+
selector: self.selector,
121+
underlyingError: error as NSError
122+
)
93123
}
94124
}
95125

96-
// Replace IMP (by now we guarantee that it exists)
97-
self.storedOriginalIMP = class_replaceMethod(dynamicSubclass, self.selector, hookIMP, encoding)
98-
guard self.storedOriginalIMP != nil else {
126+
guard let imp = class_replaceMethod(subclass, self.selector, hookIMP, method_getTypeEncoding(method)) else {
99127
// This should not happen if the class implements the method or we have installed
100128
// the super trampoline. Instead, we should make the trampoline implementation
101129
// failable.
102130
throw InterposeError.implementationNotFound(
103-
class: dynamicSubclass,
131+
class: subclass,
104132
selector: self.selector
105133
)
106134
}
135+
136+
self.appliedHookIMP = hookIMP
137+
self.storedOriginalIMP = imp
138+
ObjectHookRegistry.register(self.handle, for: hookIMP)
139+
107140
Interpose.log("Replaced implementation for -[\(self.class) \(self.selector)] IMP: \(self.storedOriginalIMP!) -> \(hookIMP)")
108141
}
109142

110-
func restoreImplementation() throws {
143+
internal func restoreImplementation() throws {
111144
guard let hookIMP = self.appliedHookIMP else { return }
112145
guard let originalIMP = self.storedOriginalIMP else { return }
113146

@@ -126,7 +159,11 @@ final class ObjectHookStrategy: HookStrategy {
126159
}
127160

128161
guard let currentIMP = class_getMethodImplementation(dynamicSubclass, self.selector) else {
129-
throw InterposeError.unknownError("No Implementation found")
162+
// Do we need this???
163+
throw InterposeError.implementationNotFound(
164+
class: self.class,
165+
selector: self.selector
166+
)
130167
}
131168

132169
// We are the topmost hook, replace method.
@@ -156,6 +193,10 @@ final class ObjectHookStrategy: HookStrategy {
156193
// self.dynamicSubclass = nil
157194
}
158195

196+
// ============================================================================ //
197+
// MARK: Helpers
198+
// ============================================================================ //
199+
159200
/// Traverses the object hook chain to find the handle to the parent of this hook, starting
160201
/// from the topmost IMP for the hooked method.
161202
///

Sources/InterposeKit/InterposeError.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ObjectiveC
1+
import Foundation
22

33
public enum InterposeError: Error {
44

@@ -79,9 +79,21 @@ public enum InterposeError: Error {
7979
object: NSObject,
8080
actualClass: AnyClass
8181
)
82+
83+
/// Failed to add a super trampoline for the specified class and selector.
84+
///
85+
/// When interposing an instance method on a dynamic subclass, InterposeKit installs
86+
/// a *super trampoline*—a method that forwards calls to the original implementation
87+
/// in the superclass. This allows the hook to delegate to the original behavior when needed.
88+
///
89+
/// This error is thrown when the trampoline cannot be added, which is very rare.
90+
/// Refer to the underlying error for more details.
91+
case failedToAddSuperTrampoline(
92+
class: AnyClass,
93+
selector: Selector,
94+
underlyingError: NSError
95+
)
8296

83-
/// Generic failure
84-
case unknownError(_ reason: String)
8597
}
8698

8799
extension InterposeError: Equatable {
@@ -143,10 +155,13 @@ extension InterposeError: Equatable {
143155
return false
144156
}
145157

146-
case let .unknownError(lhsReason):
158+
case let .failedToAddSuperTrampoline(lhsClass, lhsSelector, lhsError):
147159
switch rhs {
148-
case let .unknownError(rhsReason):
149-
return lhsReason == rhsReason
160+
case let .failedToAddSuperTrampoline(rhsClass, rhsSelector, rhsError):
161+
return lhsClass == rhsClass
162+
&& lhsSelector == rhsSelector
163+
&& lhsError.domain == rhsError.domain
164+
&& lhsError.code == rhsError.code
150165
default:
151166
return false
152167
}

0 commit comments

Comments
 (0)