Skip to content

Example of implementing VIPER πŸ’Ž and Clean Architecture πŸ’ in Objective-C

License

Notifications You must be signed in to change notification settings

vladimir-kaltyrin/exchanger

Repository files navigation

Build Status

Exchanger

Exchanger

The Exchanger is a simple iOS application demonstrating one of approaches to implement VIPER πŸ’Ž architecture in modern Objective-C.

Table of contents

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:

Exchanger app video

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

Architecture

The app is intended to implement the clean architecture.

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.