I do not recommend the way of mutating Data
using this:
data.replaceSubrange(0..<2, with: UnsafeBufferPointer(start: &self.crc, count: 1))
Please try this:
data.replaceSubrange(0..<2, with: &self.crc, count: 2)
It is hard to explain why, but I'll try...
In Swift, inout
parameters work in copy-in-copy-out semantics. When you write something like this:
aMethod(¶m)
Swift allocates some region of enough size to hold the content of param
,
copies param
into the region, (copy-in)
calls the method with passing the address of the region,
and when returned from the call, copies the content of the region back to param
(copy-out).
In many cases, Swift optimizes (which may happen even in -Onone
setting) the steps by just passing the actual address of param
, but it is not clearly documented.
So, when an inout
parameter is passed to the initializer of UnsafeBufferPointer
, the address received by UnsafeBufferPointer
may be pointing to a temporal region, which will be released immediately after the initializer is finished.
Thus, replaceSubrange(_:with:)
may copy the bytes in the already released region into the Data
.
I believe the first code would work in this case as crc
is a property of a struct, but if there is a simple and safe alternative, you should better avoid the unsafe way.
ADDITION for the comment of Brandon Mantzey's own answer.
data.append(UnsafeBufferPointer(start: &self.crcOfRecordData, count: 1))
Using safe in the meaning above. This is not safe, for the same reason described above.
I would write it as:
data.append(Data(bytes: &self.crcOfRecordData, count: MemoryLayout<UInt16>.size))
(Assuming the type of crcOfRecordData
as UInt16
.)
If you do not prefer creating an extra Data
instance, you can write it as:
withUnsafeBytes(of: &self.crcOfRecordData) {urbp in
data.append(urbp.baseAddress!.assumingMemoryBound(to: UInt8.self), count: MemoryLayout<UInt16>.size)
}
This is not referred in the comment, but in the meaning of above safe, the following line is not safe.
let uint32Data = Data(buffer: UnsafeBufferPointer(start: &self.someData, count: 1))
All the same reason.
I would write it as:
let uint32Data = Data(bytes: &self.someData, count: MemoryLayout<UInt32>.size)
Though, observable unexpected behavior may happen in some very limited condition and with very little probability.
Such behavior would happen only when the following two conditions are met:
Swift compiler generates non-Optimized copy-in-copy-out code
Between the very narrow period, since the temporal region is released till the append
method (or Data.init
) finishes copying the whole content, the region is modified for another use.
The condition #1 becomes true only limited cases in the current implementation of Swift.
The condition #2 happens very rarely only in the multi-threaded environment. (Though, Apple's framework uses many hidden threads as you can find in the debugger of Xcode.)
In fact, I have not seen any questions regarding the unsafe cases above, my safe may be sort of overkill.
But alternative safe codes are not so complex, are they?
In my opinion, you should better be accustomed to use all-cases-safe code.