Почему мое приложение зависает, когда я загружаю данные в firebase?

ОБНОВЛЕНИЕ: **** небольшое редактирование, я обнаружил, что на самом деле единственное, что зависает, — это получение данных из firebase, особенно изображений, которые необходимо получить. функция initOberserver предназначена для получения данных из firebase. поэтому это нужно делать каждый раз в фоновом режиме. но в то же время табличное представление должно быть пригодным для использования? ****

Я немного борюсь с фоновыми потоками. Я делаю приложение firebase, но мое приложение на некоторое время зависает, когда я загружаю что-то в firebase и возвращаю его обратно в приложение.

У меня есть 2 константы в отдельном открытом файле:

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

У меня есть контроллер просмотра: ListVC, который извлекает данные из firebase с помощью этой функции.

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()

        })

    })

}

И затем у меня есть контроллер представления addVC, который отправляет данные в firebase с помощью этой функции:

@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)

    }

Как видите, я попробовал это с кодом и объяснениями, которые нашел в Интернете, а также в Stack Overflow. Но все же, если я открываю свое приложение и перехожу к listVC, оно зависает через 2 секунды, может быть, на 10 секунд, и когда я что-то публикую, оно также зависает на некоторое время, когда оно переходит в listVC.

У меня также есть этот код:

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

без этого кода он не зависает и делает «свое дело» за секунду. но мне нужен этот код для публикации изображений в firebase?


person adams.s    schedule 03.05.2016    source источник
comment
Можете ли вы объяснить, что вы пытаетесь сделать? Какова функция dispatch_async? Похоже, у вас может быть дополнительный код — вы можете переместить свой presentViewController в блок, который идет с firebase.setValue (с блоком).   -  person Jay    schedule 03.05.2016
comment
Похоже, вы вызываете self.tableView.reloadData() в фоновом потоке. Вы должны обернуть его в dispatch_async(disatch_get_main_queue())   -  person almas    schedule 03.05.2016
comment
@Jay У меня есть кнопка в моем addVC, когда я нажимаю на нее, она должна отправить данные в firebase в фоновом режиме и перейти к listVC в основном, тогда список VC должен получить данные в фоновом режиме и когда они извлекается, он должен отображать его на главном. Тем временем, пока он извлекает данные из firebase, приложение должно быть пригодным для использования...   -  person adams.s    schedule 03.05.2016
comment
@almas я пытался поместить его в основную очередь dispart_async, но это не сработало. все равно зависает.. :(   -  person adams.s    schedule 03.05.2016
comment
Хорошо, возможно, вы боретесь со встроенной природой Firebase. Если вы добавите наблюдателей и будете использовать блоки для функций, ваше приложение будет продолжать использоваться, пока Firebase «делает свое дело», поскольку Firebase по своей природе асинхронен. Если вы записываете данные в Firebase, вам не нужно идти и получать их — наблюдатель сообщит вашему приложению об этих данных.   -  person Jay    schedule 03.05.2016
comment
@Jay, так ты имеешь в виду, что я просто оставлю все фоновые вещи, потому что firebase автоматически сделает это «на заднем плане», но если я оставлю это, он все равно немного зависнет, и дело доходит до этого кода (см. Ответ ниже)   -  person adams.s    schedule 03.05.2016
comment
Я нашел его, он отлично работает на моем iPhone, но работает очень медленно на симуляторе и / или если я запускаю его на своем iPhone, когда он подключен к Mac. я должен сделать новый вопрос об этом?   -  person adams.s    schedule 05.05.2016


Ответы (2)


Давайте упростим - это концептуально, но может помочь.

Сначала заполните источник данных tableView. Существует лучший способ первоначально заполнить dataSource и перезагрузить tableView, поэтому предположим, что у вас есть только небольшой набор данных для этого примера.

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
}

Теперь позже, например, в вашем IBAction, запишите некоторые данные в 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)

С этого момента каждый раз, когда данные Firebase записываются на наблюдаемый узел, ваше приложение уведомляется об этих новых данных и добавляется в источник данных и обновляется tableView.

Если вам нужно сначала загрузить табличное представление, а затем отслеживать события .childAdded, вот метод.

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        
})
person Jay    schedule 03.05.2016
comment
разве это не то, что я сделал? - person adams.s; 03.05.2016
comment
@adams.s Своего рода ... У вас есть фоновая очередь, которую можно удалить. Каждый раз, когда происходит изменение, вы перезагружаете все свои данные, что может значительно увеличить время загрузки в зависимости от размера ваших данных. Я бы предложил обновить этот код и упростить процесс. Кроме того, сообщение в firebase может стать проблемой, поскольку контроллер представления может быть представлен до того, как появятся какие-либо данные (код выполняется быстрее, чем в Интернете), поэтому вы можете подождать, чтобы представить контроллер, когда вы знаете, что данные firebase были успешно записаны... Например, в блоке firebase .setValue. - person Jay; 03.05.2016
comment
хорошо, попробую, но ваш первый код выдает ошибку в snapchot.value? - person adams.s; 03.05.2016
comment
@adams.s Я действительно думаю, что вы сталкиваетесь с проблемами при написании этих изображений из-за фоновой очереди, конфликтующей с firebase. И все это выходит из синхронизации с пользовательским интерфейсом. - person Jay; 03.05.2016
comment
Этот код является концептуальным. Вы можете преобразовать моментальный снимок в диктовку, как вы это сделали в своем коде, если это то, что ожидает tableView. Сейчас я на высоте 30 тысяч футов, поэтому я обновлю ответ, когда вернусь на землю через пару часов. Идея состоит в том, чтобы позволить Firebase выполнять асинхронную тяжелую работу за вас. - person Jay; 03.05.2016
comment
@Jay Это кажется действительно логичным способом (я думаю, что могу использовать его с pull для обновления новых данных, не так ли?). Но первая загрузка узлов в массив и процесс перезагрузки. Нужно ли использовать observeSingleEventOfType в viewDidLoad()? Также его можно ограничить, чтобы сократить время загрузки. - person iamburak; 03.05.2016
comment
Если вы загрузите пример FireChat и найдёте в коде переменную с именем initialLoad. Комментарии в разделах .ChildAdded и .Value объясняют процесс и представленный код. Это отличный способ сначала загрузить табличное представление, а затем отслеживать события .ChildAdded. Если вы не найдете его или вам нужна версия Swift, я могу опубликовать ее позже. - person Jay; 03.05.2016
comment
@tobeiosdev Я обновил свой ответ кодом, чтобы сначала загрузить tableView, а затем отслеживать события .childAdded. Использование асинхронного характера Firebase устранит необходимость в фоновых процессах, многопоточности и значительно упростит поддержку кода. - person Jay; 04.05.2016
comment
@Jay Спасибо за внимание, я попытался сделать это в обратном порядке и подключить tableView.reloadData() процесс к элементу навигации, кроме первой загрузки. Но после прокрутки, когда я снова пошел вверх, новый узел перезагрузился. Я думаю, что это dequeueReusableCell рабочий процесс. Кроме того, если у меня есть дочерний формат yyyyMMddHHmmss или NSDate().timeIntervalSince1970 для каждого сообщения. Как я могу повторно изменить этот код, чтобы предоставить orderByChild в обратном порядке? Вы можете ознакомиться с некоторыми моими изменениями по этой основной ссылке: gist.github.com/tobeiosdev/44eb57e7fc7ccc182900f67d8cef0b95 - person iamburak; 05.05.2016
comment
В конце я изменил его gist.github.com/tobeiosdev/1ac87bfd4ed0bd33d35e21dd7b810c6e. - person iamburak; 05.05.2016
comment
@tobeiosdevПосмотрите мой ответ на этот вопрос в порядке сортировки по убыванию Порядок сортировки - person Jay; 05.05.2016

В вашей последней функции, postToFirebase, вы выполняете операцию пользовательского интерфейса в фоновом потоке, кажется, это ваша ошибка.. говоря о

LoadingOverlay.shared.showOverlay(self.overlayView)

Как написано в комментариях под вашим вопросом, то же самое произошло с reloadData() в конце вашей первой функции под названием initObservers.

Настоятельно рекомендуется не обновлять элементы управления пользовательского интерфейса и т. д. из фонового потока (например, таймера, связи и т. д.). Это может быть причиной сбоев, которые иногда очень трудно идентифицировать. Вместо этого используйте их для принудительного выполнения кода в потоке пользовательского интерфейса (который всегда является «основным» потоком).

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

ОБНОВЛЕНИЕ. Что касается вашего последнего вопроса к UIImageJPEGRepresentation, это пример того, как его запустить:

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

Свифт 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")
    }
}
person Alessandro Ornano    schedule 03.05.2016
comment
Что ты имеешь в виду? а сообщение в firebase не имеет ничего общего с получением из firebase в listVC? поэтому, когда я открываю свое приложение и перехожу к listVC, оно все еще зависает на некоторое время, просто чтобы получить данные. поэтому я думаю, что что-то не работает в фоновом режиме, как должно? - person adams.s; 03.05.2016
comment
теперь я вижу, да, LoadingOverlay ... я удалил сейчас, он больше не был нужен ... но все равно не решает проблему ... - person adams.s; 03.05.2016
comment
Посмотрите теперь.. все в вашем коде, касающееся модификации пользовательского интерфейса любого типа, должно выполняться в основном потоке, иначе у вас не будет этого... - person Alessandro Ornano; 03.05.2016
comment
что вы имеете в виду под «imagePath» и «remoteImage»? - person adams.s; 03.05.2016
comment
не акцентируйте внимание на деталях, это просто пример. В этом примере вы можете увидеть, как организовать dispatch_async вокруг UIImageJPEGRepresentation. - person Alessandro Ornano; 03.05.2016
comment
хорошо понял. Но все же мое приложение на некоторое время зависает, когда я открываю ListScreen, но я не понимаю, какие элементы пользовательского интерфейса затем переходят в фоновый поток? tableview.reloaddata теперь находится в основном блоке очереди. остальное в функции initObservers должно быть на фоне? и initObservers — единственная функция, которую я вызываю в viewDidLoad. - person adams.s; 03.05.2016
comment
Я нашел его, он отлично работает на моем iPhone, но работает очень медленно на симуляторе и / или если я запускаю его на своем iPhone, когда он подключен к Mac. я должен сделать новый вопрос об этом? - person adams.s; 05.05.2016
comment
Я думаю, что это не обязательно, потому что симулятор не считается надежным. - person Alessandro Ornano; 05.05.2016
comment
Иногда firebase возвращает снимок в основном потоке. Добавьте логику обработки моментального снимка в фоновую очередь. - person user1046037; 01.12.2016