This repository provides an implementation for a StateModel database based on SQLite, with key paths that can be represented as SQLite values. It can be used as a simple storage solution for mobile devices.
Integrate this package like you would any other:
...
dependencies: [
.package(url: "https://github.com/christophhagen/SQLiteStateDB", from: "6.0.0")
],
...
.target(
name: "MyTarget",
dependencies: [
.product(name: "SQLiteDB", package: "SQLiteStateDB")
]
),
...
Consult the StateModel Documentation on how to define a model database.
The library provides different implementations depending on the feature set required.
SQLiteDatabase offers basic value storage and conforms to Database, while SQLiteTimestampedDatabase also stores the last modified timestamp for each property, but no historic values.
For features like HistoryView, use a SQLiteHistoryDatabase instead.
The SQLite database stores natively supported types in separate tables, e.g. all integer values are stored in a table with an INTEGER column.
For types that can't be natively represented, each value is encoded to binary data before insertion.
For these operations you need to supply an encoder and decoder, which are provided to the initializer:
SQLiteDatabase(file: URL, encoder: any GenericEncoder, decoder: any GenericDecoder)The GenericEncoder and GenericDecoder protocols are defined by the StateModel library, and a few basic encoders can be used directly.
Since the Foundation module already provides JSONEncoder and JSONDecoder, you can simply use those:
let database = MyDatabase(encoder: JSONEncoder(), decoder: JSONDecoder())While encoding values as JSON is great for debugging, it is not recommended for production use, due to the inefficient encoding as a string. Additionally, there is a bug with nested optionals that may produce unexpected behaviour:
let value: Int?? = .some(.none)
let encoded = try JSONEncoder().encode(value)
let decoded = try JSONDecoder().decode(Int??.self, from: encoded)
print(value == decoded) // Prints "false"
print(decoded) // Prints "nil"Any nil value will be decoded as a top-level nil, even if nested in another optional.
You need to consider this behaviour if you want to use JSONEncoder.
Since SQLiteDatabase encodes optionals using SQLite NULL values, double optionals will still work correctly when used in properties:
@Property(id: 1)
var value: Int?? // SafeMore deeply nested optionals will not be decoded correctly (not sure where those would be needed).
Note that this behaviour also applies to Codable types:
struct MyType: Codable {
var value: Int?? // Not consistent with `JSONEncoder`
}The Foundation module also supplies PropertyListEncoder for codable types, but it's very restricted in the types it can encode, so its use is not recommended.
There is an efficient encoder available for Codable types with BinaryCodable, which does not suffer from any known inconsistencies.
To use it, install the package and conform the classes to the required protocols:
import SQLiteDB
import BinaryCodable
extension BinaryEncoder: GenericEncoder { }
extension BinaryDecoder: GenericDecoder { }To improve query times for repeated access to the same properties, it is recommended to perform caching.
StateModel already provides a CachedDatabase wrapper, that can be used with SQLiteDatabase.
You can use the provided caches, or implement your own DatabaseCache.