Swift Combine Networking
Combine을 활용해서 쉽게 서버와 통신을 처리할 수 있음.
난이도는 총 3단계로 아래 파일에 있는 Refactoring(상)과 Advanced Model에 대해서만 설명.
목차
- 1. Refactoring (상)
모델 파일
//
// 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("리팩토링엔진 장착!")
}
}
}
}
}
}
다만 네비게이션뷰를 설정할 때 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에 대한 생각정리 (2022년 06월 SwiftUI + Combine)
요즘 개발에서 SwfitUI + MVVM과 관련한 글들이 많이 보임.
SwiftUI + Combine이나 Swift Concurrency를 사용하면서 UIKit + RxSwift의 구성 방식과는 조금 다른 것 같음.
MVVM이 필요하지 않다고 주장하는 글들이 종종 있던데, 개인적으로는 객체지향과 관련한 문제이지 해당 ViewModel의 필요 여부가 본질이 아닌 것 같다는 생각이 듬.
서비스가 커질수록 객체를 잘 나누는 것이 중요한데, 아키텍처에 따라 interactor, worker, router로도 분리할 수 있을 것이며,
useCase, service, provider 등으로도 구분 가능할 것임.
사실 네이밍의 차이일 뿐 결국의 객체지향의 이해도에 달려있지 않았나 싶음.
SwiftUI는 선언형이라서 Store와 Reducer로 분리하려는 시도나 Swift를 사용하는 개발자의 성숙도가 이전보다 올라감에 따라서 자연스레 상속(문제가 꽤나 많다고 느낌)보다는 합성으로 넘어가려는 시도가 많아 보임
예전에 작성된 MVVM에서는 무분별한 상속 사용이 많던데 이런 문제점들이 UIKit에서 발생했다고 생각하는 것이 문제가 아닐까란 생각이 들었음. 즉, MVVM이 문제가 아니다.
View는 개인적으로 수동적인 개체일 뿐 View가 비지니스 로직을 드는건 재사용의 문제 뿐만아니라 좋지 않다고 생각함.
뷰에서 인터렉터나 워커, 혹은 유즈케이스나 서비스 등을 드는 건 ViewController가 사실상 모든 걸 든다와 같다고 느껴짐.
정리하자면 UI 프레임워크가 달라졌다고 해서, VIewModel이 없어야 한다는 논쟁보다는 객체지향이라는 본질에 집중하는게 좋지 않을까 싶음.
'apple > SwiftUI, Combine' 카테고리의 다른 글
[SwiftUI] EqutableView (feat. POD) (0) | 2023.08.08 |
---|---|
[iOS] NavigationSplitView (0) | 2023.08.05 |
[Combine] Let's study Combine! (0) | 2022.06.03 |
[SwiftUI] @StateObject (0) | 2022.06.02 |
[SwiftUI] State and Data Flow (0) | 2022.06.02 |