Is it possible to persist struct on iOS? This is a great question since most available solutions require a type of Object/NSManagedObject/NSObject. How about wrappers? But they are so boring to write… Find out how to deal with this problem!

Structs are great. They are lightweight, value-type and thread safe. Certainly, they’re a good choice for your model if you have a bunch of properties representing some kind of entity. I chose structs to represent Dribble shots and applied a UI from our designer who wanted to have a beautiful, yet simple Dribbble Client on iOS:

I started simple, with the least effort possible to present shots on screen. We did, as they say, ‘fake it till you make it’, boosting development in it’s beginning phase and not to worrying too much about implementing API. Then, a quick call to Dribbble API and et voila – the beautiful table view is filled with the latest shots!

struct Shot { let id: Int let url: String let likes: Int let comments: Int let buckets: Int let views: Int } 1 2 3 4 5 6 7 8 struct Shot { let id : Int let url : String let likes : Int let comments : Int let buckets : Int let views : Int }

My beloved designer asked me about a new feature – I’d love to see some shots, even when I’m offline. My Internet connection is often too weak to handle these lovely shots. Is it possible to pre-fetch, under the right conditions, and present them immediately at launch? Good point! This solution is efficient, yet user-friendly – the app will fetch the latest shots on startup when it’s possible, otherwise it will just reuse those that were persisted last time. Even though our app heavily relies on external API, we need to remember that a wireless connection might be unreachable at anytime – a user might want to turn on airplane mode, for example.

Okay then, we will grab the latest shots and store them for the next launch. To satisfy this requirement we need to persist our Shot struct into memory, so the first thing that came into my mind was: I will implement NSCoding protocol.

Uh-oh, this protocol requires that type to be a subclass of an NSObject. We will not be giving up easily by converting our Shot entity to class, however. We want to utilize all the sweetness that struct provides. But… Why? ? Why not just change it to class, and boom – all the stuff just works and we can proceed to implement the NSCoding protocol?

Well, we have one type that doesn’t depend on a persistence library/solution that we have in our app. Now it’s NSCoding but, later on, we might want to go with Realm; it requires that the entity is a subclass of Object – the base class of all entities.

Okay, but the problem persists(no pun intended) – how to keep the value type on disk? Why not make a persistable wrapper – PersistableShot? It will decorate the Shot struct and implement the NSCoding protocol whilst being a subclass of NSObject.

final class ShotCoding: NSObject, NSCoding { fileprivate let shot: Shot init(shot: Shot) { self.shot = shot super.init() } required init?(coder aDecoder: NSCoder) { guard let url = aDecoder.decodeObject(forKey: "url") as? String else { return nil } shot = Shot(id: aDecoder.decodeInteger(forKey: "id"), url: url, likes: aDecoder.decodeInteger(forKey: "likes"), comments: aDecoder.decodeInteger(forKey: "comments"), buckets: aDecoder.decodeInteger(forKey: "buckets"), views: aDecoder.decodeInteger(forKey: "views")) super.init() } func encode(with aCoder: NSCoder) { aCoder.encode(shot.id, forKey: "id") aCoder.encode(shot.url, forKey: "url") aCoder.encode(shot.likes, forKey: "likes") aCoder.encode(shot.comments, forKey: "comments") aCoder.encode(shot.buckets, forKey: "buckets") aCoder.encode(shot.views, forKey: "views") } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 final class ShotCoding : NSObject , NSCoding { fileprivate let shot : Shot init ( shot : Shot ) { self . shot = shot super . init ( ) } required init ? ( coder aDecoder : NSCoder ) { guard let url = aDecoder . decodeObject ( forKey : "url" ) as ? String else { return nil } shot = Shot ( id : aDecoder . decodeInteger ( forKey : "id" ) , url : url , likes : aDecoder . decodeInteger ( forKey : "likes" ) , comments : aDecoder . decodeInteger ( forKey : "comments" ) , buckets : aDecoder . decodeInteger ( forKey : "buckets" ) , views : aDecoder . decodeInteger ( forKey : "views" ) ) super . init ( ) } func encode ( with aCoder : NSCoder ) { aCoder . encode ( shot . id , forKey : "id" ) aCoder . encode ( shot . url , forKey : "url" ) aCoder . encode ( shot . likes , forKey : "likes" ) aCoder . encode ( shot . comments , forKey : "comments" ) aCoder . encode ( shot . buckets , forKey : "buckets" ) aCoder . encode ( shot . views , forKey : "views" ) } }

I could have ended here by saying that it’s possible to keep structs and being able to persist them via NSKeyedArchiver, but that would just be bizarre. What about when we want to persist comments as well? Similarly, how about other types of content, like News from The Verge? Are we going to make another wrapper and implement all the same repetitive NSCoding? We would have ended up with a fragile implementation, just to satisfy the initial assumption – to keep struct as the main type for modeling my data.

Sourcery for the rescue!

This is a perfect opportunity for using a new tool – Sourcery. What’s this about? Let me bring up the description from Github:

Sourcery scans your source code, applies your personal templates and generates Swift code for you, allowing you to use meta-programming techniques to save time and decrease potential mistakes. Have you ever: Had to write equatable/hashable? Had to write NSCoding support? Had to implement JSON serialization? Wanted to use Lenses?

You probably responded “Yes” to at least some of these questions. I was intrigued by the second-to-last question, actually. It seems that Sourcery already provides some templates, including generation of NSCoding implementations for NSObject. These templates are written in a language called Stencil. The syntax is clear and easy to get to, so even though there was a template for NSObject, I managed to create a similar one, but for wrapping up struct in one simple swoop.

{% for type in types.structs|annotated:"NSCodingWrapper" %} {% set objectForCoding %}{{ type.name|lowerFirstWord }}{% endset %} // sourcery:file:Generated/{{ type.name }}+NSCodingWrapper import Foundation final class {{ type.name }}Coding: NSObject, NSCoding { fileprivate let {{ objectForCoding }}: {{ type.name }} init({{ objectForCoding }}: {{ type.name }}) { self.{{ objectForCoding }} = {{ objectForCoding }} super.init() } required init?(coder aDecoder: NSCoder) { guard {% for variable in type.storedVariables where variable.typeName.name != "Bool" and variable.typeName.name != "Int" %}let {{ variable.name }} = aDecoder.decodeObject(forKey: "{{ variable.name }}") as? {{variable.typeName.name }}{% if forloop.last %} else { return nil }{% else %},{% endif %}{% endfor %} {{ objectForCoding }} = {{ type.name }}({% for variable in type.storedVariables %}{{ variable.name }}: {% if variable.typeName.name == "Bool" %}aDecoder.decodeBool(forKey: "{{ variable.name }}"){% elif variable.typeName.name == "Int" %}aDecoder.decodeInteger(forKey: "{{variable.name }}"){% else %}{{ variable.name }}{% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}) super.init() } func encode(with aCoder: NSCoder) { {% for variable in type.storedVariables %}aCoder.encode({{ objectForCoding }}.{{ variable.name }}, forKey: "{{ variable.name }}") {% endfor %} } } extension {{ type.name }}Coding: StructRepresentable { var `struct`: {{ type.name }} { return {{ objectForCoding }} } } extension {{ type.name }}: Encoded { var encoder: {{ type.name }}Coding { return {{ type.name }}Coding({{ objectForCoding }}: self) } } // sourcery:end {% endfor %} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 { % for type in types . structs | annotated : "NSCodingWrapper" % } { % set objectForCoding % } { { type . name | lowerFirstWord } } { % endset % } // sourcery:file:Generated/{{ type.name }}+NSCodingWrapper import Foundation final class { { type . name } } Coding : NSObject , NSCoding { fileprivate let { { objectForCoding } } : { { type . name } } init ( { { objectForCoding } } : { { type . name } } ) { self . { { objectForCoding } } = { { objectForCoding } } super . init ( ) } required init ? ( coder aDecoder : NSCoder ) { guard { % for variable in type . storedVariables where variable . typeName . name ! = "Bool" and variable . typeName . name ! = "Int" % } let { { variable . name } } = aDecoder . decodeObject ( forKey : "{{ variable.name }}" ) as ? { { variable . typeName . name } } { % if forloop . last % } else { return nil } { % else % } , { % endif % } { % endfor % } { { objectForCoding } } = { { type . name } } ( { % for variable in type . storedVariables % } { { variable . name } } : { % if variable . typeName . name == "Bool" % } aDecoder . decodeBool ( forKey : "{{ variable.name }}" ) { % elif variable . typeName . name == "Int" % } aDecoder . decodeInteger ( forKey : "{{variable.name }}" ) { % else % } { { variable . name } } { % endif % } { % if not forloop . last % } , { % endif % } { % endfor % } ) super . init ( ) } func encode ( with aCoder : NSCoder ) { { % for variable in type . storedVariables % } aCoder . encode ( { { objectForCoding } } . { { variable . name } } , forKey : "{{ variable.name }}" ) { % endfor % } } } extension { { type . name } } Coding : StructRepresentable { var ` struct ` : { { type . name } } { return { { objectForCoding } } } } extension { { type . name } } : Encoded { var encoder : { { type . name } } Coding { return { { type . name } } Coding ( { { objectForCoding } } : self ) } } // sourcery:end { % endfor % }

I won’t be explaining the syntax here, since the documentation speaks for itself. I’d like to highlight some interesting features though:

{% for type in types.structs|annotated:"NSCodingWrapper" %} 1 { % for type in types . structs | annotated : "NSCodingWrapper" % }

This for-loop goes through all structs in our project that were annotated with NSCodingWrapper. Here’s an example of it in use:

//This struct will get a wrapper! //sourcery: NSCodingWrapper struct Shot { let url: String let likes: Int let comments: Int let buckets: Int let views: Int } //...And this won't. struct Comment { let content: String } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //This struct will get a wrapper! //sourcery: NSCodingWrapper struct Shot { let url : String let likes : Int let comments : Int let buckets : Int let views : Int } //...And this won't. struct Comment { let content : String }

Next up, a separate file for each wrapper.

// sourcery:file:{{ type.name }}+NSCodingWrapper 1 // sourcery:file:{{ type.name }}+NSCodingWrapper

The result? It will generate a separate file with a wrapper for each struct. I’d like to separate wrappers in the respective file to keep the project structure clean.

Finally, Set block.

{% set objectForCoding %}{{ type.name|lowerFirstWord }}{% endset %} 1 { % set objectForCoding % } { { type . name | lowerFirstWord } } { % endset % }

I often use the struct name with lowercase as a variable in my wrapper, so we can pack it in handy variable to avoid repetitive code.

We’re getting our persisted struct out of ShotCoding wrapper by referencing the shot variable.

let shotCoding = ShotCoding(shot: Shot(url: ...)) shotCoding.shot = //do something... 1 2 let shotCoding = ShotCoding ( shot : Shot ( url : . . . ) ) shotCoding . shot = //do something...

But, if we’re having a CommentCoding wrapper, we need to remember to get it out by comment variable.

let newsCoding = NewsCoding(news: News(content: "Breaking news!")) newsCoding.news = //Uhh, how about one variable for getting out the encoded struct...? 1 2 let newsCoding = NewsCoding ( news : News ( content : "Breaking news!" ) ) newsCoding . news = //Uhh, how about one variable for getting out the encoded struct...?

It’s not a big deal, but we can do better than this. Let’s add a protocol that will allow us to extract Shot struct out of any type conforming to that:

protocol StructRepresentable { associatedtype StructType var `struct`: StructType { get } } 1 2 3 4 5 protocol StructRepresentable { associatedtype StructType var ` struct ` : StructType { get } }

Now, with any wrapper or object, we will get the struct by struct variable.

let shotCoding = ShotCoding(shot: Shot(url: ...)) shotCoding.struct = //Shot struct here! let newsCoding = NewsCoding(news: News(content: "Breaking news!")) newsCoding.struct = //News struct here! 1 2 3 4 let shotCoding = ShotCoding ( shot : Shot ( url : . . . ) ) shotCoding . struct = //Shot struct here! let newsCoding = NewsCoding ( news : News ( content : "Breaking news!" ) ) newsCoding . struct = //News struct here!

Database

Sidenote: You might not want to use CoreData or Realm in an app like this, since I don’t really mind updating locally; if I’d had to, I would have just called Dribbble API. But for the sake of an example… ?

After some time, our app will be getting bigger and bigger, so we would like to step up our game and add some handy database. The most popular ones are Realm and Core Data. How do they perform when it comes to using structs as our model types? Let’s find out!

Core Data

Here, we have the same problem we experienced when coding with NSCoding conformance – the type needs to be a NSObject subclass or, to be more precise: NSManagedObject – a root class of Core Data objects. Once again, let’s take a challenge of not using class. Here, the approach would be different – no wrappers, just smart converting struct to NSManagedObject subclass when we want to persist and, likewise, reversing this process for fetching from Core Data storage.

Before diving in, let us ask ourselves again – why do we care about structs? Long story short – one type of entity separated from a type of persistence (we can juggle between KeyedArchiver, CoreData, Realm or whatever we like), thread-safety and ensuring that the object won’t be updated from a completely different place, since we have a plain, efficient, immutable struct. That’s a great benefit to include(or, at least, consider in our future apps). This sounds great, but it means that we will be writing more boilerplate code just for CoreData handling, right? ? Fear not! If you read the NSCoding section, you now know we have a new powerful tool – code generation. Let’s do it!

First, create a new file called “Data Model” – you can find it in the Core Data section in the template window. Then, recreate the entity by adding attributes matching our initial struct. One thing to keep in mind – call the entity something simple and proper, such as, “CoreDataShot”. (sidenote: it doesn’t need to have Int64 for these attributes, for sure!)

Then, in the Utilities side screen, customize the Class options:

Next, go to Editor and choose Create NSManagedObject Subclass…:

The result should be a codegen entity that we can now use for… you guessed right, codegen in Sourcery! Frankly, this subclass could also be made via template but, for CoreData, I’d rather play safe and use Xcode generator(which isn’t considered the best tool, unfortunately ? )

{% for class in types.classes|annotated:"CoreDataStruct" %} {% set structName %}{{ class.name|replace:"CoreData",""}}{% endset %} // sourcery:file:Generated/{{ structName }}+CoreDataStruct extension {{ structName }}: CoreDataRepresentable { var managedObject: {{ class.name }} { let {{ class.name|lowerFirstWord }} = {{ class.name }}(context: ShotsProvider.container.viewContext) {% for variable in class.variables where variable.readAccess == "public" %} {{ class.name|lowerFirstWord }}.{{ variable.name }} = {{ variable.typeName.name }}({{ variable.name }}) {% endfor %} return {{ class.name|lowerFirstWord }} } } extension {{ class.name }}: StructRepresentable { var `struct`: {{ structName }} { {% for struct in types.structs where struct.name == structName %} return {{ struct.name }}({% for variable in struct.storedVariables %}{{ variable.name }}: {{ variable.typeName.name }}({{ variable.name }}){% if not forloop.last %}, {% endif %}{% endfor %}){% endfor %} } } // sourcery:end {% endfor %} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { % for class in types . classes | annotated : "CoreDataStruct" % } { % set structName % } { { class . name | replace : "CoreData" , "" } } { % endset % } // sourcery:file:Generated/{{ structName }}+CoreDataStruct extension { { structName } } : CoreDataRepresentable { var managedObject : { { class . name } } { let { { class . name | lowerFirstWord } } = { { class . name } } ( context : ShotsProvider . container . viewContext ) { % for variable in class . variables where variable . readAccess == "public" % } { { class . name | lowerFirstWord } } . { { variable . name } } = { { variable . typeName . name } } ( { { variable . name } } ) { % endfor % } return { { class . name | lowerFirstWord } } } } extension { { class . name } } : StructRepresentable { var ` struct ` : { { structName } } { { % for struct in types . structs where struct . name == structName % } return { { struct . name } } ( { % for variable in struct . storedVariables % } { { variable . name } } : { { variable . typeName . name } } ( { { variable . name } } ) { % if not forloop . last % } , { % endif % } { % endfor % } ) { % endfor % } } } // sourcery:end { % endfor % }

The template here is quite similar to the previous one – this time, however, we’re initializing NSManagedObject, setting up the instance variables according to struct ones and returning it. Thus, it would be possible, at some point, to create an object for Core Data out of struct:

let shots: [Shot] = ... let coreDataShot = shots.map { $0.managedObject } //Now we can save it via Core Data context 1 2 3 let shots : [ Shot ] = . . . let coreDataShot = shots . map { $ 0 . managedObject } //Now we can save it via Core Data context

There are some caveats in this template, though:

You need to name you Core Data entity like a struct with the “CoreData” prefix. This could probably be improved, so I highly recommend doing some quick research!

Not only do we need to use a singleton for accessing the context to insert to, but also we’re restricting ourselves with no option to inject it – but you could definitely make a method that takes a context as an argument. Yet, for the sake of clarity and simplicity, I kept it just as it is.

I defined a constraint on CoreDataShot entity – for the id field. What’s that about?

This small change allows us to make an upsert – if we already have a Shot with a matching id – and update it, or otherwise insert it. To make this possible, we also need to define a merge policy for the context responsible for inserting; the default behavior is set to fail when we want to create an entity when there’s already one with the same value on the defined constraint. To avoid this, we’re declaring mergePolicy as:

ShotsProvider.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 1 ShotsProvider . container . viewContext . mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

By the way – you will surely want to use NSFetchedResultsController in your production app as dataSource for UITableView ?

So, when the shots came from API, we can finally persist it:

private func saveShotsToCoreData(shots: [Shot]) { let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateContext.parent = ShotsProvider.container.viewContext privateContext.perform { _ = shots.map { $0.managedObject } try? ShotsProvider.container.viewContext.save() ShotsProvider.container.viewContext.performAndWait { try? ShotsProvider.container.viewContext.save() } } } 1 2 3 4 5 6 7 8 9 10 11 private func saveShotsToCoreData ( shots : [ Shot ] ) { let privateContext = NSManagedObjectContext ( concurrencyType : . privateQueueConcurrencyType ) privateContext . parent = ShotsProvider . container . viewContext privateContext . perform { _ = shots . map { $ 0 . managedObject } try ? ShotsProvider . container . viewContext . save ( ) ShotsProvider . container . viewContext . performAndWait { try ? ShotsProvider . container . viewContext . save ( ) } } }

Realm

Last stop, Realm! The great thing here, when compared to CoreData, is that we can confidently generate the subclass of Realm’s root entity class – Object. The template does almost the same thing as with CoreData, and arguably even more – it generates subclass of Realm Object and a conversion from struct when we mark struct as //sourcery: RealmStruct:

{% for type in types.structs|annotated:"RealmStruct" %} // sourcery:file:Generated/{{ type.name }}+RealmStruct import RealmSwift {% set objectForCoding %}{{ type.name|lowerFirstWord }}{% endset %} final class {{ type.name }}Object: Object { {% for variable in type.storedVariables %} dynamic var {{ variable.name }}: {{ variable.typeName }} = {% if variable.typeName.name == "String" %}""{% elif variable.typeName.name == "Int" %}0{% endif %} {% endfor %} override static func primaryKey() -> String? { return "id" } } extension {{ type.name }}: RealmRepresentable { var realmObject: {{ type.name }}Object { let {{ objectForCoding }} = {{ type.name }}Object() {% for variable in type.storedVariables %} {{ objectForCoding }}.{{ variable.name }} = {{ variable.name }} {% endfor %} return {{ objectForCoding }} } } extension {{ type.name }}Object: StructRepresentable { var `struct`: {{ type.name }} { return {{ type.name }}({% for variable in type.storedVariables %}{{ variable.name }}: {{ variable.name }}{% if not forloop.last %}, {% endif %}{% endfor %}) } } // sourcery:end {% endfor %} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { % for type in types . structs | annotated : "RealmStruct" % } // sourcery:file:Generated/{{ type.name }}+RealmStruct import RealmSwift { % set objectForCoding % } { { type . name | lowerFirstWord } } { % endset % } final class { { type . name } } Object : Object { { % for variable in type . storedVariables % } dynamic var { { variable . name } } : { { variable . typeName } } = { % if variable . typeName . name == "String" % } "" { % elif variable . typeName . name == "Int" % } 0 { % endif % } { % endfor % } override static func primaryKey ( ) - > String ? { return "id" } } extension { { type . name } } : RealmRepresentable { var realmObject : { { type . name } } Object { let { { objectForCoding } } = { { type . name } } Object ( ) { % for variable in type . storedVariables % } { { objectForCoding } } . { { variable . name } } = { { variable . name } } { % endfor % } return { { objectForCoding } } } } extension { { type . name } } Object : StructRepresentable { var ` struct ` : { { type . name } } { return { { type . name } } ( { % for variable in type . storedVariables % } { { variable . name } } : { { variable . name } } { % if not forloop . last % } , { % endif % } { % endfor % } ) } } // sourcery:end { % endfor % }

Regarding the primaryKey() method – to make an upsert, it needs to return a field that defines out the Object as unique, just like the primary key in SQL. Additionaly, the subclass has already set up properties with default values.

Saving structs to Realm? Here you go:

private func saveShotsToRealm(shots: [Shot]) { do { let realm = try Realm() try realm.write { shots.forEach { realm.add($0.realmObject, update: true) } } ...rest of the method... 1 2 3 4 5 6 7 private func saveShotsToRealm ( shots : [ Shot ] ) { do { let realm = try Realm ( ) try realm . write { shots . forEach { realm . add ( $ 0 . realmObject , update : true ) } } . . . rest of the method . . .

I marked update as true, since it will update existing Shot or create a new one.

Conclusions

You can find the final project here.

All in all, few notes for our summary:

It’s good to separate your model from persistance logic and constraints that are enforced on us

Persistance in iOS was designed for classes, not Swift structs

To make things work with structs, we need to ensure our wrappers can figure out the serialization of struct ?

…but we can codegen it! ?

Sourcery will keep up-to-sync with your templates and files in project, just define a step in the Build Phases!

Unfortunately, we are going to have a bad time with more complex structures (and relations) when other types are involved ?

Good news: with Swift 4 and Codable protocol, wrappers(kinda) will go away

Useful links and libraries: