【问题标题】:Convert Data into UnsafeRawPointer and vice versa in swift快速将数据转换为 UnsafeRawPointer,反之亦然
【发布时间】:2020-10-04 17:46:23
【问题描述】:

我一直在开发一个使用 Pencil Kit 的应用程序,我正在尝试将画布上的绘图存储到 sqlite3 数据库中。为此,我必须将绘图(类型:数据)转换为 UnsafeRawPointer。但是,转换后,当我尝试通过指针访问(打印)图形时,它返回 0 字节而不是 42 字节。我在下面的代码中添加了一些打印语句以及它们返回的内容,希望对您有所帮助。

 // Function that converts drawing data to UnsafeRawPointer
 func dataToPtr(drawing: Data) -> UnsafeRawPointer {
     
     let nsData = drawing as NSData
     print(nsData) // shows 42 bytes

     let rawPtr = nsData.bytes
     print(rawPtr.load(as: Data.self))// Shows 0 bytes
     return rawPtr
 }

// Drawing before conversion
print(canvas.drawing) // Prints: 42 bytes

let drawingPtr = dataToPtr(drawing: canvas.drawing)

// Drawing when accessing the pointer
print(drawingPtr.load(as: Data.self)) // shows 0 bytes

我是制作 iOS 应用程序的初学者,很难快速理解指针。提前谢谢你。

编辑:保存绘图方法:

func save(canvas: Canvas) {
    connect()
    
    // prepare
    var statement: OpaquePointer!
    
    // update the drawing given the row id
    if sqlite3_prepare_v2(database, "UPDATE drawings SET drawing = ? WHERE rowid = ?", -1, &statement, nil) != SQLITE_OK {
        print("Could not create (update) query")
    }
    
    // bind place holders
    print("DRAWING SAVED: \(canvas.drawing)") // shows 42 bytes
    let drawingPtr = dataToPtr(drawing: canvas.drawing)

    sqlite3_bind_blob(statement, 1, drawingPtr, -1, nil)
    sqlite3_bind_int(statement, 2, Int32(canvas.id))
    
    // execute
    if sqlite3_step(statement) != SQLITE_DONE {
        print("Could not execute update statement")
    }
    
    // finalise
    sqlite3_finalize(statement)
}

我想使用 .load() 将指针转换为数据的方法:

// Function to check if canvas for a certain date is already in the database, if exists, return canvas
func check(selectedDate: Date) -> [Canvas] {
    connect()
    
    var result: [Canvas] = []

    // prepare
    var statement: OpaquePointer!

    if sqlite3_prepare_v2(database, "SELECT rowid, date, drawing FROM drawings WHERE date = ?", -1, &statement, nil) != SQLITE_OK {
        print("Could not create (select) query")
        return []
    }
    
    // bind
    sqlite3_bind_text(statement, 1, NSString(string:dateToStringFormat(dateDate: selectedDate)).utf8String, -1, nil)
    
    // executes
    while sqlite3_step(statement) == SQLITE_ROW {
        
        // change string date into Date date
        let Date_date = stringToDateFormat(stringDate: String(cString: sqlite3_column_text(statement, 1)))
        // if canvas is not empty
        if sqlite3_column_blob(statement, 2) != nil {
            let drawingPtr = sqlite3_column_blob(statement, 2)
            
            result.append(Canvas(id: Int(sqlite3_column_int(statement, 0)), date: Date_date, drawing: drawingPtr!.load(as: Data.self)))
            print("DRAWING NOT NIL")
        }
        else {
            let drawing = Data.init()
            result.append(Canvas(id: Int(sqlite3_column_int(statement, 0)), date: Date_date, drawing: drawing))
            print("DRAWING IS NIL")
        }
    }
    // finalise
    sqlite3_finalize(statement)
    
    return result
}

【问题讨论】:

  • 请显示您的画布类型和绘图声明。 canvasView.drawing 属性应该返回 PKDrawing 而不是 Data
  • 如果你需要保存一个PKDrawing你需要使用它的dataRepresentation方法。 canvas.drawing.dataRepresentation()
  • 嗨,对不起,我没有说清楚。我已经在绘图上执行了 .dataRepresentation,所以它应该有数据类型。
  • 您在此处编写的代码是未定义的行为。您可能错误地解决了问题。我强烈怀疑您在通话中只需要& 而不是您正在做的事情,但您可能需要withUnsafeBytes。你能显示你正在尝试进行的 sqlite3 调用吗? (具体来说,函数签名是什么?)
  • (关键是您实际上不会将 Data 转换为 UnsafeRawPointer。相反,您将让 Data 使 UnsafeRawPointer 可用于传递给函数。我知道这听起来很细微的差别,但它是 Swift 管理内存的一个非常重要的差别。你不能只使用 UnsafeRawPointers。除非你使用正确的工具,否则它们指向的东西可能随时消失。)

标签: ios swift sqlite unsafe-pointers pencilkit


【解决方案1】:

你需要在这里做的是将你的函数体包裹在一个withUnsafeBytes:

func save(canvas: Canvas) {
    connect()

    let drawingData = canvas.drawing.dataRepresentation()

    drawingData.withUnsafeBytes { drawingBuffer in

        let drawingPtr = drawingBuffer.baseAddress!

        // ... In here you can use drawingPtr, for example:

        sqlite3_bind_blob(statement, 1, drawingPtr, Int32(drawingBuffer.count), nil)

        // ...
    }
}

withUnsafeBytes 块内部,您不得引用drawingData 本身。块外,千万不要引用drawingPtr

withUnsafeBytes 的意义在于,它确保了字节的连续表示(如果需要,进行复制),然后为您提供指向在块期间有效的那些字节的指针。该指针在块外无效。您不得将其退回或让它逃逸。但在块内,您可以将其用作void *。这意味着您必须确保 sqlite3 不会将 drawingPtr 存储在此块的末尾之后,这就是为什么您必须将 withUnsafeBytes 放在整个准备/完成序列周围,而不仅仅是 bind_blob 语句。

通常,您不能将 UnsafeRawPointers 传递给您自己没有分配的东西。当你认为它确实存在时,他们所指向的东西并不能保证会继续存在。在 Data 的情况下,甚至没有保证它代表单个内存块(例如,Data 可以从 dispatch_data 支持)。您访问数据字节的方式是使用withUnsafeBytes

您的check 函数也有一些错误。首先,您的 NSString 转换是不必要的。这一行:

sqlite3_bind_text(statement, 1, NSString(string:dateToStringFormat(dateDate: selectedDate)).utf8String, -1, nil)

可以写成:

sqlite3_bind_text(statement, 1, dateToStringFormat(dateDate: selectedDate), -1, nil)

Swift will automatically convert String to C-string when passed to a C function that takes a char *.

此代码完全错误,可能是您得到零字节的原因:

let drawingPtr = sqlite3_column_blob(statement, 2)
result.append(Canvas(id: Int(sqlite3_column_int(statement, 0)), date: Date_date, drawing: drawingPtr!.load(as: Data.self)))

指向 Blob 的指针不是数据。你不能这样load。你需要知道它有多长。这是您需要的代码:

// Get the pointer
let drawingPtr = sqlite3_column_blob(statement, 2)!

// Get the length
let drawingLength = Int(sqlite3_column_bytes(statement, 2))

// Copy the bytes into a new Data
let drawing = Data(bytes: drawingPtr, count: drawingLength)

// Now construct your Canvas.
result.append(Canvas(id: Int(sqlite3_column_int(statement, 0)), date: Date_date, drawing: drawing))

【讨论】:

  • drawing 返回 PKDrawing 而不是 Data。你的意思是canvas.drawing.dataRepresentation().withUnsafeBytes { drawingPtr in
  • 我在回答中提到的最重要的部分是使用 Data 初始化程序而不是 load(as: Data.self) 并使用 sqlite3_column_bytes 来获取我上面评论的 bob 大小。
  • @RobNapier 谢谢你的详细解释!按照您的建议,我已将 save 方法包装在 withUnsafeBytes 中,但遇到了问题。该行弹出错误: "sqlite3_bind_blob(statement, 1, drawingPtr, -1, nil)" 。错误是:“无法将'UnsafeRawBufferPointer'类型的值转换为预期的参数类型'UnsafeRawPointer?'”无论如何我都找不到将UnsafeRawBufferPointer转换为UnsafeRawPointer。您对如何解决此错误有任何建议吗?再次感谢您。
  • 我很抱歉;我想我比我必须更仔细地检查了这段代码。请参阅我将缓冲区解包为指针的编辑:let drawingPtr = drawingBuffer.baseAddress!“缓冲区指针”只是将指针和长度组合到一个集合中,这通常更易于使用。
  • @RobNapier 感谢您的解释,修复了错误消息。但是,这并没有解决我保存绘图的问题。在上面的检查方法中,第二个 if 语句中的“sqlite3_column_blob(statement, 2)”总是返回 nil,因此代码实际上不会尝试加载数据。这样做有什么原因吗?
猜你喜欢
  • 1970-01-01
  • 2021-11-23
  • 2010-12-03
  • 2011-10-08
  • 2013-03-15
  • 2019-09-19
  • 2013-12-29
  • 2011-09-22
  • 2012-05-14
相关资源
最近更新 更多