SQLite
FMDB is cool...
... said someone in 2011.
Core Data
Learning curve anyone?
ORM on top of SQLite
Complex API
Offline JSON/Plist files
Ok for small and simple transactions
Limited Flexibility
Too much I/O
Thread-safety? ACID?
Realm is not an ORM on top of SQLite/CoreData/x
Custom C++ core with bit-packing, caching, vectorization and zero-copy architecture.
It has it's own conventions and data-structures that are easy to grasp and use.
Basically just 3 classes (Object, Arrays and Realms)
You can use Realm models convention to write all your app models if you want.
More later...
Objective-C, Swift
iOS, watchOS, tvOS
React-Native
Android
Writer
Reader
HEAD
HEAD~1
HEAD
HEAD~1
As you make changes, you're forking the tree.
The top level pointer "HEAD" is always pointing to the latest non-corrupt tree.
When Realm verifies the newest write transaction (fsync()) it move's the top level pointer and get a new "official version"
How most ORMs deal with data
How Realm does it
pod 'RealmSwift'
Supported Property Types
Bool
Int8, Int16, Int32, Int64
Storing optional numbers is done using RealmOptional
Double, Float
String (can be optional)
NSDate (truncated to the second, can be optional)
NSData (can be optional)
import RealmSwift
class User: Object {
dynamic var firstname = ""
dynamic var lastname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
}
import RealmSwift
class User: Object {
dynamic var id = ""
dynamic var firstname = ""
dynamic var lastname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
override static func primaryKey() -> String? {
return "id"
}
}
import RealmSwift
class Post: Object {
dynamic var id = NSUUID().UUIString
dynamic var postDescription = ""
dynamic var postPhotoURL = ""
dynamic var numOfLikes = 0
dynamic var createdAt = NSDate(timeIntervalSince1970: 1)
override static func primaryKey() -> String? {
return "id"
}
}
import RealmSwift
class User: Object {
dynamic var id = ""
dynamic var firstname = ""
dynamic var lastname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
let posts = List<Post>()
override static func primaryKey() -> String? {
return "id"
}
}
import RealmSwift
class Post: Object {
dynamic var id = ""
dynamic var postDescription = ""
dynamic var postPhotoURL = ""
dynamic var numOfLikes = 0
dynamic var createdAt = NSDate(timeIntervalSince1970: 1)
var postOwners: [User] {
return linkingObject(User.self, forProperty: "posts")
}
override static func primaryKey() -> String? {
return "id"
}
}
import RealmSwift
class User: Object {
dynamic var id = ""
dynamic var firstname = ""
dynamic var lastname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
let posts = List<Post>()
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["nickname"]
}
}
import RealmSwift
class User: Object {
dynamic var id = ""
dynamic var firstname = ""
dynamic var lastname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
let posts = List<Post>()
override static func primaryKey() -> String? {
return "id"
}
override static func indexedProperties() -> [String] {
return ["id", "firstName"]
}
}
import RealmSwift
var user = User()
user.id = NSUUID().UUIString
user.firstname = "Thanos"
user.lastname = "Theodoridis"
user.email = "at@atworks.gr"
import RealmSwift
var user = User(value: [
"id": NSUUID().UUIString,
"firstname": "Thanos",
"lastname": "Theodoridis",
"email": "at@atworks.gr"
])
import RealmSwift
var user = User(value: [
"Thanos",
"Theodoridis",
"at@atworks.gr"
])
var post1 = Post(value: [
"postDescription": "Look, my dog! #dog #igers",
"postURL": "http://lala.com/dog.jpg",
])
var post2 = Post(value: [
"postDescription": "Look, my cat! #cute",
"postURL": "http://lala.com/cat.jpg",
])
var user = User(value: [
"id": NSUUID().UUIString,
"firstname": "Thanos",
"lastname": "Theodoridis",
"email": "at@atworks.gr",
"posts": [post1, post2]
])
let realm = try! Realm()
var user = User(value: [
"firstname": "Thanos",
"lastname": "Theodoridis",
"email": "at@atworks.gr",
"posts": [post1, post2]
])
try! realm.write {
realm.add(user)
}
let realm = try! Realm()
user.lastname = "Papadopoulos"
try! realm.write {
realm.add(user, update: true)
}
This will work only if the User object has a primary key.
If not, you need to enclose `user.lastname` in a `write()` block.
If you omit `update: true` and an object with that key already exists, you get an exception (if unhandled, you crash)
All queries return a `Results` instance with a collection of Objects.
All queries are lazy.
Data is only read when the properties are accessed.
Results are not copies.
Modifying a Results object within a write transaction will persist to disk.
Query execution is deferred until the results are used.
Once a query has been executed, the Results object is up to date with any other changes in Realm.
The equivalent of `SELECT * FROM 'USERS'`
let realm = try! Realm()
let users = realm.objects(User)
NSPredicate()
let realm = try! Realm()
// Forward relationship
let user = realm.objects(User).first
let posts = user.posts
// Inverse (backward) relationship
let post = realm.objects(Post).first
let postUser = post.postOwners.first
let realm = try! Realm()
// Get a user with a matching id
let predicate = NSPredicate(format: "id == %@", id)
let user1 = realm.objects(User).filter(predicate)
// Get users with GMail accounts
predicate = NSPredicate(format: "email ENDSWITH %@", "gmail.com")
let users = realm.objects(User).filter(predicate)
// Get top active users
predicate = NSPredicate(format: "posts.@count > 100")
let activeUsers = realm.objects(User).filter(predicate)
// Get users registered before Christmas
predicate = NSPredicate(format: "date <= %@", christmasDate)
Realm() gets you the default Realm database.
A file named `default.realm` in /Documents
var config = Realm.Configuration()
config.path = NSURL.fileURLWithPath(config.path!)
.URLByDeletingLastPathComponent?
.URLByAppendingPathComponent("NewName.realm")
.path
// set tihs as the config for the default realm
Realm.Configuration.defaultConfiguration = config
But you can use as many different Realms as you want!
let config = Realm.Configuration(
path: myPathToBackupRealm.path
readOnly: false
)
// Default Realm (default.realm)
let realm = try! Realm()
// Backup Realm (backup.realm)
let otherRealm = try! Realm(configuration: config)
You can also have in-memory Realms
Cool for caching layers
let memoryCache = try! Realm(configuration:
Realm.Configuration(inMemoryIdentifier: "com.me.cache")
)
Beware of in-memory instances going out of scope with no references.
Garbage collector will wipe your data off
Keep a strong reference to your in-memory Realms.
You can also bundle prepopulated Realms with your app.
In the code where you generate the prepopulated Realm, make sure to "compact" it:
Realm().writeCopyToPath(_:encryptionKey:)
Drag and drop and add it in "Copy Bundle Resources" of your build phase.
If you need to mutate this Realm, your first need to copy it to your application's Documents directory. Bundles are read-only.
One golden rule:
let realm = try! Realm()
for each thread
Realm state for each thread will be based off the most recent confirmed transaction commit and will remain there until that version is refreshed.
States are automatically refreshed at the start of every NSRunLoop iteration.
Background threads that do not have run loops must explicitly call Realm.refresh()
Two options for monitoring updates on Realm objects:
Collection Notifications
KVO
let userModelNotificationToken: NotificationToken!
userModelNotificationToken = realm.objects(User).addNotificationBlock {
notification, realm in
usersTabelView.reloadData()
}
// When no longer needed
userModelNotificationToken.stop()
// v1 Model
class User: Object {
dynamic var id = ""
dynamic var firstname = ""
dynamic var lastname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
}
// v2 Model
class User: Object {
dynamic var id = ""
dynamic var fullname = ""
dynamic var nickname: String? = nil
dynamic var email = ""
dynamic var password = ""
dynamic var registrationDate = NSDate(timeIntervalSince1970: 1)
}
// application(application:didFinishLaunchingWithOptions:)
Realm.Configuration.defaultConfiguration = Realm.Configuration(
// Increment the db schema version
schemaVersion: 1,
// Perform the migration
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerate(User.className()) { oldObject, newObject in
let firstname = oldObject!["firstname"] as! String
let lastname = oldObject!["lastname"] as! String
newObject!["fullname"] = "\(fistname) \(lastname)"
}
}
}
)
let key = NSMutableData(length: 64)!
// Generate random key
SecRandomCopyBytes(
kSecRandomDefault,
key.length,
UnsafeMutablePointer<UInt8>(key.mutableBytes)
)
// Save it securely in keychain
// Always pass it in default Realm configuration
// to be able to use encrypted Realm file
Realm.Configuration.defaultConfiguration.encryptionKey = key
print(Realm.Configuration.defaultConfiguration.path!)
// or
(lldb) po Realm.defaultPath