Combine 리팩토링

Jisu·2023년 9월 11일
0

iOS

목록 보기
8/9

일반적으로 프로그래밍에서 태스크가 실행되면 결과값이 바로 반환된다. 하지만 통신과 같은 태스크는 통신을 하고 응답을 받아오기까지 시간이 걸린다.
이렇게 결과값이 바로 반환되지않고 이후에 반환되는 시점을 인식해서 후처리를 하는 것을 비동기 태스크라고한다.

iOS에서 비동기 태스크를 처리하는 방식을 다양하게 있는데 최신에는 async await를 지원하고 있다. (escaping closure, Notification Center 등이 있었다) 이런 비동기 처리 방식을 통일시킨 프레임워크가 combine이다.

여기서는 기존의 async await 통신 코드를 combine으로 리팩토링 해보는 과정을 소개한다

1. 기존의 URLSession 구조

import SwiftUI
import Foundation

final class FoodCategoryFilter {
    
    static let instance = FoodCategoryFilter()
    
    @MainActor
    func queryFoods(category: String) async -> [Properties] {
        
        var foodsData: [Properties] = []
        
        let filterParameter = """
            {
              "filter": {
                "property": "category",
                "select": {
                  "equals": \(category)
                }
              }
            }
            """.data(using: .utf8) ?? Data()
        
        let request = NSMutableURLRequest(url: NSURL(string: DataBaseInfo.readURL)! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
        request.httpMethod = "POST"
        request.allHTTPHeaderFields = DataBaseInfo.headers
        request.httpBody = filterParameter
        
        do {
            let (data, response) = try await URLSession.shared.data(for: request as URLRequest)

            let httpResponse = response as! HTTPURLResponse
            if !(200...299).contains(httpResponse.statusCode) {
                throw NetworkError.responseError
            }
            print(httpResponse.statusCode)
            print("data \(data)")
            
            guard let decodedData = try? JSONDecoder().decode(Test.self, from: data) else { throw
                NetworkError.decoidngError }
            
            for data in decodedData.results {
                foodsData.append(data.properties)
            }
            
            
        } catch NetworkError.decoidngError {
            print(NetworkError.decoidngError.localizedDescription)
        } catch NetworkError.responseError {
            print(NetworkError.responseError.localizedDescription)
        } catch {
            print(error)
        }
        
        return foodsData
    }
}

2. Combine을 사용한 URLSession

URLSession.DataTaskPublisher라는 Combine publisher을 제공하고 있다.

import SwiftUI
import Combine

final class FoodListVM: ObservableObject {

    @Published var foods: [Properties] = []
    @Published var favorites: [Properties] = []
    @Published var isLoading: Bool = false
    var cancellables = Set<AnyCancellable>()

    func queryFoods(category: String) {
        let parameters = "{\n\"filter\": {\n\"property\": \"category\",\n\"select\": {\n\"equals\": \"\(category)\"\n}\n}\n}"
        let postData = parameters.data(using: .utf8)

        var request = URLRequest(url: URL(string: "https://api.notion.com/v1/databases/\(DataBaseInfo.databaseID)/query")!,timeoutInterval: Double.infinity)
        request.addValue("2022-06-28", forHTTPHeaderField: "Notion-Version")
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue(DataBaseInfo.token, forHTTPHeaderField: "Authorization")
        request.httpMethod = "POST"
        request.httpBody = postData

        self.isLoading = true

        URLSession.shared.dataTaskPublisher(for: request)
            .tryMap(handleOutput)
            .decode(type: NotionDTO.self, decoder: JSONDecoder())
            .map { decodedData in
                decodedData.results.map { $0.properties }
            }
            .receive(on: DispatchQueue.main)
            .sink { completion in
                self.isLoading = false
            } receiveValue: { [weak self] result in
                print("receive value work")
                self?.foods = result
            }
            .store(in: &cancellables)
    }
    
        func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
        guard let response = output.response as? HTTPURLResponse,
              response.statusCode >= 200 && response.statusCode < 300 else {
           				 throw URLError(.badServerResponse)
      	 		 }
		        return output.data
   	 		}
	}

훨씬 코드 가독성이 증가하였다.
특히나 에러처리 방법은 tryMap 오퍼레이트안에 함수로 전달함으로서 에러처리에 대한 코드를 따로 관리할 수 있다.

profile
비즈니스에 관심많은 DevOps Engineer 장지수입니다.

0개의 댓글