2

UPDATE: ****little edit, i found out that actually the only thing that's freezing is retrieving the data from firebase, especially the images that needed to retrieve. the func initOberserver is for retrieving the data from firebase. so this needs to be done every time in background. but the tableview has to be usable in the mean while?****

I'm struggling a bit with background threads. I'm making a firebase app but my app freezes for a while when I upload something to firebase and retrieve it back to the app.

I have 2 constant's in a separate open file:

let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)

I have a viewcontroller: ListVC that retrieves the data from firebase with this function.

func initObservers() {

    //LoadingOverlay.shared.showOverlay(self.view)
    dispatch_async(backgroundQueue, {
        DataService.ds.REF_LISTS.observeEventType(.Value, withBlock: { snapshot in
            print(snapshot.value)

            self.lists = []

            if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {

                for snap in snapshots {
                    print("SNAP: \(snap)")

                    if let listDict = snap.value as? Dictionary<String, AnyObject> {
                        let key = snap.key
                        let list = List(listKey: key, dictionary: listDict)
                        self.lists.insert(list, atIndex:0)
                    }
                }
            }

            //LoadingOverlay.shared.hideOverlayView()
            self.tableView.reloadData()

        })

    })

}

And then I have a view controller addVC that posts the data to firebase with this function:

@IBAction func postListItem(sender: AnyObject) {

        if let addTitle = addTitle.text where addTitle != "", let addDescription = addDescription.text where addDescription != "", let addLocation = addLocation.text where addLocation != "" {

            dispatch_async(backgroundQueue, {
                self.postToFirebase(nil)

                dispatch_async(dispatch_get_main_queue(), { () -> Void in

                    let storyboard = UIStoryboard(name: "Main", bundle: nil)
                    let listVC = storyboard.instantiateViewControllerWithIdentifier("TBC") as! UITabBarController
                    listVC.selectedIndex = 1
                    self.presentViewController(listVC, animated: false, completion: nil)
                })
            })
        }
    }

func postToFirebase(imgUrl: String?) {

        LoadingOverlay.shared.showOverlay(self.overlayView)

        var post: Dictionary<String, AnyObject> = ["username": currentUsername, "description": addDescription.text!, "title": addTitle.text!, "location": addLocation.text!, "images": self.base64String]

        let firebasePost = DataService.ds.REF_LISTS.childByAutoId()
        firebasePost.setValue(post)

    }

As you can see I tried it with code and explanations I found on the Internet and as on Stack Overflow. But still if I open my app and go to the listVC it freezes after 2 sec for maybe 10 seconds, and when I post something, it also freezes for a while when it goes to the listVC.

I've also this code:

let uploadImage = image
        let imageData = UIImageJPEGRepresentation(uploadImage, 0.5)
        self.base64String = imageData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)

without this code, it doesn't freezes and it does 'it's thing' in a sec. but i need this code to post images to firebase?

adams.s
  • 185
  • 1
  • 14
  • Can you explain what you are trying to do? What's the function of the dispatch_async? It seems like you may have some extra code here - you may be able to move your presentViewController to a block that goes with firebase.setValue (with Block). – Jay May 03 '16 at 17:21
  • 2
    It looks like you are calling "self.tableView.reloadData()" on the background thread. You should wrap it into "dispatch_async(disatch_get_main_queue())" – almas May 03 '16 at 17:23
  • @Jay i have a button in my addVC when i click on it, it needs to post the data to firebase in the background and go to the listVC in the main, then the list VC has to retrieve the data in the background and when it is retrieved it has to display it on the main. In the meanwhile while it's retrieving the data from firebase the app has to be usable... – adams.s May 03 '16 at 17:28
  • @almas i tried to put it in the dispart_async main queue, but it didn't work. still freezes.. :( – adams.s May 03 '16 at 17:32
  • Ok - you may be fighting the built-in nature of Firebase here. If you add observers and use the blocks for functions, your app will continue to be usable while Firebase is 'doing it's thing' as Firebase is asynchronous in nature. If you write data to Firebase you don't need to go and get it - the observer will tell your app about that data. – Jay May 03 '16 at 17:33
  • @Jay so you mean i just leave all the background stuff because firebase will automatically do it 'on the background', but if i leave it, it still freezes a bit and it's comes to this code (see answer below) – adams.s May 03 '16 at 17:42
  • i found it, it works pretty fine on my iPhone but it goes really slow on the simulator and/or if i run it on my iPhone when it's plugged in the mac. should i make a new question of this? – adams.s May 05 '16 at 15:16

2 Answers2

5

Let's simplify things - this is conceptual but may help.

First, populate the tableView dataSource. There is a better way to initially populate the dataSource and reload the tableView so assume you only have a small dataset for this example.

self.lists = []

DataService.ds.REF_LISTS.observeEventType(.ChildAdded, withBlock: { snapshot in
  self.lists.insert(snapshot.value, atIndex:0)
  self.tableView.reloadData //this is for example only
}

Now later on, in your IBAction for example, write some data to Firebase

var post: Dictionary<String, AnyObject> = ["username": currentUsername, "description": addDescription.text!, "title": addTitle.text!, "location": addLocation.text!, "images": self.base64String]

let firebasePost = DataService.ds.REF_LISTS.childByAutoId()
firebasePost.setValue(post)

From here on, any time Firebase data is written to the observed node, your app is notified of that new data and it's added to the dataSource and the tableView updates.

If you need to load a tableview initially and then watch for .childAdded events after, here's a technique.

var lastNamesArray = [String]()
var initialLoad = true

//add observers so we load the current lastNames and are aware of any
// lastName additions

peopleRef.observeEventType(.ChildAdded, withBlock: { snapshot in

  let lastName = snapshot.value["last_name"] as? String      
  self.lastNamesArray.append(lastName)

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

//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

peopleRef.observeSingleEventOfType(.Value, withBlock: { snapshot in

    print("inital data loaded so reload tableView!  /(snapshot.childrenCount)")
    self.lastNamesTableView.reloadData()
    self.initialLoad = false        
})
Paul Beusterien
  • 27,542
  • 6
  • 83
  • 139
Jay
  • 34,438
  • 18
  • 52
  • 81
  • isn't this what i did? – adams.s May 03 '16 at 18:08
  • 1
    @adams.s Kinda... You've got a background queue going on which can be removed. Every time there's a change you are reloading all of your data which can dramatically increase loading times based on the size of your data. I would suggest updating that code and simplify the process. Also the post to firebase may become an issue as the view controller may get presented before there's any data (code executes faster than the Internet) so you may want to wait to present the controller when you know firebase data has been successfully written... Within the a firebase .setValue block for example. – Jay May 03 '16 at 18:25
  • ok, gonna try, but your first code gives me an error on the snapchot.value? – adams.s May 03 '16 at 18:33
  • @adams.s I really think you are running into issues when writing those images because of the background queue conflicting with firebase. And the whole getting out of sync with the UI. – Jay May 03 '16 at 18:36
  • 1
    That code is conceptual - You can convert the snapshot to a dict as you did in your code if that's what the tableView is expecting. I'm at 30k feet at the moment so I will update the answer when I get back on the ground in a couple of hours. The idea is to let Firebase do the asynchronous heavy lifting for you. – Jay May 03 '16 at 18:40
  • @Jay It seems really logic way (I think that I can use it with pull to refresh new datas, isn't it?). But first time loading nodes to array and reload process, Do I need to use **observeSingleEventOfType** in **viewDidLoad()?** Also it can be limited to make shorter loading time. – iamburak May 03 '16 at 19:29
  • 1
    If you download the FireChat example, and look for a variable called initialLoad in the code. The comments in the .ChildAdded and .Value section explain the process and code presented there is a great way to load your tableview initially and then watch for .ChildAdded events thereafter. If you don't find it or need a Swift version, I can post it later. – Jay May 03 '16 at 20:12
  • 1
    @tobeiosdev I have updated my answer with code to load the tableView initially and then watch for .childAdded events after. Leveraging Firebase's asynchronous nature will eliminate the need for background processes, threading and make the code much easier to maintain. – Jay May 04 '16 at 12:47
  • @Jay Thanks your attention, I have tried to do it as reverse and connect `tableView.reloadData()` process on a navigation item except first loading. But after scrolling when I went again top, new node reloading itself. I think this is `dequeueReusableCell` working process. In addition, If I have `yyyyMMddHHmmss` or `NSDate().timeIntervalSince1970` child format for each post. How Can I re-modify this code to provide `orderByChild` as reverse? You can reach my some changes with this gist link: https://gist.github.com/tobeiosdev/44eb57e7fc7ccc182900f67d8cef0b95 – iamburak May 04 '16 at 21:13
  • At the end, I modified it https://gist.github.com/tobeiosdev/1ac87bfd4ed0bd33d35e21dd7b810c6e – iamburak May 04 '16 at 22:07
  • @tobeiosdevSee my answer to this question orderBy sort descending [Sort Order](http://stackoverflow.com/questions/36589452/in-firebase-how-can-i-query-the-most-recent-10-child-nodes/36665442#36665442) – Jay May 04 '16 at 22:38
2

In your last function, postToFirebase, you make an UI operation under a background thread, seems this is your mistake..speaking about

LoadingOverlay.shared.showOverlay(self.overlayView)

As written in comments below your question, same thing happened to reloadData() at the end of your first function called initObservers.

It is strongly recommended not to update UI controls etc from a background thread (e.g. a timer, comms etc). This can be the cause of crashes which are sometimes very hard to identify. Instead use these to force code to be executed on the UI thread (which is always the “main” thread).

dispatch_async(dispatch_get_main_queue()) {
    // this code will be running on the main thread
}

UPDATE: About your last question to UIImageJPEGRepresentation this is an example to how launch it:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
    let getImage =  UIImage(data: NSData(contentsOfURL: NSURL(string: self.remoteImage)))
    UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)

    dispatch_async(dispatch_get_main_queue()) {
        self.image?.image = getImage
        return
    }
}

Swift 3:

DispatchQueue.global(attributes: .qosBackground).async {
    print("This is run on the background queue")

    DispatchQueue.main.async {
        print("This is run on the main queue, after the previous code in outer block")
    }
}
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • what do you mean by that? and the post to firebase doesn't have anything to do with the retrieving from firebase in the listVC? so when i open my app and go to the listVC it still freezing for a while just to retrieve the data. so i think something is not running in the background like it should? – adams.s May 03 '16 at 17:36
  • ow i see, yeah the LoadingOverlay... i deleted now, it was not necessary anymore.. but still doesn't fix the problem... – adams.s May 03 '16 at 17:38
  • Look now..everything in your code concern UI modification of any type must be running on the main thread, otherwise you will have this lacks... – Alessandro Ornano May 03 '16 at 17:40
  • what do you mean with 'imagePath' and 'remoteImage'? – adams.s May 03 '16 at 18:16
  • dont focus your attention do details, it's just an example. In this example you can see how to organize dispatch_async around UIImageJPEGRepresentation – Alessandro Ornano May 03 '16 at 18:18
  • ok, got it. But still my app freezes for a while when i open up the ListScreen, but i don't understand wich UI elements then go to the background thread? the tableview.reloaddata is now in the main queue block. the rest in the function of initObservers has to be on the background? and initObservers is the only function i call in viewDidLoad – adams.s May 03 '16 at 18:26
  • i found it, it works pretty fine on my iPhone but it goes really slow on the simulator and/or if i run it on my iPhone when it's plugged in the mac. should i make a new question of this? – adams.s May 05 '16 at 15:16
  • I think it's not necessary because the simulator is not considered reliable. – Alessandro Ornano May 05 '16 at 19:26
  • Sometimes firebase returns the snapshot in the main thread. Add the processing logic of the snapshot in a background queue. – user1046037 Dec 01 '16 at 12:08