Observing realm changes are not firing - swift

In my application I have a custom RealmDatabase class. It initializes the Realm database for me.
public class RealmDatabase: Database {
let realm: Realm
//
// I also provide a shared() singleton
//
public init(fileURL: URL) {
let config = Realm.Configuration(fileURL: fileURL)
Realm.Configuration.defaultConfiguration = config
realm = try! Realm()
}
public func observe<T>(_ block: #escaping ((T) -> Void), for object: T.Type) where T : Storable {
realm.objects(object).observe { (changes) in
print("Changes: ", changes)
}
}
}
Now, I also wrote a class called SyncEngine so that I can start syncing with CloudKit. The class looks like this:
public class SyncEngine: NSObject {
private let database: Database
public init(database: Database) {
self.database = database
super.init()
}
public func start() {
database.observe({ restaurant in
print("changes!")
}, for: Restaurant.self)
}
}
Now, in my AppDelegate I do the following:
let database = RealmDatabase.shared()
let syncEngine = SyncEngine(database: database)
syncEngine.start()
The problem, though, is that my observer is never fired and print("Changes: ", changes) is never printed to the console. I don't know what I'm doing wrong, though. Any ideas?

You're discarding the observation as you create it. To solve this, you need to retain the NotificationToken returned by observe.
var token: NotificationToken?
func start() {
token = database.observe { changes in ... }
}

Related

Observing non-property object for changes [duplicate]

I'm rewriting parts of an app, and found this code:
fileprivate let defaults = UserDefaults.standard
func storeValue(_ value: AnyObject, forKey key:String) {
defaults.set(value, forKey: key)
defaults.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: "persistanceServiceValueChangedNotification"), object: key)
}
func getValueForKey(_ key:String, defaultValue:AnyObject? = nil) -> AnyObject? {
return defaults.object(forKey: key) as AnyObject? ?? defaultValue
}
When CMD-clicking the line defaults.synchronize() I see that synchronize is planned deprecated. This is written in the code:
/*!
-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.
-synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...
- ...before reading in order to fetch updated values: remove the synchronize call
- ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify
- ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
- ...for any other reason: remove the synchronize call
*/
As far as I can interpret, the usage in my case fits the second description: synchronizing after writing, in order to notify others.
It suggests using KVO to ovserve, but how? When I search for this, I find a bunch of slightly older Objective-C-examples. What is the best practice for observing UserDefaults?
As of iOS 11 + Swift 4, the recommended way (according to SwiftLint) is using the block-based KVO API.
Example:
Let's say I have an integer value stored in my user defaults and it's called greetingsCount.
First I need to extend UserDefaults with a dynamic var that has the same name as the user defaults key you want to observe:
extension UserDefaults {
#objc dynamic var greetingsCount: Int {
return integer(forKey: "greetingsCount")
}
}
This allows us to later on define the key path for observing, like this:
var observer: NSKeyValueObservation?
init() {
observer = UserDefaults.standard.observe(\.greetingsCount, options: [.initial, .new], changeHandler: { (defaults, change) in
// your change logic here
})
}
And never forget to clean up:
deinit {
observer?.invalidate()
}
From the blog of David Smith
http://dscoder.com/defaults.html
https://twitter.com/catfish_man/status/674727133017587712
If one process sets a shared default, then notifies another process to
read it, then you may be in one of the very few remaining situations
that it's useful to call the -synchronize method in: -synchronize acts
as a "barrier", in that it provides a guarantee that once it has
returned, any other process that reads that default will see the new
value rather than the old value.
For applications running on iOS 9.3
and later / macOS Sierra and later, -synchronize is not needed (or
recommended) even in this situation, since Key-Value Observation of
defaults works between processes now, so the reading process can just
watch directly for the value to change. As a result of that,
applications running on those operating systems should generally never
call synchronize.
So in most likely case you do not need to set to call synchronize. It is automatically handled by KVO.
To do this you need add observer in your classes where you are handling persistanceServiceValueChangedNotification notification. Let say you are setting a key with name "myKey"
Add observer in your class may be viewDidLoad etc
UserDefaults.standard.addObserver(self, forKeyPath: "myKey", options: NSKeyValueObservingOptions.new, context: nil)
Handle the observer
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//do your changes with for key
}
Also remove your observer in deinit
For anyone who will be looking for the answer in the future, didChangeNotification will be posted only if changes are made on the same process, if you would like to receive all updates regardless of the process use KVO.
Apple doc
This notification isn't posted when changes are made outside the current process, or when ubiquitous defaults change. You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.
Here is a link to demo Xcode project which shows how to setup block based KVO on UserDefaults.
Swift 4 version made with reusable types:
File: KeyValueObserver.swift - General purpose reusable KVO observer (for cases where pure Swift observables can't be used).
public final class KeyValueObserver<ValueType: Any>: NSObject, Observable {
public typealias ChangeCallback = (KeyValueObserverResult<ValueType>) -> Void
private var context = 0 // Value don't reaaly matter. Only address is important.
private var object: NSObject
private var keyPath: String
private var callback: ChangeCallback
public var isSuspended = false
public init(object: NSObject, keyPath: String, options: NSKeyValueObservingOptions = .new,
callback: #escaping ChangeCallback) {
self.object = object
self.keyPath = keyPath
self.callback = callback
super.init()
object.addObserver(self, forKeyPath: keyPath, options: options, context: &context)
}
deinit {
dispose()
}
public func dispose() {
object.removeObserver(self, forKeyPath: keyPath, context: &context)
}
public static func observeNew<T>(object: NSObject, keyPath: String,
callback: #escaping (T) -> Void) -> Observable {
let observer = KeyValueObserver<T>(object: object, keyPath: keyPath, options: .new) { result in
if let value = result.valueNew {
callback(value)
}
}
return observer
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context && keyPath == self.keyPath {
if !isSuspended, let change = change, let result = KeyValueObserverResult<ValueType>(change: change) {
callback(result)
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
File: KeyValueObserverResult.swift – Helper type to keep KVO observation data.
public struct KeyValueObserverResult<T: Any> {
public private(set) var change: [NSKeyValueChangeKey: Any]
public private(set) var kind: NSKeyValueChange
init?(change: [NSKeyValueChangeKey: Any]) {
self.change = change
guard
let changeKindNumberValue = change[.kindKey] as? NSNumber,
let changeKindEnumValue = NSKeyValueChange(rawValue: changeKindNumberValue.uintValue) else {
return nil
}
kind = changeKindEnumValue
}
// MARK: -
public var valueNew: T? {
return change[.newKey] as? T
}
public var valueOld: T? {
return change[.oldKey] as? T
}
var isPrior: Bool {
return (change[.notificationIsPriorKey] as? NSNumber)?.boolValue ?? false
}
var indexes: NSIndexSet? {
return change[.indexesKey] as? NSIndexSet
}
}
File: Observable.swift - Propocol to suspend/resume and dispose observer.
public protocol Observable {
var isSuspended: Bool { get set }
func dispose()
}
extension Array where Element == Observable {
public func suspend() {
forEach {
var observer = $0
observer.isSuspended = true
}
}
public func resume() {
forEach {
var observer = $0
observer.isSuspended = false
}
}
}
File: UserDefaults.swift - Convenience extension to user defaults.
extension UserDefaults {
public func observe<T: Any>(key: String, callback: #escaping (T) -> Void) -> Observable {
let result = KeyValueObserver<T>.observeNew(object: self, keyPath: key) {
callback($0)
}
return result
}
public func observeString(key: String, callback: #escaping (String) -> Void) -> Observable {
return observe(key: key, callback: callback)
}
}
Usage:
class MyClass {
private var observables: [Observable] = []
// IMPORTANT: DON'T use DOT `.` in key.
// DOT `.` used to define `KeyPath` and this is what we don't need here.
private let key = "app-some:test_key"
func setupHandlers() {
observables.append(UserDefaults.standard.observeString(key: key) {
print($0) // Will print `AAA` and then `BBB`.
})
}
func doSomething() {
UserDefaults.standard.set("AAA", forKey: key)
UserDefaults.standard.set("BBB", forKey: key)
}
}
Updating defaults from Command line:
# Running shell command below while sample code above is running will print `CCC`
defaults write com.my.bundleID app-some:test_key CCC
As of iOS 13, there is now a cooler way to do this, using Combine:
import Foundation
import Combine
extension UserDefaults {
/// Observe UserDefaults for changes at the supplied KeyPath.
///
/// Note: first, extend UserDefaults with an `#objc dynamic` variable
/// to create a KeyPath.
///
/// - Parameters:
/// - keyPath: the KeyPath to observe for changes.
/// - handler: closure to run when/if the value changes.
public func observe<T>(
_ keyPath: KeyPath<UserDefaults, T>,
handler: #escaping (T) -> Void)
{
let subscriber = Subscribers.Sink<T, Never> { _ in }
receiveValue: { newValue in
handler(newValue)
}
self.publisher(for: keyPath, options: [.initial, .new])
.subscribe(subscriber)
}
}

Local Realm dbs for multiple users

I'm trying to implement a user based realm db. So each time a new users registers, a Realm file is created with his UID (from Firebase), or if exists, accesses that Realm.
To do that i created a singletone for the Realm calls and setting the Realm Configuration.
My issue now is that, every time i switch the users, the realm path changes (i'm printing after users logged in), but the Results reading are the same, as the users didn't change, or the realm did not switch. To be more clear, the queries results are giving same data.
If i open the actual Realms, the data is different.
I'm seeing this behavior in my tableView, just by loading data.
What am i doing wrong?
RealmServices:
import Foundation
import RealmSwift
import Firebase
class RealmServices {
private init() {}
static let shared = RealmServices()
let userID = Auth.auth().currentUser!.uid
var realm = try! Realm(configuration: Realm.Configuration.init(
fileURL: Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(Auth.auth().currentUser!.uid).realm")))
func create<T:Object> (_ object: T) {
do {
try realm.write {
realm.add(object)
}
} catch {
post(error)
}
}
func update <T: Object>(_ object: T, with dictionary: [String : Any?]) {
do {
try realm.write {
for (key,value) in dictionary {
object.setValue(value, forKey: key)
}
}
} catch {
post (error)
}
}
func delete<T:Object> (_ object: T) {
do {
try realm.write{
realm.delete(object)
}
} catch {
post (error)
}
}
func post (_ error: Error) {
NotificationCenter.default.post(name: NSNotification.Name("RealmError"), object: error)
}
func observeRealmErrors (in vc: UIViewController, completion: #escaping (Error?)-> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name("RealmError"), object: nil, queue: nil) { (Notification) in
completion(Notification.object as? Error)
}
}
func stopObservingError (in vc: UIViewController){
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name("RealmError"), object: nil)
}
}
Here's what I think is going on (I could be totally off but let me give it a shot)
Your RealmServices is a singleton and initialized only once. It's not a subclass where each time one is created, it's initialized
So any var's are set up and populated on first run - the realm property is not going to re-initialize each time it's accessed. For example, take this singleton
final class MySingleton {
private init() {}
var someUuid = UUID().uuidString
static let sharedInstance = MySingleton()
}
as you can see, someUuId is a 'randomly' generated uuid. If you access that property twice, it presents the same value, there's nothing to force an update.
print(MySingleton.sharedInstance.someUuid)
print(MySingleton.sharedInstance.someUuid)
results in
EBE131DE-B574-4CE1-8D74-E680D80A577B
EBE131DE-B574-4CE1-8D74-E680D80A577B
So, the solution is to set the users uid property in your question to a different value each time you want it changed. Here's a quick example
final class RealmService {
private init() {}
var uid: String!
var usersRealm: Realm! {
let path = Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(uid!).realm")
let realm = try! Realm(configuration: Realm.Configuration.init(fileURL: path))
return realm
}
static let sharedInstance = RealmService()
}
and here's how it's called; in this example we write item0 to user0's realm and item1 to user1's realm:
RealmService.sharedInstance.uid = "uid_0"
let user0Realm = RealmService.sharedInstance.usersRealm!
let item0 = ItemClass(name: "item 0")
try! user0Realm.write {
user0Realm.add(item0)
}
RealmService.sharedInstance.uid = "uid_1"
let user1Realm = RealmService.sharedInstance.usersRealm!
let item1 = ItemClass(name: "item 1")
try! user1Realm.write {
user1Realm.add(item1)
}
the result is a realm being created for each user with item0 in user0's realm and item1 being in user1's realm

Swift: Realm array empty after writing object

First of all, i'm new to Swift, so probably I have made some stupid mistake, but I have tried everything and I can't figure it out why this is happening.
So let me start. List property inside realm object is not being stored. That is first problem and second problem is that object I'm calling after adding object to realm loses it property value.
Here is the picture, so you guys can understand better what I'm saying
And now this is output:
PRODUCT BEFORE UPDATE: 34
Added new object
PRODUCT AFTER UPDATE:
I will show how my DBManager class looks:
import Foundation
import RealmSwift
class DBManager {
private var database:Realm
static let sharedInstance = DBManager()
private init() {
database = try! Realm()
}
func getDataFromDB() -> Results<Product> {
let results: Results<Product> = database.objects(Product.self)
return results
}
func getSingleDataFromDB(primaryKey: String) -> Product {
let product: Product = database.object(ofType: Product.self, forPrimaryKey: primaryKey)!
return product
}
func addData(object: Product) {
try! database.write {
database.add(object)
print("Added new object")
}
}
func updateData(object: Product) {
try! database.write {
database.add(object, update: true)
}
}
func deleteAllFromDatabase() {
try! database.write {
database.deleteAll()
}
}
func deleteFromDb(object: Product) {
try! database.write {
database.delete(object)
}
}
}
So If someone is seeing something that I'm not seeing, please let me know because I have lost many hours finding solution for this problem.
Thank you in advance

How to set watchers on Realm model?

For debugging purposes, I would like to set watchers/observers on models but I didn't find hint so far.
Notice I'm rather new in iOS development (less than a month) so I might be missing something.
If you want to observe a whole class of objects, you can make a query, apply filters and then observe these Results for Notifications.
If you want to observe changes to a single object, you can retrieve it and then observe the properties you're interested in via Key-Value Observation (KVO).
Here is an example on KVO in Realm (Swift):
Just for demonstration on how KVO i Realm with persistent objects work.
class MyRealmClass: Object {
dynamic var id = NSUUID().UUIDString
dynamic var date: NSDate?
override static func primaryKey() -> String? {
return "id"
}
}
class ViewController: UIViewController {
var myObject: MyRealmClass?
private var myContext = 0
override func viewDidLoad() {
super.viewDidLoad()
myObject = MyRealmClass()
try! uiRealm.write({ () -> Void in
myObject?.date = NSDate()
uiRealm.add(myObject!, update: true)
print("New MyClass object initialized with date property set to \(myObject!.date!)")
})
myObject = uiRealm.objects(MyRealmClass).last
myObject!.addObserver(self, forKeyPath: "date", options: .New, context: &myContext)
//Sleep before updating the objects 'date' property.
NSThread.sleepForTimeInterval(5)
//Update the property (this will trigger the observeValueForKeyPath(_:object:change:context:) method)
try! uiRealm.write({ () -> Void in
myObject!.date = NSDate()
})
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
print("Date property has changed, new value is \(change![NSKeyValueChangeNewKey]!)")
}
}
}

Singleton in Swift

I've been trying to implement a singleton to be used as a cache for photos which I uploaded to my iOS app from the web. I've attached three variants in the code below. I tried to get variant 2 working but it is causing a compiler error which I do not understand and would like to get help on what am I doing wrong. Variant 1 does the caching but I do not like the use of a global variable. Variant 3 does not do the actual caching and I believe it is because I am getting a copy in the assignment to var ic = ...., is that correct?
Any feedback and insight will be greatly appreciated.
Thanks,
Zvi
import UIKit
private var imageCache: [String: UIImage?] = [String : UIImage?]()
class ImageCache {
class var imageCache: [String : UIImage?] {
struct Static {
static var instance: [String : UIImage?]?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = [String : UIImage?]()
}
return Static.instance!
}
}
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(data: NSData(contentsOfURL: NSURL(string: "http://images.apple.com/v/iphone-5s/gallery/a/images/download/photo_1.jpg")!)!)
//variant 1 - this code is working
imageCache["photo_1"] = imageView.image
NSLog(imageCache["photo_1"] == nil ? "no good" : "cached")
//variant 2 - causing a compiler error on next line: '#lvalue $T7' is not identical to '(String, UIImage?)'
//ImageCache.imageCache["photo_1"] = imageView.image
//NSLog(ImageCache.imageCache["photo_1"] == nil ? "no good" : "cached")
//variant 3 - not doing the caching
//var ic = ImageCache.imageCache
//ic["photo_1)"] = imageView.image
//NSLog(ImageCache.imageCache["photo_1"] == nil ? "no good" : "cached")
}
}
The standard singleton pattern is:
final class Manager {
static let shared = Manager()
private init() { ... }
func foo() { ... }
}
And you'd use it like so:
Manager.shared.foo()
Credit to appzYourLife for pointing out that one should declare it final to make sure it's not accidentally subclassed as well as the use of the private access modifier for the initializer, to ensure you don't accidentally instantiate another instance. See https://stackoverflow.com/a/38793747/1271826.
So, returning to your image cache question, you would use this singleton pattern:
final class ImageCache {
static let shared = ImageCache()
/// Private image cache.
private var cache = [String: UIImage]()
// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.
private init() { }
/// Subscript operator to retrieve and update cache
subscript(key: String) -> UIImage? {
get {
return cache[key]
}
set (newValue) {
cache[key] = newValue
}
}
}
Then you can:
ImageCache.shared["photo1"] = image
let image2 = ImageCache.shared["photo2"])
Or
let cache = ImageCache.shared
cache["photo1"] = image
let image2 = cache["photo2"]
Having shown a simplistic singleton cache implementation above, we should note that you probably want to (a) make it thread safe by using NSCache; and (b) respond to memory pressure. So, the actual implementation is something like the following in Swift 3:
final class ImageCache: NSCache<AnyObject, UIImage> {
static let shared = ImageCache()
/// Observer for `UIApplicationDidReceiveMemoryWarningNotification`.
private var memoryWarningObserver: NSObjectProtocol!
/// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.
///
/// Add observer to purge cache upon memory pressure.
private override init() {
super.init()
memoryWarningObserver = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] notification in
self?.removeAllObjects()
}
}
/// The singleton will never be deallocated, but as a matter of defensive programming (in case this is
/// later refactored to not be a singleton), let's remove the observer if deallocated.
deinit {
NotificationCenter.default.removeObserver(memoryWarningObserver)
}
/// Subscript operation to retrieve and update
subscript(key: String) -> UIImage? {
get {
return object(forKey: key as AnyObject)
}
set (newValue) {
if let object = newValue {
setObject(object, forKey: key as AnyObject)
} else {
removeObject(forKey: key as AnyObject)
}
}
}
}
And you'd use it as follows:
ImageCache.shared["foo"] = image
And
let image = ImageCache.shared["foo"]
For Swift 2.3 example, see previous revision of this answer.
Swift 3:
class SomeClass
{
static let sharedInstance = SomeClass()
fileprivate override init() {
//This prevents others from using the default '()' initializer
super.init()
}
func sayHello()
{
print("Hello!")
}
}
Invoke some Method:
SomeClass.sharedInstance.sayHello() //--> "Hello"
Invoke some Method by creating a new class instance (fails):
SomeClass().sayHello() //--> 'SomeClass' cannot be constructed it has no accessible initailizers
Swift-5
To create a singleton class:
import UIKit
final class SharedData: NSObject {
static let sharedInstance = SharedData()
private override init() { }
func methodName() { }
}
To access
let sharedClass = SharedClass.sharedInstance
OR
SharedClass.sharedInstance.methodName()
Following are the two different approaches to create your singleton class in swift 2.0
Approach 1) This approach is Objective C implementation over swift.
import UIKit
class SomeManager: NSObject {
class var sharedInstance : SomeManager {
struct managerStruct {
static var onceToken : dispatch_once_t = 0
static var sharedObject : SomeManager? = nil
}
dispatch_once(&managerStruct.onceToken) { () -> Void in
managerStruct.sharedObject = SomeManager()
}
return managerStruct.sharedObject!
}
func someMethod(){
print("Some method call")
}
}
Approach 2) One line Singleton, Don't forget to implement the Private init (restrict usage of only singleton)
import UIKit
class SomeManager: NSObject {
static let sharedInstance = SomeManager()
private override init() {
}
func someMethod(){
print("Some method call")
}
}
Call the Singleton method like :
SomeManager.sharedInstance.someMethod()