
📁NewsApp
├📁Util
│ └─📄ColorTheme.swift
│ └─📄Constant.swift
├📁Model
│ └─📄RestProcessor.swift
├📁View
│ └─📄MainCell.swift
├📁Controllers
│ └─📄MainViewController.swift
├📄AppDelegate.swift
├📄SceneDelegate.swift
이번에는 EndPoint와 RestProcessor를 구현했습니다.
해당 모듈들이 어떠한 역할을 하는 것인지 지금부터 이야기하겠습니다.
먼저 이전에 MainViewController에 CollectionView cell안에 301234이런 식에 StoryID를 뿌려주기 위해 fetchData()함수에서 https://hacker-news.firebaseio.com/v0/topstories.json 에 url을 담아 서버에 요청하여 데이터를 가져왔습니다.
이렇게 하면 MainViewController에 역할이 많아지고 코드도 길어집니다.
따라서, Constant라는 파일을 만들어 EndPoint를 공통화하여 APICall을 할 수 있도록 해봅시다.
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 Pattern에 대해서 학습하였습니다.
현재 MainViewController에는 fetchData메서드에서 topstories에 대한 정보 500개를 Call하고 있다.
하나의 API를 Call한다면 문제가 없지만, 위에서 언급했듯이 한 개 이상에 API를 Call할 예정이다.
따라서, MainViewController에 각 API에 맞는 Call이 될 수 있도록 코드를 변경해 보고자 한다.
위임자라고 생각하면 된다.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()
}
}
RestProcessorDelegate 프로토콜을 만들고, 해당 프로토콜을 구현한 곳에서 서버에서 error과 data에 대해서
핸들링 할 수 있다.
RestProcessorDelegate는 class-Type에서만 채택하고 싶기 때문에 AnyObject를 상속해준다.
RestProcessorDelegate타입에 delegate변수 선언
weak var delegate: RestProcessorDelegate?
이때, 변수의 속성은 꼭 var로해주어야 한다. ARC관련 이슈
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)
var restProcessor: RestProcessor!
MainViewController에 서버와 통신하기 위해서 만든 RestProcessor클래스에 접근 하기 위해서 restProcessor라는 변수를 만들었다.
func setConfigureRestProcessor() {
restProcessor = RestProcessor()
// restProcessor nil이 되면 메모리 해제됨
restProcessor.delegate = self
}
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) {
}
}