[Combine] Networking(feat. RestAPI)
Networking(feat. RestAPI)
Combine을 활용해서 쉽게 서버와 통신을 처리할 수 있습니다.
이 글의 제일 아래 부분에서는 MVVM에 대한 고찰도 들어 있습니다.
난이도는 총 3가지로 나뉘어 있습니다. 파일을 확인해주세요!
이 포스팅에서는 Refactoring(상)과 Advanced Model에 대해서만 설명합니다.
자세한 코드는 아래 첨부 파일을 확인해주세요!!!
🥕 목차 🥕
1. Refactoring (상)
2. MVVM에 대한 고찰(Combine 한달차,, 2022.06.11)
🟠 모델
//
// UserModel.swift
// CombineNetworking
//
// Created by Hamlit Jason on 2022/06/10.
//
// let user = try? newJSONDecoder().decode(User.self, from: jsonData)
import Foundation
// MARK: - UserElement
struct UserElement: Codable, Hashable {
let id: Int
let name, username, email: String
let address: Address
let phone, website: String
let company: Company
static func == (lhs: UserElement, rhs: UserElement) -> Bool {
return lhs.name == rhs.name && lhs.username == rhs.username
}
}
// MARK: - Address
struct Address: Codable, Hashable {
let street, suite, city, zipcode: String
let geo: Geo
static func == (lhs: Address, rhs: Address) -> Bool {
return lhs.street == rhs.street
}
}
// MARK: - Geo
struct Geo: Codable, Hashable {
let lat, lng: String
}
// MARK: - Company
struct Company: Codable, Hashable {
let name, catchPhrase, bs: String
}
typealias User = [UserElement]
모델에서 주목해야 하는 부분은 Hashable입니다. 이 부분을 채택해야지 비교가 가능하기 때문에 Hashable을 상속받고 구현합니다. == 메소드 안에서 구현하는데, 단순한 예제라서 정말 하나의 속성만 맞으면 같다고 판단하게 설정하였습니다.
🟠 뷰
//
// RefactoringView.swift
// CombineNetworking
//
// Created by Hamlit Jason on 2022/06/11.
//
import SwiftUI
struct RefactoringView: View {
@EnvironmentObject var engine: RefactoringEngine
var body: some View {
NavigationView {
VStack {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(Color.green)
TextField("", text: $engine.typing, onCommit: {
engine.input = engine.typing
})
}
.padding(.horizontal, 20)
.frame(height: 40)
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.green, lineWidth: 1)
)
.padding([.horizontal, .top], 16)
.padding(.bottom, 10)
Spacer()
List {
ForEach(engine.users, id: \.self) { user in
Text("id필터링 에시!: \(user.id)")
}
.listStyle(.plain)
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("리팩토링엔진 장착!")
}
}
}
}
}
}
view 부분입니다. 특별한 점은 없습니다.
다만 네비게이션뷰를 설정할 때 navigationBarTitleDisplayMode를 지정해주지 않으면, 우리가 원한 값보다 큰 영역으로 네비게이션 뷰를 차지해서 원하는 결과가 아닐 수 있습니다.
🟠 엔진
//
// RefactoringEngine.swift
// CombineNetworking
//
// Created by Hamlit Jason on 2022/06/11.
//
// 고급 json: https://jsonplaceholder.typicode.com/users
import Combine
import SwiftUI
class RefactoringEngine: ObservableObject {
static let urlString = "https://jsonplaceholder.typicode.com/users"
@Published var users: User = []
var cancleables = Set<AnyCancellable>()
@Published var typing: String = ""
@Published var input: String = "" {
didSet {
send(filterId: Int(input))
}
}
init() {
send()
}
func send(filterId: Int? = nil) {
guard let url = URL(string: RefactoringEngine.urlString) else { return }
URLSession.shared.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global(qos: .background))
.receive(on: DispatchQueue.main)
.tryMap(handleOutput)
.decode(type: User.self, decoder: JSONDecoder())
.map { users in
return users.filter { user in
user.id != filterId
}
}
.sink { completion in
print("✅ completion \(completion)")
} receiveValue: { [weak self] responseElements in
print("✅ responseElements \(responseElements)")
self?.users = responseElements
}
.store(in: &cancleables)
}
func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
print("✅ \(#function)")
guard
let response = output.response as? HTTPURLResponse,
(200..<300) ~= response.statusCode else {
print("✅ \(#function) guard에 걸렸습니다.")
throw URLError(.badServerResponse)
}
print("✅ \(#function) guard를 탈출했습니다.")
return output.data
}
}
여기도서도 그리 어렵지 않게 처리하고 있습니다. 해당 코드는 텍스트에서 원하는 조건값을 입력하면 필터링이 가능하게끔 지원해줍니다.
기존에는 클로저를 통해서 작성해서 위의 코드보다 조금 더 긴 코드가 복잡하게 다가왔습니다.
하지만 위의 코드를 통해 정말 깔끔하게 처리할 수 있습니다.
2. MVVM에 대한 고찰(Combine 한달차,, 2022.06.11)
SwiftUI + Combine을 공부할 때 UIKit + RxSwift를 사용하니까 UI의 구성 방식이던가, delegate와 같은 부분이 어려웠다. 이해가 깊어질수록 어려운 점이 더 생겼는데, 'MVVM이 잘 어울리는가?' 이다.
파일을 열어보면 알겠지만, 예를들어 EasyView가 있다고 하면 EasyViewModel로 네이밍하면 좋을 파일을 EasyEngine으로 네이밍 했다. 이게 더 직관적이고, 잘 어울린달까..?
내가 진행하는 프로젝트의 app단을 UIKit에서 SwiftUI로 전환하였는데, viewModel이라는 네이밍보다 해당 기능에 더 알맞는 네이밍을 채택해서 사용하고 있다.