This is a preview of one of the new features in the upcoming version 2 of my Clean Swift Xcode templates.
The VIP cycle isolates your code dependency, while the models that are passed through the VIP cycle isolate your data dependency.
The underscore way
In version 1 of my templates, you create the Request, Response, and View Model as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
struct ListOrders_FetchOrders_Request { } struct ListOrders_FetchOrders_Response { var orders: [Order] } struct ListOrders_FetchOrders_ViewModel { struct DisplayedOrder { var id: String var date: String var email: String var name: String var total: String } var displayedOrders: [DisplayedOrder] } |
The underscores separate the three different roles taken on by the names of the structs. The basic form is:
1 2 |
<Scene>_<Use Case>_<Model Type> |
This naming convention already works very well. But we can do better.
Add a little nested-ness
Thanks to Ismail for pointing it out, Swift allows you to nest structs within structs. In version 2, you can take advantage of nested structs to create your models:
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 |
struct ListOrders { struct FetchOrders { struct Request { } struct Response { var orders: [Order] } struct ViewModel { struct DisplayedOrder { var id: String var date: String var email: String var name: String var total: String } var displayedOrders: [DisplayedOrder] } } } |
This is how you create them:
1 2 3 4 |
let request = ListOrders.FetchOrders.Request() let response = ListOrders.FetchOrders.Response() let viewModel = ListOrders.FetchOrders.ViewModel() |
If underscores already work so well, why am I changing it? There are several benefits which I personally encountered in my own projects.
Share formatted struct for multiple view models
Sometimes, you’ll want to share a single formatted struct with multiple view models.
Let’s say you have an account screen. The requirement is to show the user’s currently open orders, if any, and up to three past orders.
The old way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
struct Account_FetchOpenOrders_ViewModel { struct FormattedOrder { var total: String var date: String var status: String } var formattedOrders: [FormattedOrder]? } struct Account_FetchOrderHistory_ViewModel { struct FormattedOrder { var total: String var date: String var status: String } var formattedOrders: [FormattedOrder]? } |
The FormattedOrder
struct in both Account_FetchOpenOrders_ViewModel
and Account_FetchOrderHistory_ViewModel
are identical. Wouldn’t it be nice if you can DRY it up?
The new way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
struct Account { struct FormattedOrder { var total: String var date: String var status: String } struct FetchOpenOrders { struct ViewModel { var formattedOrders: [FormattedOrder]? } } struct FetchOrderHistory { struct ViewModel { var formattedOrders: [FormattedOrder]? } } } |
The FormattedOrder
struct is now moved out of the view model structs but remain inside the Account
scene. It only needs to be defined once.
Isolate data dependency. Avoid namespace collision
I hear ya. But you could just move the FormattedOrder
struct to the global namespace so it can be shared.
However, like global variables, global types should be avoided.
First, FormattedOrder
is intended to be shared by ONLY FetchOpenOrders
and FetchOrderHistory
. Nothing else. Unnecessary sharing because of convenience is a code smell.
Second, if you have a OrderDetails
scene, it’s going to need its own FormattedOrder
. And it’ll be vastly different. Showing a single order’s details may also require you to provide the order date, individual line items, payment info, and shipping address, …etc.
You could certainly lump all these order details onto the same FormattedOrder
struct, so it can be shared throughout your app.
1 2 3 4 5 6 7 8 9 10 11 |
struct FormattedOrder { var total: String? var date: String? var status: String? var orderDate: String? var lineItems: [LineItem]? var paymentInfo: String? var shippingAddress: String? } |
Such a fat model is confusing and hard to use. You may need to make all your variables optional, simply because they’re returned by your API. You’ll then have to deal with a lot of if let
.
The most evil of all. You’ve just coupled the data dependency between three use cases across two scenes.
If you continue to go down this wrong path, you could name the structs differently – FormattedOrder1
and FormattedOrder2
. Or whatever creative juice you drink. Yes, you avoid data dependency, but you further contaminate the global scope. Keeping the same name would cause a collision in the global namespace.
By separating the Account
and OrderDetails
scenes into their own respective namespace, you can use the same name for your FormattedOrder
struct while having the exact data you need for display.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
struct Account { struct FormattedOrder { var total: String var date: String var status: String } struct FetchOpenOrders { struct ViewModel { var formattedOrders: [FormattedOrder]? } } struct FetchOrderHistory { struct ViewModel { var formattedOrders: [FormattedOrder]? } } } struct OrderDetails { struct FormattedOrder { var total: String var date: String var status: String var orderDate: String var lineItems: [LineItem] var paymentInfo: String var shippingAddress: String } struct FetchOrderDetails { struct ViewModel { var formattedOrders: [FormattedOrder]? } } } |
Best of both worlds. Voila.
Less cluttered auto completion
The auto completion feature in Xcode is great. Faster typing. Fewer typos.
The auto completion suggestion list for the old underscore way looks like:
If you have many scenes and use cases, it can get long very quickly. It becomes harder to pick the one you want.
For the new and improved nested struct, it looks like:
It takes you step by step. First, pick your scene. Second, pick your use case. Third, pick your model. More focus. Less clutter.
Finally, it’s just more conventional. First, Objective-C. Now, Swift. Entity tokens use the upper camel case naming convention. Underscores are popular in other languages such as Ruby and C. Not so much in the iOS world. Now, you can go back to being a purist.
If you’ve downloaded my templates from Gumroad, you’ll get an email when version 2 becomes available. If not, sign up below to download it now.
I like the update! However, I must say I was expecting you to give some credit to Ismail, who commented the idea in “Clean Swift iOS Architecture for Fixing Massive View Controller”. I’ve been using it since he commented that and it’s an amazing improvement.
You’re right. I’ve added it.
Thank you.
I’m fortunate enough to work with Ismail, the dude’s brilliant. Always nice to see someone of his calibre contributing something to the wider ecosystem.
Agreeing with you here. This improvement is awesome.
@Raymond Hey, since you are changing the templates, will you also be rearranging your site on clean-swift? Since new dudes may be reading up on old resources, and comments.
I’ll likely do a ‘Get Started Here’ page to group the posts under different categories. There are some posts that are prerequisites before others.
That would be nice. Right now the blog’s not too big to able to start from the beginning and work forward but it could get real messy real quick.
Very nice update!
Well done 🙂
One Swifty improvement for structs that are simply containers of other structs, you could transform them to enums, because an enum with no cases cannot be instantiated like ˋOrderDetails()ˋ which should not be possible 😉
Hi Kevin,
I already did that change while improving the router. You’ll see this change in the next update.
Great!
How are you handling date fields when the interactor gets them as Date objects and it’s the presenter’s responsibility to format it into a String? I see in all the models above they are Strings. Does this you are converting them into Strings in your domain layer??
Hi jody,
You models should look like:
Your interactor passes a
Date
object to your presenter, which converts it into aString
object.Thanks Raymond. I also took a look at your latest sample project and noticed there as well. You are passing domain entities into the presenter. I’ve been debating doing the same. I’ve watched uncle bob’s videos a number of times and was left with the impression that we shouldn’t be doing that, i.e. the response model is a different model than the domain model. But I think this is fine since it’s an outer layer depending on an inner layer.
Raymond, after thinking about what you suggested above for a model, it seems to violate the clean architecture.
The use case’s request and response DTOs are part of the business logic (they form the boundary between View and the Use Cases) and the view model belongs to the view layer. In the above model the use case struct contains both DTOs and the ViewModel.
Hi jody,
You could define an extra model layer between the interactor and presenter to further isolate the data dependency. But I haven’t found any benefit in doing so.
What if, instead of separate structs, a “Global Model” can just conform into specific protocols in which we can pass on different view models? Is this not a good practice? I’m extremely new to CleanSwift, and I have a lot of questions. But Imma start with this. Thank you.