Cannot retrieve recently added values with Firebase - ios

I have two view controllers in my app: the first one displays items pulled from a Firebase database, and the second one lets users add and remove items from the database.
The problem is when a user adds an item in the second view controller and goes back to the first controller really fast. This will sometimes cause the Firebase database not save the item. As a consequence the first view controller won't to display the newly added items.
As far as the implantation goes, I fetch items in viewWillAppear and then remove the Firebase listener observer in viewDidDisappear. How can I fix this?
// first view controller code
var items = [Item]()
var ref: FIRDatabaseReference!
func fetchItems() {
ref.child("items").observeSingleEvent(of: .value, with: { (snapshot) in
self.items = []
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
if let dictionary = child.value as? [String: AnyObject] {
// Create item and append it to an items array
}
}
self.tableView.reloadData()
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.fetchItems()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
ref.removeAllObservers()
}

You shouldn't use observeSingleEvent since you want the code to be executed every time the firebase database changes. So, change it:
func fetchItems() {
ref.child("items").observe(.value, with: { (snapshot) in
self.items = []
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
if let dictionary = child.value as? [String: AnyObject] {
// Create item and append it to an items array
}
}
self.tableView.reloadData()
})
}
This will create a listener. Also, since you didn't create an observer before, you didn't have to use removeAllObservers(). Now that you have one it's correct to use it.
Hope it helps!

Related

UITableview does not respond while loading data from firebase

My Tableview does not respond to any touches when it starts loading data from firebase. (It already shows the cells, but does not react) After a while it does the scrolling you tried to do when the tableview wasn't reacting.
var filteredData = [archivCellStruct]()
func firData() {
filteredData.removeAll()
var databaseRef : DatabaseReference!
databaseRef = Database.database().reference()
databaseRef.child("Aufträge").child("Archiv").queryOrderedByKey().observe(.childAdded, with:{
snapshot in
let snap = snapshot.value as? NSDictionary
//extracting the data and appending it to an Array
self.filteredData.append(//myData)
self.tableView.reloadData()
})
}
So it is working for smaller amounts of data (tableview rows) but with big delay(and I am already filtering the data to limit the data displayed in the tableView).
I think it has something to do with the tableView.reloadData() (maybe userinteraction is disabled while reloading?)
everytime you have to reload your tableview asynchronously -
var filteredData = [archivCellStruct]()
func firData() {
filteredData.removeAll()
var databaseRef : DatabaseReference!
databaseRef = Database.database().reference()
databaseRef.child("Aufträge").child("Archiv").queryOrderedByKey().observe(.childAdded, with:{
snapshot in
let snap = snapshot.value as? NSDictionary
//extracting the data and appending it to an Array
self.filteredData.append(//myData)
DispatchQueue.main.async { //change this in you code
self.tableView.reloadData()
}
})
}
With the firebase you can go manual way:
UseCase:
struct UseCaseName {
struct Request {}
struct Response {
let firebaseCallbackData: [FirebaseModelType]
}
struct ViewModel {
let data: [DisplayType]
}
}
ViewController:
var filteredData: [DisplayType]! = []
override func viewWillAppear() {
// this one can make you trouble, adjust the observation logic to whatever you need. `WillAppear` can fire multiple times during the view lifecycle
super.viewWillAppear()
interractor?.observe()
}
func showData(viewModel: Scene.Usecase.ViewModel) {
// this is where the different approach begins
tableView.beginUpdates()
var indexPaths = [NSIndexPath]()
for row in (filteredData.count..<(FilteredData.count + viewModel.data.count)) {
indexPaths.append(NSIndexPath(forRow: row, inSection: 0))
}
filteredData += viewModel.data
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)
tableView.endUpdates()
}
Interractor:
func observe(request: Scene.UseCase.Request) { /* signup for updates with observe(_ eventType: DataEventType, with block: #escaping (DataSnapshot) -> Void) -> UInt */
callback is something like { DataSnapshot in presenter.presentData(response: Scene.Usecase.Response())
}
Presenter:
func presentData(response: Scene.UseCase.Response) {
/* format for representation */
DispatchQueue.main.async {
controller.present(viewModel: Scene.UseCase.ViewModel())
}
}
Sorry for the separation of the flow, I've got addicted to this way.
Also I'm assuming, that the data in the firebase is not modified, but added (because of observe(.childAdded, part. If I'm wrong, please edit your question to reflect that. Another assumption is that you have the single section. Don't forget to change the inSection: 0 to the proper section. I'm to lazy and SO isn't that friendly for mobile devices
This way only appends new values sent by Firebase and works faster
EDIT: on another answer.
DispatchQueue.main.async { //change this in you code
self.tableView.reloadData()
}
Sometimes it's not good to reload all items. It depends on the count however. If my assumptions are correct, it'll be better to update separate cells without reloading the whole table on change. Quick example: something like a chat with the Firebase "backend". explanation: adding a single row will work faster then reloadData() anyway, but the difference is heavy only when you call those methods often. With the chat example, the difference may be huge enough in case the chat is spammy to optimize the UITableView reload behaviour in your controller.
Also it'll be nice to see the code, which affects the methods. It maybe a threading issue, like the John Ayers told in the comments. Where do you call func firData()?

Swift iOS -How To Reload TableView Outside Of Firebase Observer .childAdded to Filter Out Duplicate Values?

I have a tabBar controller with 2 tabs: tabA which contains ClassA and tabB which contains ClassB. I send data to Firebase Database in tabA/ClassA and I observe the Database in tabB/ClassB where I retrieve it and add it to a tableView. Inside the tableView's cell I show the number of sneakers that are currently inside the database.
I know the difference between .observeSingleEvent( .value) vs .observe( .childAdded). I need live updates because while the data is getting sent in tabA, if I switch to tabB, I want to to see the new data get added to the tableView once tabA/ClassA is finished.
In ClassB I have my observer in viewWillAppear. I put it inside a pullDataFromFirebase() function and every time the view appears the function runs. I also have Notification observer that listens for the data to be sent in tabA/ClassA so that it will update the tableView. The notification event runs pullDataFromFirebase() again
In ClassA, inside the callback of the call to Firebase I have the Notification post to run the pullDataFromFirebase() function in ClassB.
The issue I'm running into is if I'm in tabB while the new data is updating, when it completes, the cell that displays the data has a count and the count is thrown off. I debugged it and the the sneakerModels array that holds the data is sometimes duplicating and triplicating the newly added data.
For example if I am in Class B and there are 2 pairs of sneakers in the database, the pullDataFromFirebase() func will run, and the tableView cell will show "You have 2 pairs of sneakers"
What was happening was if I switched to tabA/ClassA, then added 1 pair of sneakers, while it's updating I switched to tabB/ClassB, the cell would still say "You have 2 pairs of sneakers" but then once it updated the cell would say "You have 5 pairs of sneakers" and 5 cells would appear? If I switched tabs and came back it would correctly show "You have 3 pairs of sneakers" and the correct amount of cells.
That's where the Notification came in. Once I added that if I went through the same process and started with 2 sneakers the cell would say "You have 2 pairs of sneakers", I go to tabA, add another pair, switch back to tabB and still see "You have 2 pairs of sneakers". Once the data was sent the cell would briefly show "You have 5 pairs of sneakers" and show 5 cells, then it would correctly update to "You have 3 pairs of sneakers" and the correct amount of cells (I didn't have to switch tabs).
The Notification seemed to work but there was that brief incorrect moment.
I did some research and the most I could find were some posts that said I need to use a semaphore but apparently from several ppl who left comments below they said semaphores aren't meant to be used asynchronously. I had to update my question to exclude the semaphore reference.
Right now I'm running tableView.reloadData() in the completion handler of pullDataFromFirebase().
How do I reload the tableView outside of the observer once it's finished to prevent the duplicate values?
Model:
class SneakerModel{
var sneakerName:String?
}
tabB/ClassB:
ClassB: UIViewController, UITableViewDataSource, UITableViewDelegate{
var sneakerModels[SneakerModel]
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(pullDataFromFirebase), name: NSNotification.Name(rawValue: "pullFirebaseData"), object: nil)
}
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
pullDataFromFirebase()
}
func pullDataFromFirebase(){
sneakerRef?.observe( .childAdded, with: {
(snapshot) in
if let dict = snapshot.value as? [String:Any]{
let sneakerName = dict["sneakerName"] as? String
let sneakerModel = SneakerModel()
sneakerModel.sneakerName = sneakerName
self.sneakerModels.append(sneakerModel)
//firebase runs on main queue
self.tableView.reloadData()
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sneakerModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SneakerCell", for: indexPath) as! SneakerCell
let name = sneakerModels[indePath.row]
//I do something else with the sneakerName and how pairs of each I have
cell.sneakerCount = "You have \(sneakerModels.count) pairs of sneakers"
return cell
}
}
}
tabA/ClassA:
ClassA : UIViewController{
#IBAction fileprivate func postTapped(_ sender: UIButton) {
dict = [String:Any]()
dict.updateValue("Adidas", forKey: "sneakerName")
sneakerRef.?.updateChildValues(dict, withCompletionBlock: {
(error, ref) in
//1. show alert everything was successful
//2. post notification to ClassB to update tableView
NotificationCenter.default.post(name: Notification.Name(rawValue: "pullFirebaseData"), object: nil)
}
}
}
In other parts of my app I use a filterDuplicates method that I added as an extension to an Array to filter out duplicate elements. I got it from filter array duplicates:
extension Array {
func filterDuplicates(_ includeElement: #escaping (_ lhs:Element, _ rhs:Element) -> Bool) -> [Element]{
var results = [Element]()
forEach { (element) in
let existingElements = results.filter {
return includeElement(element, $0)
}
if existingElements.count == 0 {
results.append(element)
}
}
return results
}
}
I couldn't find anything particular on SO to my situation so I used the filterDuplicates method which was very convenient.
In my original code I have a date property that I should've added to the question. Any way I'm adding it here and that date property is what I need to use inside the filterDuplicates method to solve my problem:
Model:
class SneakerModel{
var sneakerName:String?
var dateInSecs: NSNumber?
}
Inside tabA/ClassA there is no need to use the Notification inside the Firebase callback however add the dateInSecs to the dict.
tabA/ClassA:
ClassA : UIViewController{
#IBAction fileprivate func postTapped(_ sender: UIButton) {
//you must add this or whichever date formatter your using
let dateInSecs:NSNumber? = Date().timeIntervalSince1970 as NSNumber?
dict = [String:Any]()
dict.updateValue("Adidas", forKey: "sneakerName")
dict.updateValue(dateInSecs!, forKey: "dateInSecs")//you must add this
sneakerRef.?.updateChildValues(dict, withCompletionBlock: {
(error, ref) in
// 1. show alert everything was successful
// 2. no need to use the Notification so I removed it
}
}
}
And in tabB/ClassB inside the completion handler of the Firebase observer in the pullDataFromFirebase() function I used the filterDuplicates method to filter out the duplicate elements that were showing up.
tabB/ClassB:
func pullDataFromFirebase(){
sneakerRef?.observe( .childAdded, with: {
(snapshot) in
if let dict = snapshot.value as? [String:Any]{
let sneakerName = dict["sneakerName"] as? String
let sneakerModel = SneakerModel()
sneakerModel.sneakerName = sneakerName
self.sneakerModels.append(sneakerModel)
// use the filterDuplicates method here
self.sneakerModels = self.sneakerModels.filterDuplicates{$0.dateInSecs == $1.dateInSecs}
self.tableView.reloadData()
}
})
}
Basically the filterDuplicates method loops through the sneakerModels array comparing each element to the dateInSecs and when it finds them it excludes the copies. I then reinitialize the sneakerModels with the results and everything is well.
Also take note that there isn't any need for the Notification observer inside ClassB's viewDidLoad so I removed it.

Firebase removeAllObservers() keeps making calls when switching views -Swift2 iOS

I read other stack overflow q&a's about this problem but it seems to be a tabBarController issue which I haven't found anything on.
I have a tabBarController with 3 tabs. tab1 is where I successfully send the info to Firebase. In tab2 I have tableView which successfully reads the data from tab1.
In tab2 my listener is in viewWillAppear. I remove the listener in viewDidDisappear (I also tried viewWillDisappear) and somewhere in here is where the problem is occurring.
override func viewDidDisappear(animated: Bool) {
self.sneakersRef!.removeAllObservers()
//When I tried using a handle in viewWillAppear and then using the handle to remove the observer it didn't work here either
//sneakersRef!.removeObserverWithHandle(self.handle)
}
Once I switch from tab2 to any other tab, the second I go back to tab2 the table data doubles, then if I switch tabs again and come back it triples etc. I tried setting the listener inside viewDidLoad which prevents the table data from duplicating when I switch tabs but when I send new info from tab1 the information never gets updated in tab2.
According to the Firebase docs and pretty much everything else I read on stackoverflow/google the listener should be set in viewWillAppear and removed in viewDidDisappear.
Any ideas on how to prevent my data from duplicating whenever I switch between back forth between tabs?
tab1
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class TabOneController: UIViewController{
var ref:FIRDatabaseReference!
#IBOutlet weak var sneakerNameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.ref = FIRDatabase.database().reference()
}
#IBAction func sendButton(sender: UIButton) {
let dict = ["sneakerName":self.sneakerNameTextField.text!]
let usersRef = self.ref.child("users")
let sneakersRef = usersRef.child("sneakers").childByAutoID()
sneakersRef?.updateChildValues(dict, withCompletionBlock: {(error, user) in
if error != nil{
print("\n\(error?.localizedDescription)")
}
})
}
}
tab2
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class TabTwoController: UITableViewController{
#IBOutlet weak var tableView: UITableView!
var handle: Uint!//If you read the comments I tried using this but nahhh didn't help
var ref: FIRDatabaseReference!
var sneakersRef: FIRDatabaseReference?
var sneakerArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.ref = FIRDatabase.database().reference()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let usersRef = self.ref.child("users")
//Listener
self.sneakersRef = usersRef.child("sneakers")
//I tried using the handle
//---self.handle = self.sneakersRef!.observeEventType(.ChildAdded...
self.sneakersRef!.observeEventType(.ChildAdded, withBlock: {(snapshot) in
if let dict = snapshot.value as? [String:AnyObject]{
let sneakerName = dict["sneakerName"] as? String
self.sneakerArray.append(sneakerName)
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
}), withCancelBlock: nil)
}
//I also tried viewWillDisappear
override func viewDidDisappear(animated: Bool) {
self.sneakersRef!.removeAllObservers()
//When I tried using the handle to remove the observer it didn't work either
//---sneakersRef!.removeObserverWithHandle(self.handle)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sneakerArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("sneakerCell", forIndexPath: indexPath)
cell.textLabel?.text = sneakerArray[indexPath.row]
return cell
}
}
tab3 does nothing. I just use it as an alternate tab to switch back and forth with
The removeAllObservers method is working correctly. The problem you're currently experiencing is caused by a minor missing detail: you never clear the contents of sneakerArray.
Note: there's no need for dispatch_async inside the event callback. The Firebase SDK calls this function on the main queue.
The official accepted Answer was posted by #vzsg. I'm just detailing it for the next person who runs into this issue. Up vote his answer.
What basically happens is when you use .childAdded, Firebase loops around and grabs each child from the node (in my case the node is "sneakersRef"). Firebase grabs the data, adds it to the array (in my case self.sneakerArray), then goes goes back to get the next set of data, adds it to the array etc. By clearing the array on the start of the next loop, the array will be empty once FB is done. If you switch scenes, you'll always have an empty array and the only data the ui will display is FB looping the node all over again.
Also I got rid of the dispatch_async call because he said FB calls the function in the main queue. I only used self.tableView.reloadData()
On a side note you should subscribe to firebase-community.slack.com. That's the official Firebase slack channel and that's where I posted this question and vzsg answered it.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//CLEAR THE ARRAY DATA HERE before you make your Firebase query
self.sneakerArray.removeAll()
let usersRef = self.ref.child("users")
//Listener
self.sneakersRef = usersRef.child("sneakers")
//I tried using the handle
//---self.handle = self.sneakersRef!.observeEventType(.ChildAdded...
self.sneakersRef!.observeEventType(.ChildAdded, withBlock: {(snapshot) in
if let dict = snapshot.value as? [String:AnyObject]{
let sneakerName = dict["sneakerName"] as? String
self.sneakerArray.append(sneakerName)
//I didn't have to use the dispatch_async here
self.tableView.reloadData()
}
}), withCancelBlock: nil)
}
Thank for asking this question.
I have also faced same issue in past. I have solved this issue as follow.
1) Define array which contain list of observer.
var aryObserver = Array<Firebase>()
2) Add observer as follow.
self.sneakersRef = usersRef.child("sneakers")
aryObserver.append( self.sneakersRef)
3) Remove observer as follow.
for fireBaseRef in self.aryObserver{
fireBaseRef.removeAllObservers()
}
self.aryObserver.removeAll() //Remove all object from array.

How to keep from having repeated items on a tableView due to a function triggered several times?

My issue is that I am querying elements in viewDidLoad and adding to the tableView. On another view, the user can add elements to Parse, that I am querying again on viewDidAppear to display only the newly added elements without requiring the view to re-load.
These elements are class of user with name, age etc...
On the viewDidAppear, I am querying the elements from Parse, going through a messy filtering to find out the ones that are not already displayed on the tableView, and then I trigger my function to add it.
It appears that even though I have removed duplicate items from my array, the function to set up my user (adding his name etc) gets called several times and consequently I still end up with duplicated items on my tableView.
The corresponding codes as below:
override func viewWillAppear(animated: Bool) {
var query = PFUser.query()
query!.whereKey("username", equalTo: PFUser.currentUser()!.username!)
query!.findObjectsInBackgroundWithBlock { (objects, NSError) -> Void in
if let objects = objects as? [PFObject] {
for member in objects {
if member["Network"] != nil {
var acceptedMembers: [String] = member["Network"] as! [String]
self.usernames = acceptedMembers
////
var query2 = PFUser.query()
query2?.findObjectsInBackgroundWithBlock({ (objects2, error2) -> Void in
if let objects2 = objects2 as? [PFUser] {
for otherUser in objects2 {
if contains(self.usernames, otherUser.username!) {
var arrayName1 = [String]()
arrayName1.append(otherUser.username!)
var arrayName2 = [String]()
for n in self.arrayMem {
arrayName2.append(n.name)
dispatch_async(dispatch_get_main_queue(), {
for extra in arrayName1 {
if contains(arrayName2, extra) {
} else {
var arrayName3 = [String]()
arrayName3.append(extra)
let unique3 = NSSet(array: arrayName3).allObjects
self.plusOne = unique3.first as! String
self.nameMember = self.plusOne as String
self.setName()
self.tableView.reloadData()
}
}
})
}
}}}}) }}}}
}
PS: I have tried cleaner solution to remove duplicate, converting my class to Hashable, then Equatable and using a simple function, however it turns up that this solution, messier as it is works more efficiently.
Anyway, the question here touches the function "self.setName()" that gets called repeatedly.
Do you have any idea how could this be fixed ?
Thank you infinitely,
A different approach would be to add a new object to your array after it's added in the other view controller and then add a new row for it into your table view. I would suggest communicating the newly formed object via an NSNotification.
View Controller with Table
Fetch your objects from Parse here, do it once in viewDidLoad
func fetchParseObjects () {}
Add an observer for this class to the NSNotificationCenter, also in viewDidLoad
NSNotificationCenter.defaultCenter().addObserver(self, selector: "objectUpdated:", name: "objectUpdated", object: nil)
Remove the observer in a deinit method
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "objectUpdated", object: nil)
}
func objectUpdated(notification: NSNotification) {
if let yourKindOfObject = notification.object as? YOUR_TYPE_HERE {
// Add, update or remove an item from the array that holds the original fetched data here based on the object
// Update your tableView accordingly (add, remove or update)
}
Where I have done something similarly I have created a custom object to hold some properties of what object needs to be updated and how it should be updated.
View Controller that Updates
Do what you normally do, but send a notification with a descriptive object to use to update your original data accordingly
NSNotificationCenter.defaultCenter().postNotificationName("objectUpdated", object: YOUR_OBJECT_WITH_UPDATE_DATA)

Firebase removing observers

I have a problem removing a Firebase observer in my code. Here's a breakdown of the structure:
var ref = Firebase(url:"https://MY-APP.firebaseio.com/")
var handle = UInt?
override func viewDidLoad() {
handle = ref.observeEventType(.ChildChanged, withBlock: {
snapshot in
//Do something with the data
}
}
override func viewWillDisappear(animated: Bool) {
if handle != nil {
println("Removed the handle")
ref.removeObserverWithHandle(handle!)
}
}
Now when I leave the viewcontroller, I see that "Removed the handle" is printed, but when I return to the viewcontroller, my observer is called twice for each event. When I leave and return again, it's called three times. Etc. Why is the observer not being removed?
I do also call ref.setValue("some value") later in the code, could this have anything to do with it?
Thought I was having this bug but in reality I was trying to remove observers on the wrong reference.
ORIGINAL CODE:
let ref: FIRDatabaseReference = FIRDatabase.database().reference()
var childAddedHandles: [String:FIRDatabaseHandle] = [:]
func observeFeedbackForUser(userId: String) {
if childAddedHandles[userId] == nil { // Check if observer already exists
// NOTE: - Error is caused because I add .child(userId) to my reference and
// do not when I call to remove the observer.
childAddedHandles[userId] = ref.child(userId).observeEventType(.ChildAdded) {
[weak self] (snapshot: FIRDataSnapshot) in
if let post = snapshot.value as? [String:AnyObject],
let likes = post["likes"] as? Int where likes > 0 {
self?.receivedFeedback(snapshot.key, forUserId: userId)
}
}
}
}
func stopObservingUser(userId: String) {
// THIS DOES NOT WORK
guard let cah = childAddedHandles.removeValueForKey(userId) else {
print("Not observing user")
return
}
// Error! I did not add .child(userId) to my reference
ref.removeObserverWithHandle(cah)
}
FIXED CODE:
func stopObservingUser(userId: String) {
// THIS WORKS
guard let cah = childAddedHandles.removeValueForKey(userId) else {
print("Not observing user")
return
}
// Add .child(userId) here
ref.child(userId).removeObserverWithHandle(cah)
}
Given it's April 2015 and the bug is still around I'd propose a workaround for the issue:
keep a reference of the handles (let's say in a dictionary and before initiating a new observer for the same event type check if the observer is already there.
Having the handles around has very low footprint (based on some official comments :) ) so it will not hurt that much.
Observers must be removed on the same reference path they were put upon. And for the same number of times they were issued, or use ref.removeAllObservers() for each path.
Here's a trick I use, to keep it tidy:
var fbObserverRefs = [FIRDatabaseReference]() // keep track of where observers defined.
...then, put observers in viewDidLoad():
fbObserverRefs.append(ref.child("user/\(uid)"))
fbObserverRefs.last!.observe(.value, with: { snap in
// do the work...
})
...then, in viewWillDisappear(), take care of removing any issued observers:
// Only true when popped from the Nav Controller stack, ignoring pushes of
// controllers on top.
if isBeingDismissed || isMovingFromParentViewController {
fbObserverRefs.forEach({ $0.removeAllObservers() })
}

Resources