MauMaxxa / Sync

Modern JSON synchronization to Core Data

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Hyper Sync™

Version License Platform Join the chat at https://gitter.im/hyperoslo/Sync

Sync eases your every day job of parsing a JSON response and getting it into Core Data. It uses a convention over configuration paradigm to facilitate your workflow.

  • Handles operations in safe background threads
  • Thread safe saving, we handle retrieving and storing objects in the right threads
  • Diffing of changes, updated, inserted and deleted objects (which are automatically purged for you)
  • Auto-mapping of relationships (one-to-one, one-to-many and many-to-many)
  • Smart-updates, only updates your NSManagedObjects if the server values are different (useful when using NSFetchedResultsController delegates)
  • Uniquing, Core Data does this based on objectIDs, we use your remote key (such as id) for this

Table of Contents

Interface

Swift

Sync.changes(
  changes: [AnyObject]!,
  inEntityNamed: String!,
  dataStack: DATAStack!,
  completion: ((NSError!) -> Void)!)

Objective-C

+ (void)changes:(NSArray *)changes
  inEntityNamed:(NSString *)entityName
      dataStack:(DATAStack *)dataStack
     completion:(void (^)(NSError *error))completion
  • changes: JSON response
  • entityName: Core Data’s Model Entity Name (such as User, Note, Task)
  • dataStack: Your DATAStack

Example

Model

Model

JSON

[
  {
    "id": 6,
    "name": "Shawn Merrill",
    "email": "shawn@ovium.com",
    "created_at": "2014-02-14T04:30:10+00:00",
    "updated_at": "2014-02-17T10:01:12+00:00",
    "notes": [
      {
        "id": 0,
        "text": "Shawn Merril's diary, episode 1",
        "created_at": "2014-03-11T19:11:00+00:00",
        "updated_at": "2014-04-18T22:01:00+00:00"
      }
    ]
  }
]

Sync

[Sync changes:JSON
inEntityNamed:@"User"
    dataStack:dataStack
   completion:^{
       // New objects have been inserted
       // Existing objects have been updated
       // And not found objects have been deleted
    }];

Alternatively if you only want to sync users that have been created in the last 24 hours, you could do this by using a NSPredicate.

NSDate *now = [NSDate date];
NSDate *yesterday = [now dateByAddingTimeInterval:-24*60*60];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"createdAt > %@", yesterday];

[Sync changes:JSON
inEntityNamed:@"User"
    predicate:predicate
    dataStack:dataStack
   completion:^{
       //...
    }];

More Examples

Getting Started

Installation

Sync is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Sync'

Requisites

Core Data Stack

Replace your Core Data stack with an instance of DATAStack.

self.dataStack = [[DATAStack alloc] initWithModelName:@"Demo"];

Then add this to your App Delegate so everything gets persisted when you quit the app.

- (void)applicationWillTerminate:(UIApplication *)application {
    [self.dataStack persistWithCompletion:nil];
}

Primary key

Sync requires your entities to have a primary key, this is important for diffing otherwise Sync doesn’t know how to differentiate between entries.

By default Sync uses id from the JSON and remoteID from Core Data as the primary key. You can mark any attribute as primary key by adding hyper.isPrimaryKey and the value YES.

For example in our Designer News project we have a Comment entity that uses body as the primary key.

Custom primary key

Attribute Mapping

Your attributes should match their JSON counterparts in camelCase notation instead of snake_case. For example first_name in the JSON maps to firstName in Core Data and address in the JSON maps to address in Core Data.

There are two exceptions to this rule:

  • ids should match remoteID
  • Reserved attributes should be prefixed with the entityName (type becomes userType, description becomes userDescription and so on). In the JSON they don't need to change, you can keep type and description for example. A full list of reserved attributes can be found here

If you want to map your Core Data attribute with a JSON attribute that has different naming, you can do by adding hyper.remoteKey in the user info box with the value you want to map.

Custom remote key

Attribute Types

Array/Dictionary

To map arrays or dictionaries just set attributes as Binary Data on the Core Data modeler.

screen shot 2015-04-02 at 11 10 11 pm

Retreiving mapped arrays

{
  "hobbies": [
    "football",
    "soccer",
    "code"
  ]
}
NSArray *hobbies = [NSKeyedUnarchiver unarchiveObjectWithData:managedObject.hobbies];
// ==> "football", "soccer", "code" 

Retreiving mapped dictionaries

{
  "expenses" : {
    "cake" : 12.50,
    "juice" : 0.50
  }
}
NSDictionary *expenses = [NSKeyedUnarchiver unarchiveObjectWithData:managedObject.expenses];
// ==> "cake" : 12.50, "juice" : 0.50

Dates

We went for just supporting ISO8601 out of the box because that's the most common format when parsing dates, also we have a quite performant way to parse this strings which overcomes the performance issues of using NSDateFormatter.

{
  "created_at": "2014-01-01T00:00:00+00:00",
  "updated_at": "2014-01-02",
  "number_of_attendes": 20
}
NSDate *createdAt = [managedObject valueForKey:@"createdAt"];
// ==> "2014-01-01 00:00:00 +00:00" 

NSDate *updatedAt = [managedObject valueForKey:@"updatedAt"];
// ==> "2014-01-02 00:00:00 +00:00" 

Networking

You are free to use any networking library.

Supported iOS Versions

iOS 7 or above

Components

Sync wouldn’t be possible without the help of this fully tested components:

  • DATAStack: Core Data stack and thread safe saving

  • DATAFilter: Helps you purge deleted objects, internally we use it to diff inserts, updates and deletes. Also it’s used for uniquing Core Data does this based on objectIDs, DATAFilter uses your remote keys (such as id) for this

  • NSManagedObject-HYPPropertyMapper: Maps JSON fields with their Core Data counterparts, it does most of it’s job using the paradigm “convention over configuration

FAQ

Using hyper.primaryKey in addition to hyper.remoteKey:

Well, the thing is that if you add hyper.primaryKey it would uses the normal attribute for the local primary key, but the remote primary key is the snake_case representation of it. Some people might expect that the local keeps been the same (remoteID), or that the remote keeps been the same (id).

For example if you add the flag hyper.PrimaryKey to the attribute article_body then:

  • Remote primary key: article_body
  • Local primary key: articleBody

If you want to use id for the remote primary key you also have to add the flag hyper.remoteKey and write id as the value.

How uniquing works (many-to-many, one-to-many)?:

In a one-to-many relationship IDs are unique for a parent, but not between parents. For example in this example we have a list of posts where each post has many comments. When syncing posts 2 comment entries will be created:

[
  {
    "id": 0,
    "title": "Story title 0",
    "comments": [
      {
        "id":0,
        "body":"Comment body"
      }
    ]
  },
  {
    "id": 1,
    "title": "Story title 1",
    "comments": [
      {
        "id":0,
        "body":"Comment body"
      }
    ]
  }
]
```

Meanwhile in a `many-to-many` relationship childs are unique across parents.

For example a author can have many documents and a document can have many authors. Here only one author will be created.

```json
[
  {
    "id": 0,
    "title": "Document name 0",
    "authors": [
      {
        "id":0,
        "name":"Michael Jackson"
      }
    ]
  },
  {
    "id": 1,
    "title": "Document name 1",
    "comments": [
      {
        "id":0,
        "body":"Michael Jackson"
      }
    ]
  }
]
```

#### Logging changes:

Logging changes to Core Data is quite simple, just subscribe to changes like this and print the needed elements:

```objc
[[NSNotificationCenter defaultCenter]addObserver:self
                                        selector:@selector(changeNotification:)
                                            name:NSManagedObjectContextObjectsDidChangeNotification
                                          object:self.dataStack.mainContext];
                                          
- (void)changeNotification:(NSNotification *)notification {
    NSSet *updatedObjects   = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
    NSSet *deletedObjects   = [[notification userInfo] objectForKey:NSDeletedObjectsKey];
    NSSet *insertedObjects  = [[notification userInfo] objectForKey:NSInsertedObjectsKey];
}
```

#### Crash on NSParameterAssert

This means that the local primary key was not found, Sync uses `remoteID` by default but if you have another local primary key make sure to mark it with `"hyper.isPrimaryKey" : "YES"` in your attribute's user info.

```objc
NSString *localKey = [entity sync_localKey];
NSParameterAssert(localKey);

NSString *remoteKey = [entity sync_remoteKey];
NSParameterAssert(remoteKey);
```

## Credits

[Hyper](http://hyper.no) made this. We’re a digital communications agency with a passion for good code and delightful user experiences. If you’re using this library we probably want to [hire you](https://github.com/hyperoslo/iOS-playbook/blob/master/HYPER_RECIPES.md) (we consider remote employees too, the only requirement is that you’re awesome).


## License

**Sync** is available under the MIT license. See the [LICENSE](https://github.com/hyperoslo/Sync/blob/master/LICENSE.md) file for more info.

About

Modern JSON synchronization to Core Data

License:Other


Languages

Language:Objective-C 97.1%Language:Ruby 2.9%