Может кто-нибудь помочь мне понять, как сохранить и извлечь Dimension
из SQLite в Swift? Dimension
соответствует NSSecureCoding
, поэтому я полагаю, что смогу сериализовать его.
Здесь я сохраняю измерение (похоже, работает)
func add(value: Int, unit: Dimension) throws -> SQLMeasurement {
let insertSql = "INSERT INTO Measurements (value, unit) VALUES (?,?);"
let insertStatement = try database.prepareStatement(sql: insertSql)
guard sqlite3_bind_int(insertStatement, 1, Int32(value)) == SQLITE_OK else { fatalError() }
guard let dimensionData = try? NSKeyedArchiver.archivedData(withRootObject: unit, requiringSecureCoding: false) else { fatalError() }
print(dimensionData.base64EncodedString())
let bindResult = sqlite3_bind_blob(insertStatement, 2, dimensionData.base64EncodedString(), Int32(dimensionData.count), SQLITE_TRANSIENT)
let result = sqlite3_step(insertStatement)
let lastId = database.lastId()
sqlite3_finalize(insertStatement)
return SQLMeasurement(id: lastId, db: database)
}
Вот где Я должен извлечь это. (Сбой с кодом ошибки:)
func unit() -> Dimension {
let sql = "SELECT unit FROM Measurements WHERE id = ?"
let queryStatement = try! db.prepareStatement(sql: sql) //TODO: Remove Bang!
guard sqlite3_bind_int(queryStatement, 1, Int32(id)) == SQLITE_OK else { fatalError() }
guard sqlite3_step(queryStatement) == SQLITE_ROW else { fatalError() }
guard let dataBlob = sqlite3_column_blob(queryStatement, 0) else { fatalError() }
let dataBlobLength = sqlite3_column_bytes(queryStatement, 0)
let data = Data(bytes: dataBlob, count: Int(dataBlobLength))
do {
let result = try NSKeyedUnarchiver.unarchivedObject(ofClass: Dimension.self, from: data)
return result!
}
catch { print("unarchive error: \(error)") }
//let dimension2 = dimension(data: data)
sqlite3_finalize(queryStatement)
return UnitPower.kilowatts //FIXME:
}
Вот код ошибки из запроса разархивирования:
unarchive error: Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver _initForReadingFromData:error:throwLegacyExceptions:]: incomprehensible archive version (-1)" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver _initForReadingFromData:error:throwLegacyExceptions:]: incomprehensible archive version (-1)}
Вот ошибка при разархивировании:
Вот база данных sqlite, подтверждающая сохранение как работающее:
Эта игровая площадка работает:
import UIKit
func dimension4(string64: String) -> Dimension {
guard let data = Data(base64Encoded: string64) else { fatalError() }
guard let dimension2 = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Dimension else { fatalError() }
return dimension2
}
func dimension(data: Data) -> Dimension {
guard let dimension2 = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Dimension else { fatalError() }
return dimension2
}
let dimension = UnitMass.grams
guard let dimensionData = try? NSKeyedArchiver.archivedData(withRootObject: dimension, requiringSecureCoding: false) else { fatalError() }
let dimensionFromData = dimension(data: dimensionData)
print("dimensionFromData: \(dimensionFromData.description)")
let stringData = dimensionData.base64EncodedString()
let dimensionFromString64 = dimension4(string64: stringData)
print("dimensionFromString64: \(dimensionFromString64.description)")
ФИНАЛЬНЫЙ ОТВЕТ
import Foundation
import SQLite3
class SQLMeasurements
{
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) //FIXME: Why do i need this to work?
private let database: SQLiteDatabase
var index: Int = 1 //SQL indexes start at 1...TODO: violating the mutability principle
init(database: SQLiteDatabase) {
self.database = database
let sql = """
CREATE TABLE Measurements(
id INTEGER PRIMARY KEY AUTOINCREMENT,
value Number(6) NOT NULL,
unit BLOB NOT NULL
);
"""
do { try database.createTable(sql: sql) }
catch { print(database.errorMessage) }
}
func add(value: Int, unit: Dimension) throws -> SQLMeasurement {
let sql = "INSERT INTO Measurements (value, unit) VALUES (?,?);"
let insertStatement = try database.prepareStatement(sql: sql)
defer { sqlite3_finalize(insertStatement) } //Free Memory
guard sqlite3_bind_int(insertStatement, 1, Int32(value)) == SQLITE_OK else { fatalError() }
guard let dimensionData = try? NSKeyedArchiver.archivedData(withRootObject: unit, requiringSecureCoding: false) else { fatalError() }
let rc = dimensionData.withUnsafeBytes {
sqlite3_bind_blob(insertStatement, 2, $0.baseAddress, Int32(dimensionData.count), SQLITE_TRANSIENT)
}
guard rc == SQLITE_OK else { fatalError() }
guard sqlite3_step(insertStatement) == SQLITE_DONE else { fatalError() }
let lastId = database.lastId()
return SQLMeasurement(id: lastId, db: database)
}
///
import Foundation
import SQLite3
class SQLMeasurement
{
private let db: SQLiteDatabase
let id: Int
init(id: Int, db: SQLiteDatabase) { //FIXME: Construct w value and unit
self.db = db
self.id = id
}
func value() -> Int {
let sql = "SELECT value FROM Measurements WHERE id = ?"
let queryStatement = try! db.prepareStatement(sql: sql) //TODO: Remove Bang!
guard sqlite3_bind_int(queryStatement, 1, Int32(id)) == SQLITE_OK else { fatalError() }
guard sqlite3_step(queryStatement) == SQLITE_ROW else { fatalError() }
let queryResultCol1 = sqlite3_column_int(queryStatement, 0)
sqlite3_finalize(queryStatement)
return Int(queryResultCol1)
}
func unit() -> Dimension {
let sql = "SELECT unit FROM Measurements WHERE id = ?"
guard let queryStatement = try? db.prepareStatement(sql: sql) else { fatalError() }
defer { sqlite3_finalize(queryStatement) }
guard sqlite3_bind_int(queryStatement, 1, Int32(id)) == SQLITE_OK else { fatalError() }
guard sqlite3_step(queryStatement) == SQLITE_ROW else { fatalError() }
guard let dataBlob = sqlite3_column_blob(queryStatement, 0) else { fatalError() }
let dataBlobLength = sqlite3_column_bytes(queryStatement, 0)
let data = Data(bytes: dataBlob, count: Int(dataBlobLength))
guard let unit = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Dimension.self, from: data) else { fatalError() }
return unit
}`
////
import XCTest
@testable import FitWrench
class MeasurementsTests: XCTestCase
{
var measurements: SQLMeasurements!
override func setUp() {
let db = SQLiteDatabase()
self.measurements = SQLMeasurements(database: db)
}
override func tearDown() { }
func testAddMeasurement() {
do {
let measurement = try measurements.add(value: 90, unit: UnitPower.watts)
XCTAssertEqual(measurement.value(), 90)
XCTAssertEqual(measurement.unit(), UnitPower.watts)
}
catch {
print(error.localizedDescription)
XCTFail()
}
}
func testRemoveMeasurement() { XCTAssertEqual(99, 100) }
}`