서론
- 앱 개발자로서 안정적이고 끊임없는 네트워크 연결을 기대합니다. 하지만, 네트워크 연결은 언제나 변수가 존재
- 이런 예측할 수 없는 변수들 속에서 사용자에게 명확한 UI를 제공하는 것은 당연
- 이번 글에서는 Network 프레임워크의 NWPathMonitor라는 것을 이용하여 기본 원리와 사용 방법을 소개
NWPathMonitor
- NWPathMonitor는 Network 프레임워크에 포함된 클래스
- 글자 그대로 Apple이 제공하는 다양한 플랫폼에서 네트워크 연결성을 모니터링할 수 있는 역할, 뿐만 아니라 인터넷 연결, 셀룰러 데이터 인지 파악
- 각 다양한 인터페이스마다 다양한 적절히 조치를 취하는 것이 가능
Singleton 객체 만들기
final class NetworkCheck {
static let shared = NetworkCheck()
private let queue = DispatchQueue.global()
private let monitor: NWPathMonitor
private init() {
self.monitor = NWPathMonitor()
}
}
- 먼저 NetworkCheck라는 Singleton 디자인으로 구현된 클래스를 제작. 왜냐하면 네트워크 연결 상태를 체크하는 것은 앱을 대표해서 1개로 족하기 때문
- 네트워크를 체크하는 것은 항상 비동기로 이루어져야함. 왜냐하면 언제, 어디서 네트워크 연결이 불안정해지기 때문에 그것을 대비해서 Main Thread보다는 Global Thread에서 감지하는 것이 당연
- 이제 NetworkCheck가 초기화되면 NWPathMonitor를 초기화
네트워크 감지 시작하기
final class NetworkCheck {
static let shared = NetworkCheck()
private let queue = DispatchQueue.global()
private let monitor: NWPathMonitor
// monotior 초기화
private init() {
self.monitor = NWPathMonitor()
}
// Network Monitoring 시작
public func startMonitoring() {
self.monitor.start(queue: queue)
self.monitor.pathUpdateHandler = { path in
}
}
}
- startMonitoring() 메서드에서 먼저 monitor의 start(queue:)를 호출, 여기에는 우리가 먼저 정의한 Global Queue를 주입
- 이제 네트워크 변경이 감지되면, monitor의 pathUpdateHandler를 통해 클로저로 반환
- 우리는 이 클로저 로직에 UI를 업데이트하는 로직을 구현
UI 관련된 프로퍼티 준비하기
final class NetworkCheck {
// ...
public private(set) var isConnected: Bool = false
public private(set) var topVCIsNetworkVC: Bool = false
private var topVC: UIViewController? {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
return window?.rootViewController
}
// ...
}
- isConnected는 현재 네트워크 연결 상태를 저장해주는 Boolean 프로퍼티입니다. 왜냐하면 pathUpdateHandler의 path는 여러 번 호출되어서 이것을 단 한 번의 시그널로 바꿔주는 역할을 합니다.
- topVCIsNetworkVC는 현재 가장 상위의 있는 ViewController가 NetworkVC인지 판단합니다. NetworkVC는 네트워크 연결이 불안정할 때 나타나는 ViewController입니다.
- topVC는 현재 상위 뷰에 있는 ViewController를 참조할 수 있는 프로퍼티입니다. NetworkCheck 클래스는 UI와 완전히 독립되어 있는 객체이기떄문에, UIApplication.shared를 참조하여 다른 화면으로 이동할 수 있도록 하는 징검다리 역할을 합니다.
pathUpdateHandler 로직 구성하기
final class NetworkCheck {
// ...
// Network Monitoring 시작
public func startMonitoring() {
self.monitor.start(queue: queue)
self.monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
self.isConnected = path.status == .satisfied
if self.isConnected && self.topVCIsNetworkVC {
// 네트워크와 연결됨, 제일 위에 뷰가 NetworkVC임. -> NetworkVC 사라지기
self.dismissNetworkVC()
}
if self.isConnected == false && self.topVCIsNetworkVC == false {
// 네트워크와 연결안됨, 제일 위에 뷰가 NetworkVC가 아님. -> NetworkVC 보여주기
self.presentNetworkVC()
}
}
}
// ...
}
- path.status는 NWPath.Status라는 enum타입을 가지고 있습니다.
- satisfied는 네트워크 연결이 정상적으로 연결되어 있는 경우를 말합니다.
- unsatisfied는 네트워크 연결이 끊겨있는 상태입니다.
- requiresConnection는 네트워크 연결을 시도하고 있는 상태입니다.
- 이 세 가지 경우 중 satisfied를 제외한 나머지 경우는 모두 네트워크 연결이 정상적이기 않기때문에, satisfied 상태만 고려를 해주었습니다.
- self.isConnected = path.status == .satisfied를 통해 self.isConnected의 상태 값을 변경해줍니다.
final class NetworkCheck {
// ...
private func presentNetworkVC() {
DispatchQueue.main.async {
let networkViewController = NetworkViewController()
networkViewController.modalPresentationStyle = .overFullScreen
guard let topVC = self.topVC else { return }
topVC.present(networkViewController, animated: true)
self.topVCIsNetworkVC = true
}
}
private func dismissNetworkVC() {
DispatchQueue.main.async {
guard let topVC = self.topVC else { return }
topVC.dismiss(animated: true)
self.topVCIsNetworkVC = false
}
}
// ...
}
- 만약 isConnected가 False고, 제일 최상위 VC가 NetworkVC가 아닐 경우에는 NetworkVC를 보여줍니다.
- 만약 isConnected가 True고, 제일 최상위 VC가 NetworkVC면 NetworkVC를 사리지게합니다.