神刀安全网

Better Models

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:

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:

<Scene>_<Use Case>_<ModelType>   

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:

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:

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:

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:

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.

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.

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:

Better Models

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:

Better Models Better Models

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.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Better Models

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址