3

I'm trying to build an asynchronous file download in Swift based on the Erica Sadun's method. But I need it to handle bigger files, so I found this answer about using a NSOutputStream instead of NSData, makes sense.

However, I can't get it to work. I get this error when I try adding the NSData bytes (in my NSURLConnection didReceiveData function) to the NSOutputStream write function: '()' is not identical to 'UInt8' on this row: bytesWritten = self.downloadStream.write(data.bytes, maxLength: bytesLeftToWrite).

data.bytes is of the type ConstUnsafePointer<()> and the .write() function expects the type to be ConstUnsafePointer<UInt8>, so in that sense, the error make perfect sense. But since I'm new to iOS and of course Swift programming, I can't get my head around how to fix this.

So, how do I convert the data.bytes: ConstUnsafePointer<()> to ConstUnsafePointer<UInt8> alt. make this work some other way?

My didReceiveData function:

func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {

    var bytesLeftToWrite: NSInteger = data.length
    var bytesWritten: NSInteger = 0

    while bytesLeftToWrite > 0 {

        bytesWritten = self.downloadStream.write(data.bytes, maxLength: bytesLeftToWrite)

        if bytesWritten == -1 {

            break

        }

        bytesLeftToWrite -= bytesWritten

        let responseExpectedlenght: NSNumber = NSNumber(longLong: self.downloadResponse!.expectedContentLength)
        let dataLength: NSNumber = NSNumber(long: data.length)

        self.downloadProgressPercentage = ((dataLength / responseExpectedlenght) * 100)

        println("Downloaded: \(self.downloadProgressPercentage)%")

    }

}
Community
  • 1
  • 1
Niklas
  • 1,729
  • 1
  • 12
  • 19

2 Answers2

4

You can cast the pointer with UnsafePointer():

bytesWritten = self.downloadStream.write(UnsafePointer(data.bytes), maxLength: bytesLeftToWrite)

There is also a problem in your write loop, because you always write the initial bytes of the data object to the output stream.

It should probably look similar to this (untested):

var bytes = UnsafePointer<UInt8>(data.bytes)
var bytesLeftToWrite: NSInteger = data.length

while bytesLeftToWrite > 0 {
    let bytesWritten = self.downloadStream.write(bytes, maxLength: bytesLeftToWrite)
    if bytesWritten == -1 {
        break // Some error occurred ...
    }

    bytesLeftToWrite -= bytesWritten
    bytes += bytesWritten // advance pointer

    // ...
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Easy as that, brilliant! Thanks a lot! – Niklas Aug 01 '14 at 13:04
  • @Niklas: You are welcome. But note that your write loop is not correct, because you write always the *initial* bytes of `data` to the output stream, not the *yet unwritten* bytes. – Martin R Aug 01 '14 at 13:06
  • I hadn't though about that, since I ran in to trouble before I got that far. But I saw what you where saying I was just thinking about how to move the pointer when I saw your edit just now. Great, I'll have a go at something like that then! Thanks again! – Niklas Aug 01 '14 at 13:15
  • (I'm not the down-voter; in fact I up-voted.) But, alas, in the shifting sands of Swift betas, I think this syntax has changed in beta 5. I use `var bytes = UnsafePointer(buffer)`. – Rob Aug 06 '14 at 07:16
  • @Rob: You are completely right, and that is much more elegant. - There are probably a lot of answers that need to be revised for beta 5. I will have a look at mine later. Thanks for reminding me! – Martin R Aug 06 '14 at 07:32
2

I'd would suggest availing yourself of enumerateByteRangesUsingBlock, because NSData no longer guarantees that the underlying data will be held in a single contiguous memory block. For example, according to the documentation for didReceiveData of the NSURLSessionDataDelegate protocol:

Because the NSData object is often pieced together from a number of different data objects, whenever possible, use NSData’s enumerateByteRangesUsingBlock: method to iterate through the data rather than using the bytes method (which flattens the NSData object into a single memory block).

Thus, for example, you could do an extension of NSOutputStream that writes the contents of a NSData:

extension NSOutputStream {

    /// Write contents of NSData to `NSOutputStream`
    ///
    /// - parameter data:   The `NSData` being written to the stream.
    ///
    /// - returns:          The number of bytes written. In case of error, returns -1.

    func writeData(data: NSData) -> Int {
        var totalBytesWritten = 0

        data.enumerateByteRangesUsingBlock() {
            buffer, range, stop in

            var bytes = UnsafePointer<UInt8>(buffer)
            var bytesWritten = 0
            var bytesLeftToWrite = range.length

            while bytesLeftToWrite > 0 {
                bytesWritten = self.write(bytes, maxLength: bytesLeftToWrite)
                if bytesWritten < 0 {
                    stop.initialize(true)
                    totalBytesWritten = -1
                    return
                }

                bytes += bytesWritten
                bytesLeftToWrite -= bytesWritten
                totalBytesWritten += bytesWritten
            }
        }

        return totalBytesWritten
    }

}

Note, the technique of stopping the enumeration in case of error, namely stop.initialize(true), requires Xcode 6 beta 4 or later. Earlier versions of Xcode (and associated compiler) used a more awkward construction for updating the boolean reference to stop the enumeration.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • BTW, I'm not suggesting you change the accepted answer, because Martin got to the root of your error message (+1 Martin). I'm just suggesting that you don't use the `data.bytes` construction, but rather enumerate through the `NSData`. – Rob Aug 01 '14 at 20:12