Error (“'()' is not identical to 'UInt8'”) writing NSData bytes to NSOutputStream using the write function in Swift
Question
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)%")
}
}
Solution
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
// ...
}
OTHER TIPS
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.