iOS Application Architecture

Part One

CocoaheadsSKG

Dimitri James Tsiflitzis

Intro

Application architecture guides application design. Application architecture paradigms provide principles that influence design decisions and patterns that provide design solutions. It aims to make apps scalable, reliable, testable and manageable

Light View Controllers

 

Apple MVC

 

Model

App Data

Controller

Updates Model

Controls View

View

User

Presentation

Human / Non-human

Table Views and Collection Views

 

UITableViewController

  • Dedicated view controller for table views
  • Eliminate a lot of the boilerplate code that arises by using a tableview in a view controller
  • UITableView displayed full screen so you are restricted in your layout options

 

Handle Cell Configuration internally

 

override func tableView(tableView: UITableView, 
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

     let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemCell
     let item = items[indexPath.row] as! Item
     cell.nameLabel.text = myItem.name

     return cell
}

You may have come across this often time

 

Handle Cell Configuration internally

 

Bring all of these assignments you see above into the table view cell subclass

 

class ItemCell: UITableViewCell {

      func configure(item: Item) {
	      self.nameLabel.text = item.name
      }
  }

Handle Cell Configuration internally

 


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell            = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemCell
    cell.configure(items[indexPath.row] as! Item)
	   
    return cell
 }

And then we have simplified the data source right away

 

Use a Data Source Class

 

class ItemDataSource: NSObject, UITableViewDataSource {

    let items = [Item(name:"1"), Item(name:"2"), Item(name:"3"), Item(name:"4")]
 
    // MARK: - DATA SOURCE
 
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
 
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
 
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemCell
        cell.configure(items[indexPath.row] as! Item)
   
        return cell
    }
 }

Use a Data Source Class

 

class ItemListViewController: UITableViewController {

    let dataSource = ItemDataSource()
 
    // MARK: - LIFECYCLE
 
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = dataSource
    }
 }

Just assign your data source

 

Use a Data Source Class

 

 class ItemStore {

     private let items = [Item(name:"1"), Item(name:"2"), Item(name:"3"), Item(name:"4")]
 
     func allItems() -> [String] {
         return items
     }
 }

Use a separate class for you items too

 

Use a Data Source Class

 

class ItemDataSource: NSObject, UITableViewDataSource {

   let items = ItemStore()
 
   // MARK: - DATA SOURCE
 
   func numberOfSectionsInTableView(tableView: UITableView) -> Int {
       return 1
   }
 
   func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return items.allItems()
   }
 
   func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
       let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemCell
       cell.configure(items.allItems()[indexPath.row])
   
       return cell
   }
 }

Your final product

 

Container & Child View Controllers

 

Adding a container view controller in Interface Builder

View Controller

Container

Container View Controllers

 

/* add */
let viewController = self.storyBoard.instantiateViewControllerWithIdentifier("test")
self.addChildViewController(viewController)

self.view.addSubview(viewController.view)
viewController.didMoveToParentViewController(self)

/* remove */
let controller = self.childViewControllers.lastObject()
controller.view.removeFromSuperview()
controller.removeFromParentViewController()

In code

 

Communication

Delegation

 

The delegating object holds a reference to the delegate object and at the appropriate time sends a messages or calls a method on it

 

Delegate

Container

Table View

height for row

Interlude: Objective-C Categories, Extensions and Swift extensions.

 

Airport

Airport.m

Airport+Runway.m

Interlude: Objective-C Categories, Extensions and Swift extensions.

 

extension SomeClass {

    /* add new functionality here */
}

Notification Centre

 


NSNotificationCenter.defaultCenter().postNotificationName("notificationReceived", object: nil)

This is how you post an NSNotification.

 

Notification Centre

 

NSNotificationCenter.defaultCenter().addObserver(self, 
selector:#selector(notificationReceived(_:)), name: notificationReceived, object: nil)

This is how you observe an NSNotification

 

Notification Centre

 

func receiveNotification(notification : NSnotification) {

}

And of course, you’ll need to define the method that will be executed.

 

Key Value Observing (KVO)

 

Key value observing is a mechanism that allows objects, as specific instances, to be notified of changes on properties of other object

 

Second Object

Container

First Object

notifies

 

Property A

Key Value Observing (KVO)

 

private var globalContext = 0

class Participant: NSObject {
    dynamic var name = "Maria"
}

class Observer: NSObject {

    var participant = Participant()
    
    override init() {
    
        super.init()
        participant.addObserver(self, forKeyPath: "name", options: .New, context: &globalContext)
    }

    override func observeValueForKeyPath(keyPath: String?, 
                                 ofObject object: AnyObject?, 
                                          change: [String : AnyObject]?, 
                                         context: UnsafeMutablePointer<Void>) {
        
        if context == &globalContext {
            if let newValue = change?[NSKeyValueChangeNewKey] {
                print("Date changed: \(newValue)")
            }
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }

    deinit {
        participant.removeObserver(self, forKeyPath: "name", context: &globalContext)
    }
}

When to use the Notification Centre, Delegation or Key Value Observing

Receiver

Knows

Sender

Many

Recipients

Notification

Notification

Two Way

Delegate

Yes

No

No

Yes

KVO

Notification

Yes

No

Patterns

Patterns

 

/* definition */
class SwiftSingleton {
    static let sharedInstance = SwiftSingleton()

    private init() {}
}

/* usage */
SwiftSingleton.sharedInstance

Singletons in IOS

 

Patterns

 

protocol AnimationBehavior {
    func animate()
}

class ParallaxAnimationBehavior: AnimationBehavior {

    func animate() {
        println("Funky parallax animation code")
    }
}

Strategies

 

Patterns

 

class AnimatingView: UIView {

    var animationBehavior: AnimationBehavior?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        animationBehavior?.animate()
    }
}

Strategies

 

MVVM

 

  • Decouples the View from the Model
  • Moves the presentation logic out of the View Controller and into the View Model

 

MVVM

 

Model

App Data

Controller

Updates Model

Controls View

View

User

Presentation

Human / Non-human

View Model

MVVM

 

import Foundation

class Car {
    
    let make  : String
    let model : String
    let date  : NSDate
    
    init(make : String, model : String, date : NSDate) {
        
        self.make  = make
        self.model = model
        self.date  = date
    }
}

This is a simple model that might pop up in your app and it represents a Car Model

 

MVVM

 

func displayCar() {
    
    let dateFormatter        = NSDateFormatter()
    dateFormatter.dateFormat = "dd MMM yyyy"
    self.dateLabel.text      = dateFormatter.stringFromDate(self.car.date)
    
    self.brandLabel.text     = "\(self.car.make) \(self.car.model)"
}

Displaying a car in MVC

 

MVVM

 

class CarViewModel {

    let car       : Car
    let brandText : String
    let dateText  : String
    
    init(car : Car) {
        
        self.car       = car
        self.brandText = "\(self.car.make) \(self.car.model)"
        
        let dateFormatter        = NSDateFormatter()
        dateFormatter.dateFormat = "dd MMM yyyy"
        self.dateText            = dateFormatter.stringFromDate(self.car.date)
    }
}

If we have a Car view model

 

MVVM

 

func displayCar() {
    
    self.nameLabel.text      = self.viewModel.brandText;
    self.birthdateLabel.text = self.viewModel.dateText;
}

In our view controller

 

MVVM

 

func testInitialization() {
    
    let car          = Car()
    let carViewModel = CarViewModel(car: car)
    
    XCTAssertNotNil(carViewModel, "CarViewModel cannot be nil.")
    XCTAssertTrue(carViewModel.car === car, "Car should be equal to the car parameter")
}

Testing is convenient too

 

Ευχαριστούμε