Mặc dù
Combine
đã tập trung vàoconcept
cácpublisher
sẽemit
ra cácsequence value
theo dòng thời gian cũng như đã cung cấp một sốAPI
thuận tiện và đầy đủ chức năng để người sử dụng không cần phải thiết lập tùy chỉnh cho cácpublisher
từ đầu.Lấy ví dụ như khi chúng ta muốn
Combine
hỗ trợ chúng ta với cácAPI
có sẵn nhưImageProcessor
để xử lý theocompletion handle pattern
trongclosure
trong các hoạt độngasynchronously
khi tiến trình xử lýimage
hoàn tất hoặc thất bại:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">struct</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span> <span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">,</span> then handler<span class="token punctuation">:</span> @escaping <span class="token punctuation">(</span><span class="token builtin">Result</span><span class="token operator"><</span><span class="token builtin">UIImage</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Void</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Process the image and call the handler when done</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Thay vì việc viết lại
ImageProcessor
chúng ta có thể xử lý theo cách thức mới được giới thiệu trongCombine
. Chúng ta không chỉ giữ được cáchimplement
trên mà vẫn có thể sử dụngCombine
để xử lýcompletion handle
từng trường hợp ngay cả khi chúng ta thêmcode
mới:
1/ Future được giới thiệu:
Chúng ta sẽ tập trung sử dụng
Future
type
được biết đến trongFuture/Promise pattern
, 1pattern
rất phổ biến trong việc lập trình.Combine
cung cấp cho chúng taclosure
promise
để chúng ta có thể nhận biết khi cácoperation
asynchronous
hoàn tất cũng như sẽ tự độngmap
Result
vào các eventPublisher
.Điều thực sự tiện lợi ở đây là trong trường hợp cụ thể bên trên với
completion handle closure
cũ sử dụngResult
làInput
, điều đó có nghĩa chúng ta có thể dụngFuture
với thiết lập đơn giản trongfunc
process
cũ như sau:
1 2 3 4 5 6 7 8 | <span class="token keyword">extension</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Future</span><span class="token operator"><</span><span class="token builtin">UIImage</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token builtin">Future</span> <span class="token punctuation">{</span> promise <span class="token keyword">in</span> <span class="token function">process</span><span class="token punctuation">(</span>image<span class="token punctuation">,</span> then<span class="token punctuation">:</span> promise<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Chúng ta chỉ đơn giản là sử dụng một
completion handle closure
chuyên dụng và tự chuyển kết quả thủ công vàopromise
:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">extension</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Future</span><span class="token operator"><</span><span class="token builtin">UIImage</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token builtin">Future</span> <span class="token punctuation">{</span> promise <span class="token keyword">in</span> <span class="token function">process</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span> <span class="token punctuation">{</span> result <span class="token keyword">in</span> <span class="token function">promise</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Với
Future
chúng ta cần có một cách triển khai chặt chẽ cácclosure API
cơ bản theo cáchreactive
trongCombine
vàfuture
cũng chỉ đơn giản làpublisher
, tương đương với việc chúng ta có thể sử dụng cách sau:
1 2 3 4 5 6 7 | processor<span class="token punctuation">.</span><span class="token function">process</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">replaceError</span><span class="token punctuation">(</span>with<span class="token punctuation">:</span> <span class="token punctuation">.</span>errorIcon<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token builtin">map</span> <span class="token punctuation">{</span> $<span class="token number">0</span><span class="token punctuation">.</span><span class="token function">withRenderingMode</span><span class="token punctuation">(</span><span class="token punctuation">.</span>alwaysTemplate<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span><span class="token function">receive</span><span class="token punctuation">(</span>on<span class="token punctuation">:</span> <span class="token builtin">DispatchQueue</span><span class="token punctuation">.</span>main<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span>to<span class="token punctuation">:</span> <span class="token punctuation">.</span>image<span class="token punctuation">,</span> on<span class="token punctuation">:</span> imageView<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">store</span><span class="token punctuation">(</span><span class="token keyword">in</span><span class="token punctuation">:</span> <span class="token operator">&</span>cancellables<span class="token punctuation">)</span> |
- Tuy nhiên thì
Future
trongCombine
chỉ có thểemit
một giá trịresult
duy nhất và sẽ lập tức hoàn thành và được giải phóng khipromise
được gọi đến.
2/ Xử lý nhiều loại giá trị Output:
- Quay lại với
closure
ImageProcessor
, nếu chúng ta sử dụng 2closure
ở đây, 1 cái theo dõi cácupdate
trongprogress
nhưimage
đang đượcprocess
và 1 cái được gọi khi quá trình này kết thúc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">struct</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">typealias</span> <span class="token builtin">CompletionRatio</span> <span class="token operator">=</span> <span class="token builtin">Double</span> <span class="token keyword">typealias</span> <span class="token builtin">ProgressHandler</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token builtin">CompletionRatio</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Void</span> <span class="token keyword">typealias</span> <span class="token builtin">CompletionHandler</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token builtin">Result</span><span class="token operator"><</span><span class="token builtin">UIImage</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Void</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span> <span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">,</span> onProgress<span class="token punctuation">:</span> @escaping <span class="token builtin">ProgressHandler</span><span class="token punctuation">,</span> onComplete<span class="token punctuation">:</span> @escaping <span class="token builtin">CompletionHandler</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Process the image and call the progress handler to</span> <span class="token comment">// report the operation's ongoing progress, and then</span> <span class="token comment">// call the completion handler once the image has finished</span> <span class="token comment">// processing, or if an error was encountered.</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Trước hết chúng ta cần thêm vào
ProgressEvent
enum
để chỉ địnhtype
choOutput
khiPublisher
được chúng ta khởi tạo:
1 2 3 4 5 6 7 | <span class="token keyword">extension</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">enum</span> <span class="token builtin">ProgressEvent</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token function">updated</span><span class="token punctuation">(</span>completionRatio<span class="token punctuation">:</span> <span class="token builtin">CompletionRatio</span><span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token function">completed</span><span class="token punctuation">(</span><span class="token builtin">UIImage</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Mong muốn ban đầu là làm sao để
update
cácCombine API
bằng việc sử dụngFuture
nhưng nay chúng ta sẽ sử dụngpromise
closure
nhiều lần để thông báo cácupdate
trong cáccompleted events
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">extension</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Future</span><span class="token operator"><</span><span class="token builtin">ProgressEvent</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token builtin">Future</span> <span class="token punctuation">{</span> promise <span class="token keyword">in</span> <span class="token function">process</span><span class="token punctuation">(</span>image<span class="token punctuation">,</span> onProgress<span class="token punctuation">:</span> <span class="token punctuation">{</span> ratio <span class="token keyword">in</span> <span class="token function">promise</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span> <span class="token punctuation">.</span><span class="token function">updated</span><span class="token punctuation">(</span>completionRatio<span class="token punctuation">:</span> ratio<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> onComplete<span class="token punctuation">:</span> <span class="token punctuation">{</span> result <span class="token keyword">in</span> <span class="token function">promise</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token builtin">ProgressEvent</span><span class="token punctuation">.</span>completed<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Tuy nhiên cách triển khai trên vẫn chưa hoạt động đúng như mong muốn vì chúng ta chỉ nhận được mỗi kết quả đầu tiên được
update
trước khi tiến trình trên hoàn thành.
3/ Sử dụng Subject để gửi value:
Chúng ta có thể gửi nhiều
value
như chúng ta đã làm bên trên bằng cách sử dụng 2subject
chính được giới thiệu trongCombine
PassthroughSubject
hoặcCurrentValueSubject
. Chúng ta sẽ sử dụngPassthroughSubject
để có thể truyềnvalue
cho từngsubscriber
mà không giữ lạivalue
nào.Chúng ta có thể sử dụng
subject
đểupdate
ImageProcessing
có thể hoạt động tốt cho cả việc theo dõiprogress
cũng nhưcompleted event
.
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 | <span class="token keyword">extension</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">AnyPublisher</span><span class="token operator"><</span><span class="token builtin">ProgressEvent</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token comment">// First, we create our subject:</span> <span class="token keyword">let</span> subject <span class="token operator">=</span> <span class="token builtin">PassthroughSubject</span><span class="token operator"><</span><span class="token builtin">ProgressEvent</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Then, we call our closure-based API, and whenever it</span> <span class="token comment">// sends us a new event, then we'll pass that along to</span> <span class="token comment">// our subject. Finally, when our operation was finished,</span> <span class="token comment">// then we'll send a competion event to our subject:</span> <span class="token function">process</span><span class="token punctuation">(</span>image<span class="token punctuation">,</span> onProgress<span class="token punctuation">:</span> <span class="token punctuation">{</span> ratio <span class="token keyword">in</span> subject<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token function">updated</span><span class="token punctuation">(</span>completionRatio<span class="token punctuation">:</span> ratio<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> onComplete<span class="token punctuation">:</span> <span class="token punctuation">{</span> result <span class="token keyword">in</span> <span class="token keyword">switch</span> result <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token keyword">let</span> image<span class="token punctuation">)</span><span class="token punctuation">:</span> subject<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token function">completed</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">)</span> subject<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>completion<span class="token punctuation">:</span> <span class="token punctuation">.</span>finished<span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token function">failure</span><span class="token punctuation">(</span><span class="token keyword">let</span> error<span class="token punctuation">)</span><span class="token punctuation">:</span> subject<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>completion<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">failure</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token comment">// To avoid returning a mutable object, we convert our</span> <span class="token comment">// subject into a type-erased publisher before returning it:</span> <span class="token keyword">return</span> subject<span class="token punctuation">.</span><span class="token function">eraseToAnyPublisher</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Công việc còn lại của chúng ta phải
update
việc sử dụngfunc
trên trước khiProgressEvent
được xử lý thay vì dùnginstance
UIImage
như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | processor<span class="token punctuation">.</span><span class="token function">process</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">replaceError</span><span class="token punctuation">(</span>with<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">completed</span><span class="token punctuation">(</span><span class="token punctuation">.</span>errorIcon<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">receive</span><span class="token punctuation">(</span>on<span class="token punctuation">:</span> <span class="token builtin">DispatchQueue</span><span class="token punctuation">.</span>main<span class="token punctuation">)</span> <span class="token punctuation">.</span>sink <span class="token punctuation">{</span> event <span class="token keyword">in</span> <span class="token keyword">switch</span> event <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token function">updated</span><span class="token punctuation">(</span><span class="token keyword">let</span> completionRatio<span class="token punctuation">)</span><span class="token punctuation">:</span> progressView<span class="token punctuation">.</span>completionRatio <span class="token operator">=</span> completionRatio <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token function">completed</span><span class="token punctuation">(</span><span class="token keyword">let</span> image<span class="token punctuation">)</span><span class="token punctuation">:</span> imageView<span class="token punctuation">.</span>image <span class="token operator">=</span> image<span class="token punctuation">.</span><span class="token function">withRenderingMode</span><span class="token punctuation">(</span> <span class="token punctuation">.</span>alwaysTemplate <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span><span class="token function">store</span><span class="token punctuation">(</span><span class="token keyword">in</span><span class="token punctuation">:</span> <span class="token operator">&</span>cancellables<span class="token punctuation">)</span> |
Hãy lưu ý khi sử dụng
PassthroughSubject
là mỗisubscriber
sẽ chỉ nhậnvalue
khisubscription
đã đượcactive
Việc chuyển đổi giữa các
subject
thực sự rất đơn giản khi chúng ta dùngCurrentValueSubject
với cácvalue
hiện tại mà chúng ta cần theo dõi:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">extension</span> <span class="token builtin">ImageProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token number">_</span> image<span class="token punctuation">:</span> <span class="token builtin">UIImage</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">AnyPublisher</span><span class="token operator"><</span><span class="token builtin">ProgressEvent</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> subject <span class="token operator">=</span> <span class="token builtin">CurrentValueSubject</span><span class="token operator"><</span><span class="token builtin">ProgressEvent</span><span class="token punctuation">,</span> <span class="token builtin">Error</span><span class="token operator">></span><span class="token punctuation">(</span> <span class="token punctuation">.</span><span class="token function">updated</span><span class="token punctuation">(</span>completionRatio<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">return</span> subject<span class="token punctuation">.</span><span class="token function">eraseToAnyPublisher</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |