[Swift Combine] - Các phương pháp tạo Publisher

April 14, 2021

image info

1 . Khởi tạo từ một Collection

Combine cung cấp thuộc tính publisher cho phép chuyển đối tượng Collection thành một publisher. Đương nhiên, khi tạo publisher từ Collection sẽ ko bao giờ return value là nil .

import Combine
let subscriber = Subscribers.Sink(receiveCompletion: { completion in
    print(completion)
}) { value in
    print(value)
}
"Techbase VN".publisher.subscribe(subscriber) subscriber.cancel()
///// OUTPUT /////
// T e c h b a s e  V N finished

Lưu ý, dù chỗ này mình tạo publisher với String nhưng khi bắn ra thì lại là từng character (String được ngầm hiểu là Array).

Nếu tạo publisher từ 1 giá trị đơn sẽ bị error do thuộc tính publisher không hỗ trợ. Có thể work around bằng cách đưa về Collection.

let publisher = 123.publisher // ERROR 
let publisher = [123].publisher // OK

2. Khởi tạo từ các phép biến đổi Operator

Chúng ta có thể tạo mới 1 publisher bằng cách biến đổi từ 1 publisher khác thông qua các Combine operator ( prepend(), append(), switchToLatest(), merge(), combineLatest(), zip() ,… ).

Combine hỗ trợ những operator nào? Apple cung cấp các operator phong phú đủ cho iOS developer sáng tạo. Có thể tham thảo thêm trong tài liệu Apple. https://developer.apple.com/documentation/combine/publishers-share-publisher-operators

3. Sử dụng @Published

Swift Combine cung cấp annotation @Published (một kiểu property wrapper) vào trước khai báo biến. Cho phép subscribe khi các biến này thay đổi (bằng phép gán = )

Một ứng dụng hữu ích của @Published là giúp chuyển đổi giữa UIKIT và Swift UI.

import Combine
class User {
    @Published var name = "---" 
    @Published var age = 0
}
let user = User()
let subscriber = user.$name.sink { (value) in 
    print("user.$name = \(value)")
}
user.name = "Thái Hà" 
subscriber.cancel()

let subscriber1 = user.$age.sink { (value) in 
     print("user.$age = \(value)")
}
user.age = 30 
subscriber1.cancel()
///// OUTPUT ///// 
user.$name = --- 
user.$name = Thái Hà 
user.$age = 0 
user.$age = 30

4. PassthroughSubject vs CurrentValueSubject

PassthroughSubjectCurrentValueSubject khác với trường hợp tạo Publisher từ Collection hay @Published , Publisher tạo từ các Subject có thể emit value, error và trạng thái kết thúc (finished).

Sự khác nhau giữa PassthroughCurrentValue nằm ở thời điểm các subscriber nhận value.

Với PassthroughSubject, subscriber nhận value SAU thời điểm subscribe.

Với CurrentValueSubject, subscriber nhận value HIỆN TẠI & SAU thời điểm subscribe.

import Combine
let subject = PassthroughSubject()
// Send value(0), chưa subscriber nào đăng ký nhận value cho subject..
// ...nên value(0) không được xử lý.
subject.send(0)
let subscriberX = subject.sink { (value) in 
    print("X status: ", value)
} receiveValue: { (value1) in 
    print("X value: ", value1)
}
// Send value (1 -> 4), chỉ subscriber1 nhận được những value này
subject.send(1) 
subject.send(2) 
subject.send(3) 
subject.send(4)
let subscriberY = subject.sink { (value) in 
    print("Y status: ", value)
} receiveValue: { (value1) in 
    print("Y value: ", value1)
}
subject.send(5) // subscriber1 và subscriber2 sẽ nhận được 
subject.send(completion: .finished) // Kết thúc publisher
subject.send(6) // không subscriber nào nhận được do publisher đã finish
subscriberX.cancel() 
subscriberY.cancel()

///// OUTPUT /////
X value:  1
X value:  2
X value:  3
X value:  4
Y value:  5
X value:  5
Y status:  finished
X status:  finished

CurrentValueSubject tương đối giống PassthroughSubject, khác cái, CurrentValue nhận được value ngay khi vừa subscribe.

import Combine
let subject = CurrentValueSubject(0)
// Ngay khi vừa subscribe, subscriberX sẽ nhận được value(0) HIỆN TẠI.
let subscriberX = subject.sink { (value) in 
    print("X status: ", value)
} receiveValue: { (value1) in 
    print("X value: ", value1)
}
// Send value (1 -> 4), subscriberX nhận được những value này
subject.send(1) 
subject.send(2) 
subject.send(3) 
subject.send(4)
// Ngay khi vừa subscribe, subscriberY sẽ nhận được value(4) HIỆN TẠI.
let subscriberY = subject.sink { (value) in 
    print("Y status: ", value)
} receiveValue: { (value1) in 
    print("Y value: ", value1)
}
subject.send(5) // subscriberX và subscriberY sẽ nhận được 
subject.send(completion: .finished) // Kết thúc publisher
subject.send(6) // không subscriber nào nhận được do publisher đã finish
subscriberX.cancel() 
subscriberY.cancel()
///// OUTPUT /////
X value:  0 // recieve khi vừa subcribe subscriberX
X value:  1
X value:  2
X value:  3
X value:  4
Y value:  4 // recieve khi vừa subcribe subscriberY
Y value:  5
X value:  5
Y status:  finished
X status:  finished

5. AnyPublisher - Publisher “thụ động”

Mình ví đây là một Publisher “thụ động” vì đây là 1 loại wrapper cho bất kỳ Publisher nào. Gọi là “thụ động” vì AnyPublisher không có khả năng send value, error hay trạng thái finished cho các subscriber.

AnyPublisher chỉ có thể operation value và có thể tạo … ra các AnyPublisher mới.

AnyPublisher có thể được wrap từ bất kỳ Publisher nào thông qua method eraseToAnyPublisher() .

import Combine
// Tạo 1 PassthroughSubject hoặc một loại Publisher bất kỳ.
let subject = PassthroughSubject() 
// Tạo AnyPublisher để subcribe value từ subject
let publisherX = subject.eraseToAnyPublisher()
// Tạo AnyPublisher để subcribe value từ publisherX kết hợp với operator map()
let publisherY = publisherX.map { $0 * 100 }.eraseToAnyPublisher()
let subcriber1 = publisherX
    .map { 1000 + $0 }
    .sink { print("X \($0)") }
subject.send(0)
let subcriber2 = publisherY
    .sink { print("Y \($0)") }

subject.send(1) 
subcriber1.cancel() 
subject.send(2) 
subcriber2.cancel()

///// OUTPUT /////
X 1000 // recieve khi subject.send(0) 
Y 100 // recieve khi subject.send(1) 
X 1001 // recieve khi subject.send(1)
Y 200 // recieve khi subject.send(2)