티스토리 뷰

iOS

[iOS] URLSession

Peppo 2022. 3. 27. 18:17
728x90

네트워크 통신으로 alamofire만 공부를 했었는데 

URLSession 을 기반으로 만들어져 해당 내용을 알아야 도움이 될것 같다는 생각에 정리를 해봅니다. 

SwiftUI 에선 URLSession + Combine 조합을 더 많이 사용한다는 말도 있더라구요 ㅠ 

 


GET 으로 데이터를 받아와서 CollectionView에 보여주는 작업을 해보려고 합니다. 

 

 

 

네이버 API 를 사용하려면 clientID와 clientSecret이 필요합니다.

https://developers.naver.com/apps/#/register?defaultScope=search

 

애플리케이션 - NAVER Developers

 

developers.naver.com

 


 

URLSession

HTTP/ HTTPS를 통해 콘텐츠 및 데이터를 주고받기 위해 API를 제공하는 class

 

URLSession 통신 단계

  • Model 생성
  • URL 생성
  • URLSession 생성
  • URLSession에 task 주기
  • task 시작

Model 생성

Json 데이터를 decode 해서 원하는 데이터를 받아오려면 struct 또는 class 형식의 데이터 모델을 만들어줘야 합니다.

데이터 모델을 파싱 하기 위해선 Codable 프로토콜 채택 해줘야 합니다.

 

Decodable: JSON -> Model
Encodable: Model -> JSON

 

우리가 받아올 데이터의 형태는 아래같이 생겼어요 (Postman 이용)

 

위의 형태에 맞게 Model을 세팅해줍니다. 

받아오는 json 데이터를 복사 후 여기 에 붙여넣기 하면 자동으로 Model 형태를 잡아줍니다.

// MovieModel.swift

import Foundation

// MARK: MovieModel

struct MovieModel: Codable {
    let items: [Movie]
}

// MARK: Movie

struct Movie: Codable {
    let title, subtitle: String
    let link: String
    let image: String
    let director, actor: String
    let pubDate, userRating: String
}

 

URLSession 통신 부분의 코드 입니다. 

아래 하나씩 설명 하니 슥 읽어보세요 !

// MovieService.swift

import Foundation

struct MovieService {
    static let shared = MovieService()
// 1
    let urlString = "https://openapi.naver.com/v1/search/movie.json?query=avengers"
    let clientID = APIConstant.clientID
    let clientSecret = APIConstant.clientSecret
    
    func fetchMovieData(completion: @escaping (Result<Any, Error>) -> ()) {
// 2
        if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)
            
            var requestURL = URLRequest(url: url)
            requestURL.addValue(clientID,
                                forHTTPHeaderField: "x-naver-client-id")
            requestURL.addValue(clientSecret,
                                forHTTPHeaderField: "x-naver-client-secret")
// 3
            let dataTask = session.dataTask(with: requestURL) { (data, response, error) in
                if error != nil {
                    print(error!)
                    return
                }
                if let safeData = data {
                    do {
                        let decodedData = try JSONDecoder().decode(MovieModel.self, from: safeData)
                        completion(.success(decodedData))
                    } catch {
                        print(error.localizedDescription)
                    }
                }
            }
// 4
            dataTask.resume()
        }
    }
}

 

1. URL 생성

let urlString = "https://openapi.naver.com/v1/search/movie.json?query=avengers"

 

2. URLSession 생성

if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)

옵셔널 바인딩(if let)을 통해 위에서 만든 url 값이 nil인지 아닌지에 따라 분기 처리를 해줍니다.

그 후에 URLSession을 생성해줍니다.

 

 

addValue를 통해 HeaderclientIDclientSecret을 추가해줍니다.

var requestURL = URLRequest(url: url)
requestURL.addValue(clientID, forHTTPHeaderField: "x-naver-client-id")
requestURL.addValue(clientSecret, forHTTPHeaderField: "x-naver-client-secret")

 

3. URLSession에 task 주기

 

dataTask(with:) 메소드를 통해 특정 RequestURL 객체로 부터 데이터를 받아올 수 있다. 

let dataTask = session.dataTask(with: requestURL) { (data, response, error) in
                if error != nil {
                    print(error!)
                    return
                }

dataTask가 완료 되었을 때 실행 될 completionHandler (data, response, error)를 작성해줍니다.

 

 

if let safeData = data {
                    do {
                        let decodedData = try JSONDecoder().decode(MovieModel.self, from: safeData)
                        completion(.success(decodedData))
                    } catch {
                        print(error.localizedDescription)
                    }
                }

• JSONDecoder: JSON을 decode 해주는 클래스 

 

 decode( dataType, from: decode하려는 Data)

     - dataType은 꼭 Decodable 프로토콜을 따라야 합니다. 

        여기서 MovieModelCodable을 채택하고 있기 때문에 사용할 수 있습니다.

 

do { try code  } catch { code } 

  - decode를 하다가 에러가 발생할 때 처리해주는 구문

do {
     let decodedData = try JSONDecoder().decode(MovieModel.self, from: safeData)  // 에러가 발생할 수도 있는 부분
         completion(.success(decodedData))
     } catch {
           print(error.localizedDescription)                // 에러가 발생한 부분
              }

 

4.  task 시작

 

dataTask.resume()

 


ViewController.swift 역할

 

movieLists라는 [Movie] 모델 타입의 빈배열을 호출 합니다. -> 여기에 API 호출해서 받아오는 데이터를 넣어줄 겁니다.

 

CollectionView 부분 코드는 제외 하였습니다.

맨 하단에 전체 코드 첨부 한게 있으니 일단은 아래 getMovieData() 메소드의 역할을 이해하는데 따라오세요~

// URLSessionController.swift

class UrlSessionViewController: UIViewController {

  var movieLists: [Movie] = []
  
   override func viewDidLoad() {
        super.viewDidLoad()
        
        getMovieData()
        
    }
    
    func getMovieData() {
        MovieService.shared.fetchMovieData { (response) in
            switch response {
            // 1
            case .success(let movieData):
            
            // 2
                if let decodedData = movieData as? MovieModel {
                    self.movieLists = decodedData.items
            // 3        
                    DispatchQueue.main.async {
                        self.movieListCollectionView.reloadData()
                    }
                    
                    return
                }
            case .failure(let movieData):
                print("fail", movieData)
            }
        }
    }
 }

 

코드 풀이

더보기

1. MovieService.shared.fetchMovieData 를 호출해서 데이터를 성공적으로 호출하고, 디코딩 된 데이터를 상수 movieData에 넣어줍니다.

 

2. movieData 의 값은 이전에 fetchMovieData() 에서 디코딩이 다된 상태입니다.

// MovieService.swift 
// func fetchMovieData()

 do {
      let decodedData = try JSONDecoder().decode(MovieModel.self, from: safeData)
          completion(.success(decodedData))
}

   movieData가 잘 디코딩 되었는지 확인후 빈배열로 선언했던 movieLists에 넣어줍니다.

 

3. 데이터를 이제 UI에 업데이트 해줘야 하는 작업인데요. 

    UI를 업데이트 하는 부분이 생기면 main thread 에서 처리해야 하므로, 아래와 같이 코드를 작성해 줍니다.

// URLSessionController.swift
// func getMovieData()


DispatchQueue.main.async {
  self.movieListCollectionView.reloadData()
}

 


 

받아온 데이터를 CollectionViewCell 에 매칭

 

// URLSessionController.swift

// MARK: UICollecionViewDataSource

extension UrlSessionViewController: UICollectionViewDataSource {
	// 1
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return movieLists.count
    }
    
    // 2
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: movieListCollectionViewCell.identifier, for: indexPath) as! movieListCollectionViewCell
        
    // 3
        cell.setUpCell(with: movieLists[indexPath.row])
        return cell
    }
    
}

 

코드 풀이 

더보기

1. numberOfItemsInSection

   collectionViewCell에 나타낼 Cell의 수

 

2. cellForItemAt

  cell이 어떤 CollectionViewCell인지, 어떤 데이터를 넘겨 받을건지 정할 수 있습니다. 

 

3. movieListCollectionViewCell 에 있는 메소드 setUpCell(with:)에

    받아온 데이터(movieLists)를 하나씩 파라미터 값으로 전달 합니다.

// MovieListCollectionViewCell.swift

func setUpCell(with movies: Movie) {
        guard let url = URL(string: movies.image) else { return }
        guard let data = try? Data(contentsOf: url) else { return }
        movieImageView.image = UIImage(data: data)
        movieTitleLabel.text = movies.title
        
        contentView.addSubview(movieImageView)
        contentView.addSubview(movieTitleLabel)
        
        movieImageView.snp.makeConstraints {
            $0.leading.top.trailing.equalToSuperview()
            $0.bottom.equalToSuperview().inset(60)
        }
        
        movieTitleLabel.snp.makeConstraints {
            $0.leading.trailing.equalToSuperview()
            $0.top.equalTo(movieImageView.snp.bottom)
            $0.bottom.equalToSuperview()
        }
    }

 

 

 

전체 코드는 여기 참고해주세요. 

 

API 요청에 필요한 clientID , clientSecret 키는 꼭!! 개인이 받아서 입력해주세요.

 

오늘은 URLSession의 GET 방법을 알아보았는데요. 

동시에 CollectionView도 공부가 되었던것 같습니다. 

CollectionView는 TableView와는 비슷하면서도 또 다른 느낌이네요.


 

참고

1. roniruny

728x90