Publisher는 데이터 배출
Subscriber는 Publisher에게 데이터 요청
.sink와 assign이 있음
Just는 오직 하나의 값만을 출력하고 끝나게 되는 가장 단순한 형태의 Publisher
let just = Just(1000)
let subscription = just.sink { value in
print("Recevied Value: \(value)")
}
// Recevied Value: 1000
.pulisher로 Publisher 만들기
.sink가 Subscriber로 받아서 출력let arrayPublisher = [1,3,5,7,9].publisher let subscription2 = arrayPublisher.sink {value in print("Recevied Value: \(value)") } // Recevied Value: 1 // Recevied Value: 3 // Recevied Value: 5 // ..
arrayPubsliher가 배출 될때 마다 property에 값 셋팅
class MyClass { var property: Int = 0{ didSet{ print("Did set property to \(property)") } } } let object = MyClass() object.property = 3 // Did set property to 3 let subscription3 = arrayPublisher.assign(to: \.property, on: object) // Did set property to 1 // Did set property to 3 // Did set property to 5 // .. print("Final Value: \(object.property)") // Final Value: 9
output type String,
실패시 형태는 Never(실패를 안한다)let relay = PassthroughSubject<String, Never>() relay.send("Initial text") 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!
초기값이 필요함.
.value로 마지막 값을 확인 가능let variable = CurrentValueSubject<String, Never>("") variable.send("Initial text") let subscription2 = variable.sink {value in print("subscription2 received value: \(value)") } // subscription2 received value: Initial text // 위에 send로 초기를 지정하지 않으면 // ubscription2 received value: // 가 출력이 됨 variable.send("More text") // subscription2 received value: More text print(variable.value) // More text
let publisher = ["Here", "we", "go"].publisher publisher.subscribe(relay) // subscription1 received value:Here // subscription1 received value:we // subscription1 received value:go """ 아래와 같은 효과 relay.send("Here") relay.send("we") relay.send("go") """ publisher.subscribe(variable) // subscription2 received value: Here // subscription2 received value: we // subscription2 received value: go
구독권 같은 느낌
Subscriber가 publisher에게 붙음
Subscription이라는 티켓을 줌let subject = PassthroughSubject<String, Never>() let subscription = subject .print("[Debug]") // Debug를 넣어서 subscription 오면 출력함. .sink{ value in print("Subscriber received value \(value)") } subject.send("Hello") subject.send("Hello again") subject.send("Hello for the last time!") // completion을 보내서 관계가 끝남 // finish를 해서 끝낼 수도 있고 Cancle을 해서 끊을 수도 있음 // subject.send(completion: .finished) subscription.cancel() subject.send("Hello ?? :(")
[Debug]: receive subscription: (PassthroughSubject) [Debug]: request unlimited [Debug]: receive value: (Hello) Subscriber received value Hello [Debug]: receive value: (Hello again) Subscriber received value Hello again [Debug]: receive value: (Hello for the last time!) Subscriber received value Hello for the last time! [Debug]: receive cancel
@Published로 선언된 프로퍼티를 퍼블리셔로 만들어주는 것
final class SomeViewModel{ // 1. @Published로 선언 @Published var name: String = "Kim" var age: Int = 25 } final class Label{ var text: String = "" } let label = Label() let vm = SomeViewModel() print("text : \(label.text)") // text : // $을 해서 넣어서 해주면 Publisher로 만들어줌(Subscriber할 수 있음) // Label의 text를 Vm의 name으로 vm.$name.assign(to: \.text, on: label) print("text : \(label.text)") // text : Kim // Publisher로 Vm의 name을 수정하면 똑같이 수정됨. vm.name = "Min" print("text : \(label.text)") // text : Min vm.name = "Seok" print("text : \(label.text)") // text : Seok label.text = "\(vm.age)" print("text: \(label.text)") // text : 25
URL에서 받은 데이터를 디코딩해서 원하는 데이터형을 변환 시켜주는 것
struct SomeDecodable: Decodable{ } URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.com")!) .map {data, response in return data } .decode(type: SomeDecodable.self, decoder: JSONDecoder())
Notifications에 실어서 데이터를 전송하는 방법도 있음
let center = NotificationCenter.default let noti = Notification.Name("MyNoti") let notiPublisher = center.publisher(for: noti, object: nil) let subscription1 = notiPublisher .print("[Debug]") .sink { _ in print("Noti Received") } // [Debug]: receive subscription: (NotificationCenter Observer) // [Debug]: request unlimited center.post(name: noti, object: nil) subscription1.cancel() // [Debug]: receive value: (name = MyNoti, object = nil, userInfo = nil) // Noti Received // [Debug]: receive cancel
UILabel에 Text라는 프로퍼티의 데이터를 assign할 수 있게
let ageLabel = UILabel() print("text: \(ageLabel.text)") // text: nil // \.text가 keypass Just(28) .map { "Age is \($0)"} .assign(to: \.text, on: ageLabel) print("text: \(ageLabel.text)") // text: Optional("Age is 28")
autoconnect 를 이용하면 subscribe 되면 바로 시작함.
timer pulisher 만들고 subscription이 connect하면 자동으로 connect 됨.let timerPublisher = Timer.publish(every: 1, on: .main, in: .common) .autoconnect() let subscription2 = timerPublisher.sink { time in print("time: \(time)") } // DispatchQueue 없으면 쭉 시간이 출력 됨. // 5초뒤에 끊어버림 DispatchQueue.main.asyncAfter(deadline: .now() + 5) { subscription2.cancel() } // time: 2024-03-12 14:15:27 +0000 // time: 2024-03-12 14:15:28 +0000 // time: 2024-03-12 14:15:29 +0000 // ..
custom number == 1 -> 메인 스레드
custom number != 1 -> 메인이 아닌 스레드(queue)
서버나 데이터 처리 같은 작업 헤비한건 백그라운드에서 돌리고
다 작업하고 UI 업데이트시 메인 스레드에서 받는다
아래 코드는 queue라는 다른 스레드를 만들어서 돌리고
밑에 DispatchQueue.main이라는 메인 스레드에서 돌리는 부분
let arrPubsliher = [1,2,3].publisher
let queue = DispatchQueue(label: "custom")
let subscrition = arrPubsliher
.subscribe(on: queue)
.map{ value in
print("transform: \(value), thread: \(Thread.current)")
return value
}
// transform: 1, thread: <NSThread: 0x600001711b80>{number = 2, name = (null)}
// transform: 2, thread: <NSThread: 0x600001711b80>{number = 2, name = (null)}
// transform: 3, thread: <NSThread: 0x600001711b80>{number = 2, name = (null)}
.receive(on: DispatchQueue.main)
.sink { value in
print("Receive Value: \(value), thread: \(Thread.current)")
}
// Receive Value: 1, thread: <_NSMainThread: 0x60000170c000>{number = 1, name = main}
// Receive Value: 2, thread: <_NSMainThread: 0x60000170c000>{number = 1, name = main}
// Receive Value: 3, thread: <_NSMainThread: 0x60000170c000>{number = 1, name = main}
Publisher에게 온 데이터를 가공해서 Subscriber에게 보내는 역할!
Map은 데이터를 가공해주는 역할 - 숫자 2배로 곱해서 출력
let numPublisher = PassthroughSubject<Int, Never>() let subscription1 = numPublisher // .print("[Debug]") .map { $0 * 2 } .sink{value in print("Transformed Value: \(value)") } numPublisher.send(10) numPublisher.send(20) numPublisher.send(30) subscription1.cancel() // Transformed Value: 20 // Transformed Value: 40 // Transformed Value: 60
필터 이용해서 a를 포함한거만 value 출력
let stringPublisher = PassthroughSubject<String, Never>() let subscription2 = stringPublisher // .print("[Debug]") .filter{ $0.contains("a")} .sink{value in print("Filterd Value: \(value)") } stringPublisher.send("abc") stringPublisher.send("Kim") stringPublisher.send("Min") stringPublisher.send("Seok") stringPublisher.send("age") subscription2.cancel() // Filterd Value: abc // Filterd Value: age
Publisher와 CombineLatest 선언 부분
let strPublisher = PassthroughSubject<String, Never>() let numPubsliher = PassthroughSubject<Int, Never>() Publishers.CombineLatest(strPublisher, numPubsliher).sink{ (str, num) in print("Received: \(str), \(num)") }
출력 부분 | a,1 | a,2 | b,3 | c,3 | c,5 | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|
strPublisher | a | b | c | ||||||||
numPubsliher | 1 | 2 | 3 | 5 |
위와 같이 되므로
아래와 같이 보내면 출력 되는게 없음.
strPublisher.send("a")
strPublisher.send("b")
strPublisher.send("c")
numPubsliher.send(1)
numPubsliher.send(2)
numPubsliher.send(3)
위의 표와 똑같이 보내면 이렇게 됨.
strPublisher.send("a")
numPubsliher.send(1)
// Received: a, 1
numPubsliher.send(2)
// Received: a, 2
strPublisher.send("b")
// Received: b, 2
numPubsliher.send(3)
// Received: b, 3
strPublisher.send("c")
// Received: c, 3
numPubsliher.send(5)
// Received: c, 5
numPubsliher.send(3)
// Received: c, 3
응용해서 로그인과 비밀번호 테스트를 위한 코드
아이디가 있고 비밀번호 12자 이상let usernamePublisher = PassthroughSubject<String, Never>() let passwordPublisher = PassthroughSubject<String, Never>() let validatedCrendentialsSubscription = usernamePublisher.combineLatest(passwordPublisher) .print("[Debug]") .map{ (username, password) -> Bool in return !username.isEmpty && !password.isEmpty && password.count > 12 } .sink{ valid in print("Credential valid? : \(valid)") } usernamePublisher.send("minseok") passwordPublisher.send("hello") // [Debug]: receive value: (("minseok", "hello")) // Credential valid? : false passwordPublisher.send("verystrongpassword") // [Debug]: receive value: (("minseok", "verystrongpassword")) // Credential valid? : true
타입이 같아야 머지가 됨 아니면 위에 처럼 튜플형식으로
let publisher1 = [1,2,3,4,5].publisher let publisher2 = [300,400,500].publisher // 아래 부분은 String으로 타입이 같지 않아서 에러가 발생 // let publisher2 = ["300","400","500"].publisher let mergePublisherSubscrition = Publishers.Merge(publisher1, publisher2) // .print("[Debug]") .sink { value in print("Merge: subscrition recevied value: \(value)") } // Merge: subscrition recevied value: 1 // Merge: subscrition recevied value: 2 // Merge: subscrition recevied value: 3 // Merge: subscrition recevied value: 4 // Merge: subscrition recevied value: 5 // Merge: subscrition recevied value: 300 // Merge: subscrition recevied value: 400 // Merge: subscrition recevied value: 500
중복 제거하기
var subscriptions = Set<AnyCancellable>() let words = "hey hey there! Mr Mr ?" // 공백으로 자르기 .components(separatedBy: " ") .publisher words // 중복 자르기 .removeDuplicates() .sink{ value in print(value) }.store(in: &subscriptions) // hey (hey 하나 없어짐) // there! // Mr (Mr 하나 없어짐 중복이라.) // ?
해당으로 캐스팅이 가능한 녀석들만 출력하도록 Float("a")는 안되니까
var subscriptions = Set<AnyCancellable>() let strings = ["a", "1.24", "3", "def", "45", "0.23"].publisher strings // Float으로 캐스팅이 가능한 녀석들만 출력이 됨. .compactMap{ Float($0) } .sink{ value in print(value) }.store(in: &subscriptions) // 1.24 // 3.0 // 45.0 // 0.23
다 ignore해서 그냥 끝남
let number = (1...10_000).publisher number // 그냥 다 무시하고 Completed 출력 .ignoreOutput() .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) // Completed with: finished
prefix로 앞에 데이터 2개 받고 끝남
let tens = (1...10).publisher tens // 2개 출력하고 끝남 .prefix(2) .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) // 1 // 2 // Completed with: finished