728x90
그동안 서버 통신을 ViewController에서 작업을 했다면, 이를 보다 효율적으로 사용해보도록 하자.
싱글톤 패턴을 적용해서 서버 통신 시, APIManager를 만들어 ViewController와 서버통신을 나누어 작업을 진행해 볼 수 있다.
맥주 정보를 랜덤으로 받아오는 서버통신을 다음과 같이 구현했다고 할 때,
final class BeerViewController: UIViewController {
// MARK: - UI Property
@IBOutlet weak var collectionView: UICollectionView!
// MARK: - Property
private var beerList: [BeerResponse] = []
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
requestBeerData()
}
// MARK: - Custom Method
private func configureUI() {
configureCollectionView()
}
private func configureCollectionView() {
let layout = UICollectionViewFlowLayout()
let spacing: CGFloat = 8
let width = UIScreen.main.bounds.width - (spacing * 3)
layout.itemSize = CGSize(width: width / 2, height: (width / 2) * 1.8)
layout.scrollDirection = .vertical
layout.sectionInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
layout.minimumLineSpacing = spacing
layout.minimumInteritemSpacing = spacing
collectionView.collectionViewLayout = layout
collectionView.dataSource = self
collectionView.register(UINib(nibName: BeerCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: BeerCollectionViewCell.identifier)
}
}
// MARK: - UICollection Protocol
extension BeerViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return beerList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BeerCollectionViewCell.identifier, for: indexPath) as? BeerCollectionViewCell else { return UICollectionViewCell() }
cell.setData(beerList[indexPath.item])
return cell
}
}
// MARK: - Network
extension BeerViewController {
func requestBeerData() {
let url = Constant.EndPoint.beerURL
AF.request(url, method: .get).validate(statusCode: 200...500).responseData { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let statusCode = response.response?.statusCode ?? 500
if statusCode == 200 {
for beer in json.arrayValue {
let imageURL = beer["image_url"].stringValue
let name = beer["name"].stringValue
let description = beer["description"].stringValue
let data = BeerResponse(imageURL: imageURL, name: name, welcomeDescription: description)
self.beerList.append(data)
self.collectionView.reloadData()
}
} else {
}
case .failure(let error):
print(error)
}
}
}
}
APIManager를 만들어 작업을 하면 아래와 같이 코드를 분리할 수 있다.
1. ViewController에서 서버통신을 하는 코드를 먼저 따로 분리한다.
class BeerAPIManager {
static let shared = BeerAPIManager()
private init() { }
func fetchBeerInfo() {
let url = Constant.EndPoint.beerURL
AF.request(url, method: .get).validate(statusCode: 200...500).responseData { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let statusCode = response.response?.statusCode ?? 500
if statusCode == 200 {
for beer in json.arrayValue {
let imageURL = beer["image_url"].stringValue
let name = beer["name"].stringValue
let description = beer["description"].stringValue
// let data = BeerResponse(imageURL: imageURL, name: name, welcomeDescription: description)
// self.beerList.append(data)
//
// self.collectionView.reloadData()
}
} else {
}
case .failure(let error):
print(error)
}
}
}
}
위의 코드블럭에서 주석처리가 되어 있는 부분은 ViewController에서 처리할 수 있는 로직, 즉 UI와 관련된 작업이므로 APIManager 클래스에서는 처리하지 않는다.
2. 고차 함수를 이용해서 ViewController에 넘겨줄 데이터를 저장한다.
그리고 @escaping 키워드를 통해서 탈출 클로저로 데이터를 넘겨준다.
class BeerAPIManager {
static let shared = BeerAPIManager()
private init() { }
typealias completionHandler = ([BeerResponse]) -> Void
func fetchBeerInfo(completionHandler: @escaping completionHandler) {
let url = Constant.EndPoint.beerURL
var dataList: [BeerResponse] = []
AF.request(url, method: .get).validate(statusCode: 200...500).responseData { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let statusCode = response.response?.statusCode ?? 500
if statusCode == 200 {
for beer in json.arrayValue {
let imageURL = beer["image_url"].stringValue
let name = beer["name"].stringValue
let description = beer["description"].stringValue
let data = BeerResponse(imageURL: imageURL, name: name, welcomeDescription: description)
dataList.append(data)
}
completionHandler(dataList)
}
case .failure(let error):
print(error)
}
}
}
}
3. 그리고 ViewController에서 APIManager를 호출하여 서버 통신을 구현한다.
// MARK: - Network
extension BeerViewController {
func requestBeerData() {
BeerAPIManager.shared.fetchBeerInfo { beerList in
self.beerList = beerList
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
}
만약, 여기서 페이지네이션을 구현하게 된다면,
self.beerList = beerList
이 아니라, 아래와 같이 코드를 수정해야 한다.
self.beerList.append(contentsOf: beerList)
ViewController에 있는 서버 통신 코드를 분리해서 APIManger로 구현한다고 했을 때, 그 방법을 요약하면 아래와 같다.
- ViewController에서 서버 통신 코드를 갖고 와서
- APIManager 안으로 이동한다.
- 이 때, APIManager는 전역적으로 사용할 인스턴스를 만들어야 하므로
- 싱글톤 패턴을 사용해서 싱글톤 객체를 하나 생성하고
- 서버통신 이후 반환할 데이터를 전달하기 위해 completionHandler를 만든 다음에 (이를 탈출할 수 있게 해야 하므로, @escaping 키워드를 작성한다.)
- 싱글톤 패턴 개념의 연장선으로 그 안에서만, 외부에서는 초기화를 할 수 없도록 해야하므로 init()을 private으로 바꾼다.
- 마지막으로 서버통신이 필요한 시점에서 ViewController에서 통신을 한 뒤, 받은 데이터를 바탕으로 UI를 수정한다.
'iOS' 카테고리의 다른 글
Cell안의 UIButton 이벤트 처리 (+WebKit View) (0) | 2022.08.08 |
---|---|
TMDB - GET / Pagenation (0) | 2022.08.08 |
Pagenation (0) | 2022.08.05 |
.gitignore (0) | 2022.08.05 |
프로토콜 - Basic To Advanced (0) | 2022.08.03 |