스파르타 코딩 클럽 - iOS 스타터 6기/본 캠프

32. 스파르타 코딩 클럽 - 네트워크 통신 이해

seongpil Heo 2025. 4. 15. 20:25

 🛜 네트워크 기본 개념

 

🧑‍💻 네트워크란 둘 이상의 컴퓨터가 연결되고 소통하는 것을 말합니다.

  • 아이폰도 하나의 컴퓨터, 서버도 하나의 컴퓨터로 생각할 수 있기 때문에, 서버와 아이폰과의 통신도 네트워크 통신입니다.
  • 인터넷이란 전 세계 컴퓨터를 연결하는 거대한 네트워크를 말합니다.
  • 인터넷 연결을 위해서는 와이파이 연결이 돼있거나 데이터가 켜져있어야 합니다.
  • Swift로 서버와 통신하는 코드를 작성할 수 있습니다.
  • 네트워크 통신 코드를 공부하기 전에, 알아야 할 기본 개념들을 먼저 공부해 봅시다.

 JSON

🧑‍💻 JSON (JavaScript Object Notation) 은 데이터를 표현하는 형식 중 하나입니다.

  • 이름은 Adam, 전화번호는 010-1111-2222라는 전화번호 정보를 알고 있습니다. 이 정보를 다른 누군가에게 전달할 때 어떤 포맷으로 전달하는 게 좋을까요?
  • 그냥 쉽게 문자열로 “이름 Adam, 전화번호 010-1111-2222” 로 보낸다면..?
  • 아니면.. “이름:Adam, 전화번호:010-1111-2222” 이렇게? 🤔
  • 일반적으로 데이터를 표현하는 형식이 있다면, 그걸 따르는 게 좋을 것 같습니다.
  • 이렇게 네트워크에서 데이터를 주고받으려면, 아무렇게나 주고받는 것이 아니라 정해진 형식을 지켜서 데이터를 교환하는 것이 좋습니다.
  • 이 중 서버와 클라이언트가 가장 많이 사용하는 데이터 형식이 JSON 형식입니다.
  • JSON 은 key-value 형태를 가집니다.
  • 예를 들어, 앞선 강의의 전화번호 구조체를 JSON으로 표현하면 다음과 같습니다.
[
    {
        "name": "Adam",
        "phoneNumber": "010-1111-2222"
    },
    {
        "name": "Eve",
        "phoneNumber": "010-3333-4444"
    },
    {
        "name": "Abel",
        "phoneNumber": "010-5555-6666"
    }
]
  • 이 JSON 데이터는 리스트[ ] 안에 3개의 전화번호부 데이터를 표현합니다.
  • JSON 은 특정한 프로그래밍 언어 안에 속하지 않으며, 대부분의 프로그래밍 언어에서는 JSON 포맷의 데이터를 다룰 수 있는 기능을 제공합니다. Swift 역시 마찬가지입니다.

 API

🧑‍💻 API (Application Programming Interface) 란?

  • API를 이해하기 위해서는 먼저 API의 I(Interface)가 뭔지 먼저 이해해야 합니다.
  • 개발 용어에서 인터페이스(Interface)는 항상 창구를 의미합니다.
  • 너는 나의 내부가 어떻게 생겨먹었는지는 정확히 알 필요가 없어. 그저 내가 뚫어준 창구를 통해 나와 소통하면 돼.

🧐 예를 들어, TV를 컨트롤하기 위한 리모컨을 생각해 봅시다.

  • 리모컨의 전원 버튼, 채널 버튼, 음량 버튼 은 TV를 컨트롤하기 위한 **창구 = API**입니다.
  • 여러분들이 실제로 음량 버튼 내부 회로 및 하드웨어가 어떻게 동작하는지 알 필요가 있을까요?
  • 그저 음량 버튼을 눌렀을 때 → TV 음량이 조절된다. 의 결과가 잘 도출되는지가 중요합니다.
    • 음량 버튼을 눌렀다 = API에게 내가 원하는 요청을 했다 = API Request
    • 음량이 조절되었다 = API로부터 요청의 결과를 받았다 = API Response

🧑🏻‍💻 조금 더 실제 개발 상황을 예로 들어볼까요?

  • 서버의 데이터베이스에 모든 유저의 정보를 담고 있습니다. 전화번호까지요.
  • 클라이언트 (= iOS 네이티브 앱)에서 “아담”이라는 유저의 정보를 알고 싶습니다.
  • 서버는 API로 UserInfo라는 API를 뚫어 놓았고, 이 API 를 사용하면 유저의 정보를 알 수 있습니다. API 명세는 다음과 같습니다.
  • API Request는 이렇게 보내주세요.
{
    name: "Seongpil"
}
  • API Response는 이렇게 보내주겠습니다.
{
    "name": "Seongpil",
    "phoneNumber": "010-1111-2222",
    "Mbti": "ESFJ"
}
  • iOS 개발자인 여러분은 이제 이 API 명세를 보고, Request JSON과 Response JSON 형식을 지켜서 서버와 소통하는 코드를 작성하면 됩니다.
  • 서버가 이 데이터를 돌려주기 위해서 내부적으로 어떤 로직을 수행했고, 서버 데이터베이스 내부가 어떻게 생겨먹었고를 알 필요가 없습니다.
  • 그저 서버가 뚫어놓은 API라는 창구를 통해서 서버와 소통을 하고, 원하는 결과를 얻으면 iOS 개발자의 책임은 끝입니다.

👨🏻‍🍳 다시, 이번엔 레스토랑에 비유해 보겠습니다.

  • 클라이언트 앱 = 레스토랑의 손님
  • API = 레스토랑의 메뉴판, 웨이터
  • 서버 = 레스토랑의 요리사
  • 손님이라는 클라이언트는 메뉴판, 웨이터라는 창구를 통해서 요리사라는 서버의 음식을 받습니다.
    • 손님이 메뉴판을 보고 웨이터를 부른다 → API 명세를 파악한다
    • “봉골레 파스타 주세요” → API Request
    • 요리사가 파스타를 손님에게 바친다 → API Response

🤓 마지막으로 API 가 뭔지 정리해 보면,

  • API는 직역 그대로 Application Programming에 필요한 Interface입니다.
  • 즉, 어떤 프로그램을 개발할 때 원하는 기능들을 제공해 주는 창구, 설명서, 도구입니다.

  Swift Codable

🧑🏻‍💻 Swift의 인코딩과 디코딩

  • 인코딩: 데이터를 특정 형식으로 변환하는 것.
  • 디코딩: 인코딩 된 데이터를 다시 원본으로 변환하는 것.
  • Swift 의 Codable 프로토콜을 채택한다는 것은 인코딩과 디코딩이 될 수 있음을 의미.
    • Codable 안을 열어보면 Decodable & Encodable로 구현되어 있음.
  • 서버와 통신하기 위해서, JSON 형식으로 인코딩을 많이 한다.
struct PhoneBook: Codable {
    
    let name: String
    let phoneNumber: String
}

 

  • Codable을 채택함으로써 인코딩 디코딩이 가능한 객체가 됨.

🧑🏻‍💻 JSON 형식의 데이터에서 Swift로 데이터를 디코딩해서 추출하는 과정

import Foundation

struct PhoneBook: Codable {
    
    let name: String
    let phoneNumber: String
}

// string 으로 json 모양의 데이터를 생성.
let jsonString = """
[
    {
        "name": "Adam",
        "phoneNumber": "010-1111-2222"
    },
    {
        "name": "Eve",
        "phoneNumber": "010-3333-4444"
    },
    {
        "name": "Abel",
        "phoneNumber": "010-5555-6666"
    }
]
"""

// jsonString 으로 jsonData 를 생성.
let jsonData = jsonString.data(using: .utf8)!

// Swift 가 제공하는 JSON 디코더.
let jsonDecoder = JSONDecoder()

// JSON -> Codable 디코딩 진행.
do {
    let phoneBooks = try jsonDecoder.decode([PhoneBook].self, from: jsonData)
    for phoneBook in phoneBooks {
        print("name: \(phoneBook.name), phoneNumber: \(phoneBook.phoneNumber)")
    }
} catch {
    print("JSON 디코딩 실패")
}

 URL 구조

🧑‍💻 URL의 구조에 대해 자세히 공부해 봅니다.

  • URL (Uniform Resource Locators): 웹에서 특정 위치를 나타내는 주소.
  • Protocol: http, https → 인터넷 통신 규약을 의미.
  • Domain: 자원이 위치한 서버(컴퓨터)의 이름. 예를 들어 google, naver. url의 정체성을 나타낸다.
  • Port: 구체적으로 어떤 서버를 이용할지 번호로 결정. HTTP의 경우 80. HTTPS는 443.
  • Path: 서버에서 제공하는 자원의 경로를 나타냄.
  • Query: 자원에 대한 추가적인 매개변수를 전달하는 데 사용됨. 주로 key=value 형식으로 표현되며, 여러 개의 매개변수는 &로 구분.
  • Fragment: 자원 내에서 특정 부분을 가리킬 때 사용.
  • ex) https://ko.wikipedia.org/wiki/대한민국#문화

 REST API

 🧑🏻‍💻 REST API (Representational State Transfer) 란 무엇인지 공부합니다.

  • 전 세계에서 대표적으로 널리 쓰이는 API 형식 중 하나.
  • 상태 (State)를 표현해서 정보를 주고받는 API이다.
  • HTTP URL을 통해서 자원을 명시한다.
  • HTTP Method (GET, POST, PUT, DELETE 등)를 통해 해당 자원을 어떻게 할 것인지 CRUD를 결정한다.
    • GET: 자원을 조회합니다.
    • POST: 자원을 생성합니다.
    • PUT: 자원을 업데이트합니다.
    • DELETE: 자원을 삭제합니다.
  • REST API 도 결국 API 이기 때문에, 데이터를 주고받는 형식, 창구라고 생각할 수 있음.

🧐 예를 들어, https://spartacodingclub.com 서버와 REST API 통신을 한다고 가정해 봅시다.

스파르타 코딩클럽의 유저 정보 데이터들은 https://spartacodingclub.com/users에 저장되어 있다고 가정합니다. (실제로 그렇지 않습니다.)

유저 데이터를 조회하고 싶으면 GET 메서드와 해당 URL을 사용해서 네트워크 통신을 하면 됩니다.

요청 방식: GET https://spartacodingclub.com/users

응답:

[
    {
        "id": 1,
        "name": "Adam",
        "email": "adam.doe@example.com"
    },
    {
        "id": 2,
        "name": "Eve",
        "email": "eve.smith@example.com"
    }
]

위처럼 URL 을 가지고 네트워크 통신을 할 수 있게 하는 Swift의 클래스가 바로URLSession입니다.

 URLSession

🧑🏻‍💻 URLSession 은 Swift에서 서버와 통신하기 위해 제공되는 클래스.

  • URLSession을 다루기 위해서는 크게 아래 2가지 개념을 알아야 합니다.
    1. URLSessionConfiguration
    2. URLSessionTask

1. URLSessionConfiguration

Configuration 이란 환경 설정을 의미.

URLSession으로 네트워크 통신을 하되, 여러 가지 커스텀한 설정들을 할 때 URLSessionConfiguration을 이용.

예를 들어 네트워크 통신의 타임아웃 시간 설정, 네트워크 통신 캐시 정책 설정 등을 세팅할 수 있음.

URLSession 객체를 생성하려면 URLSessionConfiguration을 넣어줘야 함.

다음과 같이 default configuration을 활용해 URLSession 생성 가능.

let defaultUrlSession = URLSession(configuration .default)

 

 

2. URLSessionTask

URLSessionTask으로 네트워크 통신을 할 때 어떤 태스크를 수행할 것 인지 결정 가능.

  • URLSessionDataTask: GET 요청. 서버로부터 데이터를 가져오거나 서버에 데이터를 전송할 때 사용.
  • URLSessionDownloadTask 파일 다운로드를 처리할 때 사용. 백그라운드 다운로드 지원.
  • URLSessionUploadTask: 파일 업로드를 처리할 때 사용. 백그라운드 업로드 지원.

🧑🏻‍💻 URLSession을 통해, 서버의 데이터를 GET 해오는 예제 코드를 작성해 봅시다.

  • https://reqres.in/api/users/1
  • 위 URL 은 테스트를 위한 데이터를 내려주는 사이트입니다.
  • GET 메서드를 사용해서 REST API 통신을 수행해 봅시다.
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        fetchData()
    }
    
    // 서버 데이터를 불러오는 메서드 선언
    private func fetchData() {
        
        let defaultUrlSession = URLSession(configuration: .default)
        
        guard let url: URL = URL(string: "https://reqres.in/api/users/1") else {
            print("URL is not correct")
            return
        }
        
        // URLRequest 설정
        var request: URLRequest = URLRequest(url: url)
        
        // GET 메소드 사용
        request.httpMethod = "GET"
        
        // json 데이터 형식임을 나타냄
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        // URLSession 생성 (기본 default 세션)
        let session: URLSession = URLSession(configuration: .default)

        // dataTask
        session.dataTask(with: request) { (data, response, error) in
		        // http 통신 response 에는 status code 가 함께오는데, 200번대가 성공을 의미.
            let successRange: Range = (200..<300)
            
            // 통신 성공
            guard let data, error == nil else { return }
            
            if let response: HTTPURLResponse = response as? HTTPURLResponse{
                print("status code: \(response.statusCode)")
                
                // 요청 성공 (StatusCode가 200번대)
                if successRange.contains(response.statusCode){
                    
                    // decode
                    guard let userInfo: ResponseData = try? JSONDecoder().decode(ResponseData.self, from: data) else { return }
                    print(userInfo)
                    
                } else { // 요청 실패 (Status code가 200대 아님)
                    print("요청 실패")
                }
            }
            
        }.resume()
    }
}

// 데이터 구조체 정의
struct UserData: Codable {
    let id: Int
    let email: String
    let firstName: String
    let lastName: String
    let avatar: URL
    
    // JSON 키와 구조체 프로퍼티 간의 매핑을 위해 CodingKeys 열거형 정의
    enum CodingKeys: String, CodingKey {
        case id
        case email
        case firstName = "first_name"
        case lastName = "last_name"
        case avatar
    }
}

// Support 구조체 정의
struct SupportData: Codable {
    let url: URL
    let text: String
}

// 최상위 구조체 정의
struct ResponseData: Codable {
    let data: UserData
    let support: SupportData
}