[TIL] 11.28

Junyoung_Hong·2023년 11월 28일
0

TIL_11월

목록 보기
5/5
post-thumbnail

1. 비동기 UI 로직 구현

프로젝트에서 데이터 통신이 이루어질 때, @escaping 클로저를 사용하여 데이터 통신이 완전히 끝난 후 UI를 업데이트하는 것은 비동기 UI 로직 구현의 일반적인 방법이다.

예를 들어 Firebase와의 네트워크 호출 같은 경우, 요청이 시작되고 완료될 때까지 앱의 다른 부분은 계속 실행된다. 이는 네트워크 통신이 메인 스레드를 차단하지 않고 백그라운드에서 수행되기 때문이다. 이런 비동기 작업이 완료되었을 때 UI를 업데이트 해야 하는데, 이 때 @escaping 클로저가 중요한 역할을 한다.

@escaping 클로저는 함수의 실행이 완료된 후에도 호출될 수 있어, 비동기 작업이 완료된 후에 실행할 코드를 포함할 수 있다. 예를 들어, Firebase로부터 데이터를 성공적으로 가져온 후에, 해당 데이터를 사용하여 테이블 뷰를 리로드하거나, 뷰 컴포넌트의 데이터를 업데이트하는 것 등이 해당한다.

그러나 UI 업데이트는 메인 스레드에서 수행되어야 한다. 따라서 클로저 내부에서 UI 업데이트 로직을 작성할 때는 주로 DispatchQueue.main.async를 사용하여 메인 스레드에서 실행되도록 해야 한다.

1-1. 코드 비교

DispatchQueue.main.async 사용X

func readNoticeBoard(completion: @escaping (Bool) -> Void) {
        
    let ref = Database.database().reference().child("noticeBoards").child(club.id)
        
    ref.getData(completion: { (error, snapshot) in
        if let error = error {
            print("Error getting data: \(error)")
            completion(false)
            return
        }
            
        guard let value = snapshot?.value as? [String: Any] else {
            self.delegate?.reloadData()
            completion(false)
            return
        }
            
        var newNoticeBoards: [NoticeBoard] = DataModelCodable.decodingDataSnapshot(value: value)
            
        var myBlockList = MyProfile.shared.myUserInfo?.blockList ?? []
        myBlockList.forEach { blockUser in
            newNoticeBoards.removeAll(where: { $0.rootUser.id == blockUser.id })
        }
        self.noticeBoards = newNoticeBoards.sorted(by: { $0.createDate > $1.createDate })
            
        self.delegate?.reloadData()
        completion(true)
    })
}

DispatchQueue.main.async 사용O

func readNoticeBoard(completion: @escaping (Bool) -> Void) {
    
    let ref = Database.database().reference().child("noticeBoards").child(club.id)
    
    ref.getData(completion: { (error, snapshot) in
        if let error = error {
            print("Error getting data: \(error)")
            completion(false)
            return
        }
        
        guard let value = snapshot?.value as? [String: Any] else {
            DispatchQueue.main.async {
                self.delegate?.reloadData()
            }
            completion(false)
            return
        }
        
        var newNoticeBoards: [NoticeBoard] = DataModelCodable.decodingDataSnapshot(value: value)
        
        var myBlockList = MyProfile.shared.myUserInfo?.blockList ?? []
        myBlockList.forEach { blockUser in
            newNoticeBoards.removeAll(where: { $0.rootUser.id == blockUser.id })
        }
        self.noticeBoards = newNoticeBoards.sorted(by: { $0.createDate > $1.createDate })
        DispatchQueue.main.async {
            self.delegate?.reloadData()
        }
        completion(true)
    })
}

Firebase 데이터베이스에서 데이터를 읽는 작업은 백그라운드 스레드에서 이루어질 수 있다. 이렇게 데이터를 비동기적으로 읽고 나면, 대부분의 UI 관련 작업은 메인 스레드에서 수행되어야 한다. 이는 UIKit과 대부분의 UI 프레임워크가 메인 스레드에서만 UI 업데이트를 안전하게 보장하기 때문이다.

첫 번째 코드 예시에서는 self.delegate?.reloadData()가 백그라운드 스레드에서 호출될 수 있다. 이는 UI와 관련된 동작이므로, 예상치 못한 동작이나 앱의 충돌을 초래할 수 있다.

두 번째 코드 예시에서는 DispatchQueue.main.async를 사용하여 self.delegate?.reloadData()를 메인 스레드에서 명시적으로 호출하고 있다. 이 방식은 UI 업데이트를 메인 스레드에서 실행하도록 강제하므로, 앱이 안정적으로 동작할 수 있도록 도와준다.

따라서, UI와 관련된 작업은 DispatchQueue.main.async를 통해 메인 스레드에서 실행되어야 하며, 두 번째 코드가 이를 잘 반영하고 있어 더 바람직한 방법이다.

profile
iOS 개발자를 향해 성장 중

0개의 댓글