6

I need to send images read from the Photo Library over the wire - in chunks of 5MB.

I read an image from the library using: PHImageManager.requestImageData(for:options:resultHandler:) and get a Data object. I would then like to efficiently split the data into chunks (without copying the memory). What would be the best way to do that?

This is what I have so far:

    imageData.withUnsafeBytes { (unsafePointer: UnsafePointer<UInt8>) -> Void in

        let totalSize = data.endIndex
        var offset = 0

        while offset < totalSize {
            let chunkSize = offset + uploadChunkSize > totalSize ? totalSize - offset : uploadChunkSize
            let chunk = Data(bytesNoCopy: unsafePointer, count: chunkSize, deallocator: Data.Deallocator.none)

            // send the chunk...

           offset += chunkSize
        }
    }

However I get this error at compile time:

Cannot convert value of type 'UnsafePointer' to expected argument type 'UnsafeMutableRawPointer'

If I use mutableBytes:

data.withUnsafeMutableBytes { (unsafePointer: UnsafeMutablePointer<UInt8>) -> Void in... }

Then I get the compile-time error:

Cannot use mutating member on immutable value: 'data' is a 'let' constant

Which is correct, since I do not really want to make changes to the image data. I only want to send one chunk of it at a time.

Is there a better way to do this?

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
GK100
  • 3,664
  • 1
  • 26
  • 19

2 Answers2

16

Hi there!

I need the same behaviour and came up with this solution. You pointed the error right and the solution is just to create the UnsafeMutableRawPointer with the UnsafePointer address. It's the fastest solution I found yet.

One other thing is to add the offset to the base address of the mutRawPointer when you create a chunk.

50MB data in 2MB chunks takes ~ 0.009578s

func createChunks(forData: Data) {

    imageData.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in
        let mutRawPointer = UnsafeMutableRawPointer(mutating: u8Ptr)
        let uploadChunkSize = 2097152
        let totalSize = imageData.count
        var offset = 0

        while offset < totalSize {

            let chunkSize = offset + uploadChunkSize > totalSize ? totalSize - offset : uploadChunkSize
            let chunk = Data(bytesNoCopy: mutRawPointer+offset, count: chunkSize, deallocator: Data.Deallocator.none)
            offset += chunkSize
        }
    }
}
Johannes Knust
  • 891
  • 1
  • 11
  • 18
  • 2
    This works well for smaller files, but if you wanted to split a 4GB file you'd have to load it all in memory, which may not work most of the time. – 0x6A75616E May 14 '18 at 05:48
0

The Data(bytesNoCopy: ... initializer requires a mutable pointer. Change your code to the following to make it work:

imageData.withUnsafeMutableBytes { (unsafePointer: UnsafeMutablePointer<UInt8>) -> Void in
    // your code
}
Adam
  • 26,549
  • 8
  • 62
  • 79