簡體   English   中英

從 NSDataAsset 讀取時偶爾崩潰

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM