Whew, that’s a lot of boilerplate for something so simple! And, even then, there
are some problems that this example doesn’t address:
There’s no way to update a GHIssue with new data from the server.
There’s no way to turn a GHIssueback into JSON.
GHIssueState shouldn’t be encoded as-is. If the enum changes in the future,
existing archives might break.
If the interface of GHIssue changes down the road, existing archives might
break.
Why Not Use Core Data?
Core Data solves certain problems very well. If you need to execute complex
queries across your data, handle a huge object graph with lots of relationships,
or support undo and redo, Core Data is an excellent fit.
It does, however, come with a couple of pain points:
There’s still a lot of boilerplate. Managed objects reduce some of the
boilerplate seen above, but Core Data has plenty of its own. Correctly
setting up a Core Data stack (with a persistent store and persistent store
coordinator) and executing fetches can take many lines of code.
It’s hard to get right. Even experienced developers can make mistakes
when using Core Data, and the framework is not forgiving.
If you’re just trying to access some JSON objects, Core Data can be a lot of
work for little gain.
Nonetheless, if you’re using or want to use Core Data in your app already,
Mantle can still be a convenient translation layer between the API and your
managed model objects.
MTLModel
Enter
MTLModel.
This is what GHIssue looks like inheriting from MTLModel:
Notably absent from this version are implementations of <NSCoding>,
<NSCopying>, -isEqual:, and -hash. By inspecting the @property
declarations you have in your subclass, MTLModel can provide default
implementations for all these methods.
The problems with the original example all happen to be fixed as well:
There’s no way to update a GHIssue with new data from the server.
MTLModel has an extensible -mergeValuesForKeysFromModel: method, which makes
it easy to specify how new model data should be integrated.
There’s no way to turn a GHIssueback into JSON.
This is where reversible transformers really come in handy. +[MTLJSONAdapter JSONDictionaryFromModel:error:] can transform any model object conforming to
<MTLJSONSerializing> back into a JSON dictionary. +[MTLJSONAdapter JSONArrayFromModels:error:] is the same but turns an array of model objects into an JSON array of dictionaries.
If the interface of GHIssue changes down the road, existing archives might break.
MTLModel automatically saves the version of the model object that was used for
archival. When unarchiving, -decodeValueForKey:withCoder:modelVersion: will
be invoked if overridden, giving you a convenient hook to upgrade old data.
MTLJSONSerializing
In order to serialize your model objects from or into JSON, you need to
implement <MTLJSONSerializing> in your MTLModel subclass. This allows you to
use MTLJSONAdapter to convert your model objects from JSON and back:
In this example, the XYUser class declares four properties that Mantle
handles in different ways:
name is mapped to a key of the same name in the JSON representation.
createdAt is converted to its snake case equivalent.
meUser is not serialized into JSON.
helper is initialized exactly once after JSON deserialization.
Use -[NSDictionary mtl_dictionaryByAddingEntriesFromDictionary:] if your
model’s superclass also implements MTLJSONSerializing to merge their mappings.
If you’d like to map all properties of a Model class to themselves, you can use
the +[NSDictionary mtl_identityPropertyMapWithModel:] helper method.
When deserializing JSON using
+[MTLJSONAdapter modelOfClass:fromJSONDictionary:error:], JSON keys that don’t
correspond to a property name or have an explicit mapping are ignored:
key is the key that applies to your model object; not the original JSON key. Keep this in mind if you transform the key names using +JSONKeyPathsByPropertyKey.
For added convenience, if you implement +<key>JSONTransformer,
MTLJSONAdapter will use the result of that method instead. For example, dates
that are commonly represented as strings in JSON can be transformed to NSDates
like so:
If the transformer is reversible, it will also be used when serializing the
object into JSON.
+classForParsingJSONDictionary:
If you are implementing a class cluster, implement this optional method to
determine which subclass of your base class should be used when deserializing an
object from JSON.
Mantle doesn’t automatically persist your objects for you. However, MTLModel
does conform to <NSCoding>, so model objects can be archived to disk using
NSKeyedArchiver.
If you need something more powerful, or want to avoid keeping your whole model
in memory at once, Core Data may be a better choice.
System Requirements
Mantle supports the following platform deployment targets:
macOS 10.10+
iOS 9.0+
tvOS 9.0+
watchOS 2.0+
Importing Mantle
Manually
To add Mantle to your application:
Add the Mantle repository as a submodule of your application’s repository.
Run git submodule update --init --recursive from within the Mantle folder.
Drag and drop Mantle.xcodeproj into your application’s Xcode project.
On the “General” tab of your application target, add Mantle.framework to the “Embedded Binaries”.
If you’re instead developing Mantle on its own, use the Mantle.xcworkspace file.
Mantle
Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch application.
The Typical Model Object
What’s wrong with the way model objects are usually written in Objective-C?
Let’s use the GitHub API for demonstration. How would one typically represent a GitHub issue in Objective-C?
Whew, that’s a lot of boilerplate for something so simple! And, even then, there are some problems that this example doesn’t address:
GHIssue
with new data from the server.GHIssue
back into JSON.GHIssueState
shouldn’t be encoded as-is. If the enum changes in the future, existing archives might break.GHIssue
changes down the road, existing archives might break.Why Not Use Core Data?
Core Data solves certain problems very well. If you need to execute complex queries across your data, handle a huge object graph with lots of relationships, or support undo and redo, Core Data is an excellent fit.
It does, however, come with a couple of pain points:
If you’re just trying to access some JSON objects, Core Data can be a lot of work for little gain.
Nonetheless, if you’re using or want to use Core Data in your app already, Mantle can still be a convenient translation layer between the API and your managed model objects.
MTLModel
Enter MTLModel. This is what
GHIssue
looks like inheriting fromMTLModel
:Notably absent from this version are implementations of
<NSCoding>
,<NSCopying>
,-isEqual:
, and-hash
. By inspecting the@property
declarations you have in your subclass,MTLModel
can provide default implementations for all these methods.The problems with the original example all happen to be fixed as well:
MTLModel
has an extensible-mergeValuesForKeysFromModel:
method, which makes it easy to specify how new model data should be integrated.This is where reversible transformers really come in handy.
+[MTLJSONAdapter JSONDictionaryFromModel:error:]
can transform any model object conforming to<MTLJSONSerializing>
back into a JSON dictionary.+[MTLJSONAdapter JSONArrayFromModels:error:]
is the same but turns an array of model objects into an JSON array of dictionaries.MTLModel
automatically saves the version of the model object that was used for archival. When unarchiving,-decodeValueForKey:withCoder:modelVersion:
will be invoked if overridden, giving you a convenient hook to upgrade old data.MTLJSONSerializing
In order to serialize your model objects from or into JSON, you need to implement
<MTLJSONSerializing>
in yourMTLModel
subclass. This allows you to useMTLJSONAdapter
to convert your model objects from JSON and back:+JSONKeyPathsByPropertyKey
The dictionary returned by this method specifies how your model object’s properties map to the keys in the JSON representation, for example:
In this example, the
XYUser
class declares four properties that Mantle handles in different ways:name
is mapped to a key of the same name in the JSON representation.createdAt
is converted to its snake case equivalent.meUser
is not serialized into JSON.helper
is initialized exactly once after JSON deserialization.Use
-[NSDictionary mtl_dictionaryByAddingEntriesFromDictionary:]
if your model’s superclass also implementsMTLJSONSerializing
to merge their mappings.If you’d like to map all properties of a Model class to themselves, you can use the
+[NSDictionary mtl_identityPropertyMapWithModel:]
helper method.When deserializing JSON using
+[MTLJSONAdapter modelOfClass:fromJSONDictionary:error:]
, JSON keys that don’t correspond to a property name or have an explicit mapping are ignored:Here, the
plan
would be ignored since it neither matches a property name ofXYUser
nor is it otherwise mapped in+JSONKeyPathsByPropertyKey
.+JSONTransformerForKey:
Implement this optional method to convert a property from a different type when deserializing from JSON.
key
is the key that applies to your model object; not the original JSON key. Keep this in mind if you transform the key names using+JSONKeyPathsByPropertyKey
.For added convenience, if you implement
+<key>JSONTransformer
,MTLJSONAdapter
will use the result of that method instead. For example, dates that are commonly represented as strings in JSON can be transformed toNSDate
s like so:If the transformer is reversible, it will also be used when serializing the object into JSON.
+classForParsingJSONDictionary:
If you are implementing a class cluster, implement this optional method to determine which subclass of your base class should be used when deserializing an object from JSON.
MTLJSONAdapter
will then pick the class based on the JSON dictionary you pass in:Persistence
Mantle doesn’t automatically persist your objects for you. However,
MTLModel
does conform to<NSCoding>
, so model objects can be archived to disk usingNSKeyedArchiver
.If you need something more powerful, or want to avoid keeping your whole model in memory at once, Core Data may be a better choice.
System Requirements
Mantle supports the following platform deployment targets:
Importing Mantle
Manually
To add Mantle to your application:
git submodule update --init --recursive
from within the Mantle folder.Mantle.xcodeproj
into your application’s Xcode project.Mantle.framework
to the “Embedded Binaries”.If you’re instead developing Mantle on its own, use the
Mantle.xcworkspace
file.Carthage
Simply add Mantle to your
Cartfile
:CocoaPods
Add Mantle to your
Podfile
under the build target they want it used in:Then run a
pod install
within Terminal or the CocoaPods app.Swift Package Manager
If you are writing an application, add Mantle to your project dependencies directly within Xcode.
If you are writing a package that requires Mantle as dependency, add it to the
dependencies
list in itsPackage.swift
manifest, for example:License
Mantle is released under the MIT license. See LICENSE.md.
More Info
Have a question? Please open an issue!