Does casting from CFData to Data copy?

I've been scouring for a while, but I can't find an answer to this:

Does casting from CFData to Data in Swift give me a copy, or do they share the same data unless I mutate the data I get back?

What I'm really trying to get do is this, without any extraneous copies:

  let cgImage: CGImage = ...
  if let cfdata = cgImage.dataProvider.data {
      let digest = computeMD5Digest(data: cfdata as Data)
  }

So ideally I'm computing the digest by just looking at the data which already exists for the CGImage without any unneeded copies, but who can tell these days?

The simple answer here is… that there is no simple answer )-:

There are lots of edge cases and the exact behaviour has changed over time. For example:

  • dispatch_data_t can bridge to NSData which can bridge to CFData, so CFData can be non-contiguous, which isn’t something that (modern versions of) Data support (heck, it’s not something that CFData itself supports, at least at the API level).

  • NSData can bridge to CFData, and NSData allows for custom subclasses.

However, in the most obvious case this bridging doesn’t involve a copy. Consider:

import Foundation

func test(_ data: Data) {
    data.withUnsafeBytes { buf in
        print("    in:", buf.baseAddress!)
    }
}

func main() {
    let cf = CFDataCreate(nil, "Hello Cruel World!", 18)!
    print("before:", CFDataGetBytePtr(cf)!)
    test(cf as Data)
    print(" after:", CFDataGetBytePtr(cf)!)
}

main()

Compiling with Xcode 13.2 and running on macOS 11.6.1 I see this:

before: 0x0000000109a04180
    in: 0x0000000109a04180
 after: 0x0000000109a04180

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes

What is the source of your CGImages and do you need to support cases when dataProvider or dataProvider.data is nil?

This is the sort of answer that makes me want a replacement for Data that doesn’t use Foundation.

2 Likes

This is the sort of answer that makes me want a replacement for Data
that doesn’t use Foundation.

There’s nothing stopping you building that (indeed, the SwiftNIO folks have done exactly that). However, unless your code is completely standalone, you’ll end up needing to call system APIs that work with Data, which brings you back to exactly the place you started.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

@davidbaraff we might have a better answer if we knew where you get CGImages from.

The images are either read from a file, as PNG data, using either NSImage or UIImage apis, and then we ask the NSImage or UIImage for its CGImage or…

The other case is the images are rendered on the fly, not read back, and we get the image by pulling it out of the CGContext in the obvious manner.

(Although since I always cache the images to disk anyway it turns out to be better to compute the digest from the data, which is PNG format, just prior to writing to disk, which is cheaper since the data is now much smaller than in CGImage form.)

Still, the question was mostly about CFData vs Data and has been well answered as far as I can see. I’m happy to know that once again, a question I thought might be simple was far, far from being so…

Except ByteBuffer seems to depend on Foundation as well, and it’s also inside the rather-monolithic NIOCore module.

I’d like to see the community standardize around more flexible and simple implementations. I’ve already seen a lot of packages inherit unnecessarily strict requirements by depending on SwiftNIO just for ByteBuffer.

Unfortunately, I don’t think that’s going to be very easy until cross-import overlays are implemented.

I believe you are on the right track. Just beware that the data obtained via image.dataProvider and the original data loaded from, say, png file are different.

No, none of the NIO* modules (apart from NIOFoundationCompat which brings in the Data <--> ByteBuffer conversions) in all of the swift-nio* packages depend on Foundation.

I hedged that statement for a reason. Thank you for correcting me.

I still wish it wasn’t in such a monolithic module, though. It makes dependency management much harder for everyone involved.

For instance, I’m willing to bet most breaking changes in NIOCore never touch ByteBuffer, but it’d still have to be manually updated by packages that use it.

Fair enough, over the years this came up very frequently and I think fundamentally it comes down to SwiftNIO being a networking library. Making individual packages (for example for ByteBuffer, CircularBuffer, ...) comes at a cost and doesn't really help SwiftNIO the networking library.

Ideally, SwiftNIO wouldn't have had to implement basic data structures and types like ByteBuffer, CircularBuffer, PriorityQueue, Heap, ... but when SwiftNIO started out there weren't any high-quality implementations for those out there so NIO brought its own. These days, we fortunately have swift-collections and at some point in the future there'll hopefully be a "bag of bytes" type that ByteBuffer can just use internally rather than rolling its own from nothing.

If you need ByteBuffer yourself today, you essentially have two options:

  1. depend on swift-nio / NIOCore
  2. copy Sources/NIOCore/ByteBuffer-*, Sources/NIOFoundationCompat/*, Sources/NIOCore/IntegerTypes.swift, Sources/NIOCore/IntegerBitPacking.swift into your own project. ByteBuffer is fairly self-contained. (FWIW, if you don't care about ByteBuffer being 8 bits wider (24 bits instead of just 20 bits), then you don't even need the Integer*.swift files. You could instead just search & replace _UInt24 with UInt32.)

Please note that these are just my personal thoughts and you're obviously welcome to discuss breaking ByteBuffer out with the SwiftNIO team in a GitHub issue or so.

1 Like

Please beware that UIImage.CGImage is not guaranteed to have a data provider. You might be best off using Data directly, bypassing the entire issue.

I was thinking the same, but wasn't able to find an example (and OP didn't express any concern about that case so it might be not interesting for him). Do you know a reproducible case for that?

(also in theory UIImage's cgImage and DataProvider's data can be nil, don't know reproducible cases for those cases either).

That's if there is data to start with...

Any image from an asset catalog has a non-nil CGImage and a nil data provider. It’s also feasible that whether or not a UIImage’s underlying CGImage has a data provider might change in the future.

A UIImage created from a CIImage has a nil CGImage.

Strictly speaking there is no guarantee that any UIImage has a non-nil CGImage except for those created from a CGImage. But far too many apps rely on the current behavior to risk changing it.

I can't reproduce this... tried png / pdf in an asset catalogue, both with and without specifying "preserve vector data".

Yes, I can see this.

Hm, I wonder if someone was relying on the data provider and a bridge was written… :innocent:

Anyway, we’re getting into implementation details of Apple frameworks, which is off topic for this forum. I would definitely expect there is some way to get a CGImage with a nil data provider using API.