 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/iOS Dev Challenges

[Challenge] 간단한 네트워크 통신에 대해 탐구해보자!

singularis7 2021. 12. 16. 00:54
반응형

Overview

휴대폰에 설치되어있는 어플리케이션을 살펴보면 모두 네트워크 통신을 사용한다. 네트워크 통신 중에는 여러가지 외적인 변수가 개입될 수 있다. 예를 들어 iOS 앱이 서버에 무언가를 요청했는데 서버가 조금 늦게 처리해서 응답해줄 수도 있고 네트워크 환경에 따라 처리된 결과가 늦게 아이폰으로 도착할 수도 있을 것이다. 데이터를 송수신하는 것을 처리하는동안 사용자가 어플리케이션에 아무런 조작도 할 수 없고 기다려야 한다면.... 한국인으로써 매우 유감스러운 상황이 발생하게 될 것이다. 오늘은 iOS 상에서 간단한 네트워크 통신을 다뤄보며 병행성을 다뤄보는 것이 목표이다.

 

Basic

앱에서 네트워크 기능을 구현하기 전에 간단하게 네트워크가 동작하는 컨셉에 대하여 살펴볼 필요가 있다. HTTP롸 HTTPS 프로토콜은 웹 상에서 정보를 송수신하는데 자주 활용되는 방식인데, 사용자가 요청한 항목을 웹서버에게 전달하기위해 URL을 만들어가는 것 그리고 네트워크를 통해 데이터를 송수신하는데 Foundation 에 정의된 API를 사용하는 방법에 대해서 알아둘 필요가 있다.

URL

보통 웹사이트를 열면 사용자가 보낸 네트워크 요청이 인터넷을 통해 웹서버에게 전달된다. 이 과정에서 어떤 컴퓨터가 어떤 요청 정보를 받을지 구체적으로 명시해줄 수 있는 수단이 필요한데 웹 브라우저 주소창에 입력하는 URL이 이와 같은 수단에 해당된다. URL은 다음과 같은 두가지 부분으로 나눠질 수 있다.

URL 은 좀더 구체적인 정보를 표현해줄 수 있는데, 서브 도메인, 포트, 경로, 쿼리질의문을 포함할 수 있다. 이렇게 분해해서 생각해보면 URL이 아무리 길어지더라도 단순하게 이해해볼 수 있을 것이다.

Swift 에서 URL 객체를 통해 URL 객체를 표현할 수 있다. URL에 잘못된 문자가 포함되어있다면 URL 객체를 생성할 수 없도록 failable 생성자를 통해 구현되어있기에 nil을 반환할 수 있는 상태이다.

let url = URL(string: "https://www.apple.com")

 

Network Request

앞서 어떻게 URL 인스턴스를 생성하는지 알아보았다면 URLSession 클래스를 통해 네트워크 요청을 생성해서 실행시킬 수 있다! 어떠한 앱이던지 URLSession의 공유된 인스턴스에 접근할 수 있으며 이 세션은 모든 네트워크 요청을 관리해줄 책임을 갖고 있다. 그리고 각 요청이 끝나면 코드를 실행시킬 수 있도록 도움을 준다.

URLSession 의 data(from:delegate) async throws 메서드를 활용하면 데이터를 요청할 수 있다. from 부분에는 요청하고자 하는 URL 인스턴스를 넣어주면 되며 delegate 부분은 optional URLSessionTaskDelegate 타입이기 때문에 해당 세션의 탭을 유지할 때에는 유용할 수 있겠지만 이번의 경우 사용하지 않을 것이기에 그냥 비워둔다. 옵셔널이기 때문에 아무것도 할당하지 않으면 자동으로 nil 값을 갖게 된다.

let (data, response) = URLSession.shared.data(from: url)

 

data 메서드는 data, response 를 튜플 타입으로 반환한다. data 부분에는 HTTP response 의 body 부분의 컨텐츠를 의미하고 response 는 헤더 파일이나 상태코와 같은 정보를 담고 있다. data 는 스위프트에서의 데이터 타입이지만 response 는URLResponse 타입이라고 한다.

근데 문제는 위 코드를 그냥 입력하면 오류가 발생된다. 오류 메시지를 자세히 살펴보니 async 함수가 병행성이 지원되지 않는 환경에서 호출되었다는 경고 메시지를 출력하고 있다.

async는 무슨 역할을 해주는 키워드일까? 이전에 작성한 모든 코드는 동기식(synchronously)으로 동작한다. 우리가 생각하는 것처럼 우리가 코딩한 순서대로 실행되는 것이다. Swift와 iOS는 비동기식(asynchronous) 코드를 지원하고 있으며 async로 마킹된 함수는 현재 실행중인 코드를 중지한 후에 다른 코드를 실행하다가 추후에 다시 본래 실행 흐름을 이어갈 수 있다. 이러한 비동시식 코드 실행은 concurrency로 알려져있다.

Concurrency 는 프로그램에서 코드의 다른 부분을 다른 스레드나 큐에서 동시에 실행할 수 있도록 하는 아이디어이다. 예를 들어 네트워킹이나 애니메이션과 연관된 코드를 생각해볼 수 있다.

URLSession이 동작하는데 concurrency가 중요한 이유에 대해서 이해하기위해 어떻게 네트워크 요청이 이뤄지는지 이해하는 것이 중요하다. 네트워크 요청은 인터넷을 통해 서버에 전송된 후 서버의 응답이 인터넷을 통해 나의 컴퓨터에 도달하게 된다. 보통의 경우에 이 과정은 아주 빠르게 이뤄지지만 문제는 빠르게 처리된다는 것을 언제나 보장할 수는 없다는 점이다. 서버가 큰 파일을 처리하면서 더 많은 시간이 소요될 수도 있고 네트워크 연결 상태가 좋지 못한 상황일 수도 있다. 이런 이슈에 의해서 네트워크 요청이 처리되는 시간이 수십초에 걸쳐서 처리될 수도 있다.

스위프트은 병행성 시스템은 어떻게 비동기식 코드를 실행시킬지 제어하기위해 actors 라는 개념을 사용한다. actors 는 레퍼런스 타입인데 상태가 변할 수 있는 자료에 한번에 하나의 task 가 접근할 수 있도록 제어해준다. 이 과정을 통해 다양한 task 들이 동일한 자료에 접근하는 상황에서 안전한 코드를 구현하도록 도와준다.

iOS는 기본적으로 모든 유저 인터페이스를 실행할 main actor를 생성한다. 특정한 상황이 아닌 경우 개발자가 작성한 대부분의 코드는 main actor에서 실행되지만 네트워크 요청을 전송하거나 응답을 수신하는 것처럼 일정 시간이 소요될 수 있는 연산을 main actor 에서 동기적으로 실행시킨다면 UIKit에서 터치를 다루거나 View를 그리는 다른 동작들 또한 함께 block 될 수 있다. 사용자의 눈에는 시간이 오래 걸리는 연산이 종료될 때 까지 앱이 멈춰있는 것처럼 보이게 될 것이다.

위와 경우가 비동기 방식의 코드가 힘을 발휘할 수 있는 상황이 될 수 있다. 시간이 오래 소요되는 작업이 완료될 때까지 main actor가 기다리게 하는 상황을 피하기위해, URLSession 같은 친구들은 데이터를 송수신하는 연산을 비동기적으로 수행한다. 이 방식으로 구현하면 잠시 중단된 코드가 네트워크 응답을 받기위해 기다리는 동안 main actor는 유저 인터페이스를 갱신하고 사용자의 입력에 계속해서 응답할 수 있다.

비동기 코드를 작성해보자!

이전에 보았던 URLSession의 data 메서드는 async (비동기) 함수로 선언되어있다. 비동기 함수를 호출하려면 비동기식 코드를 실행시키는 방식인 Task를 활용해야하기 때문에 후행 클로저 방식의 문법으로 비동기식으로 동작하는 코드를 감싸주면 동작한다.

let url = URL(string: "https://www.apple.com")!

Task {
	do {
		let (data, response) = try await URLSession.shared.data(from: url)
		responseTextField.title = response.description
		dataTextField.title = String(data: data, encoding: .utf8) ?? ""
	} catch {
		print(error.localizedDescription)
	}            
}
반응형