API 통신 하면서 여러 API를 불러오게 되면
각각 URLSession을 만들어 데이터를 불러와야 됐다...
그렇다면 한개의 URLSession만 만들어서 사용할 수 있는 방법은 없을까?
에서 출발한 포스팅입니다.
그럼 시작하겠습니다. 컬렉션 뷰를 사용해서 만들어보겠습니다
일단 VC를 깔끔하게 사용하기 위해서
VC, View, Cell을 분리해주었습니다.
( 배운거 사용하고 싶었어요...)
- Cell을 먼저 만듭니다.
import UIKit
import SnapKit
class FirstCell : UICollectionViewCell {
let imageView = {
let image = UIImageView()
image.backgroundColor = .gray
image.contentMode = .scaleToFill
return image
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureView()
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureView() {
contentView.addSubview(imageView)
}
func setConstraints() {
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
- View를 만들어 줍니다.
class FirstView: UIView {
lazy var collectionView = {
let view = UICollectionView(frame: .zero, collectionViewLayout: setCollectionViewFlowLayout())
view.register(FirstCell.self, forCellWithReuseIdentifier: String(describing: FirstCell.self))
return view
}()
private func setCollectionViewFlowLayout() -> UICollectionViewFlowLayout {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let spacing: CGFloat = 8
let width = UIScreen.main.bounds.width - (spacing * 4)
layout.itemSize = CGSize(width: width / 3, height: width / 3)
layout.minimumLineSpacing = spacing
layout.minimumInteritemSpacing = spacing
layout.sectionInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
return layout
}
override init(frame: CGRect) {
super.init(frame: frame)
configureView()
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureView() {
self.addSubview(collectionView)
}
func setConstraints() {
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
- viewDidLoad()가 로드 되기 전에 loadView()에서 먼저 로드를 진행시킵니다.
- 왜냐하면 FirstVC의 RootView에 먼저 커스텀 FirstView를 적용하기 위해서 입니다. <- 시점의 차이
- 전체 코드 입니다.
//
// ViewController.swift
// URLSession+Generic
//
// Created by 염성필 on 2023/09/01.
//
import UIKit
class FristViewController: UIViewController {
let firstView = FirstView()
override func loadView() {
self.view = firstView
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
func configureView() {
firstView.collectionView.dataSource = self
firstView.collectionView.delegate = self
firstView.collectionView.register(FirstCell.self, forCellWithReuseIdentifier: String(describing: FirstCell.self))
}
}
extension FristViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: FirstCell.self), for: indexPath) as? FirstCell else { return UICollectionViewCell() }
return cell
}
}
확실히 VC에 다 넣은것보다 간결해졌쥬?
그렇다면 이제 네트워크 통신을 위해
모델과 URLSesstion을 만들어 보겠습니다
첫번째로 모델 두개를 만들어 주겠습니다
- unsplash 와 TMDB의 이미지 데이터 모델을 만들었습니다.
TMDBPhotoResult에 연산 프로퍼티로
따로 작성된 이유는TMDB에서는 이미지 query만 주기 때문에
baseURL을 추가해줬습니다.
일반적으로 API를 불러올 때 이렇게 작성합니다.
VC에 적용해보겠습니다
네 역시나 잘 나옵니다.
kingfisher를 사용하지 않고 직접적으로
Image에 비동기 작업을 적용해줬습니다.
여기까지는 어렵지 않게 다들 하셨죠?
여기서 오늘의 본론이 나옵니다.
추가로 두번째 모델인 unsplash를 데이터 통신 하려면
NetworkManager에 또 다른 callRequest 함수를 만들어줘야 할까요?
A : 당연 빠따루이지... 말모!!
만약에
( 만약에 충 아니에요 ㅡ,ㅡ)
데이터 통신을 5개, 10개 해야된다면... 하나씩 다 만들어 줄건가요??
... ( 거 말씀이 너무 심한거 아니오)
해결 방법이 있습니다!
Generic에 대해 아시나요?
공식문서에 보면...
핵심 포인트가 보입니다.
플렉시블 하고 함수를 재사용 가능하게 사용할 수있는 코드
제네릭은 Swift의 아주 강려크한 특징이다.
네 그렇습니다. Generic을 사용해서 한번 적용해보겠습니다
혹시 Generic에 대해 모르시는 분들은 개발자들에게 한 줄기 빛이 되어 주시는 소들이님의 블로그를 참고해보시길 바랍니다.
https://babbab2.tistory.com/136
Swift) 제네릭(Generic) 정복하기
안녕하세요 :) 소들입니다! 오늘의 두 번째 포스팅은 Generic에 대한 것입니다! 범용 타입이라구 하죠!! 어렵지 않은 문법이라 호딱 끝내 봅시다 :)) 모든 포스팅은 편의 말투로 합니다~!! 1. Generic이
babbab2.tistory.com
자. 그렇다면 저희 프로젝트에 어떻게 적용해보면 좋을까요?
파라미터 앞에 <T: Codable>을 적용해주고 해당 모델이 들어갔던 자리에 T를 넣어주면 됩니다.
Generic을 사용하실때 이것만 기억하시면 됩니다.
T: placeHolder - 그냥 플레이스홀더 임...
<T: ~ > : ~ 에는 타입이 들어감
처음과 비교해보면 TMDB의 자료형은 Codable로 되어있습니다. 고로 TMDB(T) : Codable 와 같은 의미입니다.
그렇다면 어떻게 적용하면 될까요??
해석을 해보면 " Codable 타입인건 알겠는데 그래서 뭘 넣어줄건데? 그냥 response만 넣으면 끝이 아니잖아? "
(너가 generic을 선택한 결과물이다...견뎌라...)
자 그렇다면 어떻게 해줘야 할까요? 아주 쉽습니다.
TMDB의 자료형 Codable이었죠?
response의 자료형을 TMDB로 설정합니다.
그렇게 되면 response == TMDB와 같아집니다.
(와웅미...) 참 신기하죠? 역시 파워풀하네요
여기까지 EBS 수학 교재였다면...
주의 ❗️
지금부터 숨마쿰라우데 버젼 시작하겠습니다.
( 벌써...싫다..)
그렇다면... unsplash를 불러볼까요?
생각해야되는 부분이 제네릭으로 만든 함수를 보면 baseUrl을 받아와서
URLSession에 적용시켜 주니까 열거형을 만들어서 human Error를 최대한 없애줍니다.
첫번째로 열거형을 먼저 만들어줍니다.
파라미터에 열겨형을 추가해줍니다.
switch 문을 통해서 들어오는 파라미터의 값에 따라 다른 url이 할당 받을 수 있도록 합니다.
이렇게 Network를 만들어주고 호출해주면 됩니다.
참 쉽죠?
물론 unsplashList를 만들어줘야 되는 센스 다 아시죠?
var list: TMDB = TMDB(results: [])
var unsplashList: Photo = Photo(total: 0, total_pages: 0, results: [])
원하는 것만 보여주기 위해 주석 처리를 하면 됩니다.
자... 여기서 끝나면 아쉽겠죠?
(그...그만 이러다가 다 죽어 )
한번에 두 API를 불러봅시다.
어떻게 한 VC에서 두가지의 API를 보여줄 수 있을까요?
Collection Section 추가
현재까지는 한개의 section에서 해주었다면 section을 2개로 설정해줍니다.
그 다음은 section에 맞게 보여주고싶은 데이터를 나눠주기만 하면 됩니다.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: FirstCell.self), for: indexPath) as? FirstCell else { return UICollectionViewCell() }
if indexPath.section == 0 {
let unsplashItem = unsplashList.results[indexPath.item].urls.thumb
let unsplashurl = URL(string: unsplashItem)!
// 비동기로 작업
DispatchQueue.global().async {
// 이미지 주소 Data화 시키기
let data = try! Data(contentsOf: unsplashurl)
// Main Thread에서 그려주기
DispatchQueue.main.async {
// 데이터 통신 해서 image에 넣기
cell.imageView.image = UIImage(data: data)
}
}
} else {
let item = list.results[indexPath.item].imageUrl
let url = URL(string: item)!
// 비동기로 작업
DispatchQueue.global().async {
// 이미지 주소 Data화 시키기
let data = try! Data(contentsOf: url)
// Main Thread에서 그려주기
DispatchQueue.main.async {
// 데이터 통신 해서 image에 넣기
cell.imageView.image = UIImage(data: data)
}
}
}
return cell
}
}
흠... 코드가 조금 지저분한 느낌이 들죠? 리팩토링 해보겠습니다.
이렇게 줄여 볼 수 있겠네요
cellForRowAt 부분에 cell.setConfigure부분이 중복되긴 하네요...
여기도 한번 더 리팩토링 해보겠습니다.
( 적당히 해라... !!!)
section에 따라 들어오는 url을 담을 변수를 지정해줍니다.
section 별로 담긴 sectionImageurl을
마지막에 담아주기만 하면 끝!
Section별로 이미지가 다른것을 볼 수 있습니다.
이상 먼저해주고 대신해주는 아무개 발자였습니다. 감사합니다.
'새싹' 카테고리의 다른 글
새싹 - 앱 출시 프로젝트 회고 (0) | 2023.10.25 |
---|---|
Realm을 사용시 이미지를 Documents에 저장할때 이미지 URL로 저장하는 방법은? (2) | 2023.09.06 |
SPM으로 SnapKit을 추가하고 UIView를 상속받을때 Import Snapkit을 하지 않아도 되는 이유! (4) | 2023.08.29 |
스토리보드 -> CodeBase 전환시 발생할 수 있는 오류 (Indicator편) (2) | 2023.08.28 |
CodeBase에서 UITabBar 적용하기 (0) | 2023.08.27 |