2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

[Swift-Study] iOS 앱 개발 숙련 1주차 - 네트워크 통신

2024. 4. 12. 18:15ㆍIOS/Swift-Study
SMALL

1. URL 구성요소의 이해

각 URL 구성요소들은 API 엔드포인트와 함께 특정 자원에 대한 요청을 식별하고 전달하는 데 사용


1-1. 프로토콜 (Protocol)

URL은 일반적으로 'http://' 또는 'https://'와 같은 프로토콜로 시작

클라이언트와 서버 간의 통신 방법을 지정
'http://'는 보안 없는 통신을, 'https://'는 'SSL/TLS 암호화'를 사용, 보안된 통신을 나타냄

1-2. 도메인 (Domain)

도메인은 API 서버가 호스팅되는 서버의 주소를 나타냄

(예시로, 'api.example.com' 은 API 서버가 위치한 서버의 도메인 주소)


1-3. 포트 (Port, 옵션)

포트 번호 = 서버에서 API 요청 수신에 사용되는 포트
대부분의 경우
HTTP 기본 포트 = 80
HTTPS 기본 포트 = 443

↓↓↓

일반적으로 생략될 수 있음

1-4. 경로 (Path)

서버에서 요청된 자원이나 서비스의 위치

예를 들어, /users 경로는 사용자 자원에 대한 요청

'엔드포인트'와 연관되어 특정 자원이나 서비스를 식별


1-5. 쿼리 매개변수 (Query Parameters, 옵션)

URL 추가 정보 전달에 사용

예시로, "?page=2&sort=desc"와 같이 사용자가 원하는 페이지 번호 혹은 정렬 방법을 서버에 전달 가능


2. REST API 의 개념

REST는 HTTP 프로토콜 기반,

클라이언트 ↔ 서버 간의 통신을 위한 규칙 정의


2-1. HTTP 메서드 (HTTP Methods)

HTTP 메서드를 사용 → 자원을 다룸

 

[가장 널리 사용되는 HTTP 메서드]

 

GET(갯)

자원을 읽기 위해 사용

(ex. 유저를 조회합니다.)

 

POST(포스트)

새로운 자원을 생성하기 위해 사용

멱등 X (ex. 유저를 생성(가입)합니다.)

쉽게 말해 수정 불가

 

PUT(풋)

기존 자원을 업데이트하기 위해 사용

멱등 O (ex. 유저를 수정합니다.)

쉽게 말해 수정 가능

 

DELETE(딜리트)

자원을 삭제하기 위해 사용

(ex. 유저를 삭제(탈퇴)합니다.)

 

멱등, 멱등성 : 여러번 요청(시도) 해도 모든 결과값이 동일한 성질


2-2. 자원 (Resources)

모든 데이터가 자원으로 표현

자원은 고유 식별자(일반적으로 URI로 표현)를 갖는다.

ex) 웹 사이트의 사용자 프로필, 글, 이미지, 동영상 등은 각각의 자원으로 표현됨.


2-3. URI (Uniform Resource Identifier)

자원은 고유한 식별자인 URI를 갖는다.

URI는 자원을 찾을 수 있는 주소다.

ex) 웹 사이트 사용자 프로필을 나타내는 URI는 "https://example.com/users/123"과 같이 표현가능

 

 URL과 URI의 차이:
URI는 리소스를 식별하기 위한 일반적인 용어,
URL은 리소스 위치를 나타내는 구체적 형태다.

2-4. 표현 (Representations)

자원의 상태는 여러 형식으로 표현가능

일반적으로 JSON 형식 사용 → 데이터 표현

클라이언트, 서버 간 통신은 이와 같은 표현을 통해 이루어짐


2-5. 연결 (Stateless Communication)

클라이언트, 서버 간의 통신을 위해 연결 유지X

각 요청 = 독립적으로 처리


3. URLSession의 이해

'URLSession'은 네트워크 데이터를 가져오거나 보내는 작업을 수행


3-1. URLSession의 주요 특징

비동기적 네트워킹
(Asynchronous Networking)
다양한 데이터 전송 방식 지원 캐시와 쿠키 관리
비동기적 네트워크 요청을 처리

네트워크 작업이 백그라운드에서 수행 가능

앱의 성능을 향상, 응답성을 유지
데이터 업로드 및 다운로드 가능 네트워크 응답을 캐싱 

쿠키 관리 기능 제공


JSON, 이미지, 파일 등
다양한 데이터 형식을 처리 가능
 
  JSON 데이터를 다운로드하여 사용  
// GET 예시
import Foundation

// URLSession 인스턴스 생성
let session = URLSession.shared

// URL 생성
if let url = URL(string: "https://api.example.com/data") {
    // URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
    let task = session.dataTask(with: url) { (data, response, error) in // response의 경우 통신응답에 의한 검증코드를 의미
        if let error = error {
            print("Error: \(error)")
        } else if let data = data {
            // 데이터를 받아온 후 처리하는 로직을 작성
            print("Received data: \(data)")
        }
    }
    
    // 네트워크 요청 시작
    task.resume()
}
// URLRequest를 사용한 POST 예시

import Foundation

// URLSession 인스턴스 생성
let session = URLSession.shared

// URL 생성
if let url = URL(string: "https://api.example.com/data") {
  // URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
  
  // URLRequest 생성
  var request = URLRequest(url: url)
  
  // HTTP 메서드 설정 (POST)
  request.httpMethod = "POST" // GET / PUT / DELETE 사용 가능

  // HTTP 헤더 설정
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
  
  // HTTP Body에 보낼 데이터 설정 (JSON 형식)
  let parameters: [String: String] = [
    "value1": "example value",
    "value2": "example value"
    // 추가적인 필요한 데이터 추가 가능
  ]
  
  // HTTP Body에 JSON 데이터 설정
  request.httpBody = try? JSONEncoder().encode(parameters)
  
  let task = session.dataTask(with: request) { (data, response, error) in
    if let error = error {
      print("Error: \(error)")
    } else if let data = data {
      // 데이터를 받아온 후 처리하는 로직을 작성
      print("Received data: \(data)")
    }
  }
  
  // 네트워크 요청 시작
  task.resume()
}

4. Decodable(디코더블), Encodable(엔코더블), Codable(코더블)의 이해

4-1. Decodable(디코더블) 프로토콜

데이터 객체로 디코딩할 때 사용

(즉, 외부 데이터(JSON)를 Swift의 데이터 모델 변환에 필요한 프로토콜)

 

"Decodable(디코더블)"을 준수하는 객체 → 외부 데이터를 해석 및 데이터를 객체의 프로퍼티로 매핑 가능해야 함

CodingKeys(코딩키즈) → 디코딩 할 때, 프로퍼티들에 대한 매핑을 제공하는 역할 (아래 예시 참고)

// id 라는 프로퍼티의 디코딩 키를 key로, name 프로퍼티의 디코딩 키를 프로퍼티 이름과 동일하게 지정한 예시
struct User: Decodable {
    let id: Int
    let name: String
		// 다른 프로퍼티들...

		public enum CodingKeys: String, CodingKey {
      case id = "key"
      case name
    }

    // Decoding - 디코딩
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
    }
}

4-2. Encodable(엔코더블) 프로토콜

객체 → 데이터로 인코딩할 때 사용

(즉, Swift의 데이터 모델을 외부 데이터(JSON)로 변환에 필요한 프로토콜)

"Encodable(엔코더블)"을 준수하는 객체는 객체의 프로퍼티를 외부 데이터 형식(JSON)으로 인코딩 가능해야 함

CodingKeys(코디키즈)는 인코딩 할 때, 프로퍼티들에 대한 매핑을 제공하는 역할 (아래 예시참고)

// id라는 프로퍼티의 인코딩 키를 key로, name 프로퍼티의 인코딩 키를 프로퍼티 이름과 동일하게 지정한 예시
struct User: Encodable {
    let id: Int
    let name: String
    // 다른 프로퍼티들...

		public enum CodingKeys: String, CodingKey {
      case id = "key"
      case name
    }

    // Encodable 프로토콜을 준수하기 위한 커스텀 인코딩 로직
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        // 다른 프로퍼티들도 인코딩 가능
    }
}

4-3. Codable(코더블) 프로토콜

"Codable(코더블)" 프로토콜 → 두 가지 하위 프로토콜, "Encodable(엔코더블)"과 "Decodable(디코더블)" 을 결합한 것

public typealias Codable = Decodable & Encodable

외부 데이터(JSON)를 Swift의 데이터 모델로 변환,

Swift의 데이터 모델을 외부 데이터(JSON)로 변환을 모두 수행하여야 할 때,

Codable(코더블) 프로토콜을 사용 가능

 

CodingKeys(코딩키즈) 는 인코딩/디코딩 할 때,

프로퍼티들에 대한 매핑을 제공하는 역할

/* 
 id라는 프로퍼티의 인/디코딩 키를 key로, name 프로퍼티의 인/디코딩 키를 
 프로퍼티 이름과 동일하게 지정한 예시
*/
struct User: Codable {
    let id: Int
    let name: String
    // 다른 프로퍼티들...

		public enum CodingKeys: String, CodingKey {
      case id = "key"
      case name
    }

    // Encodable 프로토콜을 준수하기 위한 커스텀 인코딩 로직
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        // 다른 프로퍼티들도 인코딩 가능
    }

    // Decoding - 디코딩
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
    }
}

4-4. JSON → Model 변환하기

위 내용을 바탕으로"Data Model" 작성 → URL세션으로 받은 "Data" 타입을
"JSONDecoder(제이슨디코더)" 클래스를 사용 
데이터를 모델 객체로 디코딩
do {
    let user = try JSONDecoder().decode(User.self, from: data)
    print("디코딩된 사용자: \(user)")
} catch {
    print("디코딩 에러: \(error)")
}

위의 코드 "JSONDecoder().decode(_:from:)" 메서드를 사용 → JSON 데이터를 User 모델 객체로 디코딩
[ps. 만약 디코딩에 실패하면 catch 블록에서 에러 처리]


5. URLSession 을 통한 REST API 통신

아래 JSON Dummy API (제이슨 더미)를 활용 → 상품 정보 가져와 출력

 

API 정보

[GET] https://dummyjson.com/products/{ID}

ID의 범위: 1 ~ 100

Response JSON

{
    "id": 1,
    "title": "iPhone 9",
    "description": "An apple mobile which is nothing like apple",
    "price": 549,
    "discountPercentage": 12.96,
    "rating": 4.69,
    "stock": 94,
    "brand": "Apple",
    "category": "smartphones",
    "thumbnail": "https://i.dummyjson.com/data/products/1/thumbnail.jpg",
    "images": [
        "https://i.dummyjson.com/data/products/1/1.jpg",
        "https://i.dummyjson.com/data/products/1/2.jpg",
        "https://i.dummyjson.com/data/products/1/3.jpg",
        "https://i.dummyjson.com/data/products/1/4.jpg",
        "https://i.dummyjson.com/data/products/1/thumbnail.jpg"
    ]
}
// 예시코드
struct Product: Decodable {
    let id: Int
    let title: String
    let description: String
    let price: Double
    let discountPercentage: Double
    let rating: Double
    let stock: Int
    let brand: String
    let category: String
    let thumbnail: String
    let images: [String]
}

let productID = 2

if let url = URL(string: "https://dummyjson.com/products/\(productID)") {
    // URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
            print("Error: \(error)")
        } else if let data = data {
			do {
				let product = try JSONDecoder().decode(Product.self, from: data)
				print("Deocded Product: \(product)")
			} catch {
				print("Decode Error: \(error)")
			}
        }
    }
    
    // 네트워크 요청 시작
    task.resume()
}
728x90