12

The solution of this question no longer works with Swift 3.

There is no longer a property bytes of Data (formerly NSData.

let data = dataToWrite.first!
self.outputStream.write(&data, maxLength: data.count)

With this code, I get the error:

Cannot convert value of type 'Data' to expected argument type 'UInt8'

How can you write Data to an NSOutputStream in Swift 3?

Community
  • 1
  • 1
ahyattdev
  • 529
  • 1
  • 6
  • 18

4 Answers4

17

NSData had a bytes property to access the bytes. The new Data value type in Swift 3 has a withUnsafeBytes() method instead, which calls a closure with a pointer to the bytes.

So this is how you write Data to an NSOutputStream (without casting to NSData):

let data = ... // a Data value
let bytesWritten = data.withUnsafeBytes { outputStream.write($0, maxLength: data.count) }

Remarks: withUnsafeBytes() is a generic method:

/// Access the bytes in the data.
///
/// - warning: The byte pointer argument should not be stored and used outside of the lifetime of the call to the closure.
public func withUnsafeBytes<ResultType, ContentType>(_ body: @noescape (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType

In the above call, both ContentType and ResultType are automatically inferred by the compiler (as UInt8 and Int), making additional UnsafePointer() conversions unnecessary.

outputStream.write() returns the number of bytes actually written. Generally, you should check that value. It can be -1 if the write operation failed, or less than data.count when writing to sockets, pipes, or other objects with a flow control.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
13

Just use this extension:

Swift 5

extension OutputStream {
  func write(data: Data) -> Int {
    return data.withUnsafeBytes {
      write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: data.count)
    }
  }
}

And for InputStream

extension InputStream {
  func read(data: inout Data) -> Int {
    return data.withUnsafeMutableBytes {
      read($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: data.count)
    }
  }
}

Swift 4

extension OutputStream {
  func write(data: Data) -> Int {
    return data.withUnsafeBytes { write($0, maxLength: data.count) }
  }
}
extension InputStream {
  func read(data: inout Data) -> Int {
    return data.withUnsafeMutableBytes { read($0, maxLength: data.count) }
  }
}
Dmitry Kozlov
  • 1,115
  • 10
  • 14
  • I am having some similar problem and tried to use both the "extension OutputStream" and the code "let bytesWritten = data.withUnsafeBytes { outputStream.write...." mentioned earlier. In both case I get -1 bytes written. I must be doing something wrong. – Michel Jan 16 '19 at 08:13
  • @Michel Looks like there is something wrong with your connection – Dmitry Kozlov Feb 24 '19 at 09:35
  • The compile shows this error on the input stream extension "Overlapping accesses to 'data', but modification requires exclusive access; consider copying to a local variable" – Mark Bridges Jul 07 '21 at 21:23
  • @MarkBridges get the count outside of the return closure. that way theres no race condition as far as the compiler is concerned ``` let count = data.count return data.... ``` – tonyl7126 Sep 01 '21 at 21:00
8

Martin R, thank you for your answer. That was a foundation for a complete solution. Here it is:

extension OutputStream {

    /// Write String to outputStream
    ///
    /// - parameter string:                The string to write.
    /// - parameter encoding:              The String.Encoding to use when writing the string. This will default to UTF8.
    /// - parameter allowLossyConversion:  Whether to permit lossy conversion when writing the string.
    ///
    /// - returns:                         Return total number of bytes written upon success. Return -1 upon failure.

    func write(_ string: String, encoding: String.Encoding = String.Encoding.utf8, allowLossyConversion: Bool = true) -> Int {
        if let data = string.data(using: encoding, allowLossyConversion: allowLossyConversion) {
            var bytesRemaining = data.count
            var totalBytesWritten = 0

            while bytesRemaining > 0 {
                let bytesWritten = data.withUnsafeBytes {
                    self.write(
                        $0.advanced(by: totalBytesWritten),
                        maxLength: bytesRemaining
                    )
                }
                if bytesWritten < 0 {
                    // "Can not OutputStream.write(): \(self.streamError?.localizedDescription)"
                    return -1
                } else if bytesWritten == 0 {
                    // "OutputStream.write() returned 0"
                    return totalBytesWritten
                }

                bytesRemaining -= bytesWritten
                totalBytesWritten += bytesWritten
            }

            return totalBytesWritten
        }

        return -1
    }
}
Sergey M
  • 521
  • 4
  • 17
0

Data and NSData are two separate classes in Swift 3, and Data does not have the bytes property.

The solution is to define data as being of type NSData

let data: NSData = dataToWrite.first!
self.outputStream.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)

According to Migrating to Swift 2.3 or Swift 3 from Swift 2.2:

The migrator will convert most uses of NSData to the new value type Data. However, there are certain methods on NSData that operate on UnsafeMutablePointer, while the corresponding methods on Data use UnsafeMutablePointer. (For example, NSData.getBytes(:length:) is more accepting than Data.copyBytes(:length:).) As a reminder, the in-memory layout of Swift types is not guaranteed.

ahyattdev
  • 529
  • 1
  • 6
  • 18