Weak references in UnsafeMutable*Pointer with and without ObjC interop

I am interested in the using self zeroing weak references for places where I hand off an unsafe pointer to a (usually C) callback interface and need to turn it back into a proper swift reference type without introducing deallocation races.

I can currently do this with the objc_load/storeWeak features made available via Obj-C interop.

The function signature taking an AnyObject implies this should be acceptable with swift class (and maybe?? actor) types?

I made a simple package to wrap up this basic idea https://github.com/rustle/UnsafeWeak

import ObjectiveC

public extension UnsafeMutablePointer where Pointee == AnyObject? {
    func unsafe_loadWeak<T: AnyObject>(_ type: T.Type) -> T? {
        objc_loadWeak(AutoreleasingUnsafeMutablePointer(self)) as? T
    }
    func unsafe_storeWeak<T: AnyObject>(object: T?) {
        objc_storeWeak(
            AutoreleasingUnsafeMutablePointer(self),
            object
        )
    }
}

public extension UnsafeMutableRawPointer {
    func unsafe_loadWeak<T: AnyObject>(_ type: T.Type) -> T? {
        assumingMemoryBound(to: AnyObject?.self)
            .unsafe_loadWeak(type)
    }
    func unsafe_storeWeak<T: AnyObject>(object: T?) {
        assumingMemoryBound(to: AnyObject?.self)
            .unsafe_storeWeak(object: object)
    }
}

but I have a few open questions:

  1. Is this already a solved problem? If I'm just missing an annotation I definitely want to hop on that.
  2. Is using ObjC interop like this sound?
  3. Is there a place for this in the stdlib? Most especially when ObjC interop is not available.
  4. Only loosely related, but is there a way to constrain my extension such that it's constrained to an optional with a Wrapped that conforms to AnyObject?

Manually managing weak references in raw memory can be treacherous — it's very important to initialize and uninitialize the memory correctly. It might be better to use the UnsafeRawPointer that the C API is giving you to pass a class reference back and forth using Unmanaged:

performOperation(
  function: { rawContext in
    let context = Unmanaged<MyClass>.fromOpaque(rawContext).takeRetainedValue()
    ...
  },
  context: Unmanaged.passRetained(object).toOpaque())

You can then put a weak field inside that class reference if you need to.

Some thought is necessary for how to appropriately manage the ownership of the class reference. Generally, well-designed C APIs with an opaque context like this fall into one of three basic options:

  • The callback is called exactly once and therefore can unconditionally clean up after the context. In this case, passRetained / takeRetainedValue is appropriate.
  • The callback is called multiple times, one of which is unambiguously the final call. In this case, passRetained is appropriate, but only the final call should call takeRetainedValue.
  • All calls to the callback occur within the duration of the original call. In this case, ownership is much simpler, and there's no reason to allocate a separate class reference; you can simply pass the address of a local variable and read it in the callback.
1 Like

I like the idea of using Unmanaged. It was the other API I looked at when gaming this out. But I do think I still need to use a weak reference (somewhere).

The real world API I'm writing against is AXObserver which lacks a finished event callback so you can safely do tear down and is active until the element you observe becomes invalid (which could be never).

Prior to ARC interaction with this API usually meant building your own custom reference counting and implementing weak references. Then built in self zeroing weak refs allowed us to switch to what the run time provided. It seems like I can continue using the ObjC runtime feature (via manual allocation of an UnsafePointer or via Unmanaged) but I don't think I can avoid a race in a concurrent environment between adding/removing and deinit for the whatever we passed as context.

You can see more about the specific use case and possibly point out holes in my thinking in another post where I asked an overlapping question Using SerialExecutor to make an actor that is tied to run loop

Well, you’re going to have this destruction problem one way or another. A weak reference has to be stored in memory, and at some point it has to be safe to deinitialize and free that memory because you know there can’t be a concurrent/pending call to the callback. That is the point at which you could release the Unmanaged if you went that way.

Weak references are significantly more treacherous to manage manually than strong references, so I would really recommend using the class and putting a weak reference in it over doing what you’re doing with direct calls to the ObjC runtime functions.

Ya you're definitely right about still having a destruction problem. You'll see retiredTokens here currently live forever.

I am fairly certain I can tear down the token once any other observer hits my callback, but I think your point that I have to build my own thing either way so maybe just just strong refs all the way down is valid.

If you need a weak reference, that’s fine. I’m just recommending that you make the unsafe interface only deal with strong references and then put the weak reference within the object that you strongly reference. Live weak references have fairly strong requirements on their correct use at an ABI level, and there are pointers into them from the language runtime, so if you mess up your management of them at all, things can go really wrong really quick.

I am reasonably certain I want a weak ref, but don't need one.

Introducing Unmanaged is definitely not out of the question, but I don't have a principled place for doing balancing (matching) calls to retain/release so I then have to solve when it's safe to do cleanup in the same way I currently have a "when can I deallocate this unsafe pointer" problem.

I'll test making a wrapper type holding a more typical weak ref and using Unmanaged to at least get around holding the weak calls wrong.

If you’re trying to avoid the minor overheads of an object — which seems unnecessary here, but of course it’s your call — you could also just heap-allocate a struct that holds a weak reference. UnsafeMutablePointer at least has APIs for initializing and destroying a value in a more principled way than using those runtime functions directly, and it’ll work automatically if you decide you need to capture more state than just a weak reference.

Using an object is fine for the foreseeable future but good thought on a heap allocated struct if I need to squeeze down the road.

Went ahead and revised my implementation to use Unmanaged, a wrapper class with a weak var, and added a clean up pass that will hopefully stand up to scrutiny.

1 Like