[트러블 슈팅] 비동기 처리 1

z-wook·2023년 8월 5일
0

트러블 슈팅

목록 보기
1/6
post-thumbnail

문제 상황


할 일 추가 후 TableView에 나타나기까지 상당한 시간이 걸려서 사용자 경험을 저하시킨다.


문제 상황까지의 순서

  1. UITextField에 Text를 입력 후 상단의 추가 버튼을 누르면 Firestore에 저장
  2. Firestore에 저장을 성공하면 CoreData를 사용하여 디바이스에 저장
  3. 1~2 단계를 성공적으로 수행 후 UITableView reload

즉, 사용자 생성 요청 -> 서버에 생성 요청 -> 성공하면 화면 갱신 순서로 진행되는 비관적 업데이트 방법입니다.


문제 상황의 코드

  • FirestoreManager
final class FirestoreManager {
    private let fireManager = FirebaseManager()
    private let dataBase = Firestore.firestore().collection("User")
    
    func saveTodo(data: Todo) async -> Bool {
        guard let uid = fireManager.getUID() else { return false }
        
        do {
            try await dataBase.document(uid)
                .collection(data.date).document(data.id.uuidString).setData(
                    [
                        "id": data.id.uuidString,
                        "content": data.content,
                        "date": data.date,
                        "priority": data.priority,
                        "done": data.done,
                        "alarm": data.alarm ?? "nil"
                    ]
                )
            print("Document successfully added!")
            return true
        } catch {
            print("Error adding document: \(error.localizedDescription)")
            return false
        }
    }
}

  • TodoManager
final class TodoManager {
    private let storeManager = FirestoreManager()
    
    func saveTodo(saveTodo: Todo) async -> Bool {
        let result = Task {
            if await storeManager.saveTodo(data: saveTodo) == true {
                if CoreDataManager.saveTodoData(todo: saveTodo) { return true }
            }
            return false
        }
        return await result.value
    }
}

  • PlannerDetailViewController
@objc func didTappedAddTodoButton() {
        guard let date = viewModel?.getDate,
              let text = inputTodoTextField.text else { return }
        if todoManager.textFieldIsFullWithBlank(text: text) == false {
            let strDate = DateFormatter.formatTodoDate(date: date)
            let todo = Todo(
                content: text,
                date: strDate
            )
            Task {
                let saveResult = await todoManager.saveTodo(saveTodo: todo)
                if saveResult == true {
                    tableView.reloadData()
                } else {
                    showAlert()
                }
            }
        }
        inputTodoTextField.text?.removeAll()
        addTodoButton.isEnabled = false
    }

원인 추론

위의 과정을 진행함에 있어서 실패할 수 있는 가능성이 있기 때문에 최대한 안전하게 하려고 성공 여부에 따라 이후 작업을 진행시키려고 했다.

async/await를 사용하여 비동기 처리를 한 것처럼 보이지만
Firestore에 저장 후 -> CoreData로 디바이스에 저장 후 -> TableView reload의 과정이 일련의 순서를 지키고 실행되고 있기 때문에 동기적 처리와 같이 동작해서 네트워크 속도가 느린 상황에서는 즉시 추가되지 않기 때문에 사용자 경험을 저하시키게 된다.


수정 및 조치

  • 거의 즉시 실행 성공 여부를 return 받는 CoreData를 사용하여 디바이스에 저장 후 성공하게 되면 TableView를 reload 하여 UI 업데이트를 우선으로 함으로써 사용자에게 빠른 피드백을 줄 수 있도록 만든다.
  • todoManager.saveTodo 메서드 내부에서는 Firestore에 저장을 비동기적으로 진행하는 방법으로 사용자 경험성을 높인다.
  • Firestore에 저장을 실패할 경우를 대비하여 최대 3번까지 재전송하도록 코드를 수정한다.

즉, 사용자 수정 요청 -> 화면 갱신 -> 서버에 수정 요청 순서로 진행하는 낙관적 업데이트 방법을 사용


⭐️ 문제 해결 ⭐️

  • FirestoreManager
final class FirestoreManager {
    private let fireManager = FirebaseManager()
    private let dataBase = Firestore.firestore().collection("User")
    
    func saveTodo(data: Todo) async {
        guard let uid = fireManager.getUID() else { return }
        
        var retryCount = 0
        while retryCount < 3 {
            do {
                try await dataBase.document(uid)
                    .collection(data.date).document(data.id.uuidString).setData(
                        [
                            "id": data.id.uuidString,
                            "content": data.content,
                            "date": data.date,
                            "priority": data.priority,
                            "done": data.done,
                            "alarm": data.alarm ?? "nil"
                        ]
                    )
                print("Document successfully added!")
                return
            } catch {
                print("Error adding document: \(error.localizedDescription)")
                retryCount += 1
            }
        }
        print("Max retry count reached, document could not be added.")
    }
}

  • TodoManager
final class TodoManager {
    private let storeManager = FirestoreManager()
    
    func saveTodo(saveTodo: Todo) -> Bool {
        if CoreDataManager.saveTodoData(todo: saveTodo) {
            Task {
                await storeManager.saveTodo(data: saveTodo)
            }
            return true
        }
        return false
    }
}

  • PlannerDetailViewController
@objc func didTappedAddTodoButton() {
        guard let date = viewModel?.getDate,
              let text = inputTodoTextField.text else { return }
        if todoManager.textFieldIsFullWithBlank(text: text) == false {
            let strDate = DateFormatter.formatTodoDate(date: date)
            let todo = Todo(
                content: text,
                date: strDate
            )
            let saveResult = todoManager.saveTodo(saveTodo: todo)
            if saveResult == true {
                tableView.reloadData()
            } else {
                showAlert()
            }
        }
        inputTodoTextField.text?.removeAll()
        addTodoButton.isEnabled = false
    }


Todo 생성 버튼을 클릭하면 UI에 즉시 업데이트되는 것을 볼 수 있습니다!

profile
🍎 iOS Developer

1개의 댓글

comment-user-thumbnail
2023년 8월 5일

유익한 자료 감사합니다.

답글 달기