NSObject.observationInfo
is a pointer that identifies information about all of the observers that are registered with the observed object. In objc, I can easily print it with %@
:
NSObject *observer = [NSObject new];
NSObject *observed = [NSObject new];
[observed addObserver:observer forKeyPath:@"hash" options:0 context:nil];
NSLog(@"%@", observed.observationInfo);
/*
<NSKeyValueObservationInfo 0x10045d560> (
<NSKeyValueObservance 0x10045d300: Observer: 0x10045bd70, Key path: hash, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x10045bfe0>
)
*/
In Swift however, it was represented by UnsafeMutableRawPointer
. I have no idea how to handle this:
let observer = NSObject()
let observed = NSObject()
observed.addObserver(observer, forKeyPath: "hash", options: [], context: nil)
let info = observed.observationInfo!
print(info)
// 0x00007fbc93711510
print(info.assumingMemoryBound(to: NSObject.self).pointee)
// error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x18).
print(UnsafePointer<NSObject>(OpaquePointer(info)).pointee)
// error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x18).
String(format: "%@", info)
// error: argument type 'UnsafeMutableRawPointer' does not conform to expected type 'CVarArg'
String(format: "%@", info as! CVarArg)
// error: Execution was interrupted, reason: signal SIGABRT.
Thanks, but I want to print KVO informations of arbitrary object, for debugging.
KVO relationships are stored in a global dictionary. therefore, When the same address is reused, an object could have an observer right after its creation. observationInfo
is helpful to find observer that was not properly removed.
Basically, What I'm trying to do is cast UnsafeMutableRawPointer
(void*
) to concrete type. Since [(id)info isKindOfClass:[NSObject class]] == YES
, I thought info.assumingMemoryBound(to: NSObject.self).pointee
would work.
1 Like
Try the Unmanaged
type.
let info = Unmanaged<AnyObject>
.fromOpaque(observed.observationInfo!)
.takeUnretainedValue()
Or add a breakpoint in Xcode, and edit it to use:
- a Debugger Command action, and
- the Automatically continue after evaluating actions option.
expression --language objc --object-description -- [observed observationInfo]
1 Like
Awesome! It works.
I found unsafeBitCast(info, to: AnyObject.self)
also works. but why? what's the difference between UnsafePointer.pointee
, Unmanaged.takeUnretainedValue
and unsafeBitCast
?
// <Foundation/NSKeyValueObserving.h>
@interface NSObject(NSKeyValueObservingCustomization)
@property (nullable) void *observationInfo NS_RETURNS_INNER_POINTER;
@end
To send the description
or debugDescription
message to the observationInfo
, you need a Swift reference type instead of an unsafe pointer type.
In your original example:
- the
observed.observationInfo!
is of type UnsafeMutableRawPointer
;
- the
assumingMemoryBound(to:)
method will return an UnsafeMutablePointer<NSObject>
;
- the
pointee
property will dereference the pointer. This is incorrect in both Swift and Objective-C.
Using unsafeBitCast(_:to:)
with your example is like a C++ static_cast
or reinterpret_cast
between two pointer types. It should be OK for debug-only code.
Using the Unmanaged
type is like an Objective-C bridged cast.
Thank you for your kind response. I understand. In some sense, Unmanaged<AnyObject>
represent a pointer, and UnsafePointer<AnyObject>
represent a double pointer.
Yes, I think you're right. I mistranslated between Objective-C and Swift. Usually, a pointer to an Objective-C pointer uses an AutoreleasingUnsafeMutablePointer
.
So your original example was assuming that void *
was an NSObject **
, and dereferencing to NSObject *
. But it would actually end up with the isa_t
value of the instance.
By the way, the debugger command po observed.observationInfo
might also work, but I don't know how the default language is chosen.