03.[Hacker_News] EndPoint 와 Delegate Pattern 이해하기

장일규·2022년 1월 21일

Hacker_News

목록 보기
2/2


업로드중..
📁NewsApp
├📁Util
│ └─📄ColorTheme.swift
│ └─📄Constant.swift
├📁Model
│ └─📄RestProcessor.swift
├📁View
│ └─📄MainCell.swift
├📁Controllers
│ └─📄MainViewController.swift
├📄AppDelegate.swift
├📄SceneDelegate.swift

이번에는 EndPointRestProcessor를 구현했습니다.

해당 모듈들이 어떠한 역할을 하는 것인지 지금부터 이야기하겠습니다.

먼저 이전에 MainViewController에 CollectionView cell안에 301234이런 식에 StoryID를 뿌려주기 위해 fetchData()함수에서 https://hacker-news.firebaseio.com/v0/topstories.json 에 url을 담아 서버에 요청하여 데이터를 가져왔습니다.
이렇게 하면 MainViewController에 역할이 많아지고 코드도 길어집니다.
따라서, Constant라는 파일을 만들어 EndPoint를 공통화하여 APICall을 할 수 있도록 해봅시다.

Constant.swift

import Foundation

let baseURL: String = "https://hacker-news.firebaseio.com/v0"

//EndPoint
enum ProjectURL: CustomStringConvertible {
    case topStories
    
    var description: String {
        switch self {
            case .topStories:
                return baseURL + "/topstories.json?print=pretty"
        }
    }
}

baseURL은 정적이기 때문에, 상수로 선언해주었습니다.

API-Call은 topstories, /item/{item-id} 두 가지를 할 예정입니다.

topstories는 collectionView리스트에 보여지게 될 500개의 데이터이고,

/item/{item-id}는 리스트에 item(topstories)을 클릭 시 아이템에 대한 세부 정보를 가져오는 데이터 입니다.

Delegate 패턴

Delegate Pattern에 대해서 학습하였습니다.

현재 MainViewController에는 fetchData메서드에서 topstories에 대한 정보 500개를 Call하고 있다.

하나의 API를 Call한다면 문제가 없지만, 위에서 언급했듯이 한 개 이상에 API를 Call할 예정이다.

따라서, MainViewController에 각 API에 맞는 Call이 될 수 있도록 코드를 변경해 보고자 한다.

RestProcessor.swift

  • Delegate란?
    • Delegate는 '위임한다'라는 의미를 가지고있기에 위임자라고 생각하면 된다.
    • Delegate를 사용하기 위해 RestProcessorDelegate를 상속하지 않고 Protocol로 만들어 기능을 확장한다.

수신자(RestProcessor)위임자(MainViewController) 에게 self로 자기 자신을 넘겨주고, 위임자(MainViewController)수신자(RestProcessor) 객체의 메소드를 '대신' 실행하는 것을 볼 수 있다.

import UIKit

protocol RestProcessorDelegate: AnyObject {
    func didSucessWith(data: Data, response: URLResponse, usage: ProjectURL)
    func didFailwith(error: Error?, response: URLResponse?, usage: ProjectURL)
}

class RestProcessor {
    
    weak var delegate: RestProcessorDelegate?

    
    func fetchData(urlString: String, usage: ProjectURL) {
        guard let url = URL(string: urlString) else { return }
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        let session = URLSession.shared
        let dataTask = session.dataTask(with: url) {
            data, response, error in
            
            guard error == nil,
                let httpResponse = (response as? HTTPURLResponse),
                httpResponse.statusCode == 200,
                
                    let data = data else {
                        self.delegate?.didFailwith(error: error, response: response, usage: usage)
                        
                        return
                    }
            
            self.delegate?.didSucessWith(data: data, response: httpResponse, usage: usage)
        }
        
        dataTask.resume()
    }
    
}
  1. RestProcessorDelegate 프로토콜을 만들고, 해당 프로토콜을 구현한 곳에서 서버에서 errordata에 대해서
    핸들링 할 수 있다.

  2. RestProcessorDelegate는 class-Type에서만 채택하고 싶기 때문에 AnyObject를 상속해준다.

  3. RestProcessorDelegate타입에 delegate변수 선언

    weak var delegate: RestProcessorDelegate?

    이때, 변수의 속성은 꼭 var로해주어야 한다. ARC관련 이슈

  4. URLSession로직은 이전 fetchData메서드와 거의 동일하다.

    다만, urlString: String, usage: ProjectURL 파라미터가 추가되었다.

    urlString은 어떤 API-Call인지에 대한 정보를 담아온다.

    Call이 실패 할 경우

    data가 없을 경우 didFailWith를 호출한다.

     let data = data else {
    		  self.delegate?.didFailWith(error: error, response: response, usage: usage)
    		return
      }

    Call이 성공한 경우

    위에 data가 있을 경우에는 didSuccessWith를 호출한다.

    		self.delegate?.didSuccessWith(data: data, response: httpResponse, usage: usage)

MainViewController.swift

restProcessor 변수선언

	var restProcessor: RestProcessor!

MainViewController에 서버와 통신하기 위해서 만든 RestProcessor클래스에 접근 하기 위해서 restProcessor라는 변수를 만들었다.

restProcessor 객체 생성 및 delegate 설정

    func setConfigureRestProcessor() {
        restProcessor = RestProcessor()
        // restProcessor nil이 되면 메모리 해제됨
        restProcessor.delegate = self
    }

RestProcessorDelegate 구현

didSuccessWith()함수와 didFailwith()함수를 필수로 구현하고 안에 성공실패에 대한 로직을 구현한다.

didSuccessWith()는 서버 요청에 응답이 성공적으로 수행 시 data를 받아서 파싱 후 배열에 담아 Collection View cell에 뿌려준다.

extension MainViewController: RestProcessorDelegate {
    func didSuccessWith(data: Data, response: URLResponse, usage: ProjectURL) {
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { return }
        
        switch usage {
            case .topStories:
                guard let decoded = try? JSONDecoder().decode([Int].self, from: data) else { return}
                self.storyIDs = decoded
                
                DispatchQueue.main.async {
                    self.collectionView.reloadData()
                }
            default:
                break
        }
    }
    
    func didFailwith(error: Error?, response: URLResponse?, usage: ProjectURL) {
        
    }   
}

0개의 댓글