@@ -56,20 +56,29 @@ public enum InterposeError: LocalizedError {
5656 object: NSObject
5757 )
5858
59- // ---
60-
61- /// Object-based hooking does not work if an object is using KVO.
62- /// The KVO mechanism also uses subclasses created at runtime but doesn't check for additional overrides.
63- /// Adding a hook eventually crashes the KVO management code so we reject hooking altogether in this case.
64- case kvoDetected( AnyObject )
65-
66- /// Object is lying about it's actual class metadata.
67- /// This usually happens when other swizzling libraries (like Aspects) also interfere with a class.
68- /// While this might just work, it's not worth risking a crash, so similar to KVO this case is rejected.
59+ /// Detected Key-Value Observing on the object while applying or reverting a hook.
6960 ///
70- /// @note Printing classes in Swift uses the class posing mechanism.
71- /// Use `NSClassFromString` to get the correct name.
72- case objectPosingAsDifferentClass( AnyObject , actualClass: AnyClass )
61+ /// The KVO mechanism installs its own dynamic subclass at runtime but does not support
62+ /// additional method overrides. Applying or reverting hooks on an object under KVO can lead
63+ /// to crashes in the Objective-C runtime, so such operations are explicitly disallowed.
64+ ///
65+ /// It is safe to start observing an object *after* it has been hooked, but not the other way
66+ /// around. Once KVO is active, reverting an existing hook is also considered unsafe.
67+ case kvoDetected( object: NSObject )
68+
69+ /// The object uses a dynamic subclass that was not installed by InterposeKit.
70+ ///
71+ /// This typically indicates interference from another runtime system, such as method
72+ /// swizzling libraries (like [Aspects](https://github.com/steipete/Aspects)). Similar to KVO,
73+ /// such subclasses bypass normal safety checks. Hooking is disallowed in this case to
74+ /// avoid crashes.
75+ ///
76+ /// - Note: Use `NSStringFromClass` to print class names accurately. Swift’s default
77+ /// formatting may reflect the perceived, not the actual runtime class.
78+ case unexpectedDynamicSubclass(
79+ object: NSObject ,
80+ actualClass: AnyClass
81+ )
7382
7483 /// Generic failure
7584 case unknownError( _ reason: String )
@@ -95,7 +104,7 @@ extension InterposeError: Equatable {
95104 return " Failed to allocate class pair: \( object) , \( subclassName) "
96105 case . kvoDetected( let obj) :
97106 return " Unable to hook object that uses Key Value Observing: \( obj) "
98- case . objectPosingAsDifferentClass ( let obj, let actualClass) :
107+ case . unexpectedDynamicSubclass ( let obj, let actualClass) :
99108 return " Unable to hook \( type ( of: obj) ) posing as \( NSStringFromClass ( actualClass) ) / "
100109 case . unknownError( let reason) :
101110 return reason
0 commit comments