관리 메뉴

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

[iOS - swift] 비동기 작업에서의 안전한 get, set 설정 방법, read/write lock (GCD barrier flags) 본문

iOS 응용 (swift)

[iOS - swift] 비동기 작업에서의 안전한 get, set 설정 방법, read/write lock (GCD barrier flags)

jake-kim 2021. 7. 2. 23:17

* Sync vs Async vs Serial vs Concurrent 개념: https://ios-development.tistory.com/1082

GCD(Grand Central Dispatch)

  • 쓰레드의 최상위에 만들어지며 공유된 Thread pool을 관리하여 thread safe 유지
  • GCD의 개념 참고

Queue 생성

  • DispatchQueue.global() 사용: 이미 존재하는 concurrent queue만 사용
  • DispatchQueue.init(label:qos:attributtes:) 사용: 우선순위, serial, concurrent 새롭게 사용 가능

global이 아닌 .init(label:qos:attributtes:)를 사용할때의 장점

  • 디버깅에 용이: break point를 걸고 디버깅에서 label값을 가지고 어떤 큐에서 동작하는지 파악이 용이
    • queue가 많아졌을때 label을 통해 구분 용이

  • global queue는 특정 label을 알 수 없기 때문에 디버깅에 힘들고 concurrent queue만 존재

Dispatch Barrier

  • Concurrent하게 일이 수행되다가 flag가 barrier로 설정된 작업이 실행되면 그 작업이 끝날때까지 Serial queue처럼 동작

  • ex)
let myQueue = DispatchQueue(label: "myQueue", attributes: .concurrent)

for i in 1...5 {
    myQueue.async {
        print("\(i)")
    }
}

myQueue.async(flags: .barrier) {
    print("barrier!!")
    sleep(5)
}

for i in 6...10 {
    myQueue.async {
        print("\(i)")
    }
}

// 
3
1
4
5
2
barrier!!
6
7
10
8
9

비동기 작업에서의 안전한 get, set 설정 방법

  • Barrier flag 사용
  • ex) API 요청 > AccessToken 만료 > Refresh Token 갱신 api 요청하는 상황
    (만약 refresh token도 만료되었다면 splash화면으로 이동)
    • API를 요청하는 것은 비동기적이므로, AccessToken이 만료되었을때 refresh token을 요청하는 부분이 한순간에 다수가 요청하게 되는 점 존재
    • 다수가 요청하면서 refresh token은 실제로 만료가 되지 않았지만, 여러번 요청으로 인해 서버에 refreshToken이 갱신된 상태에서 또 다른 요청이 이전의 refresh token으로 request를 날리면서 'refresh token 유효 하지 않음' 응답을 받는 상황 발생
    • flag값이 필요하여 아래와 같이 정의
    • get: queue.sync 사용
    • set: queue.async(flags: .barrier) 사용
  • get에서는 sync를 사용하고 set에서는 async를 사용하는 이유는 성능 때문 (자세한 개념은 이 포스팅 글 참고)
public protocol ServiceStatusStore {
    var isPossibleEntry: Bool { get set }
    var isInvalidRefreshToken: Bool { get set }
}

final class DefaultServiceStatusStore: ServiceStatusStore {

    public static let shared = DefaultServiceStatusStore()
    private init() {}

    private var _isPossibleEntry = true
    private var _isInvalidRefreshToken = false

    private var queue = DispatchQueue(label: "status.queue", attributes: .concurrent)

    var isPossibleEntry: Bool {
        get {
            return queue.sync {
                return _isPossibleEntry
            }
        }
        set {
            queue.async(flags: .barrier) {
                self._isPossibleEntry = newValue
            }
        }
    }

    var isInvalidRefreshToken: Bool {
        get {
            return queue.sync {
                return _isInvalidRefreshToken
            }
        }
        set {
            queue.async(flags: .barrier) {
                self._isInvalidRefreshToken = newValue
            }
        }
    }
}

* 참고

https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2#toc-anchor-010

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