App Setup
The fun just after creating a new project in Xcode
The project
So here we are, the start of it all. A few weeks ago I decided to embark on an adventure. A new Xcode project was born.
But what happens once you get that Xcode project template into a directory on your mac? How do you separate the concerns in the app?
Well, lets try and answer that question.
Packages and Dependencies
The approach that I love to take is to split up the responsibilities and concerns of the app horizontally. That is, rather than divide things by feature I’m going for separation based on the layer of interaction.
Models
The models package is the lowest level and its what contains the basic types that are used in all of the higher layers.
This layer will never contain any business logic or ability for interaction with other parts of the app. It is just the types, nothing more, nothing less.
Services
The services package is the next layer up and it is here that interactions with services are defined. The real question though, is what constitutes a service? What makes it a layer of its own rather than putting the logic into something like a View Model?
Well, the service layer is what handles interactions with different platform or network services. It fetches the data from an API endpoint. It handles interactions with SwiftData.
Some examples of what is part of a service include:
- API calls
- StoreKit interactions
- SwiftData access
- HealthKit interactions
- Platform level interactions
AI
This is a bit of a strange one, but it’s a different layer from the services and view models. In one way it can be conceptualised as a service, but the amount of extra types and interactions required means that there’s a lot here that doesn’t fit in services.
This layer isn’t as common in other projects I’ve worked on and something I’m trying to conceptualise and work through as I build the app. I might end up changing this at a later stage.
View Models
This is the business logic layer. If you’ve been around apple platform development circles for a while you might have encountered the idea of a View Model. The way I like to conceptualise this is to think of it as a layer that transform the raw types contained in the data models to types that are usable for the configuration of a view.
Typically this will involve fetching the data from one or more services and then modifying it so that it is appropriate for use in the views.
Views
This is where everything comes together. The only direct interaction is between the View and the View Model, but at the same time the view configuration types (typically a plain old swift struct because they should be immutable) are stored in the models package so the view model can know how to do the transformation.
The view package typically doesn’t have tests as there’s no straight forward way to unit test a view. Some people go for creating static functions on a view and unit testing them, though that’s bringing the business logic typically in a view model into the view and I’m less convinced about that at the moment. Some very smart people advocate for the static func in views approach, I just haven’t had the capacity to experiment with it and become comfortable with the approach.
CI and testing
The approach to testing is that everything that can be tested, should be tested. Any model transformations, service interactions or business logic should be tested. Unfortunately with apple platforms being what there are, there are caveats to this. Primarily things like StoreKit and HealthKit aren’t testable by default. You need to create abstractions and then unit test at different layers.
For Continuous Integration, the easiest approach I have is to make use of Xcode Cloud. While it isn’t scriptable in the way that a CI service such as GitHub actions or Buildkite is, it does allow for getting things done quickly. Once set up it just keeps working and the environment tends to be updated with the latest beta and release versions of Xcode very quickly.
Test Flight
The approach chosen for getting people testing the app before release is to go with TestFlight. This was chosen for having the benefit of it being a “just works” approach on Apples platforms. There’s no need for teaching users how to deal with an enterprise certificate or manage provisioning profiles. Everything is automated via Xcode Cloud and the versions are made available automatically within AppStore Connect so you can send out to beta testers.
Take aways
The take away here is to go with what you are comfortable with. Use the tools that you know how to use and if you need to learn something new, give yourself the time to approach it without extra stress of fighting your set up.
This all works for me and it’s an approach I advocate for. If you’re working in a team, have discussions with them about what is going to work best for everyone. Never take an approach that will inhibit peoples work or force them to adopt a specific style.