Welcome to the BitriseClient project. It serves the purpose of showing app modularisation on a real business case. The application itself is a simple API client that shows some basic info about Bitrise applications.
The project was created for the purpose of internal Netguru hackathon performed on Jul 18, 2022. Main goal was to perform a test and validate the thesis:
20 developers can work efficiently on the same project. The project they have not worked before and is rather simple. They can deliver good results in 4 hours.
The project was opartially prepared before the event to allow teams work more efficiently during time limited event. Originally it was developed using different repository. It contained confidential information, that part was stripped and the project is available in new repository with cleared git history.
The initial project architecture and solutions used there were created by Siemian and PatrykKaczmarek. Where the implementation of particular modules were done by the other Netguru iOS team members during the hackhaton.
Things that you need to have before start working with a project.
- Ruby
- Bundler (
gem install bundler
) - Homebrew
- Carthage (
brew install carthage
) - CocoaPods (
brew install cocoapods
) - Xcodegen (
brew install xcodegen
) - Xcode 15 (iOS SDK 17)
- Clone repository
- Install required Gems:
bundle install
- Run Carthage:
carthage bootstrap --platform iOS --cache-builds
- Regenerate project files and run pods:
swift regenerate_projects.swift
- Set up SwiftFormat (more info below).
- Update git hooks to use predefined versions.
chmod +x .githooks/pre-commit git config core.hooksPath .githooks
- Open
BitriseClient.xcworkspace
file and build the project.
When committing to the repository, please, format your code according to the team code style guide.
To automatically point out any discrepancies, please use SwiftLint tool which is currently integrated via postBuild script for every target.
There is a tool called SwiftFormat that can fix some issues in your code automatically.
You enable usage of SwiftFormat as a pre-commit git hook.
- Respect Swift API Design Guidelines
Project uses a modular architecture in order to allow different teams working on the same app in an efficient and controllable way. Currently the project is divided into multiple modules, which splits into two categories:
- domain modules
- common modules and the app itself.
Full dependency graph can be found bellow:
Each module consists of 3 targets:
- framework - the core implementation of the module
- test target - the unit tests for the framework
- demo app - the app that launches the module in a separate environment in order to test it independently
In fact, every module is wrapped by a separate
.xcodeproj
. The genuine app wraps all projects into one.xcworkspace
.
IMPORTANT:
To use the module either in the app or the demo app, you need to import it.
Every module consists of 4 predefined files which are the must to keep modules unified:
{module_name}Module.swift
- a class that should be used to initialize the module.{module_name}ModuleSetup.swift
- a class that contains all dependencies required by the module to work properly.{module_name}ModuleAction.swift
- an enum that contains all actions that goes outside the module.{module_name}ModuleInterface.swift
- a class that contains the 2way communication interface: to the module and outside of it (through{module_name}ModuleAction
).
Such module definition has been composed this way to allow the developer to choose the correct architecture per module. Some modules might be relatively small and easy, thus complex architecture might not be needed. On the other hand, the rest modules might be complex and hard to handle, thus requiring a readable and well-designed architecture. There is only one requirement to meet. The module must expose rootViewController
in order to embed it in the app's current view hierarchy. This is achieved automatically by adopting the ModuleInterface
protocol, which every domain module does by default.
Check Module .xctemplate section to read about automated modules creation.
IMPORTANT:
It's strongly encouraged to use one architecture across all modules to increase maintainability. However, this is not a must.
To show, how all generated classes are connected to each other, what they do, and what responsibilities they have, look at the following diagram:
To understand the memory management within the module, take a look at the following diagram:
There are possible scenarios of communication:
- the app wants to trigger a known behavior within the module. All behaviors are wrapped by the API exposed in
ModuleInterface
. This is called forward communication because the receiver is known. - the module wants to inform about the action/event that happened within the module and cannot (or should not) be handled by itself. The module informs the receiver through
ModuleInterface
by invokingactionHandler
closure. This is called backward communication because the receiver is unknown.
Following diagrams present both cases:
To avoid having dangling pointers or retain cycles, the app contains a class called ModulesContainer
which stores memory pointers to initialized modules. This is the generic class whose responsibility is to store in the memory only one instance of a module of the particular type.
IMPORTANT:
Every module can be stored in the memory only once. There is no way to create two instances of the same module when using
ModulesContainer
API.
Module is stored in the memory until manually released.
The project also contains the AppModules
struct that composes the ModulesContainer
class and provides strongly typed access to the modules. By default, common modules are stored in the memory all the time (through stored variables) when domain modules are created on-demand.
IMPORTANT:
When a domain module is no longer necessary, release it by invoking
AppModules.release(module:)
API.
Besides using modules in the genuine app, you can play with the implementation exposed by the module's public API in the demo app. Every demo app can be launched independently, so the newly written implementation can be checked immediately, even if the other module is not ready yet.
IMPORTANT:
Demo app is the sandbox for you! Feel free to use it to test what you want in a way you want.
Here are some examples of how to properly use demo apps:
Networking
- in Demo App, you can provide a POSTMAN-like interface for defining requests and checking the content of responses.BitriseAuthentication
- in Demo App, you can provide the UI for obtaining an access token which can be easily copied to other development tools.CommonUI
- think of it as a views browser. As a developer, you can quickly check which UI components are shared, how they look like, what can be customized, and how much they're adaptive.- etc.
To maintain consistency in the project and facilitate simultaneous work, the Xcode template Module.xctemplate
was created. You should use it while creating a new module which should be attached to the main application.
Prerequisite (to be done once): copy Module.xctemplate
directory to:
~/Library/Developer/Xcode/Templates/File Templates/Custom Templates/
- Navigate to a place in the
.pbxproj
tree where the module should be generated. It's strongly encouraged to embed it in thePublic/
subdirectory. - Open a "new file from template" dialog (File -> New File, shortcut: ⌘ + N).
- Scroll vertically from within the
iOS
tab to see theCustom Templates
section. - Double click on the
Module
tile - Provide a name for the module.
- Confirm the place where files should be created and their membership to a framework target.
- Xcode will generate 4 files for you (as mentioned in Module Architecture section:
{module_name}Module.swift
{module_name}ModuleSetup.swift
{module_name}ModuleAction.swift
{module_name}ModuleInterface.swift
- Generated files contain some placeholders to fill in order to work properly.
Pull requests and issues are always welcome. Please open any issues and PRs for bugs, features, or documentation. SLA for resolving issues is set to 30 working days.
Current repo maintainer: Siemian
This project is available as open source under the terms of the MIT License.