View on GitHub

Povio Engineering Guidelines

These are guidelines for the technologies we are using.

iOS coding guidelines

Please read the following chapters before jumping in

  1. General
  2. Code style
  3. App architecture
  4. Git flow
  5. Localization
  6. Versioning
  7. 3rd party libraries
  8. Testing and CI
  9. Code reviews
  10. Deployment
  11. App Store

1. General

1.1 Coding language

All apps are written in Swift.

1.2 IDE

We recommend Xcode for developing iOS apps. Make sure you are running the latest version of Xcode (and Swift) that is available from the App Store.

1.3 Supported iOS versions

Each project should support current iOS SDK version and one version before. Older versions are supported on client’s request.

2. Code style

Reference to Swift style guide.

2.1 Linter

Use SwiftLint for code linting. Do not install it as dependency. Install it on a system and run it as a run script:

if [ -z "$CI" ]; then
  export PATH="$PATH:/opt/homebrew/bin"
  if which swiftlint >/dev/null; then
    swiftlint
  else
    echo "error: SwiftLint not installed. Install it using `brew install swiftlint` command or download it from https://github.com/realm/SwiftLint"
    exit 1
  fi
fi

2.2 Best practices

2.3 Optionals

Use optionals visely, but do not try to abuse them. Implicitly unwrapped optionals are forbidden.

2.4 Error handling

public enum LoginError: LocalizedError {
  case invalidUsername
  case invalidPassword
  
  public var localizedTitle: String? {
    switch self {
      case .invalidUsername, .invalidPassword:
        return "error_title".localized()
    }
  }
  
  public var errorDescription: String? {
    switch self {
      case .invalidUsername:
        return "login_invalid_username_error".localized()
      case .invalidPassword:
        return "login_invalid_password_error".localized()
    }
  }
}

2.5 Large views

If views become too big, consider replacing them with scenes instead. This way, the whole logic is bundled within one complete and isolated bundle/scene.

2.6 Message broadcasting

The use of NotificationCenter for dispatching messages is highly discouraged. It’s acceptable when dealing with Apple’s frameworks, but for general use, try to avoid it and use other techniques, like delegation pattern.

3. App architecture

We are using a slightly modified version of Clean Swift (VIP) architecture/design pattern on all of our projects.

Boilerplate project setup and VIP samples can be found here.

3.1 Templates

Scenes should be created using the VIP template.

Adding a template to the Xcode

  1. mkdir -p ~/Library/Developer/Xcode/Templates/File\ Templates/Custom
  2. cd project/File\ Templates/
  3. cp -R ./* ~/Library/Developer/Xcode/Templates/File\ Templates/Custom/
  1. New File…
  2. Select Template and tap ‘Next’
  3. Give the scene a name and tap ‘Create’

3.2 App configuration

Use Xcode schemes for app configuration across dev/staging/production. Preferably use *.xcconfig files to store configurations per scheme.

3.3 Storyboards

We are not using Storyboards at all. We use SnapKit for autolayout in code instead.

3.4 Networking

We should use AlamofireNetworkClient from PovioKit preferably.

3.4.1 Serialization

When we think about serialization we immediately think about Codable protocol that is widely used in swift development. But it can be easily implemented in a wrong way. The models that we serialize responses to, are called Data Transformation Objects (DTO). They are acutally 1:1 representation of the models defined on the backend API. They often contain some kind of violations that swift developers don’t like. To avoid that and as well have clean models that we pass around the app, we should transform them to Domain models.

Network Layer

An example of the DTO to Domain model transformation:

// dto model
struct UserDto: Decodable {
  let id: String
  let firstName: String
  let lastName: String
}

// domain model
struct User {
  let id: String
  let firstName: String
  let lastName: String
  let fullName: String
}

// supporting protocol
protocol ModelMapper {
  associatedtype T
  associatedtype U
  static func map(_ objects: [T]) -> [U]
  static func transform(_ object: T) -> U?
}

extension ModelMapper {
  static func map(_ objects: [T]) -> [U] {
    objects.compactMap { transform($0) }
  }
}

// mapper that transforms dto -> domain
struct UserMapper: ModelMapper {
  static func transform(_ object: UserDto) -> User? {
    .init(id: object.id,
          firstName: object.firstName,
          lastName: object.lastName,
          fullName: "\(object.firstName) \(object.lastName)")
  }
}

// call site within the networking layer
let user: Promise<User> = client
  .request(method: .get, endpoint: Endpoints.getUser)
  .validate()
  .decode(UserDto.self, decoder: .default)
  .compactMap(with: UserMapper.transform)

3.5 Project Structure

The physical files should be kept in sync with the Xcode project files in order to avoid file sprawl. Any Xcode groups created should be reflected by folders in the filesystem.

Here is a sample of the project structure you should follow

Project
|
|AppDelegate.swift
|
|- Scenes
|—— Login // each scene generated with VIP template, in a separate folder
|——— Data Source
|——— Workers
|——— Views
|——— LoginViewController.swift
|——— LoginInteractor.swift
|——— LoginPresenter.swift
|——— LoginRouter.swift
|—— ...
|
|- Common
|—— Managers
|—— Controllers
|—— Extensions
|—— Models
|—— Enums
|—— Protocols
|—— Views
|—— Utils
|—— Workers
|—— ...
|
|- Networking
|—— Client
|—— APIs
|
|— Resources
|—— Fonts
|—— Localization
|—— Plists
|—— ...
|

3.6 New Projects

To jumpstart a new project there is a Xcode template available. You can use it to generate a new Xcode project, it includes preconfigured options such as:

You can find the latest version of the template here.

4. Git flow

The Git guidelines are a part of the general guidelines. Be sure to read them as well.

5. Localization

  1. Every project should have it’s own spreadsheet named like <ProjectName> - Localization. Please ask your PM or lead developer to create one and give you read/write access.
  2. Create the following Gemfile in project root:
source "https://rubygems.org"

gem 'fastlane'
gem 'babelish'
  1. Create folder named Localize and copy files from sample zip file
  2. Look for further instructions in Localize/README.txt

6. Versioning

We are trying to follow semantic versioning for our apps. Which is quite a standard nowadays. The details are explained in the link, so we’ll cover just some basics here.

6.1 Major release

When we do a feature or a set of features that are breaking changes, or could break the previous release, then this is the right number to change for the release. If you release a smaller feature set and it doesn’t break the previous release, look to the next section.

Keep in mind, that when we do a major release, the minor and patch parts need to be reset to 0.

Example: 1.x.x -> 2.0.0

6.2 Minor release

Similar to a major release, a minor also includes a feature or a feature set, with the difference that it doesn’t break the previous release. It can also include bugfixes until there is a feature. If it only contains bugfixes, look next section.

Example: X.2.x -> X.3.0

6.3 Patch release

We like to call it point release as well. Since it should only contain bugfixes, these releases should be really small and done when we discover bugs and want to patch them asap. Any features should not be included in this release.

Example: X.X.1 -> X.X.2

7. 3rd party libraries

We should use as few 3rd party libraries as possible. If a custom solution can be implemented in a reasonable time, do it. Otherwise, you are encouraged to use top rated and evolving libraries only that support Swift Package Manager (SPM) (or Cocoapods if SPM is non-existing).

Every project should include PovioKit dependency and use it’s AlamofireNetworkClient for networking.

DO NOT

Here is a list of a few packages you should consider using in your projects

8. Testing and CI

You should make a conscious effort to write tests early and often, this holds especially true for core functionality. Every feature should have backing tests before pushing it to the git. Tests should pass locally before pushing the code.

Keep the following naming convention when writing tests: test_methodName_expectedBehaviour

Your test flow should follow the given approach:

// given - a scenario (system under test)

// when - something happens

// then - expected behaviour

To simplify testing clean arch scenes, make sure you’ve installed VIP template for tests.

Tests will need to run and pass on a dedicated CI service. Be careful of crossing boundaries, having to mock and spy too many components may indicate a flaw in your design.

9. Code reviews

You can find more about this topic here.

10. Deployment

Deployment needs to be done with the use of Fastlane tool with predefined configurations. Please check the documentation.

Primary testing service is TestFlight. Don’t use others unless there are requirements to do that.

11. App Store

Through the project lifecycle, we usually talk about App Store right at the project start and when we prepare builds or releases. This chapter will try to cover most cases required to accomplish app publishing.

11.1 Basics

When we are talking about app setup for the App Store, we need to have in mind two Apple portals.

App Store Connect is where pretty much everything non-tech related is going on. From managing users, permissions, builds, releases, in-app purchases, etc. This is the main portal to interact when doing a release.

On the other hand, Developer Portal is purely for technical stuff like managing certificates, devices, profiles, services, keys, etc.

11.2 New App

When you kick-off a project or you just want to create another target of existing app, you need to create a new app entity on the App Store Connect. You can read about this here.

One important thing to notice here is to select unique name for the app. It needs to be unique between all available apps in the App Store.

11.3 Users

Internal user

External user

11.4 TestFlight

In order to prepare a TestFlight build, you need to have an app entity available (see New App section) and deployment to be set (see Deployment section). Once this is done, you can deploy new build to the TestFlight, add testers to the build and start playing with it.

For more info, read the documentation.

11.5 Releases

A release is usually done in a cadence with the sprint planning. But not necessarily. Here is the list of things that we need to oversee for each release: