Combine | Apple Developer Documentation
🟥 Combine 이란?
- 이벤트를 처리하는 operators들을 결합함으로써 비동기 이벤트들을 커스텀하게 다룬다.
- Combine을 사용하면 비동기 작업과 데이터 흐름을 선언적으로 작성할 수 있으며, 특히 비동기 이벤트와 데이터 스트림을 처리하는 데 매우 유용하다.
🟥 핵심 개념
- Publisher:
- Publisher는 이벤트를 발행하는 객체이다
- Publisher는 데이터를 제공하고, 구독자가 있을 때 이를 전달한다.
- 예: URLSession.DataTaskPublisher, NotificationCenter.Publisher, Just, PassthroughSubject, CurrentValueSubject 등
public protocol Publisher {
associatedtype Output
associatedtype Failure: Error
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
- Publisher 데이터 타입
- Output: Publisher가 발행하는 데이터의 타입
- Failure: Publisher가 발행할 수 있는 오류의 타입
- Publisher 예제
import Combine
// Just는 단일 값을 발행하는 Publisher입니다.
let publisher = Just("Hello, Combine!")
// Just의 Output 타입은 String이고, Failure 타입은 Never입니다.
- Subscriber:
- Subscriber는 Publisher로부터 이벤트를 수신하는 객체이다.
- Subscriber는 Publisher와 연결되어 데이터를 받거나 에러를 처리한다.
- sink(receiveCompletion:receiveValue:) 메서드로 직접 구독할 수 있다.
public protocol Subscriber: CustomCombineIdentifierConvertible {
associatedtype Input
associatedtype Failure: Error
func receive(subscription: Subscription)
func receive(_ input: Self.Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion<Self.Failure>)
}
- Subscriber 데이터 타입
- Input: Subscriber가 수신하는 데이터의 타입
- Failure: Subscriber가 수신할 수 있는 오류의 타입
- Subscriber 예제
import Combine
// Subscriber 생성
let subscriber = Subscribers.Sink<String, Never>(
receiveCompletion: { completion in
print("Completion: \(completion)")
},
receiveValue: { value in
print("Received value: \(value)")
}
)
// Publisher를 Subscriber에 연결 (구독)
publisher.subscribe(subscriber)
// 출력
// Received value: Hello, Combine!
// Completion: finished
- Operators:
- Operator는 Publisher의 출력을 변환하거나 필터링하는 데 사용되는 함수이다.
- map, filter, flatMap, combineLatest, merge, zip 등 다양한 연산자가 제공된다.
- Cancellable:
- Cancellable은 구독을 취소하는 데 사용된다.
- 구독이 더 이상 필요하지 않을 때, 메모리 누수를 방지하기 위해 구독을 취소할 수 있다.
🟥 Combine을 이용한 예제
🟥 기본 사용법
import Combine
import Foundation
// Publisher 생성
let publisher = Just("Hello, Combine")
// Subscriber 생성 및 구독
let subscription = publisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: \(error)")
}
}, receiveValue: { value in
print("Received value: \(value)")
})
// 출력
// Received value: Hello, Combine
// Finished
🟥 여러 Operator 사용
import Combine
import Foundation
// Example Publisher
let numbers = [1, 2, 3, 4, 5].publisher
// 여러 연산자를 연결하여 데이터 스트림을 변환
let subscription = numbers
.filter { $0 % 2 == 0 } // 짝수만 필터링
.map { $0 * 10 } // 값을 10배로 변환
.sink(receiveCompletion: { completion in
print("Completion: \(completion)")
}, receiveValue: { value in
print("Received value: \(value)")
})
// 출력
// Received value: 20
// Received value: 40
// Completion: finished
🟥 MVVM 패턴 - Combine을 이용한 예제
1️⃣ Model
struct User: Codable {
let name: String
}
2️⃣ ViewModel
import Foundation
import Combine
class UserViewModel: ObservableObject {
@Published var userName: String = ""
@Published var greeting: String = ""
private var cancellables = Set<AnyCancellable>()
init() {
$userName
.map { "Hello, \($0)!" }
.assign(to: \.greeting, on: self)
.store(in: &cancellables)
}
}
3️⃣ View
import UIKit
class UserView: UIView {
let textField: UITextField = {
let textField = UITextField()
textField.borderStyle = .roundedRect
return textField
}()
let label: UILabel = {
let label = UILabel()
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
}
private func setupViews() {
addSubview(textField)
addSubview(label)
textField.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textField.centerXAnchor.constraint(equalTo: centerXAnchor),
textField.topAnchor.constraint(equalTo: topAnchor, constant: 20),
textField.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.8),
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 20),
label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.8)
])
}
}
4️⃣ ViewController
import UIKit
import Combine
class UserViewController: UIViewController {
private let userView = UserView()
private let viewModel = UserViewModel()
private var cancellables = Set<AnyCancellable>()
override func loadView() {
view = userView
}
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
userView.textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
private func bindViewModel() {
viewModel.$greeting
.receive(on: DispatchQueue.main)
.assign(to: \.text, on: userView.label)
.store(in: &cancellables) // 구독 저장
}
@objc private func textFieldDidChange(_ textField: UITextField) {
viewModel.userName = textField.text ?? ""
}
}
✅ Set<AnyCancellable>()
- Combine 프레임워크에서 비동기 작업을 관리하기 위해 사용되는 구독 취소 객체(Cancellable)를 저장하는 컨테이너이다.
- Combine에서는 Publisher를 구독하면 Cancellable 객체가 생성되는데, 이 객체를 통해 언제든지 구독을 취소할 수 있다.
- AnyCancellable은 Cancellable 프로토콜을 따르는 모든 객체를 래핑할 수 있는 타입이다.
- 구체적으로 Set<AnyCancellable>은 구독의 생명주기를 관리하는 데 사용된다.
- 구독을 취소하지 않으면 메모리 누수가 발생할 수 있기 때문에, 구독을 저장하고 필요할 때 취소하는 것이 중요하다.
아래 코드에서 cancellables 는 AnyCancellable 객체를 저장하기 위한 집합으로, 구독이 완료 또는 뷰모델이 해제될 떄 자동으로 구독을 취소할 수 있다.
✅ store(in: ): 구독 저장
- store(in:) 메서드는 Cancellable 객체를 컬렉션에 저장하여, 해당 컬렉션의 생명주기가 끝날 때 자동으로 구독을 취소한다.
- 주로 Set<AnyCancellable>과 함께 사용된다.
- 이를 통해 메모리 누수를 방지하고, 수동으로 구독을 취소할 필요가 없다.
⭐ 구독을 저장하고 취소하는 이유
- 메모리 관리:
- 구독을 저장하지 않으면 메모리 누수가 발생할 수 있다.
- Set<AnyCancellable>에 저장하면, 뷰컨트롤러가 해제될 때 자동으로 구독이 취소된다.
- 구독 관리:
- 여러 구독을 관리하기 쉽다.
- 구독을 하나씩 따로 관리하는 대신, 하나의 컬렉션(Set<AnyCancellable>)에 모두 저장할 수 있다.
- 자동 취소:
- Set<AnyCancellable>에 저장된 구독은 해당 객체가 해제될 때 자동으로 취소된다.
- 이는 뷰모델이나 뷰컨트롤러의 생명주기를 따르므로, 수동으로 구독을 취소할 필요가 없다.
'iOS > Swift' 카테고리의 다른 글
영어 단어 중에 첫 번째 글자를 대문자로 하고 나머지는 소문자로 처리하는 함수 (0) | 2024.07.04 |
---|---|
ObservableObject와 @Published (0) | 2024.06.03 |
MVVM 패턴 - Binding 개념 (0) | 2024.05.30 |
MVVM 패턴 (Model - View - ViewModel) (0) | 2024.05.30 |
고차함수 (Map, Filter, Reduce) (0) | 2024.05.26 |