iOS/Toy project

[iOS : Toy Project] Github Profile (2) : Refactoring

yevdev 2022. 7. 17. 18:11

💡 Github Profile Project Refactoring!

Refactoring이 이루어질 이전 코드는 위의 링크에서 참고하자 

 

 

- ResourceNetworkService를 이용해서 코드를 줄여보자

- Resource 를 이용해서 아래의 코드 줄여보기 = URLRequest 에 필요한 것을 만들어줌

let base = "https://api.github.com/"
let path = "users/\(keyword)"
let params: [String:String] = [:]
let header: [String:String] = ["Content-Type":"application/json"]

var urlComponents = URLComponents(string: base+path)!
let queryItems = params.map {(key: String, value: String) in
    return URLQueryItem(name: key, value: value)
}
urlComponents.queryItems = queryItems

// base부터 header까지 URLRequest로 만들기
var request = URLRequest(url: urlComponents.url!)
header.forEach{ (key: String, value: String) in
    request.addValue(value, forHTTPHeaderField: key)
}

- NetworkService 를 이용해서 아래의 코드 줄여보기

URLSession.shared
        .dataTaskPublisher(for: request)
        .tryMap{ result -> Data in
            guard let response = result.response as? HTTPURLResponse,
                  (200..<300).contains(response.statusCode) else {
                      let response = result.response as? HTTPURLResponse
                      let statusCode = response?.statusCode ?? -1
                      throw NetworkError.responseError(statusCode: statusCode)
                  }
            return result.data
        }
        // 받은 데이터를 가지고 JSONDecoder을 이용하여  UserProfile로 Decoding
        .decode(type: UserProfile.self, decoder: JSONDecoder())
        .receive(on: RunLoop.main)
        .sink { completion in
            print("completion: \(completion)")
            switch completion {
            case .failure(let error):
                self.user = nil
            case .finished: break
            }
        } receiveValue: { user in
            // ViewController에 있는 User로 세팅!
            self.user = user
        }.store(in: &subscriptions)

 

 

 

그럼 Resource와 NetworkService 코드는 어떻게 생겼는가?

Resource.swift

//  Resource.swift

import Foundation

struct Resource<T: Decodable> {
    var base: String
    var path: String
    var params: [String: String]
    var header: [String: String]
    
    var urlRequest: URLRequest? {
        var urlComponents = URLComponents(string: base + path)!
        let queryItems = params.map { (key: String, value: String) in
            URLQueryItem(name: key, value: value)
        }
        urlComponents.queryItems = queryItems
        
        var request = URLRequest(url: urlComponents.url!)
        header.forEach { (key: String, value: String) in
            request.addValue(value, forHTTPHeaderField: key)
        }
        
        return request
    }
    
    init(base: String, path: String, params: [String: String] = [:], header: [String: String] = [:]) {
        self.base = base
        self.path = path
        self.params = params
        self.header = header
    }
}

base, path, params, header만 받으면 알아서 request 반환

 

 

NetworkService.swift

//  Network.swift

import Foundation
import Combine

///// Defines the Network service errors.
enum NetworkError: Error {
    case invalidRequest
    case invalidResponse
    case responseError(statusCode: Int)
    case jsonDecodingError(error: Error)
}

final class NetworkService {
    let session: URLSession
    
    init(configuration: URLSessionConfiguration) {
        session = URLSession(configuration: configuration)
    }
    
    func load<T>(_ resource: Resource<T>) -> AnyPublisher<T, Error> {
        guard let request = resource.urlRequest else {
            return .fail(NetworkError.invalidRequest)
        }
        
        return session
            .dataTaskPublisher(for: request)
            .tryMap { result -> Data in
                guard let response = result.response as? HTTPURLResponse,
                      (200..<300).contains(response.statusCode)
                else {
                    let response = result.response as? HTTPURLResponse
                    let statusCode = response?.statusCode ?? -1
                    throw NetworkError.responseError(statusCode: statusCode)
                }
                return result.data
            }
            .decode(type: T.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

load를 통해 request를 뽑아서, session에게 해당 request에 필요한 data task를 만들게 하여 데이터를 받아서 넘겨주는 것까지 하고있음

loadresource만 넣으면 알아서 데이터가 처리됨.

 

 

 

1️⃣ NetworkService 객체 가져오기

let network = NetworkService(configuration: .default)

2️⃣ UserProfile 데이터를 받기 위한 Resource 생성 (Resource.swift 이용)

let resource = Resource<UserProfile>(
    base: "https://api.github.com/",
    path: "users/\(keyword)",
    params: [:],
    header: ["Content-Type":"application/json"]
)

3️⃣ NetworkService 객체의 load 함수를 이용해 자동 데이터 처리

network.load(resource)
    .receive(on: RunLoop.main)
    .sink { completion in
        switch completion {
        case .failure(let error):
            self.user = nil
        case .finished: break
        }
    } receiveValue: { user in
        self.user = user
    }.store(in: &subscriptions)

 

 

리펙토링 끝!

 

 

 


Reference

패스트캠퍼스 온라인 강의