Sharing Model Code

Korey Hinton Blog

Shared data vs independent data access

Have you ever passed the same object pointer around to various view controllers and had them all operate on the same data object? In this post I will discuss the pros and cons of using a shared model object pointer. I will also discuss some of the more popular ways to distribute (or share) a data model.

In iOS development, each View Controller needs a way to get to its data model (or bind the model to its view). Either the View Controllers can depend on the data being shared with them or they can each access it independently themselves. Sharing a model object pointer between multiple View Controllers can be advantageous when it looks like all View Controllers will need access to the same data. Giving all View Controllers the same model is the best way to be flexible to a changing model. If you need to add a new property at a certain level of the data structure then a shared model will make it easy for view controllers to access that new property automatically. The alternate approach would be to worry about 1 screen-full of data at a time and let the View Controllers retrieve the data independently. This allows for a looser coupling between View Controllers but also causes less flexibility to change because new data model changes could require interface changes to request the new changes and each View Controller that needs that data now has to make that new request. You can also share the same object pointer with independent access as well and get the benefit of loose coupling and being flexible to change.

Shared Data Model

class FirstViewController : UIViewController {
    var sharedData : Data!
}
class SecondViewController : UIViewController {
    var sharedData : Data!
}

var first = FirstViewController()
var second = SecondViewController()
first.sharedData = Data()
second.sharedData = first.sharedData

Independent data access

class FirstViewController : UIViewController {

    var data : Data!

    func viewDidLoad() {
	super.viewDidLoad()

	data = DataAccess.requestData()
    }
}

class SecondViewController : UIViewController {

    var data : LightData!

    func viewDidLoad() {
	super.viewDidLoad()

	data = DataAccess.requestLightData()
    }
}

Shared Independent Data Model Access

class Data {
    var property1 : String!
}
let sharedData = Data()

class FirstViewController : UIViewController {

    func viewDidLoad() {
	super.viewDidLoad()

	sharedData.property1 // ...
    }
}

class SecondViewController : UIViewController {

    func viewDidLoad() {
	super.viewDidLoad()

	sharedData.property1 //...
    }
}

You'll notice in the shared data model the View Controller itself is not supposed to set its own data model. We'll look at different techniques in assigning the data in the shared data model. Notice that the independent data access example each View Controller gets to the data itself and only requests the data it needs. In the case of SecondViewController it only needs a light-weight version of the data so it doesn't request all of it. This does not lend itself well to flexible data model changes. Use this method when it makes perfect sense to have differing versions of the data model. It has to make 100% sense that SecondViewController will never need access to the full data. What if we add a third view controller that needs all of the LightData but only 1 property found on the full data object? So do you add a Medium-Light data object, do you add an extra property on the LightData object, or do you give the third view controller access to the full data? These difficult questions aren't an issue with a shared data model. With a shared data model the complexity comes in knowing which View Controller made what change since they are all operating on the exact same data but when it comes to changing the model its a piece of cake to access new properties.

Many times you drill in and out of views and at the outer level you only need a lighter version of the data but as requirements change having different model versions can make things difficult. Sharing the model will save you work later on when requirements change. By giving the View Controllers all the data they might want allows you to be flexible to change. You can't possibly know exactly how to restrict the data at a particular level with 100% certainty. If you have a books app and you have a Person data object composed like this: Person -> Book(s) -> Bookmark(s) -> Note(s). If the user is writing a note for a new bookmark it might seem like you don't need the Person object at such a fine-grained level. But what if you later add a total notes count on the Person object or if you need to display the Person's photo on the Note screen. With Core Data you can use inverse relationships to easily walk back up the hierarchy to the Person object. If you aren't using Core Data, you could give the Person object to all the View Controllers. You have to be careful doing this though because where you are avoiding certain complexities you are also now having multiple View Controllers operate on all of the data. Only share everything if at the root level down it makes sense to share all the data. A lot times it does make sense in apps. The Notes screen could need to know about its associated bookmark, book, and person. If you decide to have one model shared throughout your app the observer pattern (KVO) will definitely help manage the complexity. You can also share just some of the data like maybe the Book object gets shared with the Book, Bookmark, and Notes view controllers because you know they'll never need the Person's data.

I'm going to go over some of the techniques I know about with sharing model code.

Brute force View Controller iteration

I was surprised when I saw Apple's sample code doing this. I would have never thought to do this. The following code is found in Apple's Fit App sample code in the AAPLAppDelegate file. Notice that all View Controllers are iterated over and each is given a pointer to the same data store.

- (void)setUpHealthStoreForTabBarControllers {
    UITabBarController *tabBarController = (UITabBarController *)[self.window rootViewController];

    for (UINavigationController *navigationController in tabBarController.viewControllers) {
	id viewController = navigationController.topViewController;

	if ([viewController respondsToSelector:@selector(setHealthStore:)]) {
	    [viewController setHealthStore:self.healthStore];
	}
    }
}

This does not always work since in a lot of apps not all View Controllers are loaded at App Launch but in the case of UITabBarController this worked out great.

Singleton

The way I normally would of handled what Apple was trying to accomplish in the example above would be to have the single data store be a singleton. One of the problems with a shared pointer is knowing that it is a shared pointer in the first place. A singleton object does not disguise the fact that it is being changed in different places. I also think a Singleton could be more flexible to change because any new View Controller class can easily get access to it whereas the alternative would be to be forced into a adding a property onto the View Controller and then making sure it gets passed the data.

import HealthKit

/* Global Singleton  */
let sharedHealthStore = HKHealthStore()

class ViewController : UIViewController {

    func viewDidLoad() {
	super.viewDidLoad()

	var query = HKSampleQuery(...)
	sharedHealthStore.executeQuery(...)
    }

}

Now all View Controllers have access to the single object sharedHealthStore.

View Controller Inheritance

This almost sounds laughable, but let's consider it anyway. The idea here is that View Controllers have the same model given to them by inheritance. The one thing they have in common is the healthStore object. Since naming the superclass HealthViewController doesn't make sense if we are making a Fit app, so we'll call the superclass FitViewController instead. So the question becomes how do we ensure they all share the same exact model object. Well the superclass would be responsible for getting that 1 single object. This would remove complexity in the subclasses. There are pros and cons to this approach. The pro is that subclasses get to naturally inherit the data model without any work on their part. The con is very similar to the pro: they have to inherit from it. Also the idea of an "IS A" relationship is likely being violated. Where you get data at a convenience you pay the price of being limited and restricted by what you inherit.

private let _healthStore = HKHealthStore()
class FitViewController : UIViewController {
    let healthStore = _healthStore
}
class HomeScreenViewController : FitViewController {

    func viewDidLoad() {
	super.viewDidLoad()

	let type = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)

	healthStore.requestAuthorizationToShareTypes(NSSet(array: [type]), readTypes: NSSet(array: [type]), completion: { (success: Bool, error: NSError?) -> Void in
	    // completion code
	})

    }

}

Data Source

This is a common pattern seen over and over again in controls that display data. The View Controller file defines the delegate protocol and asks its data source for the data it needs. If multiple View Controllers want similar data then they can share the same data source model object. Obviously we'd need to use some of the other techniques mentioned in this post to assign the dataSource property. If the data required is almost exactly the same, the protocol definition can go in the data object file instead of having many of them and for data that is more specific to one View Controller it can define its own protocol that inherits from the original one. Here is an example where 2 view controllers use the same data source object instance.

protocol HealthInfoDataSource {
    func mostRecentlyWalkedDistances() -> [HKQuantitySample]
    func mostRecentSteps() -> [HKQuantitySample]
}

let sharedHealthDataSource : HealthInfoDataSource  = HealthInfoDataSource()

class HealthInfo : HealthInfoDataSource {

    let healthStore = HKHealthStore()
    var steps : [HKQuantitySample]!
    var walked : [HKQuantitySample]!

    func updateResults() {
	let query = HKSampleQuery(..)
	healthStore.executeQuery(... {
	    steps = ...
	    walked = ...
	    ...})
    }

    init() {
	updateResults()
    }

    func mostRecentlyWalkedDistances() -> [HKQuantitySample] {
	return steps
    }

    func mostRecentSteps() -> [HKQuantitySample] {
	return walked
    }

}
class HomeScreenTableViewController : UITableViewController {

    override func viewDidLoad() {
	super.viewDidLoad()

	println(sharedHealthDataSource.mostRecentSteps()[0].quantity.doubleValueForUnit(HKUnit(fromString: "count")))
    }
}
class AboutViewController : UIViewController {


    override func viewDidLoad() {
	super.viewDidLoad()


	println(sharedHealthDataSource.mostRecentlyWalkedDistances()[0].quantity.doubleValueForUnit(HKUnit(fromString: "mi")))
    }
}

View Controller coupling

There's also times where a particular View Controller needs some data that is stored at the parent View Controller level. If there is already a delegate relationship in place you could add additional methods to that delegate to query it for the data. You are now coupling the delegate to also be a data source as well now. I've done this as an oversight on my part in the past and I think there are better ways by using its own proper data source. If the data source is meant to be the parent View Controller that's fine just make sure it is architected that way for good reasons.

Pass along data using prepareForSegue or init

This is the way I've used the most. Usually when you need to add an additional View Controller in the app, the View Controller it gets segued from has the data it needs. So in prepareForSegue you pass it the data model. If you are not using storyboard segues and are doing it programmatically you can give it to the View Controller on initialization. Here is a prepareForSegue of the DataSource example and sharing the same data object pointer.

protocol HealthInfoDataSource {
    func mostRecentlyWalkedDistances() -> [HKQuantitySample]
    func mostRecentSteps() -> [HKQuantitySample]
}

class HealthInfo : HealthInfoDataSource {

    let healthStore = HKHealthStore()
    var steps : [HKQuantitySample]!
    var walked : [HKQuantitySample]!

    func updateResults() {
	let query = HKSampleQuery(..)
	healthStore.executeQuery(... {
	    steps = ...
	    walked = ...
	    ...})
    }

    init() {
	updateResults()
    }

    func mostRecentlyWalkedDistances() -> [HKQuantitySample] {
	return steps
    }

    func mostRecentSteps() -> [HKQuantitySample] {
	return walked
    }

}
class HomeScreenTableViewController : UITableViewController {

    lazy var dataSource : HealthInfoDataSource! = HealthInfo()

    override func viewDidLoad() {
	super.viewDidLoad()

	println(dataSource.mostRecentSteps()[0].quantity.doubleValueForUnit(HKUnit(fromString: "count")))
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

	var about = segue.destinationViewController as AboutViewController
	about.dataSource = dataSource
    }
}
class AboutViewController : UIViewController {
    var dataSource : HealthInfoDataSource!

    override func viewDidLoad() {
	super.viewDidLoad()

	println(dataSource.mostRecentlyWalkedDistances()[0].quantity.doubleValueForUnit(HKUnit(fromString: "mi")))
    }
}

Core Data

While Core Data is not without its drawbacks, it is by far my favorite way to share the data model. Like I mentioned earlier in the post the inverse relationships saves a lot of code complexity and Core Data also cushions you from having to do any database queries yourself. It is perfect at filling the role of Model in MVC.

Mapping from NSManaged Objects to Data objects

One way to architect your data model would be to have a service layer that abstracts away the NSManaged objects and instead provides you with objects whose properties get mapped from the NSManaged entity object. While I don't prefer this myself I think there can be valid use cases for wrapping Core Data's API and your NSManaged objects. To me personally this is adding a layer of indirection and the Core Data objects come with a lot of capability built-in already. Why re-invent the wheel?

NSManaged Objects as the Model

My favorite way to use Core Data is to let the NSManaged objects be the actual data model the View Controllers can read from and write to directly. Core Data plays well with a shared data model especially since you typically use the same context to retrieve and save data. Let the View Controllers that need the data stored from the same entity have a pointer to the same NSManaged object. You can also safely give the object at the level that makes the most sense to a particular View Controller and since the entities are interconnected with inverse relationships you can safely get to other data objects directly from properties that map to inverse relationships. In the case of the books app where the data model looks like Person -> Book(s) -> Bookmark(s) -> Note(s), the Notes View Controller can have a pointer to the Notes object while the Bookmark View Controller will point to the object that is inversely pointing to the Notes managed object. At either layer you can get entirely up or down the hierarchy.

class BookmarkViewController : UIViewController {

    @IBOutlet var personLabel: UILabel!

    var bookmark: Bookmark!

    override func viewDidLoad() {
	super.viewDidLoad()

	personLabel.text = bookmark.book.person.name
    }

}

Obviously the part that is missing in the example above is setting the bookmark object. You can use one of the techniques outlined in the sections above to set the bookmark property (ie: prepareForSegue, view controller iteration, inheritance, fetching from singleton, etc)

Fetched Results Controller

Another benefit you get from using Core Data is the fetched results controller which greatly simplifies providing a data source for UITableView. Usually table views are displayed everywhere throughout most apps. So be sure to take advantage of this feature.

class TableViewController : UITableViewController {

    var fetchedResultsController: NSFetchedResultsController!

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
	return fetchedResultsController.sections!.count
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
	let sections = fetchedResultsController.sections as [NSFetchedResultsSectionInfo]
	let sectionInfo = sections[section]

	return sectionInfo.numberOfObjects
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
	let data = fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
	var cell = tableView.dequeueReusableCellWithIdentifier("CellID") as UITableViewCell
	cell.textLabel!.text = data.valueForKey("name")
    }
}

The code above is not a full implementation. It is just to show you how nicely data can translate into cells in a table view. To learn more about NSFetchedResultsController check out a good tutorial on TutsPlus

KVO

It'll be interesting to see if Apple adds in any built-in observers for Swift. While KVO can sort of be a hairy beast it is also very powerful. It can greatly simplify the logic of observing data model changes and updating the display to resemble the data model on every change. Right now in Swift an object can be observable if it inherits from NSObject. While this is a little less than ideal because it makes you inherit from the NSObject and makes for annoying autocompletion, it currently still makes sense to have a data object inherit from NSObject if you think it might need to be observable. If you are using Core Data then your NSManaged objects do inherit from NSObject and can be observed. Since Swift gets to start fresh I wonder if they will move more into an MVVM design pattern with 2 way binding. It looks like there is currently a way to do MVVM 2 way binding via ReactiveCocoa.

NSNotification

It would feel wrong to talk about shared data and not to discuss NSNotification. NSNotification is a great way to broadcast important changes via notifications. I find these to be useful when 2 or more view controllers display similar data on the screen and that data is dynamic (changes over time). Maybe the user just started a download and there are several views in your app that need to display a progress indicator during the download. Another scenario could be that the user updates their profile pic and in an app like Facebook where your photo is displayed everyplace you comment or could comment a lot of View Controllers might need to receive the update. Each object (typicall a view controller) that responds to notifications is responsible to register/unregister for notifications and they recognized the notifications by their name (which name is usually accessed from a global String constant). Additionally a notification has an object named 'object' that can store additional context or model data.

Summary

As with every design decision there are always pros and cons and you want to find the design that fits right for the problem at hand. A shared model gives an additional benefit of being flexible to change. Let's say you add a new property to the data model and some or all of the View Controllers need to read or write to that property. There is no interface changes that need to happen to the View Controllers, just to the model. Since it is shared they all can access it without doing anything. Just tack on a new property to the shared model object and you can immediately access it everywhere else. Pretty cool. This is great when your changing requirements cause you to iterate on the model. But at the same time it can be hard to track subtle bugs because multiple view controllers are operating on the same pointer and the scope of where the bug can lie becomes a more global scope including multiple view controllers.

Author: Korey Hinton

Created: 2015-01-19 Mon 13:30

Emacs 24.4.1 (Org mode 8.2.10)

Validate