프로젝트 중에 네트워크가 끊어지는 상황에 대해서 생각해 보게 되었음
네트워크가 켜져 있지 않거나 비행기모드라면, 네트워크 연결이 필요한 부분(API 콜을 한다던가, 사진을 다운받는다던가) 하는 곳에서 에러가 날 것임. 하지만 이 부분에 대해서 예외처리가 되지 않는다면 사용자 경험측면에서 좋지 않다는 생각을 했음
그래서 네트워크 단절에 대한 처리를 해보게 되었음
일단 아래와 같은 NetworkView를 만들고 네트워크 통신이 일어나는 모든 뷰 컨트롤러에 추가해놓았음
그리고 아래와 같이 별도로 NetworkMonitor 클래스를 만들어서 startMonitoring을 호출하는 시점부터 계속해서 네트워크 상태를 감지하도록 했음
import Foundation
import Network
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let queue = DispatchQueue.global()
private let monitor = NWPathMonitor()
var isConnected = false
private init(){ }
func startMonitoring(){
monitor.start(queue: queue)
monitor.pathUpdateHandler = { [weak self] path in
if path.status == .satisfied {
self?.isConnected = true
}else{
self?.isConnected = false
}
}
}
func stopMonitoring(){
monitor.cancel()
}
}
AppDelegate에서 처음 앱이 실행될 때마다 startMonitoring으로 네트워크 상태를 감지하게 하였음
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
NetworkMonitor.shared.startMonitoring()
return true
}
각 뷰컨트롤러에서는 NetworkMonitor의 의 isConnected를 확인해서 서버통신이 일어나기 전 확인을 하고, 만약 false라면 네트워크 단절에 대한 화면이 나오도록 하였음
if NetworkMonitor.shared.isConnected {
네트워크 뷰 띄우기
} else{ API 호출 }
후 ... 길다. 여기까지 다 잘 되었네? 라고 생각할 수 있음
근데 여기서 문제가 발생함
1. 실시간적인 처리가 안됨
네트워크 통신하기 전에 항상 isConnected 값을 받아서 처리하고 있는데, 항상 네트워크 통신을 할때만 네트워크 상태를 감지할 수 있다는 것임. 그게 아니라 사용자가 중간에 네트워크를 꺼버려도, 토스트라던지 얼럿으로 알려주고 싶음
2. 모든 뷰 컨트롤러에 뷰를 얹어주어야 함
통신이 일어나는 뷰 컨트롤러가 100개면 100개를 다 얹어주어야 하는데, 이게 과연 효율적인 방법일까? 라는 생각을 하게 되었음
문제를 해결해보겠음
1번을 해결하기 위해서는 어떻게 해야할까?NWPathMonitor의 pathUpdateHandler는 연결상태를 가져옴
주석을 살펴보면 start를 호출하기 이전에는 호출되지 않다가, 그 이후에 네트워크 경로가 변경되면 호출된다고 나와있음
그렇기 때문에 이부분에서 네트워크가 없을때, 있을 때의 처리를 해주면 실시간으로 반영할 수 있지 않을까 생각을 하게되었음
2번을 해결해야 하는데, 어떻게 모든 뷰컨 위에 네트워크가 없을 때마다 표시해줄 수 있을 까 생각해보았음. 이것저것 찾아보다가 window를 사용해보자는 생각을 하게됨.
아래는 JDStatusBarNotification 라이브러리 샘플인데 뷰컨 위에 노티피케이션뷰를 띄우는 형태임. 대충 아래처럼 생김
코드를 몇 부분만 살펴보았는데, windowLevel을 statusBar로 설정하고 window의 rootViewController를 지정해서 사용하고 있음
이 코드와 비슷하게 네트워크 윈도우를 만들어서 띄워볼 수 있지 않을까 라는 생각을 하게 되었음
이제 코드를 수정해보겠음.
위의 코드처럼 윈도우를 상속받는 네트워크 윈도우를 만듬.
import UIKit
final class NetworkWindow: UIWindow {
private let networkViewController = NetworkViewController()
override init(windowScene: UIWindowScene) {
super.init(windowScene: windowScene)
rootViewController = networkViewController
autoresizingMask = [.flexibleWidth, .flexibleHeight]
windowLevel = .statusBar
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
윈도우의 레벨을 statusBar로 설정한 이유는 레벨이 높을수록 위에 쌓이기 때문임. 설정하지 않으면 normal이기 때문에 statusBar로 레벨을 높여서 기존 윈도우 위에 쌓이도록 함
NetworkMonitor 클래스에서 네트워크 상태에 따라 window를 추가하고 제거하는 함수인 presentNetworkWindow(), dismissNetworkWindow()를 만듬
네트워크가 연결되어있지 않으면
- NetworkWindow 생성
- 키윈도우로 설정해서(makeKeyAndVisible) 화면을 표시하고, 같은레벨 or 그보다 낮은 레벨의 가장 위의 나오도록 함
네트워크가 연결되어있고, 다시시도 버튼을 누르면
- NetworkWindow 인스턴스를 nil로 설정함
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let queue = DispatchQueue.global()
private let monitor = NWPathMonitor()
var isConnected = false
private init(){ }
func startMonitoring(){
monitor.start(queue: queue)
monitor.pathUpdateHandler = { [weak self] path in
if path.status == .satisfied {
self?.isConnected = true
}else{
self?.isConnected = false
self?.presentNetworkWindow()
}
}
}
func stopMonitoring(){
monitor.cancel()
}
func presentNetworkWindow(){
DispatchQueue.main.async {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
let networkWindow = NetworkWindow(windowScene: windowScene)
networkWindow.makeKeyAndVisible()
let sceneDelegate = windowScene.delegate as? SceneDelegate
sceneDelegate?.networkWindow = networkWindow
}
}
}
func dismissNetworkWindow(){
DispatchQueue.main.async {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
let sceneDelegate = windowScene.delegate as? SceneDelegate
sceneDelegate?.networkWindow = nil
}
}
}
}
@objc private func retryButtonClicked(){
if NetworkMonitor.shared.isConnected {
NetworkMonitor.shared.dismissNetworkWindow()
}
}
아래처럼 동작하는 것을 확인할 수 있음
'iOS > iOS 지식' 카테고리의 다른 글
[iOS] ViewController 생명주기(2) - 실험 (2) | 2024.10.04 |
---|---|
[iOS] GCD Priority Inversion (우선 순위의 뒤바뀜) (0) | 2024.08.03 |
[WWDC2020] Advances in UICollectionView (0) | 2024.07.19 |
[iOS] Realm 사용하면서 생긴 문제들 (0) | 2024.07.05 |
[iOS] Data(ContentsOf)와 NSCache (0) | 2024.06.22 |