본문 바로가기

iOS

APIManager

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