Skip to content

Commit 66e0acc

Browse files
committed
Refactoring in ObjectHookStrategy 2/
1 parent a5a1ac0 commit 66e0acc

File tree

2 files changed

+43
-28
lines changed

2 files changed

+43
-28
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ internal final class ClassHookStrategy: HookStrategy {
3838
// ============================================================================ //
3939

4040
internal func validate() throws {
41+
// Ensure that the method exists.
4142
guard class_getInstanceMethod(self.class, self.selector) != nil else {
4243
throw InterposeError.methodNotFound(
4344
class: self.class,
4445
selector: self.selector
4546
)
4647
}
4748

49+
// Ensure that the class directly implements the method.
4850
guard class_implementsInstanceMethod(self.class, self.selector) else {
4951
throw InterposeError.methodNotDirectlyImplemented(
5052
class: self.class,
@@ -61,6 +63,8 @@ internal final class ClassHookStrategy: HookStrategy {
6163
let hookIMP = self.makeHookIMP()
6264

6365
guard let method = class_getInstanceMethod(self.class, self.selector) else {
66+
// This should not happen under normal circumstances, as we perform validation upon
67+
// creating the hook strategy, which itself checks for the presence of the method.
6468
throw InterposeError.methodNotFound(
6569
class: self.class,
6670
selector: self.selector
@@ -73,6 +77,8 @@ internal final class ClassHookStrategy: HookStrategy {
7377
hookIMP,
7478
method_getTypeEncoding(method)
7579
) else {
80+
// This should not happen under normal circumstances, as we perform validation upon
81+
// creating the hook strategy, which checks if the class directly implements the method.
7682
throw InterposeError.implementationNotFound(
7783
class: self.class,
7884
selector: self.selector

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

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,24 @@ internal final class ObjectHookStrategy: HookStrategy {
4646
// ============================================================================ //
4747

4848
internal func validate() throws {
49+
// Ensure that the method exists.
4950
guard class_getInstanceMethod(self.class, self.selector) != nil else {
5051
throw InterposeError.methodNotFound(
5152
class: self.class,
5253
selector: self.selector
5354
)
5455
}
5556

57+
// Ensure that the method has an associated implementation (can be in a superclass).
58+
guard self.lookUpIMP() != nil else {
59+
throw InterposeError.implementationNotFound(
60+
class: self.class,
61+
selector: self.selector
62+
)
63+
}
64+
65+
// Ensure that the object either does not have a dynamic subclass installed or that
66+
// it is the subclass installed by InterposeKit rather than by KVO or other mechanism.
5667
let perceivedClass: AnyClass = type(of: self.object)
5768
let actualClass: AnyClass = object_getClass(self.object)
5869

@@ -85,14 +96,6 @@ internal final class ObjectHookStrategy: HookStrategy {
8596
)
8697
}
8798

88-
// Ensure that the method has an associated implementation.
89-
guard self.lookUpIMP() != nil else {
90-
throw InterposeError.implementationNotFound(
91-
class: self.class,
92-
selector: self.selector
93-
)
94-
}
95-
9699
// Retrieve a ready-to-use dynamic subclass. It might be reused if the object already
97100
// has one installed or a newly created one.
98101
let subclass: AnyClass = try ObjectSubclassManager.ensureSubclassInstalled(for: self.object)
@@ -123,21 +126,26 @@ internal final class ObjectHookStrategy: HookStrategy {
123126
}
124127
}
125128

126-
guard let imp = class_replaceMethod(subclass, self.selector, hookIMP, method_getTypeEncoding(method)) else {
127-
// This should not happen if the class implements the method or we have installed
128-
// the super trampoline. Instead, we should make the trampoline implementation
129-
// failable.
129+
guard let originalIMP = class_replaceMethod(
130+
subclass,
131+
self.selector,
132+
hookIMP,
133+
method_getTypeEncoding(method)
134+
) else {
135+
// This should not fail under normal circumstances, as the subclass should already
136+
// have an associated implementation, which might be the just-installed trampoline
137+
// or an existing hook.
130138
throw InterposeError.implementationNotFound(
131139
class: subclass,
132140
selector: self.selector
133141
)
134142
}
135143

136144
self.appliedHookIMP = hookIMP
137-
self.storedOriginalIMP = imp
145+
self.storedOriginalIMP = originalIMP
138146
ObjectHookRegistry.register(self.handle, for: hookIMP)
139147

140-
Interpose.log("Replaced implementation for -[\(self.class) \(self.selector)] IMP: \(self.storedOriginalIMP!) -> \(hookIMP)")
148+
Interpose.log("Replaced implementation for -[\(self.class) \(self.selector)] IMP: \(originalIMP) -> \(hookIMP)")
141149
}
142150

143151
internal func restoreImplementation() throws {
@@ -155,42 +163,43 @@ internal final class ObjectHookStrategy: HookStrategy {
155163
) else { return }
156164

157165
guard let method = class_getInstanceMethod(self.class, self.selector) else {
158-
throw InterposeError.methodNotFound(class: self.class, selector: self.selector)
166+
throw InterposeError.methodNotFound(
167+
class: self.class,
168+
selector: self.selector
169+
)
159170
}
160171

161172
guard let currentIMP = class_getMethodImplementation(dynamicSubclass, self.selector) else {
162-
// Do we need this???
163173
throw InterposeError.implementationNotFound(
164174
class: self.class,
165175
selector: self.selector
166176
)
167177
}
168178

169-
// We are the topmost hook, replace method.
179+
// If we are the topmost hook, we have to replace the implementation on the subclass.
170180
if currentIMP == hookIMP {
171-
let previousIMP = class_replaceMethod(dynamicSubclass, self.selector, originalIMP, method_getTypeEncoding(method))
181+
let previousIMP = class_replaceMethod(
182+
dynamicSubclass,
183+
self.selector,
184+
originalIMP,
185+
method_getTypeEncoding(method)
186+
)
187+
172188
guard previousIMP == hookIMP else {
173189
throw InterposeError.revertCorrupted(
174190
class: dynamicSubclass,
175191
selector: self.selector,
176192
imp: previousIMP
177193
)
178194
}
179-
Interpose.log("Restored implementation for -[\(self.class) \(self.selector)] IMP: \(originalIMP)")
180195
} else {
196+
// Otherwise, find the next hook and set its original IMP to this hook’s original IMP,
197+
// effectively unlinking this hook from the chain.
181198
let nextHook = self._findParentHook(from: currentIMP)
182-
// Replace next's original IMP
183199
nextHook?.originalIMP = originalIMP
184200
}
185201

186-
187-
188-
// FUTURE: remove class pair!
189-
// This might fail if we get KVO observed.
190-
// objc_disposeClassPair does not return a bool but logs if it fails.
191-
//
192-
// objc_disposeClassPair(dynamicSubclass)
193-
// self.dynamicSubclass = nil
202+
Interpose.log("Restored implementation for -[\(self.class) \(self.selector)] IMP: \(originalIMP)")
194203
}
195204

196205
// ============================================================================ //

0 commit comments

Comments
 (0)