Swift Firebase removal of observers not removing - ios

I'm using Firebase listener to update state values for a remote camera. Once I have cycled through the camera lifecycle I want to remove the listeners so my camera does not start over and continue to take video.
Here is what I've done so far based on SO suggestions:
1) added FIRDatabaseHandle and called removeObserver(withHandle: handle) / no luck
2) simple called removeAllObservers() from the root reference to what you see below.
struct CameraActions {
let db = DataService.ds.db // this comes from a singleton used to for other Firebase calls
let uid = DataService.ds.curUser?.uid
var cameraRef:FIRDatabaseReference!
mutating func addCameraListener(cameraNum num:String, complete:#escaping(CameraStatus)->Void){
cameraRef = db.child("camera").child(num).child("status")
cameraRef.observe(.value, with: {
snap in
if let status = snap.value as? Int {
switch status {
case 0: complete(.ready)
case 2: complete(.isRecording)
case 4: complete(.hasStopped)
case 5: complete(.problem)
default: print("App is waiting on camera")
}
}
})
}
func cameraHasFinishedRecording(cameraNum num: String) {
cameraRef.removeAllObservers() // latest attempt here
db.child("camera").child(num).child("status").setValue(0) // this still triggers database call
}
Thanks in advance for any assistance.

Firebase works exactly as advertised. The removal of the observer was working but, another observer that should've been a single observer was firing. Thanks for the input and sorry for wasting your time.
Cheers!

Related

UIManagedDocuemnt won't open

My app (Xcode 9.2, Swift 4) uses UIManagedDocument as a basic Core Data stack. Everything was working fine for months but lately I've noticed several cases where the app won't load for existing users because the core data init isn't completing. This usually happens after a crash in the app (I think but not sure).
I've been able to recreate the problem on the debugger and narrowed the problem down to the following scenario:
App starts up --> core data is called to start up --> UIManagedDocument object is init'd --> check doc status == closed --> call open() on doc --> open never completes - the callback closure is never called.
I've subclassed UIManagedDocument so I could override configurePersistentStoreCoordinator() to check if it ever reaches that point but it doesn't. The subclass override for handleError() is never called either.
The open() process never reaches that point. What I can see if I pause the debugger is that a couple of threads are blocked on mutex/semaphore related to the open procedure:
The 2nd thread (11) seems to be handling some kind of file conflict but I can't understand what and why. When I check documentState just before opening the file I can see its value is [.normal, .closed]
This is the code to init the doc - pretty straight forward and works as expected for most uses and use cases:
class MyDataManager {
static var sharedInstance = MyDataManager()
var managedDoc : UIManagedDocument!
var docUrl : URL!
var managedObjContext : NSManagedObjectContext {
return managedDoc.managedObjectContext
}
func configureCoreData(forUser: String, completion: #escaping (Bool)->Void) {
let dir = UserProfile.profile.getDocumentsDirectory()
docUrl = dir.appendingPathComponent(forUser + GlobalDataDocUrl, isDirectory: true)
managedDoc = UIManagedDocument(fileURL: docUrl)
//allow the UIManagedDoc to perform lieghtweight migration of the DB in case of small changes in the model
managedDoc.persistentStoreOptions = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
switch (self.managedDoc.documentState)
{
case UIDocumentState.normal:
DDLogInfo("ManagedDocument is ready \(self.docUrl)")
case UIDocumentState.closed:
DDLogInfo("ManagedDocument is closed - will open it")
if FileManager.default.fileExists(atPath: self.docUrl.path) {
self.managedDoc.open() { [unowned self] (success) in
DDLogInfo("ManagedDocument is open result=\(success)")
completion(success)
}
}
else{
self.managedDoc.save(to: self.managedDoc.fileURL, for: .forCreating) { [unowned self] (success) in
DDLogInfo("ManagedDocument created result=\(success) ")
completion(success)
}
}
case UIDocumentState.editingDisabled:
fallthrough
case UIDocumentState.inConflict:
fallthrough
case UIDocumentState.progressAvailable:
fallthrough
case UIDocumentState.savingError:
fallthrough
default:
DDLogWarn("ManagedDocument status is \(self.managedDoc.documentState.rawValue)")
}
}
}
Again - the closure callback for managedDoc.open() never gets called. It seems like the file was left in some kind of bad state and cannot be opened.
BTW, if I copy the app container from the device to my mac and open the SQLLite store I can see everything is there as expected.

iOS firebase observer delays observing the changes in the node

I have one function to get the realtime location updates of the selected user.
And in that function , i need to call the current status and history of that selected user from the firebase.
So, the problem is when I start my observer for location updates only , it's fine and it's great .
I get the value from firebase observer.
But , when i make a call for current status and history of that selected user , I am getting the delay in my firebase realtime observer .
I don't understand how come call to another firebase is affecting our realtime observer code.
Guys any suggestions,
I tried dispatching to background for current status /history call but still no luck.
Left me hopeless and clueless.
Added:
I am getting current status and realtime observers from the same node.
So , does this happens that same node is observing and same time if another firebase call is made to same node , is it colliding ???
UPDATED
Firebase function to get current status :
FIRDatabaseReference().child("personal").child(userUUID).child("realtime")
.observeSingleEvent(of: .value, with: { (snapshot) in
//print(snapshot.key)
if snapshot.exists(){
let location = LocationParser(dict: snapshot.value as! [String : AnyObject])?.location
completion(location)
}else{
completion(nil)
}
}){ (error) in
completion(nil)
debugPrint(error.localizedDescription)
}
Firebase function to observe location (continuously):
FIRDatabaseReference().child("personal").child(userUUID).child("realtime")
.observe(.value) { (snapshot:FIRDataSnapshot) in
print("\nOBSERVER FROM FIREBASE FIRED")
if let value = snapshot.value as? [String:AnyObject] {
if let location = LocationParser(dict: value)?.location {
//we cannot save location to our database
//sharedFirebaseManager.saveLocationHistoric(LocationParser(dict: value)!)
result(success(location))
}
}
}
QUESTION:
I am calling both from the same controller and I see continuous observer delays on calling another firebase function call.
Any clue.

Running custom method of the main thread

class func loadData(
onCompletition: #escaping ([LocationInfo])->Void){
let workingQueue = DispatchQueue.global(qos:.utility)
let completitionQueue = DispatchQueue.main
workingQueue.sync {
print("\n Data fetch started \n")
let root = FIRDatabase.database().reference()
let locationSummary = root.child("LocSummary")
locationSummary.observeSingleEvent(of: .value,with: { (snapshot) in
for item in snapshot.children{
let locationInfo = LocationInfo(snapshot: item as! FIRDataSnapshot)
FirebaseDataController.resultsArray.append(locationInfo)
}
completitionQueue.async {
print("\n data fetch completed \n ")
onCompletition(FirebaseDataController.resultsArray)
print("After on completion method")
}
})
}
}
The problem I have no is that, every time I want to access the data inside the results array I have to go through this functions completion handler. Which is not something I can do all the time specially when I want to work with table views and such (I have a seperate class to handle all DB interactions and many other classes to handle table view interactions).
My objective is to run this code at the start of the application may be through the AppDelegate and have a populated array that I can call anytime I want access to data.
To do this I think I need to run this code on the main thread. I tried that by substituting the workingQueue with the main thread but the application keeps crashing.
Is there is anything that I can do about this?
Your application will crash only if you call this synchronously on main thread when launching application because you cause a deadlock. If you run this function in application:didFinishLaunching: method of AppDelegate you should be able to use safely this implementation:
class func loadData(onCompletition: #escaping ([LocationInfo])->Void){
let completitionQueue = DispatchQueue.main
print("\n Data fetch started \n")
let root = FIRDatabase.database().reference()
let locationSummary = root.child("LocSummary")
locationSummary.observeSingleEvent(of: .value,with: { (snapshot) in
for item in snapshot.children{
let locationInfo = LocationInfo(snapshot: item as! FIRDataSnapshot)
FirebaseDataController.resultsArray.append(locationInfo)
}
completitionQueue.async {
print("\n data fetch completed \n ")
onCompletition(FirebaseDataController.resultsArray)
print("After on completion method")
}
})
}
Your guess that you need to perform this operations on the main thread is wrong - there is no reason to do that, in fact, you will end up with stalled user interface. Fetch the data, and then asynchronously update user interface on main thread.
Since you want to call the method on main thread.
First thing you remove all the code related to DispatchQueue.
Then call the loadData using - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

Firebase Connection manager should return only one result

I am following documentation located at: https://www.firebase.com/docs/ios/guide/offline-capabilities.html#section-connection-state
However, my implementation and test of:
connectionCheck.observeEventType(.Value, withBlock: { snapshot in
let connected = snapshot.value as? Bool
if connected != nil && connected! {
print("Connected")
} else {
print("Not connected")
}
})
The output in Xcode notes:
Not connected
Connected
If however I turn off the wifi, the result is simply:
Not connected
Given I wish to allow actions to occur and present to the user if there is not a connection, how can I ensure that this Firebase listener only returns the correct response once?
As a suggestion, you may want to add some additional functionality;
If you want to know about the users connection to Firebase, you should observe the .info/connected path, as stated in the docs.
The design pattern I use is to set up a global app variable called isConnected, and attach a Firebase observer to the .info/connected path.
Then in my app wherever I need to know if the user is connected or not, I attach a KVO Observer to my global isConnected var.
When the user disconnects (or connects), the Firebase observer is called which sets isConnected = false (or true).
Then, any places in my app that are observing the isConnected var is notified of the disconnect/connect and can take appropriate action.
Here's the code
let connectedRef = rootRef.childByAppendingPath(".info/connected")
connectedRef.observeEventType(.Value, withBlock: { snapshot in
self.isConnected = snapshot.value as! Bool //KVO property
//this is just so you can see it's being set, remove
if ( self.isConnected == true ) {
print("connected")
} else {
print("not connected")
}
// testing code
})

RxSwift: How to use shareReplay to lazily get subscription

So I want to be able to lazily subscribe to shared data without it persisting when nobody is subscribed. Then if someone subscribes again, a new observable will be created. I would use a Variable, but I don’t want it to persist if no one is subscribed (because if I were using arrays or something larger than an int I don’t want to keep them in memory). My current implementation works, except when resubscribing it still gets the last value, which means the value is still persisted. I’m thinking of setting the observable to nil, but I don’t know where to do that. Can anyone help me complete this? The code below shows it mostly working, but it looks like the data is sticking around when no one is subscribed.
var switchTwoDisposable: Disposable? = nil
​
#IBAction func switchOneChanged(sender: UISwitch) {
if sender.on {
self.switchOneDisposable = currentNumber().subscribeNext { (value) in
log.debug("Switch 1: \(value)")
}
} else {
switchOneDisposable?.dispose()
}
}
​
#IBAction func switchTwoChanged(sender: UISwitch) {
if sender.on {
self.switchTwoDisposable = currentNumber().subscribeNext { (value) in
log.debug("Switch 2: \(value)")
}
} else {
switchTwoDisposable?.dispose()
}
}
​
var numberObservable: Observable<Int>? = nil
​
func currentNumber() -> Observable<Int> {
if let number = numberObservable {
return number
}
self.numberObservable = Observable<Int>.interval(5.0, scheduler: MainScheduler.instance).shareReplay(1)
return self.numberObservable!
}
​
​
// Switch 1 turned on
// logs "Switch 1: 0"
// logs "Switch 1: 1"
// Switch 2 turned on
// immediately logs "Switch 2: 1"
// logs "Switch 1: 2"
// logs "Switch 2: 2"
// Switch 1 turned off
// logs "Switch 2: 3"
// Switch 2 turned off
// nothing happens here until we take action again
// Switch 1 turned on
// logs "Switch 1: 3"
// logs "Switch 1: 0"
I finally found the convenient method that does exactly what I need. shareReplayLatestWhileConnected() on an observable will replay the latest value to the 2nd, 3rd, 4th, etc. subscribers, but when everyone unsubscribes, then the last value is not retained.
From the example above replace this line:
self.numberObservable = Observable<Int>.interval(5.0, scheduler: MainScheduler.instance).shareReplay(1)
...with this line:
self.numberObservable = Observable<Int>.interval(5.0, scheduler: MainScheduler.instance).shareReplayLatestWhileConnected()
Update
In my case, I specifically want to get a value from disk (e.g. Core data or NSUserDefaults) and then if someone updates that value, they can post to a notification that I'll observe with rx_notification. Therefore, in order for this lazy loading to truly work, I would also want an initial value. Therefore it would be helpful to use startWith in this case, where the value given to startWith is the current value on disk. So the code would be analogous to:
Observable<Int>.interval(5.0, scheduler: MainScheduler.instance).startWith(100).shareReplayLatestWhileConnected()

Resources