RelatedDB is a lightweight Swift wrapper around SQLite.
- iOS 13.0+
- Xcode 12.0+
- Swift 5.0+
CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects.
To incorporate the RelatedDB library into your Xcode project utilizing CocoaPods, please reference it within your Podfile
as shown below:
pod 'RelatedDB'
Swift Package Manager is a tool for managing the distribution of Swift code.
Once you've configured your Package.swift
manifest file, you may proceed to include RelatedDB in the dependencies section of the same file.
dependencies: [ .package(url: "https://github.com/relatedcode/RelatedDB.git", from: "1.1.5") ]
If you prefer not to use any of the dependency managers, you can integrate RelatedDB into your project manually. Just copy all the *.swift
files from the RelatedDB/Sources
folder into your Xcode project.
import RelatedDB
let db = RDatabase()
You can also specify the database filename or the complete path. The default filename database.sqlite
, and the default path is Library/Application Support
.
let db = RDatabase(file: "db.sqlite")
let db = RDatabase(path: "yourpath/db.sqlite")
RelatedDB provides a protocol that helps to manipulate database rows as regular objects.
class User: NSObject, RDObject {
@objc var userId = 0
@objc var name = ""
@objc var age = 0
@objc var approved = false
class func primaryKey() -> String {
return "userId"
}
}
By creating the User class above, RelatedDB will automatically create the following SQLite Table for you:
CREATE TABLE IF NOT EXISTS User (userId INTEGER PRIMARY KEY NOT NULL, name TEXT, age INTEGER, approved INTEGER);
Note: Since all the RDObject class property names will be used in SQLite commands, try to avoid using SQLite keywords in your class definition.
Using the User class above, creating an object would look like this:
let user = User()
user.userId = 1001
user.name = "John Smith"
user.age = 42
user.approved = false
user.insert(db)
An existing object can be updated:
user.age = 43
user.update(db)
If you are not 100% sure if an object already exists in the database or not, then you can use the following methods as well:
user.insertUpdate(db)
It will try to execute the INSERT command first and if it fails (silently), then executes the UPDATE command.
Also, you can use
user.updateInsert(db)
which will try to execute the UPDATE command first and if it fails (silently), then executes the INSERT command.
An existing object can be deleted:
user.delete(db)
Fetching one object would look like:
let user = User.fetchOne(db, key: 1001)
Fetching multiple objects can be done in the following ways:
let users = User.fetchAll(db)
let users = User.fetchAll(db, "age > 40")
let users = User.fetchAll(db, "age = ?", [42])
let users = User.fetchAll(db, "age >= :min AND age <= :max", [":min": 18, ":max": 99])
You can also use the limit
and offset
parameters.
let users = User.fetchAll(db, limit: 10)
let users = User.fetchAll(db, "age > 40", limit: 5, offset: 10)
The database write actions are serialized. This means, the Insert, Update and Delete actions will be executed one after the other (managed by RelatedDB) automatically.
The Fetch methods are thread-safe. This means you will have back the results in the same thread you have initiated the request from.
Note: You can initiate both read and write actions from any thread you like.
RelatedDB can manage the following data types: Bool
, Int8
, Int16
, Int32
, Int64
, Int
, Float
, Double
, String
, Date
, Data
.
The Date
values will be stored in the database as ISO formatted String
. The default format is ISO 8601 ("1970-01-01T01:01:01.000Z"), produced by the ISO8601DateFormatter
class.
You can also specify your own date format by using:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
RDDate.custom(formatter)
Note: You only need to specify the date format once in your codebase. Practically before doing any database action.
Although managing the database rows as objects is an easy and elegant way, you might want to manage the data as Dictionary
instead.
Insert (using Dictionary)
Insert a new object as Disctionary:
let values: [String: Any] = ["userId": 1001, "name": "John Smith", "age": 42, "approved": false]
db.insert("User", values)
Update (using Dictionary)
Update an existing object as Dictionary:
let values: [String: Any] = ["userId": 1001, "age": 43]
db.update("User", values)
The primary key must be included in the values Dictionary. Otherwise, nothing will happen.
Fetch Result(s) (as Dictionary)
Fetching one object as Dictionary:
let user = db.fetchOne("User", key: 1001)
In this case, the result type will be a [String: Any]
.
Fetching multiple objects as Dictionary:
let users = db.fetchAll("User")
let users = db.fetchAll("User", "age > 40")
let users = db.fetchAll("User", "age = ?", [42])
let users = db.fetchAll("User", "age >= :min AND age <= :max", [":min": 18, ":max": 99])
In the cases above, the result types will be a [[String: Any]]
.
Convert Object (to Dictionary)
Convert an existing object to Dictionary:
let values = user.values()
Date Values (in Dictionary)
When using a values dictionary (to insert or update data), the Date values can be placed into the Dictionary as both Date
or ISO formatted String
.
When fetching data (as dictionary), the Date values will always be represented in the Dictionary as ISO formatted String
.
You can update multiple objects by specifying a condition.
let values = ["approved": true]
User.updateAll(db, values, "age >= ? AND age <= ?", [30, 35])
Alternatively, update one object by specifying the primary key value.
let values = ["approved": true]
User.updateOne(db, values, key: 1001)
You can delete multiple objects by specifying a condition.
User.deleteAll(db, "age >= ? AND age <= ?", [30, 35])
Alternatively delete one object by specifying the primary key value.
User.deleteOne(db, key: 1001)
You can get the number of objects by specifying a condition.
let count = User.count(db)
let count = User.count(db, "age > 40")
let count = User.count(db, "age >= ? AND age <= ?", [30, 35])
You can check whether an object exists or not (by specifying the primary key value).
if (User.check(db, key: 1001)) {
// do something
}
Or you check whether a set of objects exist or not (by specifying a condition).
if (User.check(db, "age >= ? AND age <= ?", [30, 35])) {
// do something
}
For refreshing the user interface upon database changes, you can use the Database Observers.
Checking all the possible changes for the User class would be:
let types: [RDObserverType] = [.insert, .update, .delete]
let observerId = User.createObserver(db, types) { method, objectId in
// do something
}
However you can narrow down the number of changes by using a condition:
let observerId = User.createObserver(db, types, "OBJ.age > 40") { method, objectId in
// do something
}
Also you can check only specific database changes (separated or combined) by using the following Observer types: .insert
, .update
, .delete
.
To get notified about new users, but not the updated and/or deletes ones:
let observerId = User.createObserver(db, .insert) { method, objectId in
// do something
}
Or to get notified about deleted users dedicated would look like this:
let observerId = User.createObserver(db, .delete) { method, objectId in
// do something
}
Once a Database Observer no longer required, you can remove it by using:
User.removeObserver(db, observerId)
If you would ever need it, you can execute plain SQL commands.
db.execute("DELETE FROM User WHERE age = 42;")
Although the SQLite tables are created automatically, you can also DROP and/or CREATE tables manually.
db.dropTable("User")
db.createTable("User")
In these cases, the User
class also needs to be defined first.
If you would ever need, all the tables can be destroyed and recreated by using:
db.cleanupDatabase()
The crucial issues will cause a fatalError
, every other situation will be reported in the Xcode output window.
You can alter the debug level by using:
RDDebug.level(.none)
RDDebug.level(.error)
RDDebug.level(.all)
The RelatedDB toolkit is in its initial release. It is functional and can handle most workloads. However, there are some features that are currently not supported:
- Database migration
- Database encryption
- Combine framework integration
© Related Code 2022 - All Rights Reserved