withUnsafeBytes deprecated, is this correct usage?

Hi,


I have a network stream which I want to send data. I had an original few lines (copied from an example) which seemed to do the job, but gave the Xcode warning. After a couple of days I'm seemingly going round in circles, and down the deadend of empty documentation ('No overview available' on withUnsafeBytes).


Is my refactored verison correct? Both versions of 'write' work, as in the remote endpoint gets the data. But..


1. in the second version, withUnsafeBytes should be a throwing function so I presume that I'm not actually using the correct method?

2. in the first version the Int is returned by .write(), but this was never returned in the second version. What am I not understanding?



func sendMessage(msg: String) {
       
        if var data = msg.data(using: .utf8) {
           
            // original
            // WARNING: 'withUnsafeBytes' is deprecated: use `withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) rethrows -> R` instead
            let dataSent = data.withUnsafeBytes({
                self.outputStream?.write($0, maxLength: data.count)
            })
            print("dataSent: \(String(describing: dataSent))")
           
            // refactored
            // pointer type chicanery, and method signatures from the Apple docs:
            // Data: func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType
            // OutputStream: func write(_ buffer: UnsafePointer, maxLength len: Int) -> Int
            data.withUnsafeBytes({
                let unsafeBufferPtr = $0.bindMemory(to: UInt8.self)
                if let unsafePtr = unsafeBufferPtr.baseAddress {
                    let dataSent2 = self.outputStream?.write(unsafePtr, maxLength: $0.count)
                    print("dataSent2: \(String(describing: dataSent2))")
                }
            })
        }
    }

thanks,

Accepted Reply

1. in the second version, withUnsafeBytes should be a throwing function so I presume that I'm not actually using the correct method?


You are mistaking something. Both `withUnsafeBytes` are declared as `rethrows`.

Old

public func withUnsafeBytes<resulttype, contenttype="">(_ body: (UnsafePointer) throws -> ResultType) rethrows -> ResultType

New

@inlinable public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType

No change about throwing. So, throwing cannot be a clue to judge if you are using the correct method or not.


2. in the first version the Int is returned by .write(), but this was never returned in the second version. What am I not understanding?

In your first version, you have only one expression inside the closure passed to `withUnsafeBytes`. So swift applies implicit return feature.


The second version, you have one declaration and one if-statement. You may need to put an explicit return statement in such a case.


You may not be understanding the implicit return feature of Swift closures.

Please check the part Implicit Returns from Single-Expression Closures of the Swift book in Closure Expressions carefully.

Replies

1. in the second version, withUnsafeBytes should be a throwing function so I presume that I'm not actually using the correct method?


You are mistaking something. Both `withUnsafeBytes` are declared as `rethrows`.

Old

public func withUnsafeBytes<resulttype, contenttype="">(_ body: (UnsafePointer) throws -> ResultType) rethrows -> ResultType

New

@inlinable public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType

No change about throwing. So, throwing cannot be a clue to judge if you are using the correct method or not.


2. in the first version the Int is returned by .write(), but this was never returned in the second version. What am I not understanding?

In your first version, you have only one expression inside the closure passed to `withUnsafeBytes`. So swift applies implicit return feature.


The second version, you have one declaration and one if-statement. You may need to put an explicit return statement in such a case.


You may not be understanding the implicit return feature of Swift closures.

Please check the part Implicit Returns from Single-Expression Closures of the Swift book in Closure Expressions carefully.

Ah yes, I didn't understand enough about 'more than simple' closures. So I re-read, and I think this is now better (in the sense of more correct, and safer). Still verbose to be clear.


I think I was also mis-understanding that when a method signature (say, withUnsafeBytes) contains a closure that 'throws', it means that the closure may throw and not that it must throw. By which I mean that if the closure omits any 'throw' expression, then that's not a violation?


I also now get the implicit one-liner return value from a closure, which I no longer have with all the pointer type assignments.


// refactor 3
do {
    // explicit type for dataSent4 because the return type cannot be inferred from the closure
    let dataSent4: Int? = try data.withUnsafeBytes({
       
        // we need to re-bind with a typed UInt8 buffer pointer, from UnsafeRawBufferPointer
        let unsafeUInt8BufferPtr = $0.bindMemory(to: UInt8.self)
       
        // now we need a simple UInt8 pointer for outputStream.write(), not a buffer pointer
        if let unsafeUInt8Ptr = unsafeUInt8BufferPtr.baseAddress {
           
            if let count = self.outputStream?.write(unsafeUInt8Ptr, maxLength: unsafeUInt8BufferPtr.count) {
                if count > 0 {
                    // data was sent
                    return count
                }
            }
        }
        throw MessageError.sending
    })
    print("dataSent4: \(String(describing: dataSent4))")
}
catch MessageError.sending {
    print("sending exception")
}
catch {
    print("unhandled exception")
}


thanks,

This is trickier than it should be because the new unsafe API for

Data
interacts badly with the old API used by
Stream
. Specifically, the stream read and write APIs should work in terms of
Unsafe[Mutable]RawPointer
, not
Unsafe[Mutable]Pointer<UInt8>
.

I’m pretty sure I filed a bug about that a while ago — oh, there it is, (r. 37108374). Until that’s fixed, the best way to work around the problem is to extend stream to read and write

Data
directly. Pasted in below is the code I use for that. It’s a little complex, but most of that complexity arises because I’ve implemented a general-case solution.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
extension InputStream {

    func read(buffer: UnsafeMutableRawBufferPointer) throws -> Int {
        // This check ensures that `baseAddress` will never be `nil`.
        guard !buffer.isEmpty else { return 0 }
        let bytesRead = self.read(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count)
        if bytesRead < 0 {
            throw self.guaranteedStreamError
        }
        return bytesRead
    }

    func read(maxLength: Int) throws -> Data {
        precondition(maxLength >= 0)
        var data = Data(repeating: 0, count: maxLength)
        let bytesRead = try data.withUnsafeMutableBytes { buffer -> Int in
            try self.read(buffer: buffer)
        }
        data.count = bytesRead
        return data
    }
}

extension OutputStream {

    func write(buffer: UnsafeRawBufferPointer) throws -> Int {
        // This check ensures that `baseAddress` will never be `nil`.
        guard !buffer.isEmpty else { return 0 }
        let bytesWritten = self.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count)
        if bytesWritten < 0 {
            throw self.guaranteedStreamError
        }
        return bytesWritten
    }

    func write(data: Data) throws -> Int {
        return try data.withUnsafeBytes { buffer -> Int in
            try self.write(buffer: buffer)
        }
    }
}

extension Stream {
    var guaranteedStreamError: Error {
        if let error = self.streamError {
            return error
        }
        // If this fires, the stream read or write indicated an error but the
        // stream didn’t record that error.  This is definitely a bug in the
        // stream implementation, and we want to know about it in our Debug
        // build. However, there’s no reason to crash the entire process in a
        // Release build, so in that case we just return a dummy error.
        assert(false)
        return NSError(domain: NSPOSIXErrorDomain, code: Int(ENOTTY), userInfo: nil)
    }
}

Oh, one more thing…

If the main reason you’re using

Stream
is for TCP[+TLS] networking, you should take a look at the Network framework. It has really good Swift support.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yes, a tcp/tls stream.


And, yes, Network, of course! I just found the WWDC18 presentation on Network.framework (audio missing minutes 15-17) https://developer.apple.com/videos/play/wwdc2018/715/, for anyone else stumbling here through withUnsafeBytes 🙂


thanks both for the help. both very helpful. I'll mark OOPer's reply as the answer as it did solve my immedate question/problem. But Network is the way to go, for sure, to avoid such questions/problems in the first place 🙂