URLSession의 개념과 활용

velog_ghost·2022년 8월 30일
0

[SeSAC] TIL📚

목록 보기
19/19

서론


지금까지는 API 통신을 위해 Alamofire을 이용하여 데이터를 받아왔다. 하지만 Alamofire은 라이브러리이기 때문에 점차 라이브러리를 줄여가기 위해서 apple이 제공하는 URLSession을 사용하여 API와 통신하고 데이터를 처리해주는 방법에 대해 알아보자.


URLSession 사용 순서


URLSession을 사용하는 순서는 이와 같다.
1. Configuration 결정
2. Session 생성
3. Request에 사용할 url 설정
4. Task 결정 및 작성


URLSessionConfiguration


URSession을 통해 데이터를 다운로드하거나 업로드 할 때, URLSessionConfiguration으로 세부적인 동작과 정책을 설정할 수 있음.

  • Shard Session : 단순한 네트워크 요청을 할 때 사용. 커스터마이징 X. 네트워크 응답은 Completion Handler를 통해 전달받음
  • Default Session : URLSessionConfiguration을 통해 직접 생성하므로 Shard Session과 유사하지만 커스터마이징이 가능함. 네트워크 응답에 대해서는 Delegate를 통해 세부적인 제어 가능
  • Ephemeral Session : Shard Session과 비슷하지만 쿠키, 캐시, 인증 정보 등을 디스크에 기록하지 않으므로 private 기능을 구현할 때 사용 (ex: 시크릿 모드)
  • Background Session : URLConfiguration을 통해 직접 생성하는 세션으로 앱이 실행되어있지 않을때나 백그라운드 상태에서도 데이터를 다운로드 하거나 업로드 하게 할 수 있음.

URLSessionTask


세션이 생성된 이후에는 Task를 생성하게 되는데, URLSession을 통해 생성되는 개별 요청이 Task임
Task는 데이터 전달 방식 / 구현 목적에 따라 타입 존재

  • Data task
  • UploadTask
  • DownloadTask
  • StreamTask

Task를 생성한 이후에는 suspended 상태이기 때문에 resume메서드를 통해 Task를 시작할 수 있음. 따라서 resume을 호출해야 네트워크 통신을 시작할 수 있음.


URLRequest

네트워크 요청에 대한 정보를표현하는 객체로 네트워크 요청을 하기 위해서는 URLSession이 필요함

URLResponse

URL 로드 요청에 대한 응답과 관련된 메타데이터임.


서버로부터 응답받은 데이터를 처리하는 방법은 두가지임.


1. Completion Handler

Task에 대한 completion handler 형태로 응답받을 수 있고, Task가 종료된 시점에 한 번만 호출. 서버로부터 전달받은 data와 HTTP Header, 응답에 대한 메타데이터가 들어간 response, 요청 실패했을 때의 error에 대한 값을 전달받을 수 있음.

2. Session Delegate

Task가 실행되는 동안 발생할 수 있는 다양한 상황에 대해 세부적으로 처리를 하고자 할 때 사용.
서버로부터 최초 응답을 받았을 때, 서버로부터 데이터를 받을 때마다, 데이터 전송을 다 받았을 때의 시점 등에 대한 이벤트 처리 가능.


실습

API를 받아와 받은 API Data를 TableView에 보여주기

먼저 componet 변수를 설정해주고, URLComponets를 통해 url을 만들어주자.

class PersonAPIManager {
    static func requestPerson(query: String, completion: @escaping (Person?, APIError?) -> Void) {
        
        let scheme = "https"
        let host = "api.themoviedb.org"
        let path = "/3/search/person"
        
        let language = "ko-KR"
        let key = ""
        let query = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        
        var component = URLComponents()
        component.scheme = scheme
        component.host = host
        component.path = path
        component.queryItems = [
            URLQueryItem(name: "api_key", value: key),
            URLQueryItem(name: "query", value: query),
            URLQueryItem(name: "page", value: "1"),
            URLQueryItem(name: "region", value: language)
        ]
        
//        URLSession.request(endpoint: component.url!, completion: sucess, fail)
        
        
        URLSession.shared.dataTask(with: component.url!) { data, response, error in
            
            DispatchQueue.main.async {
                guard error == nil else {
                    print("Failed Request")
                    completion(nil, .failedRequest)
                    return
                }
                
                guard let data = data else {
                    print("No Data Returned")
                    completion(nil, .noData)
                    return
                }
                
                guard let response = response as? HTTPURLResponse else {
                    print("Unable Response")
                    completion(nil, .invalidResponse)
                    return
                }
                
                guard response.statusCode == 200 else {
                    print("Fail Response")
                    completion(nil, .failedRequest)
                    return
                }
                
                do {
                    let result = try JSONDecoder().decode(Person.self, from: data)
                    completion(result, nil)
                } catch {
                    print(error)
                    completion(nil, .invalidData)
                }
            }
        }.resume()
    }
}

이후 error 열거형으로 선언해준 뒤, 각 열거형의 case마다 처리를 해주었다.

enum APIError: Error {
    case invalidResponse
    case noData
    case failedRequest
    case invalidData
}

이후 quicktype을 통해 생성한 구조체를 새로운 파일에 선언해주면

struct Person: Codable {
    let page, totalPages, totalResults: Int
    let results: [Result]
    
    enum CodingKeys: String, CodingKey {
        case page, results
        case totalPages = "total_pages"
        case totalResults = "total_results"
    }
    
}

struct Result: Codable {
    let knownForDepartment, name: String
    
    enum CodingKeys: String, CodingKey {
        case knownForDepartment = "known_for_department"
        case name
    }
}

URLSession을 통한 API 통신을 할 수 있게 된다.

이를 tableView에 보여주기 위해 작성하면


import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView! {
        didSet {
            tableView.delegate = self
            tableView.dataSource = self
        }
    }
    @IBOutlet weak var lottoLabel: UILabel!
    
    var list: Person = Person(page: 0, totalPages: 0, totalResults: 0, results: [])
    
    override func viewDidLoad() {
        super.viewDidLoad()
        ![](https://velog.velcdn.com/images/qudgus1984/post/7ac6318a-23b4-4244-bf44-e973522801ed/image.png)

        
        PersonAPIManager.requestPerson(query: "kim") { person, errer in
            guard let person = person else {
                return
            }
            dump(person)
            self.list = person
            self.tableView.reloadData()
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return list.results.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
        cell.textLabel?.text = list.results[indexPath.row].name
        cell.detailTextLabel?.text = list.results[indexPath.row].knownForDepartment
        return cell
    }
}

이렇게 kim으로 시작하는 데이터를 받아올 수 있다.

0개의 댓글