Introduction

In this article, we are going to see some approaches to pass the data between two Operations 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!

Contents

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 Operations: one to fetch and one to parse the data.

We can start creating our two Operation classes:

FetchOperation

1

2

3

4

5

6

7

8

9

10

11

final classFetchOperation: Operation{

// 1

private(set)vardataFetched:Data?

overridefuncmain(){

// 2

self.dataFetched=// data received from HTTP request

}

}

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.

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:

1

2

3

4

finalclassDataWrapper{

vardataFetched:Data?

}

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:

Finally, we can change the method start of Handler to use the new wrapper object:

1

2

3

4

5

6

7

8

9

10

funcstart(){

letdataWrapper=DataWrapper()

letfetch=FetchOperation(dataWrapper:dataWrapper)

letparse=ParseOperation(dataWrapper:dataWrapper)

parse.addDependency(fetch)

queue.addOperations([fetch,parse],waitUntilFinished:true)

}

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:

1

2

varcompletionBlock:(()->Swift.Void)?

We can take advantage of this completion to pass the values between the two Operations:

1

2

3

4

fetch.completionBlock={[unownedparse,unownedfetch]in

parse.dataFetched=fetch.dataFetched

}

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

funcstart(){

queue.maxConcurrentOperationCount=1

letfetch=FetchOperation()

letparse=ParseOperation()

parse.addDependency(fetch)

fetch.completionBlock={[unownedparse,unownedfetch]in

parse.dataFetched=fetch.dataFetched

}

queue.addOperations([fetch,parse],waitUntilFinished:true)

}

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.

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:

parse no longer needs fetch as dependency since adapter 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!

Share this:

Related

Marco Santarossa

Hi there, I'm Marco and I'm an Italian developer. I moved to London in 2016 to work at Sky as iOS developer.
I've been an iOS Developer since 2011 and I sometimes write embarrassing PHP/JS code.
I'm keen to learn new things and I spend most of my spare time learning as self-taught.
When I don't develop, I like watching MMA fights and cooking Italian food.

2 Comments

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.

AFAI, it cannot guarantee that parse will wait for the completion block of fetch, even though set maxConcurrentOperationCount to 1.