본문 바로가기

iOS (스파르타)/앱 개발 숙련

네트워크 통신 이해

네트워크 기본 개념

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

 

  • 아이폰도 하나의 컴퓨터, 서버도 하나의 컴퓨터로 생각할 수 있기 때문에, 서버와 아이폰과의 통신도 네트워크 통신입니다.
  • 인터넷이란 전 세계 컴퓨터를 연결하는 거대한 네트워크를 말합니다.
  • 인터넷 연결을 위해서는 와이파이 연결이 되있거나 데이터가 켜져있어야 합니다.
  • Swift 로 서버와 통신하는 코드를 작성할 수 있습니다. (Swift에서 기본 제공해 주는 네트워크 통신용 클래스는 URLSession)

 

JSON 이란

 

  • 일반적으로 데이터를 표현하는 형식이 있다면, 그걸 따르는 게 좋을 것 같습니다.
  • 이렇게 네트워크에서 데이터를 주고받으려면, 아무렇게나 주고 받는 것이 아니라 정해진 형식을 지켜서 데이터를 교환하는 것이 좋습니다.
  • 이 중 서버와 클라이언트가 가장 많이 사용하는 데이터 형식이 JSON 형식입니다.
  • JSON 은 key-value 형태를 가집니다. (중괄호안에 키랑 밸류값을 넣어줌)
  • 예를 들어, 앞선 강의의 전화번호 구조체를 JSON 으로 표현하면 다음과 같습니다.
//JSON

[
    {
        "name": "Adam",
        "phoneNumber": "010-1111-2222"
    },
    {
        "name": "Eve",
        "phoneNumber": "010-3333-4444"
    },
    {
        "name": "Abel",
        "phoneNumber": "010-5555-6666"
    }
]
  • 이 JSON 데이터는 리스트[ ] 안에 3개의 전화번호부 데이터를 표현합니다.
  • JSON 은 특정한 프로그래밍 언어 안에 속하지 않으며, 대부분의 프로그래밍 언어에서는 JSON 포맷의 데이터를 다룰 수 있는 기능을 제공합니다. Swift 역시 마찬가지입니다. (JSON 인코더 디코더 스위프트에서 제공해줌)

 

API (Application Programming Interface)

 

  • API 를 이해하기 위해서는 먼저 API 의 I(Interface) 가 뭔지 먼저 이해해야 합니다.
  • 개발 용어에서 인터페이스(Interface)는 항상 창구를 의미합니다.

 

실제 개발 상황을 예로 들면

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

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

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

 

Swift Codable

 

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

  • 인코딩: 데이터를 특정 형식으로 변환하는 것.
  • 디코딩: 인코딩 된 데이터를 다시 원본으로 변환하는 것.
  • Swift 의 Codable 프로토콜을 채택한다는 것은 인코딩과 디코딩이 될 수 있음을 의미.
    • Codable 안을 열어보면 Decodable & Encodable 로 구현되어있음.
  • 서버와 통신하기 위해서, JSON 형식으로 인코딩을 많이 한다. 
struct PhoneBook: Codable {     
// Codable을 책택한다는 건 Swift에서 Encodable, Decodable을 함께 채택한 거고 
// 그말은 인코딩과 디코딩이 가능한 객체가 된다는 말이다.
    
    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) // try라서 디코딩 실패할수도있음
    for phoneBook in phoneBooks {
        print("name: \(phoneBook.name), phoneNumber: \(phoneBook.phoneNumber)")
    }
} catch {
    print("JSON 디코딩 실패")
}

 

 

 

 

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 (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 = response as? HTTPURLResponse{
            // 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
}

 

→ 서버에서 받아온 값이 잘 print 되는 것 확인.