Latest 2.0.1
Homepage https://github.com/levieggert/CacheStore
License MIT
Platforms ios 10.0
Dependencies ObjectMapper
Authors

Cache is a simple and flexible library for mapping JSON objects to CoreData. It is simple because there isn’t a lot of code and it is flexible because it gives you control over CoreData object persistence.

It also makes mapping NSManagedObject’s very easy because it uses ObjectMapper. All you have to do is conform your NSManagedObject classes to CacheableEntity and you are ready to start mapping.

Note that I will use the term entity which is short for NSManagedObject.

Requirements

Cocoapods

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '<Your Target Name>' do
    pod 'CacheStore', '2.0.1'
end

Documentation

Cache.swift – Is a class that wraps NSPersistentContainer. You use this class to initialize the NSPersistentContainer stack. Cache contains helper methods for fetching, inserting, and deleting entities as well as methods for saving the viewContext and logging entity counts for debugging purposes. https://developer.apple.com/documentation/coredata/nspersistentcontainer

CacheableEntity.swift – Is a protocol extension that your NSManagedObject classes will conform to. It acts as the glue between ObjectMapper and CacheWriter.

CacheWriter.swift – Is a static class that contains logic for writing JSON objects to CoreData. Objects are mapped to CoreData on a background context provided by NSPersistentContainer. This class will handle mapping objects that already exist as well as creating new objects and mapping them to CoreData.

Record.swift – This is a simple struct that is returned from CacheWriter once objects have been saved to CoreData. It will contain an array of NSManagedObjects as well as String IDs of entities that have been mapped to CoreData. Because NSManagedObjects cannot be passed from a background context to the view context, IDs are made available to fetch entities.

Getting Started

Initializing the cache

The first thing you are going to want to do is create your data model file in Xcode. Once that is created, it’s time to initialize the Cache.

I like to initialize it in AppDelegate by making it read-only and setting it as a lazy variable. I then provide a class level shared so cache can be fetched via AppDelegate.shared.cache.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    private(set) lazy var cache: Cache = Cache(modelName: "Model")

    static var shared: AppDelegate {
        return UIApplication.shared.delegate as! AppDelegate
    }

    ...

Setting up the model

In this example we will setup a model with 2 entities. ClubEntity and PersonEntity. So go to the Model.xcdatamodel and select Add Entity. Once the entity is added, go to the data model inspector and set the Name and Class to the name of the entity. I also set Codegen to Manual/None because I like managing NSManagedObject classes myself.

Check the Example Xcode Project to see how I setup the Model.xcdatamodel for these 2 entities. In the data model inspector I set Entity Name to ClubEntity, Class Name to ClubEntity, Class Module to Current Product Module, and Codegen to Manual/None.

Here’s how the ClubEntity and PersonEntity classes are setup.

import Foundation
import CoreData
import ObjectMapper

class ClubEntity: NSManagedObject, CacheableEntity
{
    @NSManaged var id: String?
    @NSManaged var name: String?

    @NSManaged var persons: [NSSet]?

    // MARK: CacheableEntity protocol

    static let EntityName: String = "ClubEntity"

    func mapExistingWithID(map: Map) -> String?
    {
        self.id <- map["id"]

        return self.id
    }

    func mapFromJSON(map: Map, object: [String : Any], context: NSManagedObjectContext)
    {
        self.id <- map["id"]
        self.name <- map["name"]

        //persons
        self.setToMany(jsonObject: object["persons"], parent: self, childAttribute: "persons", entityName: PersonEntity.EntityName, deleteOldFromCoreData: false, context: context)
    }

    func mapToJSON() -> [String : Any]?
    {
        return nil
    }
}
import Foundation
import CoreData
import ObjectMapper

class PersonEntity: NSManagedObject, CacheableEntity
{
    @NSManaged var age: NSNumber?
    @NSManaged var id: String?
    @NSManaged var name: String?

    @NSManaged var club: ClubEntity?

    // MARK: CacheableEntity protocol

    static let EntityName: String = "PersonEntity"

    func mapExistingWithID(map: Map) -> String?
    {
        self.id <- map["id"]

        return self.id
    }

    func mapFromJSON(map: Map, object: [String : Any], context: NSManagedObjectContext)
    {
        self.age <- map["age"]
        self.id <- map["id"]
        self.name <- map["name"]
    }

    func mapToJSON() -> [String : Any]?
    {
        return nil
    }
}

When mapping relationships there are functions for mapping a To One Relationship and a To Many Relationship.

self.setToMany(jsonObject: object["persons"], parent: self, childAttribute: "persons", entityName: PersonEntity.EntityName, deleteOldFromCoreData: true, context: context)

self.setToOne(jsonObject: object["person"], parent: self, childAttribute: "person", entityName: PersonEntity.EntityName, deleteOldFromCoreData: true, context: context)

The mapExistingWithID function is used by CacheWriter to check if an entity already exists in CoreData. If an entity with the id already exists, then the object is simply updated. If no object exists, then a new object is created in CoreData.

func mapExistingWithID(map: Map) -> String?
{
    // This object will be updated if it already exists
    self.id <- map["id"]
    return self.id

    // A new object is always created in CoreData if nil is returned.
    return nil
}

func mapFromJSON(map: Map, object: [String : Any], context: NSManagedObjectContext)
{
    // Always map an ID here if you can.  If an id is null, CacheWriter will set it to a UUID which is then made available in the returned Record.
    self.id <- map["id"]
}

Mapping JSON objects to CoreData

Mapping JSON objects to CoreData is all done on a background context and can be done by calling CacheWriter.write. The completion block is called on the main queue and will provide a Record with NSManagedObjects and IDs of those objects. Note that you cannot access managed objects here because they were passed from a background context. Instead you can use the IDs to fetch objects or use NSFetchedResultsController. https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller

CacheWriter.write will check if the json is a single object or an array of objects and perform mapping accordingly.

CacheWriter.write(cache: cache, json: json, entityName: PersonEntity.EntityName, deleteExisting: false, complete: { (record: Record) in

    //Complete is called on the main queue
})

Gotchas

  1. Don’t keep long lived references to NSManagedObjects. For example, declaring a reference at the ViewController level. Instead, keep a reference of the ID attribute or another attribute. You can pass this attribute reference around and use it to fetch the entity when you need access to its data again. I’ve seen cases where long lived NSManagedObject references have caused issues with the NSManagedObject being updated (mapped) with new data as well as them turning into faults.

  2. In your NSManagedObject classes, don’t include get / set functions which contain an attribute name. For example, say you have a persons attribute. Don’t add a getPersons function. In past Xcode versions I have seen this cause compiler errors that were very difficult to debug.

Feedback

Feel free to reach out to me if you have any questions, feedback, or ways to improve Cache.

References

CoreData Relationships and Delete Rules: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/HowManagedObjectsarerelated.html

Latest podspec

{
    "name": "CacheStore",
    "platforms": {
        "ios": "10.0"
    },
    "version": "2.0.1",
    "license": "MIT",
    "homepage": "https://github.com/levieggert/CacheStore",
    "authors": {
        "levieggert": "[email protected]"
    },
    "summary": "A simple UIView animation framework.",
    "description": "Cache is a simple wrapper for NSPersistentContainer and provides functionality for mapping JSON objects to CoreData on a background context.",
    "source": {
        "git": "https://github.com/levieggert/CacheStore.git",
        "tag": "2.0.1"
    },
    "source_files": "Source/*.swift",
    "dependencies": {
        "ObjectMapper": [
            "3.1.0"
        ]
    },
    "pushed_with_swift_version": "4.0"
}

Pin It on Pinterest

Share This