Computer >> 컴퓨터 >  >> 네트워킹 >> 인터넷

Swift로 인터넷 연결 상태 감지하고 관리하기: 실전 개발 가이드

작성자: Neo Ighodaro

모바일 애플리케이션은 대부분 정상적으로 작동하기 위해 인터넷 연결이 필요합니다. 하지만 네트워크 연결이 끊어지는 일은 언제든 일어날 수 있는 자연스러운 현상입니다. 이런 상황에서 사용자 경험을 최대한 부드럽게 유지하거나, 최소한이라도 사용자에게 현재 상태를 알려주는 것은 개발자의 몫입니다.

이 글에서는 Swift에서 인터넷 연결 문제를 감지하는 방법과, 이를 효과적으로 처리할 수 있는 몇 가지 방법을 살펴보겠습니다.

먼저 우리가 만들 샘플 애플리케이션이 다양한 네트워크 연결 상황을 어떻게 처리하는지 미리 보겠습니다.

Swift로 인터넷 연결 상태 감지하고 관리하기: 실전 개발 가이드

시작하기 전 준비 사항

이 글의 내용을 따라 하려면 다음 항목들이 준비되어 있어야 합니다.

  • 맥에 Xcode가 설치되어 있어야 합니다.
  • Swift 프로그래밍 언어에 대한 기본 지식이 필요합니다.
  • Cocoapods가 설치되어 있어야 합니다.

위 항목들이 모두 준비되었다면, 본격적으로 시작해 보겠습니다.

개발 환경 설정하기

본격적인 작업에 앞서 플레이그라운드(Playground) 프로젝트를 생성하겠습니다. 여기서 모든 사용 사례를 작성하고 테스트하게 됩니다.

사실 Swift에는 연결 문제를 감지하기 위한 Reachability 구현체가 기본으로 포함되어 있습니다. 하지만 이번 튜토리얼에서는 서드파티 라이브러리를 사용하겠습니다. 그 이유는 사용법이 더 간단하고, 기본 제공 API보다 표현력이 뛰어나기 때문입니다.

Xcode를 열고 새 프로젝트를 생성합니다.

Swift로 인터넷 연결 상태 감지하고 관리하기: 실전 개발 가이드

이 프로젝트는 자유롭게 실험해 볼 수 있는 간단한 플레이그라운드 역할을 하게 됩니다.

네트워크 연결이 끊어지는 시점을 감지하기 위해 Reachability.swift 패키지를 사용합니다. 이 라이브러리는 "Apple의 Reachability를 클로저(closure)와 함께 Swift로 재작성한 대체품"입니다.

터미널을 열고 아래 명령어를 실행하세요.

$ pod init

이 명령은 Cocoapods 의존성을 선언할 수 있는 Podfile을 생성합니다. 해당 파일을 열고 내용을 아래 코드로 교체하세요.

platform :ios, '9.0'
target 'project_name' do use_frameworks! pod 'ReachabilitySwift' pod 'Alamofire'end

project_name 부분은 반드시 실제 프로젝트 이름으로 바꿔야 합니다.

파일을 저장한 후 아래 명령어를 실행해 프로젝트에 Pods를 설치합니다.

$ pod install

설치가 완료되면 프로젝트 루트 디렉터리의 *.xcworkspace 파일을 열어 Xcode를 실행합니다.

네트워크 Reachability 매니저 만들기

새로운 NetworkManager 클래스를 생성합니다. 이 클래스는 네트워크 상태를 저장하고 Reachability 패키지의 간단한 프록시 역할을 담당합니다. 파일에 아래 코드를 붙여 넣으세요.

import Foundationimport Reachability
class NetworkManager: NSObject {
 var reachability: Reachability!
 static let sharedInstance: NetworkManager = { return NetworkManager() }()
 override init() { super.init()
 // Initialise reachability reachability = Reachability()!
 // Register an observer for the network status NotificationCenter.default.addObserver( self, selector: #selector(networkStatusChanged(_:)), name: .reachabilityChanged, object: reachability )
 do { // Start the network status notifier try reachability.startNotifier() } catch { print("Unable to start notifier") } }
 @objc func networkStatusChanged(_ notification: Notification) { // Do something globally here! }
 static func stopNotifier() -> Void { do { // Stop the network status notifier try (NetworkManager.sharedInstance.reachability).startNotifier() } catch { print("Error stopping notifier") } }
 // Network is reachable static func isReachable(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection != .none { completed(NetworkManager.sharedInstance) } }
 // Network is unreachable static func isUnreachable(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection == .none { completed(NetworkManager.sharedInstance) } }
 // Network is reachable via WWAN/Cellular static func isReachableViaWWAN(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection == .cellular { completed(NetworkManager.sharedInstance) } }
 // Network is reachable via WiFi static func isReachableViaWiFi(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection == .wifi { completed(NetworkManager.sharedInstance) } }]

위 클래스에는 네트워크 상태 모니터링을 시작하는 데 도움이 되는 유틸리티 함수들이 정의되어 있습니다. 싱글턴(singleton)인 sharedInstance를 제공하므로, NetworkManager 클래스의 인스턴스를 여러 개 만들고 싶지 않다면 이를 호출하면 됩니다.

init 메서드에서는 Reachability 인스턴스를 생성한 뒤, NotificationCenter 클래스를 통해 알림을 등록합니다. 이후 네트워크 상태가 변경될 때마다 NotificationCenter에 지정된 콜백(networkStatusChanged)이 호출됩니다. 이를 활용하면 네트워크 연결이 끊겼을 때 전역적으로 실행할 동작을 정의할 수 있습니다.

또한 인터넷 연결 상태에 따라 코드를 손쉽게 분기 처리할 수 있는 헬퍼 함수들도 마련했습니다. 바로 isReachable, isUnreachable, isReachableViaWWAN, isReachableViaWiFi입니다.

이 헬퍼 함수들의 일반적인 사용 예는 다음과 같습니다.

NetworkManager.isReachable { networkManagerInstance in print("Network is available")}
NetworkManager.isUnreachable { networkManagerInstance in print("Network is Unavailable")}

주의할 점은 이것이 이벤트 리스너가 아니며 한 번만 실행된다는 것입니다. 네트워크 변화를 실시간으로 감지하는 리스너를 사용하려면 NetworkManager.sharedInstance.reachability.whenReachable을 활용해야 합니다. 이 부분은 글 후반부에서 예제와 함께 다루겠습니다.

매니저 클래스가 준비되었으니, 이제 실제 애플리케이션에서 어떻게 활용하는지 살펴보겠습니다.

앱 실행 시점의 네트워크 상태 처리하기

애플리케이션이 인터넷 연결에 크게 의존하는 경우가 많습니다. 이럴 때는 앱이 시작될 때 네트워크 상태를 확인하는 것이 좋습니다. NetworkManager 클래스를 활용해 이를 처리하는 방법을 알아보겠습니다.

LaunchViewController라는 새 컨트롤러를 생성합니다. 스토리보드의 첫 번째 뷰 컨트롤러를 런치 컨트롤러로 사용하겠습니다. 사용자 기기가 온라인 상태인지 감지하고, 오프라인이라면 사용자가 앱에 진입하지 못하도록 오프라인 페이지를 만들어 처리합니다.

LaunchController의 내용을 아래 코드로 교체하세요.

import UIKit
class LaunchViewController: UIViewController { let network: NetworkManager = NetworkManager.sharedInstance
 override func viewDidLoad() { super.viewDidLoad()
 NetworkManager.isUnreachable { _ in self.showOfflinePage() } }
 private func showOfflinePage() -> Void { DispatchQueue.main.async { self.performSegue( withIdentifier: "NetworkUnavailable", sender: self ) } }}

이 클래스에서는 NetworkManagerisUnreachable 메서드를 사용해 네트워크가 연결되어 있지 않을 때 showOfflinePage 메서드를 호출합니다. 이제 해당 뷰 컨트롤러를 만들어 보겠습니다. OfflineViewController라는 새 뷰 컨트롤러를 생성하세요.

Main.storyboard 파일을 열고 첫 번째 뷰의 커스텀 클래스를 LaunchViewController로 설정합니다.

다음으로 스토리보드에 새 뷰 컨트롤러를 추가하고, 이 컨트롤러의 커스텀 클래스를 OfflineViewController로 지정합니다. 그런 다음 새 뷰 컨트롤러와 LaunchViewController 사이에 NetworkUnavailable이라는 이름의 수동 세그(manual segue)를 생성합니다. 완료되면 아래 이미지와 비슷한 구성이 되어 있을 겁니다.

Swift로 인터넷 연결 상태 감지하고 관리하기: 실전 개발 가이드

이제 애플리케이션을 실행해 보겠습니다. 참고로 iOS 시뮬레이터는 개발 머신의 인터넷 연결을 공유하기 때문에, 앱을 실행하기 전에 개발 머신의 네트워크를 꺼두어야 합니다. 앱을 실행하면 앞서 만든 오프라인 페이지가 나타나는 것을 확인할 수 있습니다.

이번에는 연결이 정상일 때 표시되는 뷰 컨트롤러를 만들어 보겠습니다.

기기가 온라인 상태가 될 때 이벤트 처리하기

오프라인 뷰 컨트롤러를 만들었고, 기기가 오프라인일 때 정상 작동하는 것도 확인했습니다. 이제 기기가 다시 온라인 상태가 되었을 때의 동작을 처리해 보겠습니다.

스토리보드에서 오프라인 뷰 컨트롤러 아래에 새로운 네비게이션 뷰 컨트롤러를 추가합니다. 최신 Reddit 게시물을 표시하는 컨트롤러를 만들 것입니다. PostsTableViewController라는 새 뷰 컨트롤러 클래스를 생성하고, 이를 네비게이션 뷰 컨트롤러에 연결된 뷰의 커스텀 클래스로 지정하세요.

그런 다음 네비게이션 뷰 컨트롤러에서 런치 뷰 컨트롤러 및 오프라인 뷰 컨트롤러로 향하는 MainController라는 이름의 수동 세그를 생성합니다. 완료하면 아래와 같은 화면 구성이 됩니다.

Swift로 인터넷 연결 상태 감지하고 관리하기: 실전 개발 가이드

이제 LaunchViewController 클래스를 열고 viewDidLoad 메서드 맨 아래에 다음 코드를 추가합니다.

NetworkManager.isReachable { _ in self.showMainPage()}

그리고 컨트롤러에 아래 메서드를 추가하세요.

private func showMainPage() -> Void { DispatchQueue.main.async { self.performSegue( withIdentifier: "MainController", sender: self ) }}

이렇게 하면 앱이 실행될 때 연결 상태를 점검한 후, 연결이 가능하면 PostsTableViewController를 표시하고, 그렇지 않으면 OfflineViewController를 표시하게 됩니다.

좋습니다! 그런데 사용자가 OfflineViewController에 머물러 있는 상태에서 네트워크가 다시 연결되면 어떻게 될까요? 이 시나리오도 처리해 보겠습니다.

OfflineViewController를 열고 코드를 아래 내용으로 교체하세요.

import UIKit
class OfflineViewController: UIViewController { let network = NetworkManager.sharedInstance
 override func viewDidLoad() { super.viewDidLoad()
 // If the network is reachable show the main controller network.reachability.whenReachable = { _ in self.showMainController() } }
 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated)
 navigationController?.setNavigationBarHidden(true, animated: animated) }
 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated)
 navigationController?.setNavigationBarHidden(false, animated: animated) }
 private func showMainController() -> Void { DispatchQueue.main.async { self.performSegue(withIdentifier: "MainController", sender: self) } }}

위 컨트롤러의 viewDidLoad 메서드에서 whenReachable 콜백을 설정해 메인 컨트롤러를 표시하도록 한 것을 볼 수 있습니다. 즉, 오프라인 상태인 동안 기기가 다시 온라인이 되는 순간을 계속 지켜보다가, 연결이 복구되면 PostsTableViewController를 띄우는 방식입니다.

또한 viewWillAppearviewWillDisappear 메서드를 오버라이드해 오프라인 뷰 컨트롤러에서는 네비게이션 바가 표시되지 않도록 처리했습니다.

Reddit API에서 게시물 가져오기

이제 Reddit에서 데이터를 가져와 PostsTableViewController에 표시하는 로직을 추가해 보겠습니다. 파일을 열고 내용을 아래 코드로 교체하세요.

import UIKitimport Alamofire
struct RedditPost { let title: String! let subreddit: String!}
class PostsTableViewController: UITableViewController { var posts = [RedditPost]()
 let network = NetworkManager.sharedInstance
 override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Latest Posts"
 // Fetch the posts and then reload the table fetchPosts { posts in self.posts = posts self.tableView.reloadData() } }
 private func fetchPosts(completion: @escaping (_ posts: [RedditPost]) -> Void) -> Void { // Send a request to the Reddit API Alamofire.request("https://api.reddit.com").validate().responseJSON { response in switch response.result { case .success(let JSON): let data = JSON as! [String:AnyObject] guard let children = data["data"]!["children"] as? [AnyObject] else { return } var posts = [RedditPost]()
 // Loop through the Reddit posts and then assign a post to the posts array for child in 0...children.count-1 { let post = children[child]["data"] as! [String: AnyObject]
 posts.append(RedditPost( title: post["title"] as! String, subreddit: "/r/" + (post["subreddit"] as! String) )) }
 DispatchQueue.main.async { completion(posts) } case .failure(let error): print(error) } } }
 override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }
 // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
 // Return the number of posts available override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.posts.count }
 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) let post = posts[indexPath.row] as RedditPost cell.textLabel?.text = post.title cell.detailTextLabel?.text = post.subreddit return cell }}

fetchPosts 메서드에서는 Alamofire를 사용해 Reddit API에 GET 요청을 보냅니다. 그런 다음 응답을 파싱하여 파일 상단에 정의한 RedditPost 구조체에 담습니다. 덕분에 tableView에 전달되는 데이터의 형식이 일관되게 유지됩니다.

기기가 오프라인 상태가 될 때 이벤트 처리하기

마지막으로 한 가지 더 시나리오를 처리해 보겠습니다. 최신 Reddit 게시물을 열람하던 중 갑자기 연결이 끊긴다면 어떻게 될까요? 이 경우에도 오프라인 페이지를 다시 표시하도록 만들어 보겠습니다.

앞서와 마찬가지로 PostsTableViewController에서 OfflineViewController로 향하는 NetworkUnavailable이라는 수동 세그를 생성합니다. 그리고 viewDidLoad 메서드 맨 아래에 아래 코드를 추가하세요.

network.reachability.whenUnreachable = { reachability in self.showOfflinePage()}

그리고 컨트롤러에 아래 메서드를 추가합니다.

private func showOfflinePage() -> Void { DispatchQueue.main.async { self.performSegue(withIdentifier: "NetworkUnavailable", sender: self) }}

이 코드는 기기가 오프라인 상태로 전환되는 순간을 감지하고, 그럴 때마다 showOfflinePage를 호출해 오프라인 페이지로 전환합니다.

이것으로 끝입니다! 우리는 Swift에서 NetworkManager를 활용해 오프라인·온라인 이벤트를 성공적으로 처리했습니다.

마치며

이 글에서는 애플리케이션이 온라인 및 오프라인 이벤트 발생 시 이를 안정적으로 처리할 수 있도록 만드는 방법을 살펴보았습니다. 물론 여러분의 상황에 맞게 얼마든지 다른 방식으로 구현할 수 있습니다. 질문이나 피드백이 있다면 아래 댓글로 남겨주세요.

이 플레이그라운드의 소스 코드는 GitHub에서 확인할 수 있습니다.

이 글은 Pusher에 처음 게재되었습니다.