Как рефакторингировать дубликаты идентификаторов документов Firestore в Swift?

# #ios #swift #firebase #duplicates

Вопрос:

Я создаю свое самое первое приложение для IOS с помощью Cloud Firestore, и мне приходится неоднократно делать одни и те же запросы к своей базе данных. Я хотел бы избавиться от повторяющихся строк кода. Это примеры функций, в которых дублируются идентификаторы документов. Также я использую другие запросы, такие как .delete() , .addSnapshotListener() , .setData() . Должен ли я каким-то образом рефакторировать все эти запросы или оставить их, потому что они использовались только один раз?

     @objc func updateUI() {

        inputTranslate.text = ""
        inputTranslate.backgroundColor = UIColor.clear

        let user =  Auth.auth().currentUser?.email
        let docRef = db.collection(K.FStore.collectionName).document(user!)
        docRef.getDocument { [self] (document, error) in

        if let document = document, document.exists {

            let document = document
            let label = document.data()?.keys.randomElement()!
            self.someNewWord.text = label
            
                // Fit the label into screen
            self.someNewWord.adjustsFontSizeToFitWidth = true
            
            self.checkButton.isHidden = false
            self.inputTranslate.isHidden = false
            self.deleteBtn.isHidden = false

        } else {

            self.checkButton.isHidden = true
            self.inputTranslate.isHidden = true
            self.deleteBtn.isHidden = true
            self.someNewWord.adjustsFontSizeToFitWidth = true
            self.someNewWord.text = "Add your first word to translate"

            updateUI()

            }
     }
}
          
@IBAction func checkButton(_ sender: UIButton) {
    
    let user =  Auth.auth().currentUser?.email
    let docRef = db.collection(K.FStore.collectionName).document(user!)
    docRef.getDocument { (document, error) in
        let document = document
        
        let label = self.someNewWord.text!
        let currentTranslate = document!.get(label) as? String

        let translateField = self.inputTranslate.text!.lowercased().trimmingCharacters(in: .whitespaces)

        if translateField == currentTranslate {
            self.inputTranslate.backgroundColor = UIColor.green
            DispatchQueue.main.asyncAfter(deadline: .now()   0.2) { [self] in
                self.inputTranslate.backgroundColor = UIColor.clear
                updateUI()}

        } else {
            self.inputTranslate.backgroundColor = UIColor.red
            self.inputTranslate.shakingAndRedBg()
            DispatchQueue.main.asyncAfter(deadline: .now()   0.2) { [self] in
                self.inputTranslate.backgroundColor = UIColor.clear
                self.inputTranslate.text = ""
            }
        }
    }
}

func deletCurrentWord () {

    let user =  Auth.auth().currentUser?.email
    let docRef = db.collection(K.FStore.collectionName).document(user!)
    docRef.getDocument { (document, err) in
        let document = document
        if let err = err {
            print("Error getting documents: (err)")
            
        } else {
              
            let  array = document!.data()
            let counter = array!.count
                
        if counter == 1 {
                    
                    // The whole document will deleted together with a last word in list.
            let user =  Auth.auth().currentUser?.email
            self.db.collection(K.FStore.collectionName).document(user!).delete() { err in
                if let err = err {
                    print("Error removing document: (err)")
                } else {
                    self.updateUI()
                }
            }
                        
        } else {
            // A current word will be deleted

            let user =  Auth.auth().currentUser?.email
            let wordForDelete  = self.someNewWord.text!
                    
            self.db.collection(K.FStore.collectionName).document(user!).updateData([
                wordForDelete: FieldValue.delete()
                    ]) { err in
                            
                    if let err = err {
                        print("Error updating document: (err)")
                    } else {
                        self.updateUI()
                        }
                    }
                }
            }
    }
}
 

Другой пример запроса

        func loadMessages() {
            let user =  Auth.auth().currentUser?.email
            let docRef = db.collection(K.FStore.collectionName).document(user!)
            docRef.addSnapshotListener { (querySnapshot, error) in
            
            self.messages = []
            
            if let e = error {
                print(e)
            } else {
                if let snapshotDocuments = querySnapshot?.data(){
                    
                    for item in snapshotDocuments {
                        
                        if let key = item.key as? String, let translate = item.value as? String {
                                                   
                            let newMessage = Message(key: key, value: translate)
                            self.messages.append(newMessage)
                        }
                    }
                        DispatchQueue.main.async {
                            self.messages.sort(by: {$0.value > $1.value})
                            self.secondTableView.reloadData()
                            let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
                            self.secondTableView.scrollToRow(at: indexPath, at: .top, animated: false)
                            
                        }
                    }
                }
            }
        }
}
 

Ответ №1:

 enum Error {
    case invalidUser
    case noDocumentFound
}

func fetchDocument(onError: @escaping (Error) -> (), completion: @escaping (FIRQueryDocument) -> ())  {
    guard let user = Auth.auth().currentUser?.email else {
        onError(.invalidUser)
        return
    }
    db.collection(K.FStore.collectionName).document(user).getDocument { (document, error) in
        if let error = error {
            onError(.noDocumentFound)
        } else {
            completion(document)
        }
    }
}

func updateUI() {
    fetchDocument { [weak self] error in
        self?.hideShowViews(shouldHide: true, newWordText: nil)
    } completion: { [weak self] document in
        guard document.exists else {
            self?.hideShowViews(shouldHide: true, newWordText: nil)
            return
        }
        self?.hideShowViews(shouldHide: false, newWordText: document.data()?.keys.randomElement())
    }
}


private func hideShowViews(shouldHide: Bool, newWordText: String?) {
    checkButton.isHidden = shouldHide
    inputTranslate.isHidden = shouldHide
    deleteBtn.isHidden = shouldHide
    someNewWord.adjustsFontSizeToFitWidth = true
    someNewWord.text = newWordText ?? "Add your first word to translate"
}
 

updateUI Метод можно легко реорганизовать, используя простой оператор guard, а затем вывести общий код в отдельную функцию. Я также использовал [weak self] , чтобы не происходило утечки памяти или циклов сохранения.

Теперь вы можете следовать аналогичному подходу для остальных методов.

  1. Используйте guard let вместо if let того, чтобы избегать вложенности.
  2. Используйте [weak self] для асинхронных вызовов, чтобы избежать утечки памяти.
  3. Выделите общий код в отдельный метод и используйте Bool флаг для скрытия/отображения представлений.

Обновление для шага 3: Вы можете создавать методы, аналогичные асинхронным API для getDocument() или delete() и т.д., И по завершении вы можете обновить пользовательский интерфейс или выполнить любое действие. Вы также можете создать отдельный класс, переместить fetchDocument() туда и другие подобные методы и использовать их.

Комментарии:

1. Шаги 1 и 2 были полезны! Я благодарен вам! Не могли бы вы подробнее рассказать о шаге 3, как поместить общий код в отдельный метод? Ссылка на базу данных-это оператор if-else, и я понятия не имею, как разделить его на разные методы. Спасибо

2. Вы имеете в func deletCurrentWord() виду ?

3. Я имею в виду все методы, в которых это используется, включая функцию deletCurrentWord(). Это обычные строки кода, в которых пользователь = Auth.auth().Текущий пользователь? .электронная почта пусть docRef = db.коллекция(K. FStore.Имя коллекции).документ(пользователь!) docRef.getDocument { [слабое я] (документ, ошибка) в охраннике пусть документ = документ Я не могу переместить эту ссылку в другой метод, потому что это часть if-else заявление. Ты знаешь какой-нибудь способ сделать это?

4. ух ты, это действительно то, что я искал! Этот способ рефакторинга очень интересен, он для меня в новинку. Похоже, что этот способ действительно подходит, мне нужно исправить только одну ошибку «Привязка переменных в состоянии требует инициализатора» в этой строке.Пусть документ, document. exists { Вы знаете, как это исправить?

5. Это потому, что документ не является необязательным, поэтому вы можете использовать guard document.exists else { его вместо этого. Кроме того, пожалуйста, повысьте голос и отметьте ответ как принятый. Спасибо.