Swift Firebase loading records with long delay

2020-05-07 00:43发布

I'm building a Swift app using Firebase, and I'm new to both so be gentle. Currently, when I open the app, it syncs the entire database again and causes a 2 or 3 second lag where the user stares at an empty tableview. How can I speed this up?

Any thoughts?

My code:

My loadContacts function

func loadContact(snap : FIRDataSnapshot) -> Contact {
let key = snap.key
let contact = (snap.value) as? NSDictionary

let c1 = Contact(
    id: (contact?["id"] as? String)!,
    firebasekey: key,
    first_name: (contact?["First Name"] as? String)!,
    middle_name: (contact?["Middle Name"] as? String)!,
    last_name: (contact?["Last Name"] as? String)!,
    suffix: (contact?["Suffix"] as? String)!,
    company: (contact?["Company"] as? String)!,
    phone_labe1: (contact?["Phone Label 1"] as? String)!,
    phone1: (contact?["Phone 1"] as? String)!,
    phone_label2: (contact?["Phone Label 2"] as? String)!,
    phone2: (contact?["Phone 2"] as? String)!,
    email_label1: (contact?["Email Label 1"] as? String)!,
    email1: (contact?["Email 1"] as? String)!,
    email_label2: (contact?["Email Label 2"] as? String)!,
    email2: (contact?["Email 2"] as?  String)!,
    social: (contact?["Social Security Number"] as? String)!,
    dob: (contact?["Date of Birth"] as? String)!,
    street: (contact?["Street"] as? String)!,
    city: (contact?["City"] as? String)!,
    zip: (contact?["ZIP and Postal Code"] as? String)!,
    state: (contact?["State and Province"] as? String)!,
    reg_number: (contact?["Reg Num"] as? String)!,
    stable_reg_number: (contact?["Stable Reg Num"] as? String)!,
    emergency_contact: (contact?["Emergency Contact"] as? String)!,
    emergency_phone: (contact?["Emergency Phone"] as? String)!,
    drivers_license: (contact?["Driver's License Num"] as? String)!,
    insurance_carrier: (contact?["Insurance Carrier"] as? String)!,
    details: (contact?["Details"] as? String)!,
    insurance_exp: (contact?["Insurance Expiration Date"] as? String)!,
    insurance_group: (contact?["Insurance Group Num"] as? String)!,
    insurance_member: (contact?["Insurnace Member Num"] as? String)!, // spelled wrong in database
    job_title: (contact?["Job Title"] as? String)!,
    date_modified: (contact?["Modified"] as? String)!,
    keywords: [],
    notes: []
)

return c1;
}

And in my contact table view

import UIKit
import Firebase

class ContactTableViewController: UITableViewController, UISearchBarDelegate, UISearchDisplayDelegate {
// MARK: Properties
var contactSearchResults : [Contact] = []

// FIRDatabase.database().persistenceEnabled = true
let contactRef = FIRDatabase.database().reference().child("contacts")

override func viewDidLoad() {

    contactRef.queryOrdered(byChild: "Last Name").observe(.childAdded) { (snap: FIRDataSnapshot) in
        contacts.append(loadContact(snap: snap))
        self.tableView.reloadData()
    }

    contactRef.queryOrdered(byChild: "Last Name").observe(.childChanged) { (snap: FIRDataSnapshot) in
        // this code here is wrong, but it doesn't matter for demonstration purposes
        contacts.append(loadContact(snap: snap))
        self.tableView.reloadData()
    }

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem()
}

My database has structured like

enter image description here

Contacts (my problem area) has about 4000 records in it with 33 individual children values each.

1条回答
仙女界的扛把子
2楼-- · 2020-05-07 01:47

There are number of issues with the code in the question that will affect performance.

1) The child added event fires for every child initially and then for any children added afterwards. If you have 1000 contacts, that means on startup, you are refreshing the table view 1000 times. You would be better off loading all of the contacts by .value and iterating over them, adding to an array, and then refreshing the tableView when done

2) Going along with #1, if you just want to order them by last name, observe the node by .value, iterate over the snapshot to populate the array and then sort, then reload the tableView. That will be significantly faster.

3) The childChanged event: there's no reason to query by last name as when a child changes, it notifies you of just that child and again, you can sort in code if needed

4) Firebase queries are very 'heavy' by comparison to observe events. In this case you really are not querying for any in particular so that should be eliminated. Just use observe events to load a node's worth of data and use a query when you are looking for a subset of that data.

*note that a lot of this depends on how many contacts you have. For a few thousand these suggestions work fine.

So there's a really cool design pattern that makes populating an initial dataset really clean. I think one of the Firebasers wrote it as part of an example app.

We start with defining an class level variable called initialLoad and set it to true. Then we use a childAdded observe to load in all the contacts and populate our tableView dataSource array.

var initialLoad = true

contactsRef.observeEventType(.ChildAdded, withBlock: { snapshot in
     self.handleChildAdded(withSnap: snapshot)
})

and the function to handle childAdded events and will initially load each child one at a time, and watch for children added after that.

func handleChildAdded(withSnap: snapshot: FDataSnapshot! ) {
    let myContact = Contact()
    myContact.initWithSnap(snapshot)
    myDataSourceArray.append(myContact)

    //upon first load, don't reload the tableView until all children are loaded
    if ( self.initialLoad == false ) { 
            self.contactsTableView.reloadData()
    }    
}

Now the tricky bit

//this .Value event will fire AFTER the child added events to reload the tableView 
//  the first time and to set subsequent childAdded events to load after each child is
//    added in the future
contactsRef.observeSingleEventOfType(.Value, withBlock: { snapshot in      
     print("inital data loaded so reload tableView!")
     self.itemsTableView.reloadData()
     self.initialLoad = false
})

The the 10k foot view:

The key here is that .value events fire AFTER .childAdded events so we are leveraging that to set the initialLoad variable to false AFTER all of the child added events complete.

The above code is Swift 2/3, Firebase 2 but it gives you the concept of how to do your initial load.

查看更多
登录 后发表回答