Clarification on Memory Ownership related to unsafeBitCast and takeUnretainedValue

As I migrate some Objective-C code over to Swift, I was hoping to get some confirmation on memory ownership rules as detailed below. Apologies in advance if this questions veers too far outside the spirit of a pure-Swift question...

class PixelBuffer {

  let cvPixelBuffer: CVPixelBuffer
  let surfaceRef: IOSurfaceRef?
  
  init(_ cvPixelBuffer: CVPixelBuffer) {
    self.cvPixelBuffer = cvPixelBuffer
    self.surfaceRef = CVPixelBufferGetIOSurface(cvPixelBuffer)?.takeUnretainedValue()
  }
}

Per the Objective-C naming conventions of Get and Create, I use takeUnretainedValue because CVPixelBufferGetIOSurface has "Get" in its name and not "Create". Assuming that the CVPixelBuffer is backed by an IOSurface, is the surfaceRef property guaranteed to be valid for the lifetime of a PixelBuffer instance?

Part 2:

class SurfaceWrapper {

  // Note: This is an IOSurface, not an IOSurfaceRef.
  let surface: IOSurface
  
  init(_ surface: IOSurface) {
    self.surface = surface
  }
}

let pixelBuffer = PixelBufer(...)
let surfaceWrapper = SurfaceWrapper(unsafeBitCast(pixelBuffer.surfaceRef, to: IOSurface.self))

// pixelBuffer goes out of scope...
pixelBuffer = nil

// surfaceWrapper is passed around to various other functions.
renderImage(image, toSurface: surfaceWrapper.surface)

Unlike most other types that are toll-free bridged, IOSurface and IOSurfaceRef appear to require the use of unsafeBitCast. When unsafeBitCast is used to convert an IOSurfaceRef into an IOSurface, does memory management "just work"?

In other words, does SurfaceWrapper's init correctly create a strong reference to the given IOSurface, which by definition should be a strong reference to the IOSurfaceRef that is backing the CVPixelBuffer?

While IOSurface and CoreVideo are clear Apple frameworks, it's the use of unsafeBitCast and takeUnretainedValue that I hope is relevant to this forum. Thank you.

is the surfaceRef property guaranteed to be valid for the lifetime
of a PixelBuffer instance?

The reference itself is, yes. The takeUnretainedValue brings the IOSurface into ‘ARC space’, so ARC now takes over ensuring that the reference is valid.

There’s no guaranteed that the surface itself stays valid. You could do some operation on the pixel buffer that causes it to invalidate the surface. The rules there are governed by the API in question, not Swift.

Unlike most other types that are toll-free bridged, IOSurface and
IOSurfaceRef appear to require the use of unsafeBitCast.

Does this work?

let surface = surfaceRef as AnyObject as! IOSurface

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

Using let surface = surfaceRef as AnyObject as! IOSurface appears to work.

Other than the syntax, how is that different in any way than using unsafeBitCast?

1 Like

Using let surface = surfaceRef as AnyObject as! IOSurface appears to
work.

Cool.

Other than the syntax, how is that different in any way than using
unsafeBitCast?

It’s objects all the way, so you can be reasonably assure that the reference counting will work out.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

unsafeBitCast hides what you're doing from the compiler, possibly blocking optimization or requiring an extra retain, and it's probably the most dangerous API we expose. In this case, if the cast succeeds and both sides are represented as a single reference (the implementation does no object bridging) then it happens to work. But you can get into a lot of trouble using unsafeBitCast on references or pointers, it requires intimate knowledge of the compiler/runtime implementation.

4 Likes