4 Ways To Pass Data Between Operations With Swift
Do you appreciate the power of Operations and would you like to pass the data between them to make amazing chains? Let’s read 4 ways to achieve your dreams.
Introduction
In this article, we are going to see some approaches to pass the data between two Operation
s in Swift. To avoid losing the focus on this topic, I will not explain what is and how works an Operation
. For this reason, you need a basic understanding of Operation
to understand the next sections of the article.
I may write another article to explain the Operation
if I see that you would be interested on it.
Happy Reading!
Getting Started
Before diving into these approaches, we need a scenario for our examples. I guess all of us made an application where we had to fetch the data from an API request and then parse the data received. For this reason, I think it’s quite familiar if we use a scenario where we have two Operation
s: one to fetch and one to parse the data.
We can start creating our two Operation
classes:
FetchOperation
|
|
- The data fetched to send to
ParseOperation
. - Saves the data received from an HTTP request.
For the sake of explanation, I skipped a real implementation since it would need an asynchronous operation. If you want to learn how to use an asynchronous operation, you can have a look at my gist.
ParseOperation
|
|
- The data received from
FetchOperation
. - The dictionary created from the parsing of
dataFetched
. - Checks if the data exists and then creates a dictionary from the data fetched.
For the sake of explanation, I kept both Operation
implementations as plain as possible without caring of the lifecycle.
The last step is creating a handler class which will manage these operations with an OperationQueue
:
|
|
OperationQueue
to run ourOperation
s.- Prepares our
Operation
s setting the dependencies. - Adds the
Operation
s in the queue blocking the queue thread until it’s finished.
With these 3 classes, we are ready to start looking at the approaches to pass dataFetched
from FetchOperation
to ParseOperation
.
Approaches
Internal dependency reference
The object Operation
provides an array of its dependencies with the following property:
|
|
Thanks to this information, in ParseOperation
we can have access to its dependency FetchOperation
:
|
|
At this point, we can refactor the method main
of ParseOperation
to read dataFetched
directly from its dependency:
|
|
This approach is the easiest since we don’t need any external helpers to inject dataFetched
. To be honest, I don’t like this approach. I would prefer injecting the data from outside because ParseOperation
wouldn’t have the responsibility to decide where to get the data.
Reference Wrapper
For this approach, we have to create a new class which will wrap fetchedData
:
|
|
Then, we can inject this new wrapper in both operations. FetchOperation
will use this wrapper to set the property dataFetched
, whereas ParseOperation
will read the value of dataFetched
—previously set in FetchOperation
.
We can change our FetchOperation
to inject this wrapper and set its property once we receive the HTTP response:
|
|
- Injects
DataWrapper
and keeps an internal reference to use inmain
. - Sets the wrapper property to be used in
ParseOperation
.
Then, we can change ParseOperation
to read the wrapper property:
|
|
- Injects
DataWrapper
and keep an internal reference to use inmain
. - Reads the wrapper property to parse it.
Finally, we can change the method start
of Handler
to use the new wrapper object:
|
|
To be honest, I don’t like also this approach. We cannot inject just the data but we must inject this wrapper—which may not have the data ready when we use it in ParseOperation
.
Keep reading to learn better approaches.
Completion block
The object Operation
provides a completion closure which is called once the Operation
completes its task:
|
|
We can take advantage of this completion to pass the values between the two Operation
s:
|
|
Remember to use unowned
for both operation objects otherwise you would create a retain cycle.
At this point, we can refactor the method start
of Handler
like this:
|
|
If you don’t set maxConcurrentOperationCount
of OperationQueue
to 1
, parse
would start without waiting the completion block of FetchOperation
. It means that we would inject the data too late when the operation is already started. Instead, we must inject it before running ParseOperation
.
I definitely prefer this approach rather than both Internal dependency reference
and Reference Wrapper
since we can inject dataFetched
from outside.
Update:
As the user HoNooD
pointed out, this approach is not reliable. Even if we set maxConcurrentOperationCount
of OperationQueue
to 1
, ParseOperation
may start without waiting the completion block of FetchOperation
. It’s quite inconsistent. For this reason, do not use this approach.
Adapter operation
This approach is very similar to Completion block
. Instead of using the completion block, we add a third operation which is called Adapter
.
This new Operation
has just a plain block where we can inject the data fetched inside ParseOperation
like in Completion block
:
|
|
At this point, we can refactor the method start
of Handler
like this:
|
|
- Sets new adapter operation with a trailing closure.
- The dependencies have been changed:
adapter
needsfetch
as dependency to start when we fetched the data.parse
needsadapter
as dependency to inject the data fetched.parse
no longer needsfetch
as dependency sinceadapter
is in the middle.
- Adds
adapter
in the queue.
Thanks to this adapter operation, we don’t need to care about maxConcurrentOperationCount
of OperationQueue
like in Completion block
. We can leave its default value—OperationQueue.defaultMaxConcurrentOperationCount
.
Conclusion
Personally, my favorite approach is Adapter operation
since it provides a clean way to inject the data. You may argue that Completion block
provides a clean solution as well without using another Operation
in the middle. I agree with it, but I don’t like that we must set maxConcurrentOperationCount
to 1
to avoid unexpected behaviours.
If you have better approaches, feel free to write them in the comments. Thank you!