So far, we have covered a considerable amount of ground intended to provide a sound foundation of knowledge on which to begin building iOS 16-based apps. Before plunging into more complex apps, however, you must have a basic understanding of some key methodologies associated with the overall architecture of iOS apps.
These methodologies, also called design patterns, clearly define how your apps should be designed and implemented in terms of code structure. The patterns we will explore in this chapter are Model View Controller (MVC), Subclassing, Delegation, and Target-Action.
It is also helpful to understand how iOS is structured in terms of operating system layers.
While these concepts can initially seem a little confusing if you are new to iOS development, much of this will become apparent once we start working on some examples in subsequent chapters.
An Overview of the iOS 16 Operating System Architecture
iOS consists of several software layers, each providing programming frameworks for developing apps that run on top of the underlying hardware.
These operating system layers can be diagrammatically presented as illustrated in Figure 15-1:
Some diagrams designed to graphically depict the iOS software stack show an additional box positioned above the Cocoa Touch layer to indicate the apps running on the device. In the above diagram, we have yet to do so since this would suggest that the only interface available to the app is Cocoa Touch. In practice, an app can directly call down to any of the layers of the stack to perform tasks on the physical device.
However, each operating system layer provides an increasing level of abstraction away from the complexity of working with the hardware. Therefore, as an iOS developer, you should always look for solutions to your programming goals in the frameworks located in the higher-level iOS layers before writing code that reaches down to the lower-level layers. In general, the higher level the layer you program to, the less effort and fewer lines of code you will have to write to achieve your objective. And as any veteran programmer will tell you, the less code you have to write, the less opportunity you have to introduce bugs.
Model View Controller (MVC)
In the days before object-oriented programming (and even for a time after object-oriented programming became popular), there was a tendency to develop apps where the code for the user interface was tied tightly to the code containing the app logic and data handling. This coupling made app code challenging to maintain and locked the app to a single user interface. If, for example, an app written for Microsoft Windows needed to be migrated to macOS, all the code written specifically for the Windows UI toolkits had to be ripped out from amongst the data and logic code and replaced with the macOS equivalent. If the app then needed to be turned into a web-based solution, the process would have to be repeated. Attempts to achieve this feat were usually prohibitively expensive and ultimately ended up with the apps being completely rewritten each time a new platform needed to be targeted.
The MVC design pattern separates an app’s logic and data handling code from the presentation code. In this concept, the Model encapsulates the data for the app, and the View presents and manages the user interface. The Controller provides the basic logic for the app and acts as the go-between, providing instructions to the Model based on user interactions with the View and updating the View to reflect responses from the Model. The value of this approach is that the Model knows absolutely nothing about the app’s presentation. It just knows how to store and handle data and perform specific tasks when called upon by the Controller. Similarly, the View knows nothing about the data and logic model of the app.
Within the context of an object-oriented programming environment such as the iOS SDK and Swift, the Model, View, and Controller components are objects. It is also worth pointing out that apps are not restricted to a single model, view, and controller. An app can consist of multiple view objects, controllers, and model objects.
A view controller object interacts with a Model through the methods and properties exposed by that model object. This is no different from how one object interacts with another in any object-oriented programming environment.
However, things get a little more complicated regarding the view controller’s interactions with the view. In practice, this is achieved using the Target-Action pattern and Outlets and Actions.
The Target-Action pattern, IBOutlets, and IBActions
When you create an iOS app, you will typically design the user interface (the view) using the Interface Builder tool and write the view controller and model code in Swift using the Xcode code editor. The previous section looked briefly at how the view controller interacts with the model. In this section, we will look at how the view created in Interface Builder and our view controller code interact with each other.
When a user interacts with objects in the view, for example, by touching and releasing a button control, an event is triggered (in this case, the event is called a Touch Up Inside event). The purpose of the Target-Action pattern is to allow you to specify what happens when such events are triggered. In other words, this is how you connect the objects in the interface you designed in the Interface Builder tool to the back-end Swift code you have written in the Xcode environment. Specifically, this allows you to define which method of which controller object gets called when a user interacts in a certain way with a view object.
The process of wiring up a view object to call a specific method on a view controller object is achieved using an Action. An action is a method defined within a view controller object designed to be called when an event is 106
The iOS 16 App and Development Architecture triggered in a view object. This allows us to connect a view object created within Interface Builder to the code we have written in the view controller class. This is one of the ways that we bridge the separation between the View and the Controller in our MVC design pattern. As we will see in “Creating an Interactive iOS 16 App”, action methods are declared using the IBAction keyword.
The opposite of an Action is the Outlet. As previously described, an Action allows a view object to call a method on a controller object. On the other hand, an Outlet allows a view controller object method to access the properties of a view object directly. A view controller might, for example, need to set the text on a UILabel object. To do so, an Outlet must first have been defined using the IBOutlet keyword. In programming terms, an IBOutlet is simply an instance variable that references the view object to which access is required.
Subclassing is an essential feature of any object-oriented programming environment, and the iOS SDK is no exception to this rule. Subclassing allows us to create a new class by deriving from an existing class and extending the functionality. In so doing, we get all the functionality of the parent class combined with the ability to extend the new class with additional methods and properties.
Subclassing is typically used where a pre-existing class does most, but not all, of what you need. By subclassing, we get all that existing functionality without duplicating it, allowing us to simply add on the functionality that was missing.
We will see an example of subclassing in the context of iOS development when we start to work with view controllers. The UIKit Framework contains a class called the UIViewController. This is a generic view controller from which we will create a subclass to add our own methods and properties.
Delegation allows an object to pass the responsibility for performing one or more tasks onto another object. This allows the behavior of an object to be modified without having to go through the process of subclassing it.
A prime example of delegation can be seen in the case of the UIApplication class. The UIApplication class, of which every iOS app must have one (and only one) instance, is responsible for the control and operation of the app within the iOS environment. Much of what the UIApplication object does happens in the background. There are, however, instances where it allows us to include our functionality into the mix. UIApplication allows us to do this by delegating some methods to us. For example, UIApplication delegates the didFinishLaunchingWithOptions method to us so that we can write code to perform specific tasks when the app first loads (for example, taking the user back to the point they were at when they last exited). If you still have a copy of the Hello World project created earlier in this book, you will see the template for this method in the AppDelegate.swift file.
In this chapter, we have provided an overview of several design patterns and discussed the importance of these patterns in structuring iOS apps. While these patterns may seem unclear to some, the relevance and implementation of such concepts will become more apparent as we progress through the examples in subsequent chapters of this book.