@@ -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