Combine(1)-WhatisCombine(publish&subscirbe)
Combine
Combine은 Apple이 iOS 13에서 도입한 반응형 프로그래밍 프레임워크로, 데이터 흐름과 변화 전파에 중점을 둔 선언적 API를 제공합니다. 비동기 이벤트 처리 자체가 목적이 아닌, 비동기 코드를 선언적으로 접근하여 가독성과 유지보수성을 극대화하는 것이 핵심입니다.
반응형프로그래밍이란?
데이터 흐름과 변화의 전파에 중점을 둔 프로그래밍 패러다임
- 비동기처리에 X
- 비동기코드의 선언적 접근 O
- 코드 간소화 O
🔑 핵심: Combine은 비동기 이벤트를 처리하기 위한 도구가 아니라, 비동기 코드를 선언적으로 접근하여 코드의 가독성과 유지보수성을 향상시키는 프레임워크입니다.
기존 RxSwift가 서드파티 라이브러리로서 별도의 프로젝트 설정이 필요했다면, Combine은 Apple 생태계에 완전히 통합된 퍼스트파티 프레임워크입니다. 이는 빌드 시간 단축, 유지보수 비용 절감, 그리고 Swift 언어와의 완벽한 호환성을 의미합니다.
코드를 쉽게 읽고유지관리할수 있따!!
RXswift제대로 아직 공부못햇지만 combine으로 할수 있다. 더 좋음 왜? 퍼스트 파티라서.
시간이 지남에 따라 값처리를 위해 통합된 선언적 API.
핵심요소 5가지프로토콜
Publisher - 데이터 방출의 중심
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은 방출되는 값의 타입, Failure는 발생 가능한 에러 타입을 정의합니다.
Publisher | Observable |
Value Type | Reference Type(class) |
OutputData(Data Type) | Element(Data Type) |
Failure(error Type) | X |
- output은 publisher가 방출하는 값의 종류.
- Failure; 발생할수 있는 에러타입
AnyPublisher<String,Never> --> error를 발생시키지 않는다.
AnyPublisher는 Publisher 프로토콜을 따르는 구조체.
1. Just
나를 구독한 subscriber들에게 한번만 보내주고끝이벤트 보냄. 값들을 한번에 보내준다고 생각하면 될듯?
just는 failure type설정x 왜? 타입이 never임.
2. Future
하나의 결과를 비동기적으로 생성뒤 completion event 보낸다. 비동기적으로 한번리턴하고 해당값을 계속보내는거
신기한건 나머지는 구조체인데 이놈만 class. 왜? 비동기로 작동할때 상태저장이 가능하게하려고
3. Deferred, Record..
잘안쓰임...
Subscrbier
Subscriber는 Publisher로부터 값을 수신하는 프로토콜로, 3단계 생명주기를 가집니다:
- receive(subscription:): 구독 객체 수신 및 데이터 요청
- receive(_:): Publisher가 방출한 값 수신 및 추가 요청량 반환
- receive(completion:): 완료 또는 에러 상태 수신
subscribe를 통해 연결
class practiceSubscriber: Subscriber {
typealias input = type
typealias Failure = type
//1.subscriber에게 publisher를 성공적으로 구독했음을 알려즈고 item요청.
func receive(subscription: Subscription) {
print("구독시작할게용")
subscription.request(.unlimited)
}
// 2. subscriber에게 publisher가 element를 생성햇다고 알려즘/ count
func receive(_ input: String) -> Subscribers.Demand {
print("\(input)")
return .none
}
// 3. subscriber에게 publisher가 정상적으로 또는 오류로 publish를 완료했음을 알림.
func receive(completion: Subscribers.Completion<Never>){}
1. receive(subscription:): subscriber에게 Subscription 인스턴스를 전달해줍니다! 이것을 이용해서 subscriber은 publisher의 elements를 요구하거나, 더 이상 값을 받지 않겠다고 할 수도 있다~ Subscription은 subscriber와publisher의 연결을 나타내는것
물론 이렇게 직접 구현해줄수 있다. 하지만 애플은 권장하지 않는다. 대신 sink, assign같은것을 권장
Demand : subscriber가 publisher에게 subscription을 통해 요청한 아이템 수 몇번값 요청했니?
- unlimited, .max(개수), none(no elements=.max(0))
Demand 시스템은 Publisher가 과도한 데이터를 방출하여 메모리 압박을 일으키는 것을 방지하는 백프레셔 메커니즘입니다.
sink
func sink(
receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void),
receiveValue: @escaping ((Self.Output) -> Void)
) -> AnyCancellable
publisher에 대한 subscription을 작성. publisher와 subscriber(Sink 타입)을 연결시키고 값을 받아 처리
This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber.
구독자를 직접 설정하고 만들어주는 것없이 sink를 통해 publisher과 연결.
첫번째 파라미터에는 receiveComplete : 모든게 다 끝났을때 실행되는 클로저
두번째파라미터 receiveValue: 받은 값 누가 받을지.
var var myNotification = Notification.Name("com.SSOk")
var myDefaultPublisher = NotificationCenter.default.publisher(for: myNotification)
//아직여기까지는 받는애가 없기에 아무일도 안일어남.
var mySubScription : AnyCancellable?
var mySubscriptionSet = Set<AnyCancellable>()
mySubScription = myDefaultPublisher.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("완료")
case .failure(let err) :
print("error발생")
}}, receiveValue: {val in
print("받은값: \(val)")})
//내가 원하는 동작이 다끝나면 완료를 알려주고 메모리에서 날려주기 위해(set이 없다는 가정)
// mySubScription?.cancel()
mySubscrition?.store(in: &mySubScriptionSet)
3. assign
class MyViewModel {
@Published var text: String = ""
}
let viewModel = MyViewModel()
let cancellable = publisher
.assign(to: \.text, on: viewModel)
key path로 표시된 프로퍼티에 수신된 값을 할당하는 간단한 subscriber.
sink가 간단히 완료와 값에대한 이벤트를 처리한다면 Assign은 Publisher로부터 받은 값을 주어진 instance의 property에 할당할수 있게한다..!!
publisher의 failure type == never일때만. 사용가능!
- object : 프로퍼티를 포함하는 객체, subscriber는 새로운 값을 받을때마다 여기에 할당,
subscriber는 upstream publisher가 subscriber의 receive(completion)이 나오기전까지 강한참조유지하다가 종료응답받으면 그때 nill
- keypath : 할당할 프로퍼티를 나타내는 key-path
assign(to:on:)
to에 값이 할당될 property! On에는 해당프로퍼티를 갖는 instance자리!
그런데 to안에 \.를써줘야한다..왜?그럴까?
assign은 새로운 값을 keyPath에 따라 주어진 인스턴스의 property에 할당하는 것인데 \.가 object의 프로퍼티를 특정하기 위해 사용/
⚠️ 주의사항: assign(to:on:)은 대상 객체에 대한 강한 참조를 유지합니다. 이로 인해 메모리 누수가 발생할 수 있으므로 참조 사이클에 주의해야 합니다.
Scheduler
Scheduler는 작업이 언제, 어떻게 실행될지 제어
- RunLoop: RunLoop와 관련된 스케줄러
- DispatchQueue: GCD 기반 스케줄러
- OperationQueue: Operation 기반 스케줄러
let publisher = Just("Hello")
// 메인 스레드에서 실행
let mainSubscription = publisher
.receive(on: RunLoop.main)
.sink { print("Main thread: \($0)") }
// 백그라운드 스레드에서 실행
let backgroundSubscription = publisher
.receive(on: DispatchQueue.global())
.sink { print("Background: \($0)") }
Subject
Subject는 외부에서 값을 받아 구독자에게 전달할 수 있는 Publisher입니다. 일종의 "양방향 Publisher"라고 생각할 수 있습니다
protocol Subject: AnyObject, Publisher {
func send(_ value: Self.Output)
func send(completion: Subscribers.Completion<Self.Failure>)
}
CurrentValueSubject : 단일 값을 감싸고 값이 바뀔 때마다 새로운 요소를 게시하는 주제.
가장 최근에 published된 값의 버퍼를 유지한다. send를 호출하면 현재 값도 업데이트되어 값 직접 업데이트하는거랑 동일한 효과.
PassthroughSubject: 받은 값을 그대로 전달. downstream의 subscriber들에게 전달해준다. 위에놈이랑 달리 생성할때 초기값 필요X.그런데 최신값을 저장x
결론: 비동기처리를 하기 위해 콤바인을 하러 왔습니다? XXXXXXXX