본문 바로가기

학습 노트/iOS (2021)

216. Reachability

Reachability


Reachability는 호스트의 접속 가능 상태를 의미한다.
iOS는 Reachability를 네트워크 연결에 실패했을 때 상태를 진단하는 목적으로 주로 이용한다.

Apple은 Reachability에 따라 요청의 진행 여부를 결정하는 것이 아닌,
상태에 상관없이 요청을 진행하고 이것이 실패하는 경우 Reachability를 사용하도록 안내하고 있다.

이외의 용도로서 기기의 '네트워크 상태'를 확인하거나
Wi-Fi 상태인지의 여부를 확인하는 용도로 사용하기도 한다.

Reachability는 SystemConfiguration이 제공하는 API로 구현되어있다.
이를 사용해 직접 구현하는 것보다는 이미 만들어진 오픈소스를 활용하는 것이 더 간편하다.

 

Reachability Open Source 이용하기

Reachability-Swift

 

GitHub - ashleymills/Reachability.swift: Replacement for Apple's Reachability re-written in Swift with closures

Replacement for Apple's Reachability re-written in Swift with closures - GitHub - ashleymills/Reachability.swift: Replacement for Apple's Reachability re-written in Swift with closures

github.com

실습은 위의 오픈소스 프로젝트를 사용한다.

큰 어려움 없이 프로젝트 파일 안에 코드를 넣어주고

var reachability: Reachability?

인스턴스를 생성한 뒤

var reachability: Reachability?

override func viewDidLoad() {
  super.viewDidLoad()

  reachability = Reachability()
}

Scene이 표시되는 시점에 객체를 저장해 주면 된다.

 

Reachability 사용하기

var reachability: Reachability?

override func viewDidLoad() {
  super.viewDidLoad()

  reachability = Reachability()
  
  do {
     try reachability?.startNotifier()
  } catch {
     print(error)
  }
}

네트워크 상태를 감시할 수 있도록 startNotifier 메서드를 호출하면
지금부터는 reachablity로 네트워크 상태를 확인할 수 있게 된다.

var reachability: Reachability?

override func viewDidLoad() {
  super.viewDidLoad()

  reachability = Reachability()

  reachability?.whenReachable = { reachability in
     self.updateUI(from: reachability)
  }
  reachability?.whenUnreachable = { reachability in
     self.updateUI(from: reachability)
  }
  
  do {
     try reachability?.startNotifier()
  } catch {
     print(error)
  }
}

reachability는 네트워크 상태가 변할 때마다 코드를 실행하고,
whenReachable, whenUnreachable 속성에 클로저로 저장된다.
reachability 인스턴스를 클로저의 파라미터로 받아 동작을 실행하면 된다.

func updateUI(from reachability: Reachability) {
   switch reachability.connection {
   case .wifi:
       wifiImageView.image = wifiOnImage
       cellularImageView.image = cellularOffImage
   case .cellular:
       wifiImageView.image = wifiOffImage
       cellularImageView.image = cellularOnImage

   case .none:
       wifiImageView.image = wifiOffImage
       cellularImageView.image = cellularOffImage
   }
}

whenReachable, whenUnreachable 속성이 실행하는 updateUI는 위와 같다.
reachability의 connection 속성에 따라 이미지를 toggle 한다.

기기의 네트워크 연결 유형이 변경될 때마다 이를 감지하고 UI가 업데이트된다.

deinit {
   reachability?.stopNotifier()
   reachability = nil
}

마지막으로 소멸자에서 reachability를 중지하고 초기화해 주면 된다.

 

네트워크 연결 변경 처리하기

셀룰러에서 다운로드를 시작하고 wifi로 전환한 경우엔 문제가 없지만.
wifi에서 다운로드를 시작하고 셀룰러로 전환하면 다운로드가 멈추게 된다.
뿐만 아니라 Resume 데이터를 남기게 된다.

콘솔의 에러 내용을 보면 '-1005'라는 코드를 반환한 것을 확인할 수 있고,

 

NSURLErrorDomain error codes description

This is my first experience of developing an ios app. I am trying to Post some data using Facebook graph api. I am constantly getting the following error: The operation couldn’t be completed. (

stackoverflow.com

 

 

Apple Developer Documentation

 

developer.apple.com

해당 오류는 'connection lost'에 해당하게 된다.
따라서 해당 오류가 전달되면 중지 시 저장된 resumeData를 사용해 Task를 재시작하도록 구현하면 된다.

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  guard let error = error else { return }
  let downloadError = error as NSError

  print(#function)
  print(downloadError)

   if downloadError.code == NSURLErrorNetworkConnectionLost {
       switch reachability?.connection {
       case .some(.wifi), .some(.cellular):
           if let resumeData = downloadError.userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
               let newDownloadTask = session.downloadTask(withResumeData: resumeData)
               newDownloadTask.resume()
           }
       default:
           if let resumeData = downloadError.userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
               do {
                   try resumeData.write(to: resumeDataUrl)
                   print("resume data saved")
               } catch {
                   print(error)
               }
           }
       }
   }
}

SessionDelegate에 urlSession(didCompleteWithError:) 메서드를 추가하고
오류 코드에 대한 처리를 진행한다.

wifi와 cellular를 제외하면 비행기 모드나 네트워크 자체를 사용할 수 없는 상태이므로
Task를 지연시키거나 resumeData를 저장해 뒀다가 원활해지는 시점에 다시 시작한다.

이제는 wifi에서 셀룰러로 바뀌어도 다운로드가 멈추지 않는다.

 

진입했을 때 임시파일 사용하기

func resumeDownloadIfNeeded() {
   guard reachability?.connection != .some(.none) else {
       return
   }

   if let resumeData = try? Data(contentsOf: resumeDataUrl) {
       let newDonwloadTask = session.downloadTask(withResumeData: resumeData)
       newDonwloadTask.resume()
   }
}

Scene에 진입했을 때 네트워크 상태를 확인하고,
연결이 원활하다면 기존의 resumeData를 사용해 다운로드를 이어가도록 구현한다.

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  print(#function)

  guard (try? location.checkResourceIsReachable()) ?? false else {
     return
  }

  do {
     _ = try FileManager.default.replaceItemAt(targetUrl, withItemAt: location)

      if FileManager.default.fileExists(atPath: resumeDataUrl.path) {
          try FileManager.default.removeItem(at: resumeDataUrl)
          print("resume Data deleted")
      }
  } catch {
     fatalError(error.localizedDescription)
  }
}

또한 다운로드가 완료되면 기존에 존재하던 resumeData를 삭제해 다운로드를 중복해서 진행하지 않도록 조치해야 한다.
sessionDelegate에 urlSession(didFinishDownloadingTo:) 메서드를 추가하고 기능을 구현한다.

override func viewDidLoad() {
  super.viewDidLoad()

  reachability = Reachability()
   reachability?.whenReachable = { reachability in
       self.updateUI(from: reachability)
       self.resumeDownloadIfNeeded()
   }
   reachability?.whenUnreachable = { reachability in
       self.updateUI(from: reachability)
   }

   do {
       try reachability?.startNotifier()
   } catch {
       print(error)
   }
}

새롭게 정의한 resumeDownloadIfNeeded 메서드는 Scene에 진입하는 시점인 viewDidLoad에서 호출하면 된다.

'학습 노트 > iOS (2021)' 카테고리의 다른 글

218. Adaptable Connectivity  (0) 2022.09.07
217. Cellular Connection  (0) 2022.09.07
215. Response Caching  (0) 2022.09.06
214. Background Download  (0) 2022.08.31
213. Download Task  (0) 2022.08.30