티스토리 뷰
네트워크 통신으로 alamofire만 공부를 했었는데
URLSession 을 기반으로 만들어져 해당 내용을 알아야 도움이 될것 같다는 생각에 정리를 해봅니다.
SwiftUI 에선 URLSession + Combine 조합을 더 많이 사용한다는 말도 있더라구요 ㅠ
GET 으로 데이터를 받아와서 CollectionView에 보여주는 작업을 해보려고 합니다.
네이버 API 를 사용하려면 clientID와 clientSecret이 필요합니다.
https://developers.naver.com/apps/#/register?defaultScope=search
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를 통해 Header에 clientID와 clientSecret을 추가해줍니다.
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 프로토콜을 따라야 합니다.
여기서 MovieModel은 Codable을 채택하고 있기 때문에 사용할 수 있습니다.
• 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
'iOS' 카테고리의 다른 글
[Swift] Subscripts (서브스크립트) (0) | 2022.04.01 |
---|---|
[iOS] UserDefaults 로 객체를 저장을 해보자 (0) | 2022.03.30 |
[iOS] .contentMode (scaleToFill / scaleAspectFit / scaleAspectFill) (0) | 2022.03.25 |
[Swift] 메소드 Methods (0) | 2022.03.23 |
[iOS] GCD Tutorial: Dispatch Groups (0) | 2022.03.20 |
- Total
- Today
- Yesterday
- Swift init
- RTCCameraVideoCapturer
- Swift ModernRIBs
- Swift 프로그래머스
- Swift 알고리즘
- Swift joined()
- 원티드 프리온보딩
- Swift
- Swift 내림차순
- RIBs tutorial
- swift programmers
- Combine: Asynchronous Programming with Swift
- swift reduce
- removeLast()
- swift 고차함수
- Swift inout
- 2023년 회고
- Swift joined
- Class
- Swift final
- Swift 프로퍼티
- Swift Leetcode
- Swift Error Handling
- ios
- swift protocol
- CS 네트워크
- swift (programmers)
- Swift RIBs
- swift property
- iOS error
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |