Some of my users reported a crash. I'm not sure if I do the right think, so I show the code and the part of the crash report with the error description and maybe someone has a helpful hint for me.
UseCase: A file in the Asset Catalog contains the coordinates of several border lines of countries. There are also some strings in it. This code fragment reads the file and stores the converted data into variables.
SOMETIMES the app crashes (I receive 1 or 2 reports a week) at the last line of the code fragment (let numberOfCountries = Int(dataReadBufferUInt64)). I have a group of beta testers and they have no problem at all. So it is a frustrating issue.
I think it has something to do HOW I read the data out of the file. Maybe I have to use another way?
Any help/comment is welcomed!
DataStructure:
The Asset is a programmatically produced file. The code fragment is dealing with the header of this file.
First element is a copyright string with some informations of the version of the file, creation date etc.
The string has two elements: 1) the length of the string as a UInt16 value (range 0 ... UInt16.max) 2) the string elements (if length > 0), each "character" as one UInt16 value
Second element is the number of the countries in that file. It is a UInt64 value.
The rest of the data structure is quite complex, but is not relevant here, as the file is read sequential and IF it crashes, it crashes when reading / converting the UInt64.
There is only one version of the file "out in the wild". Every app reads this file every time the UserInterface gets build up. This is why I do not understand the OCCASIONAL crashes ...
The code fragment:
// calculate the size of a record, we use "size" as this is the number of bytes used to store on record
// other possibilities:
// .stride = number of bytes used to store one record and the added nul bytes to align to next memory bounds
// .alignment = number of bytes of alignment bounds
let sizeUInt16 : UInt64 = UInt64(MemoryLayout<UInt16>.size)
let sizeUInt64 : UInt64 = UInt64(MemoryLayout<UInt64>.size)
// get the data out of the asset catalog
if let countryBorderLineData = NSDataAsset(name: "CountryBorderLine data", bundle: Bundle.main)?.data {
// the read buffers, one for each expected data type
var dataReadBufferUInt16 : UInt16 = 0
var dataReadBufferUInt64 : UInt64 = 0
// read the string with the entry comment
// read the length of the string
(countryBorderLineData as NSData).getBytes(&dataReadBufferUInt16, range: NSRange(location: nextLocation,
length: Int(sizeUInt16) ))
// advance the pointer
nextLocation += Int(sizeUInt16)
// take the number of items we should read
var numberOfItemsToRead : Int = Int(dataReadBufferUInt16)
// check if this is not an empty string
if numberOfItemsToRead > 0 {
// target buffer of the string
var UTF16Array : [UInt16] = []
// loop to read all content
for _ in 0 ..< numberOfItemsToRead {
// read next string element
(countryBorderLineData as NSData).getBytes(&dataReadBufferUInt16, range: NSRange(location: nextLocation,
length: Int(sizeUInt16) ))
// advance the pointer
nextLocation += Int(sizeUInt16)
// append read string element to the array
UTF16Array.append(dataReadBufferUInt16)
}
// convert the read array into a string
let resultString = String(utf16CodeUnits: UTF16Array, count: UTF16Array.count)
}
// read the number of countries
(countryBorderLineData as NSData).getBytes(&dataReadBufferUInt64, range: NSRange(location: nextLocation,
length: Int(sizeUInt64) ))
// advance the pointer
nextLocation += Int(sizeUInt64)
// This line SOMETIMES crashes (see crash subset of crash report)
let numberOfCountries = Int(dataReadBufferUInt64)
...
}
This part of the crash report shows the error
Date/Time: 2019-08-28 22:00:06.5042 +0200
Launch Time: 2019-08-28 22:00:02.2638 +0200
OS Version: iPhone OS 12.4 (16G77)
Baseband Version: 1.06.02
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x8000000000000010
VM Region Info: 0x8000000000000010 is not in any region. Bytes after previous region: 9223372025580486673
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
MALLOC_NANO 0000000280000000-00000002a0000000 [512.0M] rw-/rwx SM=PRV
--->
UNUSED SPACE AT END
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [22974]
Triggered by Thread: 3
All methods of this class are called within the following GCD queue:
let myQueueForBorderLines : DispatchQueue = DispatchQueue(
label: "appName.myQueueForBorderLines", qos: .userInitiated)
The used data structures etc. are only read and managed by this class methods, so I think this is not a multithreading issue.
UPDATE
With knowing that numberOfBytesToRead
in the old code cannot be the cause of the issue, I can just show you the recommended usage of Data
as for now.
let sizeUInt16 = MemoryLayout<UInt16>.size
let sizeUInt64 = MemoryLayout<UInt64>.size
// get the data out of the asset catalog
if let countryBorderLineData = NSDataAsset(name: "CountryBorderLine data", bundle: Bundle.main)?.data {
var nextLocation = 0
// the read buffers, one for each expected data type
var dataReadBufferUInt16: UInt16 = 0
var dataReadBufferUInt64: UInt64 = 0
// read the string with the entry comment
// read the length of the string
_ = withUnsafeMutableBytes(of: &dataReadBufferUInt16) {bufPtr in
countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
}
// advance the pointer
nextLocation += Int(sizeUInt16)
// take the number of items we should read
let numberOfItemsToRead = Int(dataReadBufferUInt16)
// check if this is not an empty string
if numberOfItemsToRead > 0 {
// target buffer of the string
var utf16Array: [UInt16] = Array(repeating: 0, count: numberOfItemsToRead)
utf16Array.withUnsafeMutableBufferPointer {bufPtr in
countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
}
// advance the pointer
nextLocation += numberOfItemsToRead * sizeUInt16
// convert the read array into a string
let resultString = String(utf16CodeUnits: utf16Array, count: utf16Array.count)
print(resultString)
}
// read the number of countries
_ = withUnsafeMutableBytes(of: &dataReadBufferUInt64) {bufPtr in
countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
}
// advance the pointer
nextLocation += sizeUInt64
// This line SOMETIMES crashes (see crash subset of crash report)
let numberOfCountries = Int(dataReadBufferUInt64)
//...
}
The implementation of Swift.Data
has changed recently, so it might be causing some issues, but the possibility is not high (little more than very unlikely ).
If your code runs in the multithreaded context, that may lead SOMETIMES crashes .
Anyway, when you show more context, I will check it and update my answer again.
OLD ANSWER
It depends on how your is organized (you should better show the spec of the NSDataAsset), but your code consumes numberOfBytesToRead * sizeUInt16
bytes, with this loop:
// loop to read all content
for _ in 0 ..< numberOfBytesToRead {
//...
// advance the pointer
nextLocation += Int(sizeUInt16)
//...
}
After this loop, nextLocation
may be pointing some unknown position, which may: - be exceeding the valid range of your countryBorderLineData
- cause Int(dataReadBufferUInt64)
to overflow - ...
And looping through each UTF-16 code point is not an efficient way to read UTF-16 String.
I would re-write your code as:
let sizeUInt16 = MemoryLayout<UInt16>.size
let sizeUInt64 = MemoryLayout<UInt64>.size
if let countryBorderLineData = data {
var nextLocation = 0
// the read buffers, one for each expected data type
var dataReadBufferUInt16: UInt16 = 0
var dataReadBufferUInt64: UInt64 = 0
// read the string with the entry comment
// read the length of the string
_ = withUnsafeMutableBytes(of: &dataReadBufferUInt16) {bufPtr in
countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
}
// advance the pointer
nextLocation += sizeUInt16
// take the number of bytes we should read
let numberOfBytesToRead = Int(dataReadBufferUInt16)
// check if this is not an empty string
if numberOfBytesToRead > 0 {
assert(numberOfBytesToRead.isMultiple(of: sizeUInt16))
// target buffer of the string
var utf16Array: [UInt16] = Array(repeating: 0, count: numberOfBytesToRead/sizeUInt16)
utf16Array.withUnsafeMutableBufferPointer {bufPtr in
countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
}
// advance the pointer
nextLocation += numberOfBytesToRead
// convert the read array into a string
let resultString = String(utf16CodeUnits: utf16Array, count: utf16Array.count)
print(resultString)
}
// read the number of countries
_ = withUnsafeMutableBytes(of: &dataReadBufferUInt64) {bufPtr in
countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
}
// advance the pointer
nextLocation += sizeUInt64
// This line SOMETIMES crashes (see crash subset of crash report)
let numberOfCountries = Int(dataReadBufferUInt64)
//...
}
My guess might be wrong and the code above would not solve your issue, in such cases, please show more info about your data and I can correct my answer.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.