Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to fit a "clean architecture" on an iOS app

Recently I’ve been rethinking my android architecture project, trying to adapt it to a more “clean architecture”, specifically, the kind of design suggested by “Uncle Bob”.

Which it involves several layers of abstractions, a nice isolation of responsibilities and a very strong dependency inversion achieved by dependency injection; which, ultimately, leads to a very decoupled-portable system. A perfect candidate to be tested by unit testing and integration testing.

In my android implementation I’ve ended up having three different modules or layers:

-domain: entities, interactors, presenters (pure java module)

-data: (acts as a repository to supply the data to the domain) (android library module)

-presentation: ui related stuff, fragments, activities, views, etc (android application module)

So, I’m trying to figure out what would be the best approach on the iOS ecosystem. I’ve tried creating a project with multiple targets to achieve the same solution:

-domain: command line target (which seems very weird but I think is the most pure swift target available)

-data: cocoa touch framework

-presentation: cocoa touch framework

With this approach I can use these targets in the way I did with android modules. But the first caveat I’ve found it is that I need to add manually every new file to the dependent target.

But my knowledge is very limited in projects with multiple targets. I mean I’ve never created an iOS application with multiple targets. So I don’t know even if the solution would be use a framework (cocoa touch/cocoa) as a target instead of a command line module for the domain layer.

Any thought would be really appreciate.

Thanks!

like image 747
Víctor Albertos Avatar asked Aug 25 '15 10:08

Víctor Albertos


People also ask

What is clean architecture iOS?

Traits of a Clean architectureThe business rules can be tested without the UI, database, web server, or any other external element. Independent of UI. The UI can change easily, without changing the rest of the system. A web UI could be replaced with a console UI, for example, without changing the business rules.

Which architecture is best for iOS app development?

In a nutshell, MVC is the only viable and thus the best architecture for an iOS app.

What is iOS app architecture?

Architecture of IOS is a layered architecture. At the uppermost level iOS works as an intermediary between the underlying hardware and the apps you make. Apps do not communicate to the underlying hardware directly. Apps talk with the hardware through a collection of well defined system interfaces.

Is MVVM clean architecture?

Note: You can combine Clean Architecture with the model-view-presenter (MVP) architecture as well. But since Android Architecture Components already provides a built-in ViewModel class, we are going with MVVM over MVP—no MVVM framework required!


1 Answers

Uncle Bob's Clean Architecture absolutely applies to iOS, Swift, and Obj-C. Architecture is language agnostic. Uncle Bob himself codes mostly in Java but in his talks he rarely mentions Java. All his slides do not even show any code. It is an architecture meant to be applied to any project.

Why am I so sure? Because I've studied MVC, MVVM, ReactiveCocoa, and Clean Architecture for 2 years. I like Clean Architecture the best, by far. I tested it by converting 7 Apple sample projects to using the Clean Architecture. I've used this approach exclusively for over a year. It works out better every time.

Some of the benefits are:

  • Find and fix bugs faster and easier.
  • Extract business logic from view controllers into interactors.
  • Extract presentation logic from view controllers into presenters.
  • Change existing behaviors with confidence with fast and maintainable unit tests.
  • Write shorter methods with single responsibility.
  • Decouple class dependencies with clear established boundaries.

We also added a router component so we can use multiple storyboards. No more conflicts.

Writing unit tests is greatly simplified too because I only need to test the methods at the boundaries. I don't need to test private methods. On top of that, I didn't even need any mocking framework because writing your own mocks and stubs becomes trivial.

I've written my experience for my last 2 years studying iOS architecture at Clean Swift I also put together some Xcode templates to generate all the Clean Architecture components to save a ton of time.

UPDATE - To answer @Víctor Albertos's question about dependency injection in the comment below.

This is a really great question and demands a long detailed answer.

Always keep the VIP cycle in mind. In this case, the doSomethingOnLoad() method is not a boundary method. Rather, it is an internal method invoked only within CreateOrderViewController. In unit testing, we test a unit's expected behavior. We give inputs, observe outputs, then compare the outputs with our expectations.

Yes, I could have made doSomethingOnLoad() a private method. But I chose not to. One of the goals of Swift is to make it easy for developers to write code. All the boundary methods are already listed in the input and output protocols. There is really no need to litter the class with extraneous private modifiers.

Now, we do need to test this behavior of "The CreateOrderViewController should do something on load with this request data" somehow, right? How do we test this if we can't invoke doSomethingOnLoad() because it is a private method? You call viewDidLoad(). The viewDidLoad() method is a boundary method. Which boundary? The boundary between the user and view controller! The user did something to the device to make it load another screen. So how do we invoke viewDidLoad() then? You do it like this:

let bundle = NSBundle(forClass: self.dynamicType)
let storyboard = UIStoryboard(name: "Main", bundle: bundle)
let createOrderViewController = storyboard.instantiateViewControllerWithIdentifier("CreateOrderViewController") as! CreateOrderViewController
let view = createOrderViewController.view

Simply calling the createOrderViewController.view property will cause viewDidLoad() to be invoked. I learned this trick a long time ago from someone. But Natasha The Robot also recently mentioned it too.

When we decide what to test, it is very important to only test the boundary methods. If we test every method of a class, the tests become extremely fragile. Every change we make to the code will break many, many tests. A lot of people give up because of this.

Or, think about it this way. When you ask how to mock CreateOrderRequest, first ask if doSomethingOnLoad() is a boundary method that you should write test for. If not, what is? The boundary method is actually viewDidLoad() in this case. The input is "when this view loads." The output is "call this method with this request object."

This is another benefit of using Clean Swift. All your boundary methods are listed at the top of the file under explicitly named protocols CreateOrderViewControllerInput and CreateOrderViewControllerOutput. You don't need to look elsewhere!

Think about what happens if you were to test doSomethingOnLoad(). You mock the request object, then assert that it equals to your expected request object. You are mocking something and comparing it. It's like assert(1, 1) instead of var a=1; assert(a, 1). What's the point? Too many tests. Too fragile.

Now, there is a time when you do mock CreateOrderRequest. After you've verified the correct CreateOrderRequest can be generated by the view controller component. When you test CreateOrderInteractor's doSomething() boundary method, you then mock CreateOrderRequest using interface dependency injection.

In short, unit testing is not about testing every unit of a class. It is about testing the class as a unit.

It is a mindset shift.

Hope that helps!

I have 3 series of draft posts in Wordpress on different topics:

  1. In-depth look at each of the Clean Swift components
  2. How to break up complex business logic into workers and service objects.
  3. Writing tests in Clean Swift iOS architecture

Which one of these do you want to hear more first? Should I bump up the series on testing?

like image 156
Raymond Law Avatar answered Oct 19 '22 23:10

Raymond Law