The other thread prompted me to a question of calling the prohibited retainCount – could be useful for debugging purposes. I know a few techniques to grab retainCount of an object (e.g. calling through a C helper that resides in a non-ARC obj-c file and thus is able to make a call to retainCount, the technique shown in the referenced thread) or reading the retainCount raw bits out of a swift object directly from Swift via "withUnsafeBytes". What about this other method which is based on performSelector?
let obj = NSObject()
let x = obj.perform("retainCount")!
// p x
// (Unmanaged<AnyObject>) {
// _value = (object = 0x0000000000000002)
// }
print(x) // crash
let y = x.toOpaque() // crash
print(y)
let z = x.takeUnretainedValue() // crash
print(z)
print(obj)
Could it be tweaked to work? It seems to work fine half way, getting the correct UnmanagedObject back, which I can see in the debugger via "print" command, but how do I get it from the app? Do I resort to some unsafeBitCast to read the value?
class C {}
let object = C()
typealias Proc = @convention(c) (Int) -> Int
let method = class_getInstanceMethod(type(of: object), "retainCount")!
let imp = method_getImplementation(method)
// getting "retainCount" method
let proc = unsafeBitCast(imp, to: Proc.self)
// let's convert ref counted object to Int
// so there is no RC change during parameter passing
let obj = unsafeBitCast(object, to: Int.self)
let count = proc(obj)
print(count) // 1
// double check RC via a different method:
print(myRetainCount(obj)) // 1
class_getInstanceMethod + method_getImplementation.
Works with swift classes.
performSelector: returns an object; it's nominally only intended to be used on methods which return objects, not primitives.
In Objective-C you can maybe abuse it by just casting the result to your primitive type, but if it's not pointer sized or the platform's calling convention uses different registers to return pointers vs integers, you'll get garbage (or worse).
I'm not sure if you can do that cast in Swift because Unmanaged assumes it genuinely contains a valid object reference, and will try to retain & release it appropriately.
let count = NSObject().perform("retainCount")!
print(unsafeBitCast(count, to: Int.self)) // 1
This only works for NSObject (sub)classes, and is thus less useful than the above method which is based on class_getInstanceMethod + method_getImplementation.
Conversion from a pointer to an integer value with the bit pattern of the pointer’s address in memory, or vice versa. Use the init(bitPattern:) initializer for the destination type.
But then Int(bitPattern:) takes a UInt, so it seems like that's intractable.
Beyond that, I can't decide if that's quite clever or very fragile. You're implicitly relying on the layout of Unmanaged being that its contained pointer is the first Int-sized bytes, although admittedly that does seem kinda guaranteed by the type's declaration:
@frozen
public struct Unmanaged<Instance: AnyObject> {
@usableFromInline
internal unowned(unsafe) var _value: Instance
…
I guess it's merely semantically that it's wrong - technically you should be using a proper API to extract the actual pointer value and then cast that (but I guess you already tried all the relevant APIs and they all crash due to trying to retain or release the value?).