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 NS
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, useNSData
’senumerateByteRangesUsingBlock:
method to iterate through the data rather than using thebytes
method (which flattens theNSData
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.
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
// ...
}