Tutorial: Improving Your iOS App Architecture - Part 2 of 2
This tutorial is an excerpt from our book series iOS App Development for Non-Programmers, available in the iBookStore for the iPad, and on Amazon.
Part 1 of this post looks at a sample App with standard iOS architecture and points out the pitfalls you need to avoid. Part 2 of this post demonstrates the same sample App with an improved architecture that helps make the App easier to design, create and maintain.
In Part 1 of this post, we learned that the iOS view controller is a user interface object that is tightly bound to the user interface. This is not a problem. The problem occurs when core logic is placed in the view controller because it prevents the code from being reused and extended as your App goes through its normal change life cycle.
So, how do you fix this problem? You need to put the Calculator’s core logic in some other place where you can access it from multiple Apps, or from multiple view controllers in a single App. If you shouldn’t put your core logic in a view controller, where should you put it?
Business Controller Objects
The answer is, a business controller object (also known as a business object or domain object). If you have read Book 2: Flying with Objective-C, you have already seen business objects at work—for that matter, you have even seen a Calculator business object.
A business object provides a neutral place to put your core logic that can be reused from any user interface, which includes view controllers. As shown in Figure 11.24, you can create a Calculator business object in which you put all of the Calculator’s core logic methods.
Figure 11.24 Implementing a Calculator business object
In this figure, the Calculator and View Controller are tightly bound together—and that’s OK—they are both user-interface objects. When buttons are tapped on the calculator at run time, action methods in the view controller are called directly.
The only code you should put in the view controller is code that has something to do with the user interface. However, when core logic needs to be performed, the view controller makes a call to an appropriate method in the Calculator business object.
With your core logic in the Calculator class, you can easily create a ScientificCalculator subclass, which inherits all the basic functionality of the Calculator and extends it by adding additional operations as shown in Figure 11.25.
Figure 11.25 You can easily extend the core logic of the Calculator when it resides in its own class.
Because you have elevated your core logic out of the user interface and into a Calculator business object, it makes it far easier to extend by creating a subclass.
Other Benefits of Using Business Objects
When you create business objects, you are creating a representation of objects in the real world. In the Calculator App, you are creating a representation of a real-world calculator. In a real estate App you can create House, Buyer, Owner, and RealEstateAgent business classes that represent real-world entities (Figure 11.26).
Figure 11.26 Business objects represent real world entities.
Creating business objects help you bridge something called the semantic gap. The semantic gap is the difference between real world entities and how you model, or describe these objects in your software applications. In many Apps, this gap is extremely wide because the developer has not created any business objects. You will find that when you narrow the semantic gap by creating business objects that represent real-world entities, your Apps are much easier to conceive, design, build, maintain, and extend.
You model real-world entity attributes by means of properties, and you model their behavior by means of methods. For example, a house has attributes such address, number of bedrooms, number of baths, price, and so on which can be described, or modeled, as properties. A house also has behavior such as “put on the market,” “take off the market,” and so on which can be modeled as methods on a business object class.
Another benefit of using business objects is it helps you avoid playing the game of “where’s the code?” When your core-logic code is raised out of the weeds of the user interface and into business objects, it’s far easier to find the code you want.
For example, all the core-logic code that has something to do with a homeowner is in the HomeOwner business object. All the code that has something to do with a real-estate agent is in the RealEstateAgent project, and all the code that has something to do with the user interface is in a view controller. When your code is segregated in this way, it makes it far easier to find the code you need.
In contrast, when your core logic code is tangled up in your user interface, it’s much harder to find the code you need. You can see a great example of this in the CalculatorDemo project.
If you haven't already downloaded the sample code from part 1 of this blog post, you can download it here:
Download Sample Code
- If it’s not already open, in Xcode, open the CalculatorDemo project again.
- In the Navigator panel on the left side of the Xcode window, select the Symbol Navigator by clicking the second button from the left in the Navigator toolbar, or by pressing Command+2.
As shown in Figure 11.27, there are only two classes in the project—AppDelegate and ViewController.
Figure 11.27 CalculatorDemo project classes
- Expand the ViewController node to view all of its members. As you can see, there are many methods in the class, some user interface related, and some containing core logic. This mix of methods can make it difficult to find the code you need.
In the next section, you will compare an improved Calculator sample project in the Symbol Navigator.
And one other thing—using business objects helps you avoid code redundancy. When you don’t use business objects, it’s easy to create duplicate code, because you often forget you have already created a piece of functionality in another view controller. When you are using business objects, it’s less likely you will create two methods on the same business object that perform the exact same function.
So, elevate your code, and use business objects.
Now let’s see how a Calculator business object can help in an improved Calculator sample App.
The Improved Calculator Sample App
To see this proper division of user interface and core logic, let’s check out another sample project for this chapter.
- In Xcode, open the CalculatorPlusDemo project located in this book’s sample code.
- Press the Run button in Xcode to run the project in the Simulator. Go ahead and test it out by performing calculations. As you can see, it works just like the CalculatorDemo App.
- When you’re done, go back to Xcode, and press the Stop button.
- In the Project Navigator, drill down into the CalculatorPlusDemo node and then expand the User Interface and Core Logic groups. As you can see in Figure 11.28, under the User Interface group are the storyboard and view-controller user-interface files. Under the Core Logic group are the Calculator business-object class files.
Figure 11.28 CalculatorPlusDemo project files
- Select the Symbol Navigator by clicking the second button from the left in the Navigator toolbar, or by pressing Command+2. Expand the Calculator and ViewController nodes to see their class members. As shown in Figure 11.29 (the Symbol Navigator is split in two to make it easier to view), there is a clear division of responsibilities between the Calculator class and the ViewController class.
Figure 11.29 CalculatorPlusDemo project classes
- Let’s take a closer look at the source code for these classes. Go back to the Project Navigator by clicking the first button on the left in the Navigator toolbar, or by typing Command+1. Select the ViewController.h header file in the Project Navigator. As we look through the code in this class, remember that a view controller is a user-interface class.
- The first thing to take note of is that the Operation enumeration that was declared in the view controller in the CalculatorDemo project is missing. This is appropriate because it has nothing to do with the user interface.
Take a look at the list of properties in the file:
@property (strong, nonatomic) Calculator
@property (weak, nonatomic) IBOutlet
@property (strong, nonatomic) IBOutletCollection(UIImageView) NSArray
Notice that the value property that was in the CalculatorDemo project’s view controller is missing. Again, this is appropriate, because it has nothing to do with the user interface—it contains the current value of the Calculator.
Take note of the new Calculator property. This property holds a reference to the Calculator business object. This allows any method in the view controller to easily access the Calculator object and call its methods.
- Look below the property declarations to see the public method declarations:
- (IBAction)highlightOperation:(UIButton *)
These are the same public action methods as found in the CalculatorDemo project. This is appropriate because these are all user-interface-specific methods that are tied to user-interface controls in the view.
- (IBAction)numberTouched:(UIButton *)
- (IBAction)operationTouched:(UIButton *)
- Go back to the Project Navigator and select the ViewController.m implementation file. At the top of the code file, check out the viewDidLoad method:
This code creates an instance of the Calculator class and stores a reference to the object in the calculator property.
// Create the calculator
self.calculator = [[Calculator alloc] init];
- Let’s contrast the message flow of the improved CalculatorPlusDemo App with the CalculatorDemo App. Figure 11.30 provides a high-level overview of what happens when a user touches a numeric button in the improved CalculatorPlusDemo App.
Figure 11.30 Calculator numeric button and code interaction—the improved model
- The user touches a numeric button.
- The numberTouched: action method in the view controller is called, which contains user-interface-specific code.
- After executing the user-interface processing, the view controller calls the Calculator object’s processNumber: method.
- The Calculator object’s processNumber: method executes the core logic for handling a new number and returns the Calculator’s current value.
- The view controller takes the value returned from the Calculator’s processNumber: method and stores it in the text property of the lblTotal label.
This is very similar to the high-level overview in Figure 11.21, except the processNumber: method has been moved from the ViewController class to the Calculator class.
- Now let’s look at a high-level overview of what happens when the user touches an operation button as shown in Figure 11.31
Figure 11.31 Calculator operation button and code interaction—the improved model
- The user touches an operation button.
- The operationTouched: action method in the view controller is called, which contains user-interface-specific code.
- After executing the user-interface processing, the view controller calls the Calculator object’s performOperation: method.
- The Calculator object’s performOperation: method executes the core-operation logic and returns the Calculator’s current value.
- The view controller takes the value returned from the Calculator’s performOperation: method and stores it in the text property of the lblTotal label.
Again, this is very similar to the high-level overview in Figure 11.22, except the processOperation method has been moved from the ViewController class to the Calculator class.
Although this simple App has just one business controller object referenced from a single view controller, more complex Apps may reference several business objects. For example, Figure 11.32 shows a view controller that references four different business controller objects.
Figure 11.32 A single view controller can reference multiple business objects.
You can add properties to the View Controller that reference the business controller objects just as the ViewController class references the Calculator object in the CalculatorPlusDemo project.
Business Objects are User Interface Agnostic
Before moving on, it’s important to note that the business object knows nothing about the user interface in which it’s being used. Within the business-object properties and methods there is no reference at all to any user-interface element. This means you can reuse this business object from any view controller in the App, from a completely different iOS App, or, for that matter, you could even use it in a Mac OSX desktop application!
Business Objects and the MVC Pattern
When it comes to the Model-View-Controller (MVC) design pattern, you have learned the View and View Controller comprise your user interface, and together, are the View in the MVC pattern.
You have also learned that your App’s core logic should be contained in business objects that represent real-world entities. These business objects are the Controller in the MVC pattern.
But what about the Model? Earlier in this chapter you learned that the Model is your App’s data and usually takes the form of business entities.
In Chapter 12: Working with Core Data, you are going to learn about an iOS technology known as Core Data that uses something called entity objects. These entity objects are part of the business-object picture—half of the picture to be precise. Entity objects contain attributes that describe real-world entities. Business controllers are the other half of the business object picture. They are used to model the behavior of a real-world entity as you saw with the Calculator and ScientificCalculator classes in Figure 11.25.
Figure 11.33 completes the picture and shows you how each of these pieces fit into the Model-View-Controller design pattern.
Figure 11.33 Each piece of the architecture fits into the MVC design pattern.
- Model – The business entity is the Model in the MVC pattern. The properties of the business entity contain information that comprises the App’s data.
- View – Both the View and the View Controller are user-interface objects and are therefore the View in MVC. All user-interface-specific code goes in the view controller.
- Controller – The business-object controller is the Controller in the MVC pattern. All core logic goes into the business-object controller.
For each business entity you create for your App, you can create an associated business controller. For example, in Figure 11.34, for every business entity, there is a corresponding business object. The CustomerEntity class has a Customer business object, the ShoppingCartEntity class has a ShoppingCart business object, and so on.
Figure 11.34 For each business entity in your App, you can create a corresponding business controller.
As already mentioned, the entity class models a real-world entity’s attributes by means of properties. The associated business-controller object models its behavior by means of methods.
Business controllers can also be used to retrieve and update entity objects. Unfortunately, Apple’s default core-data model places the business entity retrieval and update code in the view controller. As you might imagine, this is a bad design, because it ties your App’s data access to the user interface. Placing your App’s data access logic in the business controller allows you to use your business controller objects from any user interface. You will learn more about this in Chapter 12: Working with Core Data.