- Although
Combine
has focused on theconcept
thatpublisher
willemit
sequence value
ofemit
over the timeline, they have provided some convenient and fully functionalAPI
so that users do not need to set up custompublisher
from head. - For example, when we want
Combine
to support us with built-inAPI
likeImageProcessor
to handle thecompletion handle pattern
inclosure
in operationsasynchronously
whenimage
processing completes or fails:
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> |
- Instead of rewriting
ImageProcessor
we can handle the new way introduced inCombine
. Not only do we keep the aboveimplement
, we can still useCombine
to handle thecompletion handle
case by case even when we add newcode
:
1 / Future is introduced:
- We will focus on using the
Future
type
known in theFuture/Promise pattern
, apattern
very popular in programming.Combine
give usclosure
promise
so that we can recognize when theoperation
asynchronous
completion and will automaticallymap
Result
in the eventPublisher
. - What’s really handy is that in the above case to
completion handle closure
old usedResult
isInput
, which means we can useFuture
with simple setup infunc
process
as old as follows:
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> |
- We simply use a dedicated
completion handle closure
and manually pass the result to thepromise
:
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> |
- With
Future
we need a strict implementation of basicreactive
closure API
reactive
inCombine
and thefuture
is simplypublisher
, equivalent to that we can use the following:
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> |
- However,
Future
inCombine
can onlyemit
a singleresult
value and will be immediately fulfilled and released when thepromise
is called.
2 / Handling various types of Output values:
- Going back to the
closure
ImageProcessor
, if we use 2closure
here, one keeps track ofprogress
update
like theimage
beingprocess
and the other is called when the process completes:
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> |
- First of all we need to add the
ProgressEvent
enum
to specify thetype
for theOutput
when thePublisher
is initialized by us:
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> |
- My initial wish was how to
update
theCombine API
usingFuture
but now we will usepromise
closure
multiple times to reportupdate
incompleted 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> |
- However, the above implementation still does not work as expected because we only get the first results that are
update
before the above process completes.
3 / Use Subject to send the value:
- We can send more
value
as we did above using 2subject
be introduced inCombine
PassthroughSubject
orCurrentValueSubject
. We will usePassthroughSubject
so that we can pass avalue
to eachsubscriber
without retaining anyvalue
. - We can use the
subject
so that theImageProcessing
update
can work well for bothprogress
tracking andcompleted 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> |
- The rest of our work must
update
the above usingfunc
before theProgressEvent
is processed instead of using theUIImage
instance
like so:
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> |
- Please note when using
PassthroughSubject
that eachsubscriber
will only receivevalue
whensubscription
isactive
- The transition between the
subject
really very simple when we useCurrentValueSubject
with thevalue
of current that we need to track:
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> |