Identifying Side Effects Using Swift
2 minutes reading

This is a hard problem in any language. When we are interacting with APIs, we are constantly making requests on objects. Let’s say we have an object x. If we say x.doSomething(), this will create a new state on this object. We can only assume what parts of x is changed as a result of doSomethingfunction, by looking at the method name or documentation. And this assumption may fail at any time, for example, with a newer version of the API. This means, with every move, we are creating side effects in the system and we just don’t care. #YOLO

I will now try to overcome this problem by refactoring an imaginary iOS app. Say we have a profile screen in which we show information about the current user. I will first model the data in the screen.

If we look at this API, obviously name, dateOfBirth, email and accountStatus fields are initially empty, and we should use fetchUser and refreshAccountStatus functions to get this data from server.

Let’s see it in action:

Do you see the problem now?

We just hoped fetchUser call to update name, dateOfBirth, email; and refreshAccountStatus call to update accountStatus. Then synced UI according to this assumption.

These function calls are just our way of saying “please do something” to viewModel object, and we can’t possibly know what changes these calls make internally. For example, refreshAccountStatus call might also update name, dateOfBirth and email, we just can’t know it for sure as an outsider.

So the point is, every time you make an assumption about what an API does, your UI might show an incorrect state. And this is really bad. We should be able to identify every side effect and take action with no exceptions.

Let’s move things around.

  • Definition of data is moved out of view model. First, it’s cleaner. Second, it’s always nice to put state in a different object (preferably a struct) because you can get a snapshot of the system by copying this object with no effort. For example, this might be really useful if you want to keep history to implement rollback functionality.
  • Introduced a property named onChange to identify the changes in state. This block is called whenever a property changes in state, with its unique identifier. No side effects can escape.
  • Completion blocks are removed from view model. Because they are useless. We just say “please fetch user” and then onChange block notifies us if anything changes anyway.

Let’s see it in action:

When we first define this, it means we’re ready for any change that can happen on the state. So at this point, we just make requests to the view model and don’t care about any callbacks.

How about testability? We can simulate any action and see the state changes in order they happen.

Bottom Line

We should not let untraceable bugs into our system. So simply:

  • Be aware of the side effects you are creating.
  • Handle state changes properly.
  • Write tests.

For full implementation, you can checkout this sample project: Movies. I often update this repo to reflect my latest architectural decisions.

Hope you liked it! Please let me know what you think. All suggestions and questions are welcome.

Göksel Köksal

iOS developer. Serious gamer.

Leave a reply