관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] DispatchQueue.main.sync 사용 시 주의사항 (Deadlock) 본문

iOS 응용 (swift)

[iOS - swift] DispatchQueue.main.sync 사용 시 주의사항 (Deadlock)

jake-kim 2021. 7. 5. 23:30

Deadlock (교착 상태)

  • 두 개 이상의 process 및 thread가 서로 상대방의 작업이 끝나기만을 기다리고 있어서 task들을 처리하지 못하는 상태

ex) deadlock

  • Serial Queue인 상황: 한 번에 하나의 작업만 가능 > 내부에 sync한 작업이 존재 > deadlock 발생
let testQueue = DispatchQueue(label: "testQueue") // concurrent선언하지 않았으므로 디폴트인 Serial Queue

testQueue.async {
    testQueue.sync {
        // 외부 블록이 완료되기 전에 내부 블록이 시작되지 않는 상태
        // 외부 블록은 내부 블록이 완료되기를 기다리는 상태
    }
    
    // 영원히 실행되지 않는 부분
}
  • 해결 방법: Serial Queue > Concurrent Queue 변경
let testQueue = DispatchQueue(label: "testQueue", attributes: .concurrent)

DispatchQueue.main.sync의 deadlock

  • DispatchQueue.main은 프로그램의 메인 스레드에서 작업을 실행하는 전역적으로 사용 가능한 serial queue
    • DispatchQueue.main는 concurrent queue가 아님을 주의
    • DispatchQueue.main은 run loop와 함께 동작
    • run loop의 다른 이벤트 처리와 조율해주는 역할
  • DispatchQueue.main.sync를 호출하게 되면 끊임없이 앱의 이벤트 처리를 하고 있던 main thread가 sync호출에 의해 멈추게 되고 deadlock 발생
  • DispatchQueue.main.sync 사용 경우
    • background thread에서 이루어지는 작업들 사이에 순서에 맞게 main thread에서 작업이 이루어져야 할 때 사용
    • Background thread 내에서 사용하는 것이 아니면 DispatchQueue.main.sync 사용을 지양
DispatchQueue.global().async {
    // UI 업데이트 전 실행되는 코드
    DispatchQueue.main.sync {
        // UI 업데이트
    }
    // UI 업데이트 후 실행되는 코드
}

DispatchQueue.main.sync 사용 예제2 - Apple 문서

  • 사용처: Background Thread에서 작업이 진행되는 중 UI 업데이트 이루어져야 하는 상황
  • TableView나 CollectionView에 셀을 추가하거나 제거하는 작업이 진행 중 > 네트워크 통신과 같은 다른 비동기 작업으로 인해 DataSource의 데이터가 변경 > (셀을 추가하거나 제거하는 작업이 아직 안끝난 상태) > DataSource의 데이터와 View가 일치하지 않아 에러 발생 > 방지위해 DispatchQueue.main.sync 사용
  • 사진 앨범의 DataSource와 업데이트한 콜렉션 뷰의 데이터가 달라지는 상황을 방지하기 위해 DispatchQueue.main.sync사용
  • PHPPhotoLibraryChangeObserver 애플 문서
// PHPhotoLibraryChangeObserver 애플 문서

func photoLibraryDidChange(_ changeInstance: PHChange) {
    guard let collectionView = self.collectionView else { return }
    DispatchQueue.main.sync {
        if let albumChanges = changeInstance.changeDetails(for: assetCollection) {
            assetCollection = albumChanges.objectAfterChanges! as! PHAssetCollection
            navigationController?.navigationItem.title = assetCollection.localizedTitle
        }
        if let changes = changeInstance.changeDetails(for: fetchResult) {
            fetchResult = changes.fetchResultAfterChanges
            if changes.hasIncrementalChanges {
                collectionView.performBatchUpdates({
                    if let removed = changes.removedIndexes where removed.count > 0 {
                        collectionView.deleteItems(at: removed.map { IndexPath(item: $0, section:0) })
                    }
                    if let inserted = changes.insertedIndexes where inserted.count > 0 {
                        collectionView.insertItems(at: inserted.map { IndexPath(item: $0, section:0) })
                    }
                    if let changed = changes.changedIndexes where changed.count > 0 {
                        collectionView.reloadItems(at: changed.map { IndexPath(item: $0, section:0) })
                    }
                    changes.enumerateMoves { fromIndex, toIndex in
                        collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
                                                to: IndexPath(item: toIndex, section: 0))
                    }
                })
            } else {
                collectionView.reloadData()
            }
        }
    }
}

 

* 참고

https://developer.apple.com/documentation/photokit/phphotolibrarychangeobserver

https://yagom.net/forums/topic/%EC%95%BC%EA%B3%B0%EB%8B%B7%EB%84%B7-%EC%A7%88%EB%AC%B8%EB%AA%A8%EC%9D%8C-6/

Comments