포스트

(Deep Dive) Combine 기초(1)

코드를 통해 이해해보기.

Publisher & Subscriber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let just = Just(1000)
let subscription1 = just.sink { value in
    print("Received Value: \(value)")
} // Received Value: 1000 하나만 전송하고 끝.

let arrayPublisher = [1, 3, 5, 7, 9].publisher
let subscription2 = arrayPublisher.sink { value in
    print("Received Value: \(value)")
}
// Received Value: 1
// Received Value: 3
// Received Value: 5
// Received Value: 7
// Received Value: 9
// 5개 전송 다하고 끝

class MyClass {
    var property: Int = 0 {
        didSet {
            print("Did set property to \(property)")
        }
    }
}

let object = MyClass()
let subscription3 = arrayPublisher.assign(to: \.property, on: object)
// assign의 경우 object에 어떤 property에 값을 할당할 것을 말함.
// 여기선 object의 property에 property(arrayPublisher의 하나하나의 값)를 할당 
// 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

print("Final Value: \(object.property)") // 마지막의 값 확인.
// Final Value: 9

Subject - Publisher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// PassthroughSubject
let relay = PassthroughSubject<String, Never>()
let subscription1 = relay.sink { value in
    print("Subscription1 received value: \(value)")
}

relay.send("Hello")
relay.send("World!")
// Subscription1 received value: Hello
// Subscription1 received value: World!

// CurrentValueSubject
let variable = CurrentValueSubject<String, Never>("") // 초기값 설정이 필요

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

variable.send("More text")
// Subscription2 received value:  → 비어있는건 초기값 때문
// Subscription2 received value: More text

다른 케이스

1
2
3
4
5
6
7
8
9
10
11
12
let variable = CurrentValueSubject<String, Never>("")
variable.send("Initial text") // Subscription 전에 이렇게 initialize도 가능.

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

variable.send("More text")
// Subscription2 received value: Initial text
// Subscription2 received value: More text

variable.value // "More text" 현재 이 값을 들고있음.

PassthroughSubject 에서,

1
2
3
4
5
let publisher = ["Here", "we", "go"].publisher // 데이터가 주어진 상태의 publisher
publisher.subscribe(relay)
// Subscription1 received value: Here
// Subscription1 received value: we
// Subscription1 received value: go

이렇게 된다.

즉 저건 아래와 같다.

1
2
3
relay.send("Here")
relay.send("We")
relay.send("go")

이렇게 publisher를 통해 relay에게 ["Here", "we", "go"] 이 값들을 전달을 해주었다.

Subscription

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let subject = PassthroughSubject<String, Never>()

// The print() operator prints you all lifecycle events
let subscription = subject.sink { value in
    print("Subscriber received value: \(value)")
}

subject.send("Hello")
subject.send("Hello again")
subject.send("Hello for the last time")
subject.send(completion: .finished) // 끝
subject.send("Hello ?? :(")

// Subscriber received value: Hello
// Subscriber received value: Hello again
// Subscriber received value: Hello for the last time

completion을 통해 완료되었음을 알렸으므로, subscription도 끝났으므로 아래 Hello?? 는 나오지 않는다.

이걸 프린트를 통해 과정을 다시 한번 호출을 해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let subject = PassthroughSubject<String, Never>()

// The print() operator prints you all lifecycle events
let subscription = subject
    .print() // added
    .sink { value in
    print("Subscriber received value: \(value)")
}

subject.send("Hello")
subject.send("Hello again")
subject.send("Hello for the last time")
subject.send(completion: .finished) // subscription.cancel() 이것도 같음.
subject.send("Hello ?? :(")

// receive subscription: (PassthroughSubject)         → 관계 형성
// request unlimited                                  → 무제한 요청
// receive value: (Hello)                             → Hello를 받음
// Subscriber received value: Hello                   → Subscriber에게 Hello 전달
// receive value: (Hello again)                       → 상동 
// Subscriber received value: Hello again             → 상동
// receive value: (Hello for the last time)           → 상동
// Subscriber received value: Hello for the last time → 상동 
// receive finished                                   → Subscriber가 모든 데이터를 받았음을 알림. (그 밑에 있는건 전달하지 않음.)

이런식의 sequence를 보면 이해가 더 잘된다.

@Published

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final class SomeViewModel {
    @Published var name: String = "Jack" // Publisher 와 같은 의미로 사용
    var age: Int = 20
}

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

let label = Label()
let vm = SomeViewModel()

print("text: \(label.text)")
vm.$name.assign(to: \.text, on: label) // @Published 를통해 label의 text property에 name 프로퍼티 전달.
print("text: \(label.text)")
// text: 
// text: Jack

vm.name = "Jason" // 
print("text: \(label.text)")
// text: Jason

이걸 사용해서 값이 변할때마다 UIComponent에 변화를 줄 수 있다.

ex) UILabel.text 를 변경.

URLSessionTask에서의 Publisher

1
2
3
4
5
6
7
struct SomeDecodable: Decodable { }

URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.com")!) // publisher
    .map { data, response in
        return data
    }
    .decode(type: SomeDecodable.self, decoder: JSONDecoder())

이런 형태를 가진다.

Notifications에서의 Publisher

1
2
3
4
5
6
7
8
let center = NotificationCenter.default // center 생성
let noti = Notification.Name("MyNoti") // Notification 생성
let notiPublisher = center.publisher(for: noti, object: nil) // publisher 생성
let subscription = notiPublisher.sink { _ in // Noti를 받으면 프린트문 호출
    print("Noti Received")
}

center.post(name: noti, object: nil) // Noti에 보내본다, 실제로 호출을 하는지

KeyPath binding to NSObject instances의 Publisher

1
2
3
4
5
6
7
let ageLabel = UILabel()
print("text: \(ageLabel.text)")

Just(28)
    .map { "Age is \($0)"}
    .assign(to: \.text, on: ageLabel)
print("text: \(ageLabel.text)")

Timer에서의 Publisher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// autoconnect 를 이용하면 subscribe 되면 바로 시작함
let timerPublisher = Timer
    .publish(every: 1, on: .main, in: .common)
    .autoconnect()

let subscription2 = timerPublisher.sink { time in
    print("timne \(time)")
}

// time: 2024-05-02 12:56:42 +0000
// time: 2024-05-02 12:56:43 +0000
// time: 2024-05-02 12:56:44 +0000
// time: 2024-05-02 12:56:45 +0000
// time: 2024-05-02 12:56:46 +0000

다만 이상태로는 무한대로 시간을 출력하므로 5초뒤에 끊어주는 메서드가 필요.

1
2
3
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    subscription2.cancel()
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.