performSelector("retainCount")

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?

Slightly different method based on obj-c runtime
    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.

2 Likes

Got this working solution:

    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.

Note that the docs for unsafeBitCast specifically say not to use it for this:

  • 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?).

1 Like
import Foundation
let o = NSObject()
print(CFGetRetainCount(o))

Warning: talking about this is likely to get you brigaded by bbum and his twitter/mastodon followers. I know this from experience. :sweat_smile:

2 Likes

Wow, somehow I missed that one, thank you! Also works with swift classes.

The small caveat – I have to adjust its return value with -1, but that's not a big deal once it is known.