The Exchanger is a simple iOS application demonstrating one of approaches to implement VIPER π architecture in modern Objective-C.
The application is a fairly straightforward currency converter. It takes a reference rate from a European Central Bank by parsing its public XML and provides a feature to exchange any currency including EUR, USD and GBP in any combination. The exchange rate is automatically updated each 30 seconds.
When app starts there is a limited balance with 100 units of each currency. There are two text inputs on the screen, both are cyclic carousel views for choosing currency to exchange.
YouTube video of how it works:
The app core is carefully designed with love β€οΈ to SOLID in pure Objective-C using VIPER architecture combined with SOA. Meanwhile, unit tests are written in Swift.
If you have any questions just email me. Feel free to open issues π
To install all project dependencies just use CocoaPods:
pod install
If OCLint is not installed on your machine then run following commands in Terminal:
brew tap oclint/formulae
brew install oclint
If XCPretty is not installed on your machine then run following commands in Terminal:
gem install xcpretty
The app is intended to implement the clean architecture.
Each screen is represented as VIPER module. In this implementation of VIPER there is a Router class for navigation between screens and functional callbacks to interact with module, for example:
@protocol ExchangeMoneyModule <NSObject>
@property (nonatomic, strong) void(^onFinish)();
- (void)dismissModule;
@end
Type Inference is a common feature in Swift, but Objective-C by default doesn't provide it. It's easy to avoid this issue by using C macroses, as it's provided below:
#define let __auto_type const
#define var __auto_type
Without using __auto_type:
NSArray<Currency *> *currencies = data.currencies;
With using using __auto_type:
let currencies = data.currencies;
There is a common pattern in Objective-C to call a block:
if (block != nil) {
block();
}
With optionals and closures syntax introduced in Swift this syntax looks especially overweighted. There is a C macros to deal with that:
#define safeBlock(block, ...) if (block != nil) { block(__VA_ARGS__); }
CarouselView implementation uses dummy UITextField in order to keep some first responder on the screen. It that way the keyboard is always on the screen which is a nice UX.
App saves the state using simple Core Data storage.
Unit tests are provided for service layer and core layer including formatters. Some folks prefer to write unit tests for Presenter and Interactor. In practice it may be a case of accidental complexity. When business logic is located mainly in services, then unit tests for service layer are appropriate. Interactor just passes values from presenter to services. Presenter's code is often changing and it makes more sense to write UI tests. In this way unit tests for Interactor and Presenter are recommended but there is no strict need to write tests for them meanwhile code keeps to be testable and maintable.