https://developer.apple.com/documentation/network/nwpathmonitor
네트워크 상태 탐지를 위한 API인 NWPathMonitor 사용 중 발생한 thread safety issue이다.
근본적인 원인은 이전에 공유한 thread safety issue와 같다.
NWPathMonitor에서는 네트워크 상태 변화를 observing 할 때 사용한 DispatchQueue를 지정할 수 있게 되어있다.
아마 지속적으로 observing을 하는 행위 자체가 꽤 cost를 요구하는 일이기 때문에 main queue에서 진행하지 말기를 권장하는 차원에서 만들어 놓은 interface인 듯 하다.
Root cause
아래는 문제가 되었던 코드이다.
import UIKit
import Network
class NetworkObserver {
private let monitoringQueue = DispatchQueue(label: "com.monitoring.queue")
private let monitor = NWPathMonitor()
static let shared = NetworkObserver()
var isWifi: Bool {
return monitor.currentPath.usesInterfaceType(.wifi) // Main thread에서 접근
}
private init() {
monitor.start(queue: monitoringQueue) // Worker thread에서 업데이트
}
}
해당 코드 실행 시 monitor.currentPath는 Network 환경이 바뀔 때마다 monitoringQueue에서 업데이트가 된다.
하지만 currentPath에 access하는 isWifi block을 main thread나 다른 스레드에서 접근이 가능하도록 되어있어, currentPath의 setter와 getter가 동시에 다른 스레드에서 호출 시 dagling pointer 크래시가 발생할 수 있다.
Solution
Short-term
아래는 수정된 코드이다.
import UIKit
import Network
class NetworkObserver {
private let monitoringQueue = DispatchQueue(label: "com.monitoring.queue")
private let monitor = NWPathMonitor()
private(set) var isWifi: Bool = false
static let shared = NetworkObserver()
private init() {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
self.isWifi = path.usesInterfaceType(.wifi)
}
monitor.start(queue: monitoringQueue)
}
}
NWPathMonitor는 pathUpdateHandler라고 해서, NWPath가 업데이트 될 때마다 호출되는 block을 지정을 해줄 수 있다.
(일반적으로 이런 경우엔 delegate가 제공되는데 이 API 제작자는 무슨 의도인진 모르겠지만 block하나만 지정할 수 있게 해주었다.)
isWifi라고 하는 boolean 프로퍼티를 하나두고 block에서 NWPath가 업데이트 될 때마다 업데이트하도록 하는 것이다. Bool type의 경우엔 struct이기 때문에 태생적으로 thread safety해서 위에서 언급한 dangling pointer이슈가 발생하지 않는다.
Mid-term
여기서 좀 더 나아가 좀 더 근본적이고 똑똑한 해결법을 생각해보았을 땐 아래와 같이 우리쪽 코드에 currentPath 프로퍼티를 두고 getter와 setter와 thread safety 처리를 한 뒤 강한 참조로 들고 있는 방법도 있다.
import UIKit
import Network
class NetworkObserver {
private let monitoringQueue = DispatchQueue(label: "com.monitoring.queue", attributes: .concurrent)
private let monitor = NWPathMonitor()
var isWifi: Bool {
currentPath.usesInterfaceType(.wifi)
}
private(set) var currentPath: NWPath {
get {
monitoringQueue.sync {
return _currentPath
}
}
set {
monitoringQueue.async(flags: .barrier) {
self._currentPath = newValue
}
}
}
private lazy var _currentPath: NWPath = monitor.currentPath
static let shared = NetworkObserver()
private init() {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
self.currentPath = path
}
monitor.start(queue: monitoringQueue)
}
}
'iOS > 이슈' 카테고리의 다른 글
[iOS Issue] loadView override시 view가 생성되지 않는 문제 (0) | 2024.11.10 |
---|---|
[iOS Issue] Multi thread 환경에서 Dangling Pointer Crash 이슈 (0) | 2024.05.05 |
[iOS Issue] command codesign failed with a nonzero exit code (0) | 2024.03.17 |
[iOS Issue] Shadow 관련 run time issue (0) | 2023.01.07 |
[iOS Issue] iOS12에서 Constraint의 priority 사용 시 크래시 발생 (0) | 2022.08.17 |
댓글