[英]Occasional crashes while read from NSDataAsset
我的一些用戶報告了崩潰。 我不確定我的想法是否正確,所以我顯示了代碼和帶有錯誤描述的崩潰報告部分,也許有人對我有幫助。
用例:資產目錄中的文件包含多個國家/地區邊界線的坐標。 里面還有一些字符串。 此代碼片段讀取文件並將轉換后的數據存儲到變量中。
有時應用程序會在代碼片段的最后一行崩潰(我每周收到 1 或 2 個報告)(讓 numberOfCountries = Int(dataReadBufferUInt64))。 我有一組 beta 測試人員,他們完全沒有問題。 所以這是一個令人沮喪的問題。
我認為這與我如何從文件中讀取數據有關。 也許我必須使用另一種方式?
歡迎任何幫助/評論!
數據結構:
資產是以編程方式生成的文件。 代碼片段正在處理此文件的標頭。
第一個元素是版權字符串,其中包含文件版本、創建日期等一些信息。
字符串有兩個元素:1)字符串的長度作為 UInt16 值(范圍 0 ... UInt16.max) 2)字符串元素(如果長度 > 0),每個“字符”作為一個 UInt16 值
第二個元素是該文件中的國家/地區數。 它是一個 UInt64 值。
其余的數據結構相當復雜,但在這里不相關,因為文件是按順序讀取的,如果它崩潰,它會在讀取/轉換 UInt64 時崩潰。
只有一個版本的文件“out in the wild”。 每次用戶界面建立時,每個應用程序都會讀取此文件。 這就是為什么我不明白偶爾崩潰的原因......
代碼片段:
// 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)
...
}
崩潰報告的這部分顯示錯誤
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
此類的所有方法都在以下 GCD 隊列中調用:
let myQueueForBorderLines : DispatchQueue = DispatchQueue(
label: "appName.myQueueForBorderLines", qos: .userInitiated)
使用的數據結構等僅由此類方法讀取和管理,因此我認為這不是多線程問題。
更新
知道舊代碼中的numberOfBytesToRead
不會是問題的原因,我現在可以向您展示Data
的推薦用法。
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)
//...
}
Swift.Data
的實現最近發生了變化,所以它可能會導致一些問題,但可能性不高(幾乎不太可能)。
如果您的代碼在多線程上下文中運行,則可能會導致有時崩潰。
無論如何,當您顯示更多上下文時,我會檢查它並再次更新我的答案。
舊答案
這取決於您的組織方式(您應該更好地顯示 NSDataAsset 的規范),但是您的代碼使用numberOfBytesToRead * sizeUInt16
字節,此循環:
// loop to read all content
for _ in 0 ..< numberOfBytesToRead {
//...
// advance the pointer
nextLocation += Int(sizeUInt16)
//...
}
在此循環之后, nextLocation
可能指向某個未知位置,這可能: - 超出countryBorderLineData
的有效范圍 - 導致Int(dataReadBufferUInt64)
溢出 - ...
並且循環遍歷每個 UTF-16 代碼點並不是讀取 UTF-16 字符串的有效方法。
我會將您的代碼重新編寫為:
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)
//...
}
我的猜測可能是錯誤的,上面的代碼不能解決您的問題,在這種情況下,請顯示有關您的數據的更多信息,我可以更正我的答案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.