Modeling change

A really common task in iOS development is to validate input from a form. In this post I wanted to look at one possible implementation for doing this by extracting the form validation (business logic) from the view controller. It's not a new concept but I wanted to explore how we could do it with Swift and take advantage of generics and enum associated values to hopefully create a graceful and scalable solution.

Well we can all dream I guess 😂.

Tweaking your profile

I imagine that most of us will have used social media before and so will have created an online profile. Being social creatures from time to time we want change the details on these profiles - we are going to use this edit profile functionality as the basis of this post. Just to give you some eye candy, let's look at the form we will use for editing:

Before we can jump into the code we need to discuss some of the requirements (business rules) of editing a profile:

All fields are required

Firstnames must be at least 2 characters in length

Lastnames must be at least 2 characters in length

Email addresses must be at least 5 characters in length

Age must be between 13 and 124 years old

Error messages should be shown when any validation rules are broken

Updating should only occur when the values in the profile have been changed

Only fields that have changed should be updated

Validating your way to social success 🎉

OK, so we have our requirements but before we start building the UI let's see how we can take those rules and produce a class responsible for enforcing them.

The .success case is simple enough however .failure has an associated value which we will use for populating the error message when validation fails. We are using generics to allow this enum to be used with different concrete types, the only constraint that we place on those types are that they must conform to the Equatable protocol - in Swift this form of constraining is called type constraint and could also be used to enforce that the type is of a certain subclass. Generics are used here rather than a concrete type (which arguably would be easier to read) as the .failure case will have an associated with different types (more on this later). The ValidationResult enum itself also conforms to Equatable. This is necessary as we don't get equatable checks for free with enums that contain associated values so we need to explicitly define it. The == method under the enum is implementing this equality check. Let's look at EditProfileErrorMessages:

In the above code snippet we have the EditProfileErrorMessages struct - this will be used to hold the errors returned from our overall form validation check. We could have avoided this struct by choosing to return the error messages in a dictionary however then we would have needed to either expose the keys as static values or use magic strings between the validator and any class which used it. EditProfileErrorMessages conforms to the Equatable protocol so that it can be used as one possible associated value for the .failure case.

In the above code snippet we validate that the age value conforms to our business rules. The most interesting part is that we use ValidationResult as our return type. For this method the ValidationResult instances will use String as it's associated value type. We can see this in action:

return .failure("Must be older than \(minimumAge) and younger than \(maximumAge)")

In validateAccountDetails we are also using ValidationResult as the return type like we did with the validateAge method however here we associate the .failure case with EditProfileErrorMessages rather than a String like we did before. It's in this instance that we see the true power of using generics with enums as it allows us to express two similar but distinct interpretations of failure without polluting our codebase with conceptually-equal enum cases such as:

The above enum covers all the cases but contains a lot more cases to express what has actually failed.

One of the driving forces behind this was also to allow for easier unit testing - I won't include them in this post but if you are interested head over to the repo and check them out.

Changes

OK, so that's the validation part of EditProfileValidator but how to we know if anything has actually changed? We need to track the initial values of each field and then as it's changed determine if it's final state is different from it's initial state. It's possible for the user to edit a field and then re-edit back to what it was so a simple boolean won't do, we need to track it's actual value. We could do this by adding a new suite of properties called something like: firstNameInitialValue but we don't need to as we already have the initial values in the User instance that we pass into this class during initialisation - as User is immutable we know that it will contain the values as they were before any editing, we can then use this to determine if any changes have made:

In the above code snippet you can see that I am taking advantage of didSet to check if the value is different and I then store this information in a convenience boolean property. This boolean property isn't strictly necessary but I use it here to better by intent in the hasMadeChanges method:

As we will see in the EditProfileViewController class this method will be used to determine if we call validateAccountDetails when the user presses the update button. Now we just have one more requirement to satisfy then we can move onto the UI:

Only fields that have changed should be updated

Here we need to assume that the edit profile update process will trigger an API call and send the changes as json, to support this we want the validator to return a dictionary containing the changes that can then be passed to the API endpoint.

Here we lazy load the validator that we explored above. In this case, I have used lazy loading as a design approach to separate out the initialisation of properties - there is no meaningful performance gain here and this could easily have been init'd in viewDidLoad.

We need a User instance to populate the edit profile so we are lazy loading it here but typically a User instance would be passed into this view controller using dependency injection to allow us to more easily test this class. The next really interesting part is what happens when the user presses that "Update" button:

Due to the scope of this example I didn't implement an actual API call (which would of course be in a different class) but instead I just how an alert with the changes. So the first thing this method does is check if any changes have actually been made and if changes have been made, we check if those changes are valid and if not we show error messages under the textfields detailing what went wrong.

Thinking about alternatives

One alternative that I explored was using simple boolean returns to indicate failure however this doesn't work with the level of detail we need for the failures in validateAccountDetails. The other alternative that had more merit was throwing errors, this works well and I only choose the enum approach as I felt the level of boilerplate code required to set this obscured the purpose of the validator and resulted in a project that was harder to understand.

🤔

Looking back

There is a lot code in this post that doesn't really have to do with validating, I included it as I wanted to better show how this approach could be used against a semi-realistic form. But the beauty of this approach is that we now have a class that is independent of the UI to enforce our business rules, this should result in the business rules being easier to unit test and be clearer to understand than if we kept them in the view controller itself (please, see the GitHub repo for an implemented suite of units). It also ensures that the view controller has more cohesion as it's really only dealing manipulating the view.