Skip to content

Application Architecture

nicotin edited this page Sep 16, 2020 · 18 revisions

An iOS project is composed of many files that define different aspects of the application. This guide presents high level overview of the most common components of an application and how they fit together. A more comprehensive guide by Apple can be found here.

Structure of a UIApplication

All iOS applications are instances of UIApplication. Each application has a number of events in its lifecycle (e.g. launched, went into the background, terminated). As with other classes in the UIKit framework, you control how the application will respond to these events by providing it with a delegate. A UIApplication's delegate must implement the UIApplicationDelegate protocol.

When you create a new application in Xcode, the IDE automatically sets up the code to instantiate a UIApplication when the project is run. It also generates a class that implements UIApplicationDelegate called AppDelegate and sets the application's delegate property to an instance of this class.

NB: In Swift projects this is done via the @UIApplicationMain attribute. In Object-C projects the set up is explicitly done in the main.m file.

The entry point to your application

Of the methods that you can implement in your UIApplicationDelegate, perhaps the most important is didFinishLaunchingWithOptions. This method will be called once iOS has done most of the system initialization for your app. It is commonly used as an entry point where you can implement custom logic that initializes your application and sets up the first view controller. For example, in applications that require log ins, this is a good place to check whether there is a current user and initialize different view controllers depending on the "logged in" state.

The root view controller

The UIApplicationDelegate also provides a reference to the application's main window object. Of particular importance is the ability to set the main window's root view controller. This is the first view controller that will load and present its view to the user. More on root view controllers can be found below.

The root view controller in storyboard applications

In simple storyboard applications, you can avoid working with the UIApplicationDelegate and simply set the root view controller directly in your Main.storyboard. By default Xcode will generate an initial view controller for you and set it to be the root view controller. This is indicated by the arrow that points to the view controller. Once you start adding more view controllers to your application, you can change the root view controller by dragging this arrow around. You can also select a view controller and set it to be the root view controller by using the Is Initial View Controller checkbox in the Attributes Inspector.

Programmatically setting the root view controller

In applications not using storyboards, you must programmatically set the root view controller. This is done in your UIApplicationDelegate implementation's didFinishLaunchingWithOptions method.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        let vc = UIViewController()

        let label = UILabel()
        label.text = "hello, world!"
        label.frame = vc.view.frame

        // by default a UIViewController's .view property is set to an instance of UIView
        // of course you can set this to any custom subclass of UIView
        // one way to do this is subclass UIViewController and override the loadView method
        vc.view.backgroundColor = UIColor.cyan
        vc.view.addSubview(label)

        window = UIWindow(frame: UIScreen.main.bounds)

        // This can be set to any subclass of UIViewController.  You can also use conditional
        // logic here to set up different view controllers depending on application state
        window?.rootViewController = vc
        window?.makeKeyAndVisible()

        return true
    }
    ...
}

Other hooks in the application lifecycle

The UIApplicationDelegate protocol provides many other hooks into the application's lifecycle giving you opportunities to things like respond differently depending on how your application was launched, set up and tear down temporary state depending whether your app is in the foreground, and persist data before exiting. A detailed discussion of the application lifecycle can be found here.

Model-View-Controller in iOS

Most applications you write for iOS will use a model-view-controller (MVC) architecture similar to the one described by Apple here. This is a proven design that helps to break up your application into manageable parts with clean separation of concerns. What follows is a description of how each part of MVC typically applies in iOS programming.

NB: This description of MVC is actually slightly different than the one originally described by Krasner and Pope in the 1980s, and is probably closer to what some people would call a model-view-presenter pattern. An interesting discussion of the evolution of MVC architecture over time by Martin Fowler can be found here.

Models

Model objects encapsulate a logical unit of domain specific data in your application. In a photo-sharing social network, they might include entities like "Users" and "Photos". Models may have relationships with other models. For example, a "Photo" might have been "posted by" a "User". The model layer will generally contain any domain specific logic for manipulating your data and relationships. For example if a User has a field keeping track of the number of Photos she has posted, this field will have to updated once she posts a new Photo.

Often in an iOS application, much of the core logic around manipulating models and their relationships is offloaded to a remote API (e.g. a REST API). In these cases, the models in your iOS project will generally be some Swift (or Objective-C) representation of the resources defined by the API.

The model layer is responsible for knowing how to perform CRUD operations on models. This can include having Swift (or Objective-C) bindings to right API endpoints and implementing logic to serialize/deserialize models from/into the different data formats. Mantle and RestKit are two popular libraries that help to define the model layer—specially the serialization/deserialization logic when working with REST APIs.

In the model-view-controller pattern the model is responsible for notifying related controllers that there has been a change in its state. This happens for example when the model layer has finished loading data from a remote service and informs the view controller so that it can transition out of the loading state and notify the views to display the data.

Views

Views are responsible for displaying data (literally drawing themselves on the screen) and responding to user actions. Every view in an iOS application will be a subclass of UIView

UIKit comes with a selection of predefined views that are fairly comprehensive and, used creatively, can implement many UI designs.

You can also create custom views by subclassing any UIView class. Custom views are generally composed of one or more built-in views. If this is the case, the layout for a custom view can be defined using Interface Builder in a storyboard or nib, or done programatically in the view's class.

You'll find yourself working with two kinds of views: reusable components that are fairly generic (e.g. UITableView) and specific views that meant for presenting a specific model (e.g. a custom cell class in a UITableView). In the former case you'll want to design a good interface so that your view can be reused in many situations. In the later case, it is OK for your view to be tightly coupled to your model since its only purpose is to present a visual representation of the model.

A view may be responsible for managing the instantiation and layout of its subviews. However as you traverse up the view hieararchy ultimately you'll find some parent view that was instantiated by and inserted into the view hierarchy by a view controller. View controllers are responsible for creating views and notifying views when they need to refresh themselves with the latest data from the model.

Conversely, views are responsible for responding to user actions and propagating events to view controllers so that they can handle them appropriately, for example by updating the model.

Often views will translate a low level event into a semantic event. For example a table cell view responsible for displaying a Tweet might translate the low level of the event button tap on the "star button" into the semantic event of "user favorited Tweet". The view would then pass this high level message to the view controller who might then have the model layer make an API call to complete the "favoriting".

The delegate pattern is useful for this kind of event propagation and is used widely throughout iOS frameworks.

View Controllers

The controller sits between your model and view. It coordinates the interaction between the model and the view by passing on state changes from model to the view and propagating events from the view to the model. This relationship can be summarized as follows:

                                             update view
     -- state change -->                 -- with new state -->
Model                     View Controller                     View
     <-- handle event --                 <-- interpret and ---
      by updating model                     propagate event

Your view controllers will always subclass from UIViewController. Each view controller usually manages one "screen" within your app. For instance, an email client might have an AccountsListViewController. If you clicked on an account, it would push in to an InboxViewController, which displays a list of emails. If you tapped an email, it would display the full email within an EmailDetailsViewController.

There is a notion of container view controllers that manage the multiple view controllers and their interactions. The most common built-in container view controller is the navigation controller.

Clone this wiki locally