When you first start making an app, your choices about the model layer will impact you the most in the long run. It’ll pay dividends to think about these choices up front, instead of being left with the detritus of accidental decisions.

One of these decisions is how to cache your data: whether you’ll use Core Data or something simpler.

Another big decision is whether each unique model is represented by one instance or many. This is, in some ways, the crux of the difference between object-oriented and functional/immutable styles. More simply put, it’s the class way versus the struct way. Let’s examine the differences.

With the object-oriented style, you have living, breathing models. You can send messages to the model, and it can respond, make decisions, perform network requests, and generally act as a first-class citizen in your app.

Making this work requires a design pattern called the Identity Map, which is just a big dictionary that maps each object instance to its identifier. When fetching a model from a store (whether it’s a network store or a store persisted on the device), each instance is checked against the Identity Map. Objective-C’s flexible initialization makes this really easy.

- (instancetype)initWithObjectID:(id)objectID {  
	id existingObject = [[SKModel identityMap] objectForKey:objectID];  
	if (existingObject) {  
		self = existingObject  
		return existingObject;  
	}
	
	self = [super init];  
	if (!self) return nil;
	
	self.objectID = objectID;  
	[[SKModel identityMap] setObject:self forKey:objectID];
	
	return self;  
}  

Core Data does this for you. If you fetch the same object twice (on the same object context), you will get the same instance back. From the Core Data Documentation:

“Core Data ensures that—in a given managed object context—an entry in a persistent store is associated with only one managed object. The technique is known as uniquing. Without uniquing, you might end up with a context maintaining more than one object to represent a given record.”

Given that your stores will return the one instance for each object ID, that model can change out from under any controller-type objects that are holding on to it. Therefore, each controller needs to observe changes on its models and re-render its view to reflect those changes.

There are two cases in which this approach works really well. The first case is when there are many views on screen, some of which point to the same models. Making a change in one view should also be reflected in the other views representing the same object. It also is great with data persisted on-disk. Data on-disk changes frequently and nonatomically. For a todo app, the user might change the due date, which would save the model to disk, then the priority, which would save the model again. Using the same model object makes our program simpler.

The second approach is to use many instances for each individual model. For this approach, each time you fetch from your store (again, either network or persisted), you create a fresh struct (or struct-like object) and use that. When modifying, either ensure isolation of each object by fetching it anew in each place that you’ll need it, or by using copy-on-write to create a new instance for each modification.

This approach shines on single-screen platforms, like iOS, where the user is generally looking at one thing at a time. In cases like this, you can lazily refresh data when it comes back on screen, rather than refreshing it greedily. It also shines in systems where the “source of truth” is on a server. Any mutating REST call is an atomic change that will return a response that is fully-formed and fully-validated by the server application. It’s also great for immutable data, like tweets. When things can’t be edited by the user, it’s much safer to use a system that prefers less mutation, like structs.

While using actual Swift structs grants some guarantees about how the thing will be used, it comes with some cost as well. Drew Crawford writes about the “Structs Philosophy™”.

The insight here is that doing anything of value involves calling at least one mutating function and as soon as you do that, your function must be mutating, and everything that calls you must be mutating, and it’s mutating all the way up, except for the silly leaf nodes like CGRect or your bag of Ints.

Like Drew, I’m not sure I can advise making your whole model layer out of purely Swift structs. For one, any kind of mutation is costly, especially for deeply nested heirarchies of data. Second, structs can’t really represent members like images, data, colors, and URLs as value types yet, even though they are often components of models and clearly are values. Using those types requires bridging to Objective-C, which loses a lot of guarantees of immutability and isolation. Lastly, it requires making your models somewhat “dumb”. While you can attach functions to structs in Swift, they seem to be more for manipulating the data rather than doing any work, like making an API request that regards the model.

The choice between many instances and one is in your hands. Don’t make the decision lightly, however. The path you choose will affect the bedrock of your app, and it will be hard to change later.