minuco
article thumbnail
Published 2024. 1. 30. 20:11
[TIL] Combine (1) iOS/TIL

내용

- Publisher -> Operator -> Subscriber

- Subscription [ Subscriber(구독자)가 Publisher(크리에이터)에 연결됨을 나타낸다.]

- @Published

Combine - 놈놈놈

  • - Publisher [발행하는 놈 (크리에이터)]
  • - Subscriber [구독하는 놈]
  • - Operator [변경하는 놈]

 

이벤트 발생       이벤트 가공    이벤트 소비

Publisher -> Operator -> Subscriber

  • data를 그냥 보낼 수 있고
  • data를 알고 있는 상태로 보낼 수도 있고
  • data를 변형해서 보낼 수도 있다.

 

Publisher 크리에이터 즉, 데이터를 배출하는 놈

  •  구체적인 output 및 실패 타입을 정의
  •  Subscriber(구독자) 이 요청한 것만큼 데이터 제공

여기 까지 보면 Publisher 이놈으로 뭘 정의하고 Subscriber 이놈으로 뭔가를 처리하겠네? 라는 생각이 든다.

Operator는 data를 변형할때 쓰나? 무튼,

 

Publisher [발행자(크리에이터)]

protocol Publisher {
    associatedtype Output
    associatedtype Failure : Error
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

 

Subscriber [구독자]

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>)
}

 

위 코드를 보니 동일한 Output, Input Type를 가져야하고 동일한 Failuer Type을 가져야 하나보다.

// Publisher 변수
associatedtype Output
associatedtype Failure : Error
// Subscriber 변수
associatedtype Input
associatedtype Failure : Error

 

빌트인 Publisher인 Just, Future가 있다.

just는 값을 다루고, Future는 Funtion을 다룬다.

// Publisher & Subscriber
// Just는 한번 전송하고 끝
let just = Just(1000)
let subscription1 = just.sink { value in
    print("Receuved Value: \(value)")
}

 

iOS에서 자동으로 제공해 주는 것들도 있다. 

  • NotificationCenter
  • Timer
  • URLSession.dataTask

Subscriber(구독자)Publisher(발행자) 데이터를 요청하고 Input, Failure 타입을 정의한다.

  •  Publisher을 구독후, 필요한 데이터를 개수와 함께 요청
  • 파이프라인을 취소할 수 있음.
  • 빌트인 Subscripber인 assign과 sink가 있다.
    • sink 는 Publisher가 제공한 데이터를 받을수 있는 클로져를 제공함
    • assign 는 Publisher가 제공한 데이터를 특정 객체의 *키패스(KeyPath)에 할당
// sink
let relay = PassthroughSubject<String, Never>()
let subscription1 = relay.sink { value in
    print("subscription1 received value \(value)")
}

let variable = CurrentValueSubject<String, Never>("")

variable.send("Initial text")

let subscription2 = variable.sink { value in
    print("supscription2 received value \(value)")
}

variable.send("More text")
variable.value

/*
supscription2 received value Initial text
supscription2 received value More text
*/

let publisher = ["Here", "we", "go"].publisher // 크리에이터

publisher.subscribe(relay) // relay를 publisher에 구독
/*
subscription1 received value Here
subscription1 received value we
subscription1 received value go
*/

publisher.subscribe(variable) // relay를 publisher에 구독
/*
supscription2 received value Here
supscription2 received value we
supscription2 received value go
*/


let arrayPublisher = [1, 3, 5, 7, 9].publisher
let subscription2 = arrayPublisher.sink { value in
    print("Receuved Value: \(value)")
}
/*
Receuved Value: 1
Receuved Value: 3
Receuved Value: 5
Receuved Value: 7
Receuved Value: 9
*/

class MyClass {
    var property: Int = 0 {
        didSet {
            print("Did set property to \(property)")
        }
    }
}
/*
Did set property to 1
Did set property to 3
Did set property to 5
Did set property to 7
Did set property to 9
*/

// assign
let object = MyClass()
let subscription3 = arrayPublisher.assign(to: \.property, on: object)

print("Final Value\(object.property)") // Final Value 9
//object.property = 3 // Did set property to 3

 

*키패스(KeyPath): 키패스를 사용하면 프로퍼티의 참조를 저장할 수 있다.

즉, 그렇게 만들어진 keyPath를 subscript[keyPath:]에 매개변수로 전달하면 그 프로퍼티에 접근할 수 있다.

 

Subscription [ Subscriber(구독자)가 Publisher(크리에이터)에 연결됨을 나타낸다.]

A protocol representing the connection of a subscriber to a publisher.

: 게시자와 구독자의 연결을 나타내는 프로토콜입니다.

protocol Subscription : Cancellable, CustomCombineIdentifierConvertible
  • Publisher이 발행한 구독 티켓을 가지고 있다고 생각하면 된다.
  • 티켓을 가지고 있으면 데이터를 받을수도 있지만 이 티켓을 끊으면 구독 관계도 사라진다.
  • Cancellable protocol을 다르고있기 때문에 Subscription을 통해 연결을 Cancel 할 수 있다.

Sbject (Publisher를 채택한 프로토콜)

A publisher that exposes a method for outside callers to publish elements.
: 외부 호출자가 요소를 게시할 수 있는 메서드를 노출하는 게시자입니다.

protocol Subject<Output, Failure> : AnyObject, Publisher

 

  • send(_:) 메소드를 이용해 이벤트 값을 주입시킬수 있는 Publisher
  • 기존의 비동기 처리 방식에서 combine 전환시 유리
  • 2가지 빌트인 타입이 있다.
    • PassthroughSubject
      • Subcriber가 달라고 요청하면, 그때 부터, 받은 값을 전달해주기만 한다.
      • 전달한 값을 들고 있지 않다. 
    •  CurrentValueSubject
      • Subcriber가 달라고 요청하면, 최근에 가지고 있던 값을 전달하고, 그때 부터, 받은 값을 전달 한다.
      • 전달한 값을 들고 있다.

Ex] PassthroughSubject

// Publisher
let subject = PassthroughSubject<String, Never>()

let subscription = subject
    .print("[Debug]")
    .sink { value in
    print("Subscriber received value: \(value)")
}

subject.send("Hello")
subject.send("Hello amigo!")
subject.send("Hello for the last time!")
//subject.send(completion: .finished)
subscription.cancel() // subscription도 cancel함수가 있어 관계를 끊을 수 있다.
subject.send("가나?")

 

Ex] CurrentValueSubject

let currentValueSubject = CurrentValueSubject<String, Never>("manu")      
          
let subscriber = currentValueSubject.sink(receiveValue: {
          print($0)
})
                  
currentValueSubject.value = "안녕"
currentValueSubject.send("하이")

 

@Published (Publisher) [발행자로 만들어주는 어노테이션]

   - @Published 로 선언된 프로퍼티를 퍼블리셔로 만들어준다.

   - 클레스에 한해서 사용됨 (구조체에서 사용이안된다. why? 구조체는 값타입이기때문에.)

   - $를 이용해 퍼블리셔에 접슨할 수 있다. 

final class SomViewModel {
    @Published var name: String = "minuco"
    var age: Int = 20
}

final class Label {
    var text: String = ""
}

let label = Label()
let vm = SomViewModel()

print("text: \(label.text)")

// $로 name을 Subscriber(구독) 해서 assign을 통해 lable객체의 text에 "ninuco" 전달.
vm.$name.assign(to: \.text , on: label)
print("text: \(label.text)")

vm.name = "Jeny"
vm.age = 30 // 아무일도 일어나지 않음.
// why?? 위와 비교해보면 알게됨.
print("text: \(label.text)")

'iOS > TIL' 카테고리의 다른 글

[TIL] MVVM(3)  (0) 2023.12.05
[TIL] MVVM(2)  (0) 2023.11.28
[TIL] MVVM(1)  (2) 2023.11.25
[TIL] NaverMapAPI  (0) 2023.03.16
profile

minuco

@minuco

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!