1

I got this code here from an app I am working on. I inherited this code when the BLE guy left the team. I am not good with low level stuff and Data stuff. I am UI/UX front end person, and now I do need to get my hands dirty. This code is now a bit old and using deprecated code. I have unsuccessfully been trying to silence the warning, but I keep ending up with he same code or with errors.

This is the code that generates the warning. On the return line when using withUnsafeBytes

extension Data {
    func scanValueFromData<T>(start: Int = 0, invalid: T) -> (T, Int) {
        let length = MemoryLayout<T>.size
        guard self.count >= start + length else {
            return (invalid, start+length)
        }
        return (self.subdata(in: start..<start+length).withUnsafeBytes{ $0.pointee }, start+length)
    }
}

This method is used to decode a byte array to a struct. I get the data from a BLE service and the various vars are packed into an array of bytes.

If any one as a fix for this or a better way to do id.

Pascale Beaulac
  • 889
  • 10
  • 28
  • 2
    https://stackoverflow.com/questions/55378409/swift-5-0-withunsafebytes-is-deprecated-use-withunsafebytesr ? The part `{ $0.pointee }` needs to be updated. "load as T"? – Larme Jun 09 '21 at 15:51

1 Answers1

0

The version of withUnsafeBytes that's deprecated here is the version which binds the underlying pointer to a known type (Data.withUnsafeBytes<R, T>(_ body: (UnsafePointer<T>) throws -> R) rethrows -> R).

The preferred replacement is the version which does not bind in this way, and returns a raw buffer pointer (withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R); luckily, transitioning between these only changes how you read from the pointer:

extension Data {
    func scanValueFromData<T>(start: Int = 0, invalid: T) -> (T, Int) {
        let length = MemoryLayout<T>.size
        guard self.count >= start + length else {
            return (invalid, start + length)
        }
    
        return (self.subdata(in: start ..< start + length).withUnsafeBytes { $0.load(as: T.self) }, start + length)
    }
}

Using UnsafeRawBufferPointer.load(as:) you can safely read a trivial type T from the buffer. (Note that this method is not safe to call for non-trivial types, but this was true of the original version of the method too.)

If you want to simplify even a bit further, you can avoid repeating start + length:

func scanValueFromData<T>(start: Int = 0, invalid: T) -> (T, Int) {
    let length = MemoryLayout<T>.size
    var value = invalid
    if count >= start + length {
        value = subdata(in: start ..< start + length).withUnsafeBytes { $0.load(as: T.self) }
    }

    return (value, start + length)
}

or even shorter, at the cost of readability:

func scanValueFromData<T>(start: Int = 0, invalid: T) -> (T, Int) {
    let length = MemoryLayout<T>.size
    let value = count >= start + length ? subdata(in: start ..< start + length).withUnsafeBytes { $0.load(as: T.self) } : invalid
    return (value, start + length)
}
Itai Ferber
  • 28,308
  • 5
  • 77
  • 83
  • 1
    @LeoDabus True, but I wanted to keep the code change as minimal as possible. I can include a simplified version too. – Itai Ferber Jun 09 '21 at 16:17
  • IMO the size check shouldn't be part of the method. no need to use MemoryLayout `extension Data {` `func object(at index: Index = 0) -> T {` `subdata(in: index.. – Leo Dabus Jun 09 '21 at 16:23
  • @LeoDabus The issue is with `Data` not large enough to load a value out of. `Data([]).object() as UInt16` will fatal-error with an out-of-bounds message. The check allows you to default more gracefully to another value – Itai Ferber Jun 09 '21 at 16:31
  • if you use `load(fromByteOffset: start)` without using subdata method you might get a misaligned error. **Swift/UnsafeRawPointer.swift:354: Fatal error: load from misaligned raw pointer** – Leo Dabus Jun 09 '21 at 16:31
  • The same would happen if you try to get an element from your collection using subscript with an invalid index. I think the check should be out of the method – Leo Dabus Jun 09 '21 at 16:36
  • @LeoDabus Sure, but this is in keeping with the original spirit of the question, not a suggestion to refactor all code paths to do this. – Itai Ferber Jun 09 '21 at 16:38
  • 1
    In any case, you're right — back to the subdata suggestion since it maintains compatibility. – Itai Ferber Jun 09 '21 at 16:38