Git Product home page Git Product logo

sync's Introduction

Notice: Sync was supported from it's creation back in 2014 until March 2021

Moving forward I won't be able to support this project since I'm no longer active in making iOS apps with Core Data. I'm leaving this repository as a historical reference of what happened during this time. Sync had in total 130 releases with almost 30 contributors. If you still support this project I encourage you to fork it and continue the development. I don't feel comfortable with passing this project to another developer due to the fact that I want to have some involvement in all the projects that live under my account. 7 years was a good run, thank you everyone for using this project and thank you to everyone that has contributed to it. Best of luck in your careers and in what this constantly evolving tech world has for all of us.

Initial releases: https://github.com/3lvis/Sync/releases?after=0.4.1


Sync

Sync eases your everyday job of parsing a JSON response and syncing it with Core Data. Sync is a lightweight Swift library that uses a convention-over-configuration paradigm to facilitate your workflow.

Syncing JSON to Core Data is a repetitive tasks that often demands adding a lot of boilerplate code. Mapping attributes, mapping relationships, diffing for inserts, removals and updates are often tasks that don't change between apps. Taking this in account we took the challenge to abstract this into a library. Sync uses the knowledge of your Core Data model to infer all the mapping between your JSON and Core Data, once you use it, it feels so obvious that you'll wonder why you weren't doing this before.

  • Automatic mapping of camelCase or snake_case JSON into Core Data
  • 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 from your local ones
  • Uniquing, one Core Data entry per primary key
  • NSOperation subclass, any Sync process can be queued and cancelled at any time!

Table of Contents

Basic example

Model

Model

JSON

[
  {
    "id": 6,
    "name": "Shawn Merrill",
    "email": "[email protected]",
    "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"
      }
    ]
  }
]

DataStack

DataStack is a wrapper on top of the Core Data boilerplate, it encapsulates dealing with NSPersistentStoreCoordinator and NSManageObjectContexts.

self.dataStack = DataStack(modelName: "DataModel")

You can find here more ways of initializing your DataStack.

Sync

dataStack.sync(json, inEntityNamed: "User") { error in
    // 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.

let now = NSDate()
let yesterday = now.dateByAddingTimeInterval(-24*60*60)
let predicate = NSPredicate(format:@"createdAt > %@", yesterday)

dataStack.sync(json, inEntityNamed: "User", predicate: predicate) { error in
    //..
}

Demo Project

We have a simple demo project of how to set up and use Sync to fetch data from the network and display it in a UITableView. The demo project features both Networking and Alamofire as the networking libraries.

DataStack with Storyboards

Configuring a DataStack with Storyboard is different than doing it via dependency injection here you'll find a sample project in how to achieve this setup.

https://github.com/3lvis/StoryboardDemo

Getting Started

Core Data Stack

Replace your Core Data stack with an instance of DataStack.

self.dataStack = DataStack(modelName: "Demo")

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 id (or remoteID) from Core Data as the primary key.

You can mark any attribute as primary key by adding sync.isPrimaryKey and the value true (or YES). For example, in our Designer News project we have a Comment entity that uses body as the primary key.

Custom primary key

If you add the flag sync.isPrimaryKey to the attribute contractID then:

  • Local primary key will be: contractID
  • Remote primary key will be: contract_id

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

  • Local primary key will be: articleBody
  • Remote primary key will be: id

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 some exception to this rule:

  • 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
  • Attributes with acronyms will be normalized (id, pdf, url, png, jpg, uri, json, xml). For example user_id will be mapped to userID and so on. You can find the entire list of supported acronyms here.

If you want to map your Core Data attribute with a JSON attribute that has different naming, you can do by adding sync.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

Retrieving mapped arrays

{
  "hobbies": [
    "football",
    "soccer",
    "code"
  ]
}
let hobbies = NSKeyedUnarchiver.unarchiveObjectWithData(managedObject.hobbies) as? [String]
// ==> "football", "soccer", "code"

Retrieving mapped dictionaries

{
  "expenses" : {
    "cake" : 12.50,
    "juice" : 0.50
  }
}
let expenses = NSKeyedUnarchiver.unarchiveObjectWithData(managedObject.expenses) as? [String: Double]
// ==> "cake" : 12.50, "juice" : 0.50

Dates

We went for supporting ISO8601 and unix timestamp out of the box because those are the most common formats when parsing dates, also we have a quite performant way to parse this strings which overcomes the performance issues of using NSDateFormatter.

let values = ["created_at" : "2014-01-01T00:00:00+00:00",
              "updated_at" : "2014-01-02",
              "published_at": "1441843200"
              "number_of_attendes": 20]

managedObject.fill(values)

let createdAt = managedObject.value(forKey: "createdAt")
// ==> "2014-01-01 00:00:00 +00:00"

let updatedAt = managedObject.value(forKey: "updatedAt")
// ==> "2014-01-02 00:00:00 +00:00"

let publishedAt = managedObject.value(forKey: "publishedAt")
// ==> "2015-09-10 00:00:00 +00:00"

Relationship mapping

Sync will map your relationships to their JSON counterparts. In the Example presented at the beginning of this document you can see a very basic example of relationship mapping.

One-to-many

Lets consider the following Core Data model.

One-to-many

This model has a one-to-many relationship between User and Note, so in other words a user has many notes. Here can also find an inverse relationship to user on the Note model. This is required for Sync to have more context on how your models are presented. Finally, in the Core Data model there is a cascade relationship between user and note, so when a user is deleted all the notes linked to that user are also removed (you can specify any delete rule).

So when Sync, looks into the following JSON, it will sync all the notes for that specific user, doing the necessary inverse relationship dance.

[
  {
    "id": 6,
    "name": "Shawn Merrill",
    "notes": [
      {
        "id": 0,
        "text": "Shawn Merril's diary, episode 1",
      }
    ]
  }
]

One-to-many Simplified

As you can see this procedures require the full JSON object to be included, but when working with APIs, sometimes you already have synced all the required items. Sync supports this too.

For example, in the one-to-many example, you have a user, that has many notes. If you already have synced all the notes then your JSON would only need the notes_ids, this can be an array of strings or integers. As a side-note only do this if you are 100% sure that all the required items (notes) have been synced, otherwise this relationships will get ignored and an error will be logged. Also if you want to remove all the notes from a user, just provide "notes_ids": null and Sync will do the clean up for you.

[
  {
    "id": 6,
    "name": "Shawn Merrill",
    "notes_ids": [0, 1, 2]
  }
]

One-to-one

A similar procedure is applied to one-to-one relationships. For example lets say you have the following model:

one-to-one

This model is simple, a user as a company. A compatible JSON would look like this:

[
  {
    "id": 6,
    "name": "Shawn Merrill",
    "company": {
      "id": 0,
      "text": "Facebook",
    }
  }
]

One-to-one Simplified

As you can see this procedures require the full JSON object to be included, but when working with APIs, sometimes you already have synced all the required items. Sync supports this too.

For example, in the one-to-one example, you have a user, that has one company. If you already have synced all the companies then your JSON would only need the company_id. As a sidenote only do this if you are 100% sure that all the required items (companies) have been synced, otherwise this relationships will get ignored and an error will be logged. Also if you want to remove the company from the user, just provide "company_id": null and Sync will do the clean up for you.

[
  {
    "id": 6,
    "name": "Shawn Merrill",
    "company_id": 0
  }
]

JSON Exporting

Sync provides an easy way to convert your NSManagedObject back into JSON. Just use the export() method.

let user = //...
user.set(value: "John" for: "firstName")
user.set(value: "Sid" for: "lastName")

let userValues = user.export()

That's it, that's all you have to do, the keys will be magically transformed into a snake_case convention.

{
  "first_name": "John",
  "last_name": "Sid"
}

Excluding

If you don't want to export certain attribute or relationship, you can prohibit exporting by adding sync.nonExportable in the user info of the excluded attribute or relationship.

non-exportable

Relationships

It supports exporting relationships too.

"first_name": "John",
"last_name": "Sid",
"notes": [
  {
    "id": 0,
    "text": "This is the text for the note A"
  },
  {
    "id": 1,
    "text": "This is the text for the note B"
  }
]

If you don't want relationships you can also ignore relationships:

let dictionary = user.export(using: .excludedRelationships)
"first_name": "John",
"last_name": "Sid"

Or get them as nested attributes, something that Ruby on Rails uses (accepts_nested_attributes_for), for example for a user that has many notes:

var exportOptions = ExportOptions()
exportOptions.relationshipType = .nested
let dictionary = user.export(using: exportOptions)
"first_name": "John",
"last_name": "Sid",
"notes_attributes": [
  {
    "0": {
      "id": 0,
      "text": "This is the text for the note A"
    },
    "1": {
      "id": 1,
      "text": "This is the text for the note B"
    }
  }
]

FAQ

Check our FAQ document.

Installation

CocoaPods

pod 'Sync', '~> 6'

Carthage

github "3lvis/Sync" ~> 6.0

Supported iOS, OS X, watchOS and tvOS Versions

  • iOS 8 or above
  • OS X 10.10 or above
  • watchOS 2.0 or above
  • tvOS 9.0 or above

Backers

Finding Sync helpful? Consider supporting further development and support by becoming a sponsor: 👉 https://github.com/sponsors/3lvis

License

Sync is available under the MIT license. See the LICENSE file for more info.

sync's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sync's Issues

Request - Support Storing JSON in Dictionaries/Arrays

Sometimes JSON can look like the following, which is a dictionary with dynamic keys.

"inputConfiguration":  {
     "justAHint" : [ "a",  "b" ],
   "keyboardConfiguration": "telephone",
   "someOtherConfiguration": { "this": "that", "here": "there"}
  },

Or even a dictionary with a static key, but an array of objects (no keys):

"aliasLabels" : ["Option Three A", "Option Three B"]

These are obviously difficult to map into objects. Core Data allows storing Dictionaries and Arrays as a transformable attribute type. It would be great if Sync allowed storing a part of the JSON in this manner.

One way it could work, to use my inputConfiguration example above, would be to create a corresponding Core Data attribute called inputConfiguration with a transformable type, and defined as a dictionary in its entity's class definition. Then have another user info key/value pair, for instance hyper.isCollection, which would tell Sync not to map that out into objects, but instead just store that part of the JSON in the inputConfiguration attribute as a dictionary.

It could theoretically work the same way for the array of objects with no keys in my 2nd example. Normally Sync would see that and think it's a relationship to be mapped, but perhaps when it doesn't find a relationship named aliasLabels in the Core Data model, it could then check for an attribute by that same name and if it has the hyper.isCollection (or whatever) key/value pair, just store that piece of JSON as an array.

Mapping Option

A separate (nicer, but more complex) idea I had for actually mapping this type of JSON out into objects, could go something like this:

For the array with no keys (to use my example again), you would still have your typical to-many relationship called aliasLabels to another entity. The entity should have a single attribute (string in this case). Sync could either deduce from there only being one attribute that this must be the attribute to map the value to, or a new hyper.defaultValue (or whatever) user info key/value pair could be used to indicate this is the attribute to map to.

The dictionary with dynamic keys would probably be a lot more difficult to map, since it could contain any number of objects. So it would have to be treated as a to-many relationship, and each of those key/value pairs as a separate entity. Then in those entities you would need at least 2 attributes; one to store the dynamic key, and another for the actual value. Then you would of course have to define a user info key/value pair for them, such as hyper.dynamicKey and hyper.defaultValue so Sync would know which to map into. That would work for dictionaries with dynamic keys where the objects are always the same, but unfortunately still wouldn't work with my example above, since those objects themselves can be anything. That's where being able to store the data as a dictionary/array would still be nice.

Sorry for the long post, but I really like this library and think having support for a broader type of potential JSON would make it even more useful.

Improve Core Data User Info keys

We should add more consistency in our Core Data User Info keys, taking some inspiration from mogenerator we could do something like this => https://github.com/rentzsch/mogenerator/wiki/Core-Data-User-Info-Keys#attribute-key-value-pairs

Right now we have:
// Sync.h
static NSString * const SyncCustomPrimaryKey = @"sync.primary_key";
static NSString * const SyncCustomRemoteKey = @"hyper.remote.key";

// HYPPropertyMapper.h
static NSString * const HYPPropertyMapperCustomRemoteKey = @"mapper.remote.key";
We could have:
// Sync.h
static NSString * const SyncCustomPrimaryKey = @"hyper.isPrimaryKey";
static NSString * const SyncCustomRemoteKey = @"hyper.remoteKey";

// HYPPropertyMapper.h
static NSString * const HYPPropertyMapperCustomRemoteKey = @"hyper.remoteKey";

Custom Transformations

Hey,

do you plan to support custom transformations? Say I have a color encoded in a string using the #ARGB format (e.g. #FF00FF00 for green) and i want to persist it as a UIColor in a transformable property in my managed object. Is something like this already supported?

br
denis

Sync is crashing when syncing children JSON to Object with not existing relationship

I have a Model with many Relationship. In the JSON I have a simpler model without all relationship

When I Sync numbers, it crashes because trying to set Parent unit property on Number (unit is relationship to OrganizationUnit, but I don't want to sync organisations here, just numbers)

Model Example :
screen shot 2015-03-03 at 09 12 52

JSON Example:

[{
  "id": 1,
  "number": 10.0,
  "numberType": 0,
  "children" : [
    {
      "id": 2,
      "number": 11.0,
      "numberType": 0
    },
    {
      "id": 3,
      "number": 12.0,
      "numberType": 0,
    }
  ]
}]

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-one relationship: property = "unit"; desired type = REMAOrganizationUnit; given type = REMANumber; value = <REMANumber: 0x7aeac650>

It crash here :)
screen shot 2015-03-03 at 10 11 21

it has a relationship with name unit that point to parent organization, but there is no organization in the JOSN and I don't want to process it

Improve README with NSPredicate usage

Add a section explaining how to Sync some elements while excluding others. Related to #62.

You can exclude a few elements to be synced by providing a NSPredicate.

So for example you could say "Sync Users excluding offline ones".
Here your Users that have the isOffline flag set to YES will be skipped.

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isOffline = %@", @NO];
[Sync changes:JSON
inEntityNamed:@"User"
    predicate:predicate
    dataStack:dataStack
   completion:^{
       // New objects have been inserted
       // Existing objects have been updated
       // And not found objects have been deleted
    }];

Add ability to cancel Sync operation

I would like to be able to cancel Sync operation.
When I call sync It should probably return Sync Operation the would have a cancel method.

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

Inserting Additional Entities

In our service, we can have multiple markets, but these markets are retrieved individually and separately, not as one large request. I noticed that if I retrieve an initial market and insert it using Sync, then retrieve a subsequent (different) market and insert it, it removes the first market from Core Data, and so on, even if they have different remoteIDs. This is fine if you are feeding it one huge JSON with your entire model, but would it be possible to support use cases like ours, where you may be retrieving new entities separately and needing to add them into the existing Core Data model, instead of overwriting everything?

Doesn't work with cocoapods' use_frameworks

Unfortunately, the latest version of Sync is not compatible with the use_frameworks! option of cocoa pods.
import "DATAStack.h" (in NSArray+Sync.h) needs to be changed to import <DATAStack/DATAStack.h> for this to work (which would fail without the option).

Any ideas on how to make this work? The option is needed to include Pods written in swift, so not using the option is not an option I fear :)

Many to Many Issue

I had issues with Many to Many relationships. I found the problem happens at this line:

childPredicate = [NSPredicate predicateWithFormat:@"ANY %K.%K = %@", relationshipName, destinationLocalKey, [children valueForKey:destinationRemoteKey]];

I noticed that we generate a predicate that points to the child but since it has relationshipName in it, it ends up being invalid predicate.

Here are the models diagram:
models

The predicate that that line generates is

ANY fulfillers.xid = (<some xids>)

But it does it on fulfillers Model.

Here is a JSON sample:

{
  "xid":"mstaff_F58dVBTsXznvMpCPmpQgyV",
  "user":{
    "full_name":"Ashkan Nasseri",
    "email":"[email protected]",
    "phone":"###-###-####",
    "xid":"usr_8YQhMfiLvXEg6TaD7YKyjL",
    "photo_inventory":true
  },
  "image":"a.jpg",
  "image_xs":"s.jpg",
  "fulfillers":[
    {
      "xid":"ffr_AkAHQegYkrobp5xc2ySc5D",
      "name":"New York"
    },
    {
      "xid":"ffr_n5eGjfHQRqKr4tAfL7RNi9",
      "name":"Chicago"
    }
  ],
  "current_vehicle":{
    "xid":"vhc_test",
    "name":"Test"
  }
}

I fixed the problem by removing %k. and relationshipName from the predicate generation, everything works fine for me now but it breaks testCustomPrimaryKey.

Screencast

I was thinking on making a screencast on how to make a Designer News client under 10 minutes using Sync.

  • Create Xcode project (Swift + Core Data)
  • Add Podfile (Sync + DATASource + Networking), run pod install
  • Hook up TableView with DATASource
  • Call Sync from the ViewController
  • PROFIT

What do you guys think?

Core Data Relationships

Are there any plans to have this support relationships and nested entities by recursing through the JSON?

Relationships not being synced since 1.0.10

I updated from 1.0.8 to the latest 1.0.11 and noticed relationships are no longer being sync'd; they show nil. I tried 1.0.10 with the same results, then 1.0.9 which works. Did something change between 1.0.9 and 1.0.10 that I'm not aware of that requires an update on my end? I checked the README and didn't see anything...

Custom primary keys with custom remoteKey values cause incorrect sync behavior

When an attribute of an entity has both hyper.isPrimaryKey and hyper.remoteKey defined, an incorrect value is used for fetching existing objects from the database. -[NSEntityDescription(Sync) sync_remoteKey] doesn't take the hyper.remoteKey property into account.

For example, in my use case, I have a local property "imageId" and a remote JSON property for the primary key of "ImageId". I have "ImageId" stored under hyper.remoteKey for this property. sync_remoteKey then takes the value from sync_localKey, which is the correct "ImageId" string and pushes it through hyp_remoteString which changes it to "image_id", which isn't present in the JSON. As a result, no remote IDs are located, no local objects can be fetched, and updates and deletions never happen.

Crash when mapping many-to-many

Crash when mapping many to many relationship in NSManagedObject+Sync#sync_processToManyRelationship:usingDictionary:andParent:dataStack : 116, uses remote relationship name on entity, for ex, should be likedPosts instead of liked_posts.

[question] Database not updated second time

I'm testing the sync functionality and all seems to work fine in my initial data request (viewDidLoad). I fetch the JSON from the server, pass it to Sync and my CoreData gets updated nicely.
However, when I make a change server side a few seconds later and do a refresh I can see the modified JSON coming in but Sync doesn't seem to sync the changes to the database?

Any idea what this might be?

Attribute to skip syncing

Would it be possible to add an attribute to an entity and based on the value the changes are not synced?

This is very useful in an app where you want to support offline usage. For example: If the user is makes changes to an object but the app has no internet connection, his changes are saved in core data. Now when he does a pull to refresh his changes will be overwritten. If there would be such an attribute the syncing for this object would be skipped.

Similar to this a modified attribute would be useful, if the modified attribute doesn't differ from the value in the JSON syncing should be skipped improving performance.

Possibility to save the image completely

Right now, we're saving the URL of the image, maybe someone wants to save all the image, for some user case, like doing a ProfileImage, and you don't want to network all the time.

I was thinking in a UserInfo key, hyper.saveDataImage, or something like that.

I guess the best explanation would be, process the image if the user wants to.

I need help getting started with your great Library

Hi there!

I am sorry for bothering you but I tried to get this running for two hours right now and I seem to be stuck.

I am using the following setup in CoreData
bildschirmfoto 2015-06-22 um 23 06 48

and I have an [NSDictionary] array (that I formed out of a json response).
(I don't get to choose the json response and I get the same error using the json response instead of the array so I figured that I could go on in using the array.)

[{
    id = 224f02078c4278c2;
    name = "Pascal Blunk";
    ownCollection = 0;
    owner =     {
        id = 32492cc981632817;
        name = "Pascal Blunk";
    };
}, {
    id = e393ed8b0c1368fc;
    name = pascal7;
    ownCollection = 1;
    owner =     {
        id = 739b7ba364ddd874;
        name = pascal7;
    };
    users =     (
        739b7ba364ddd874
    );
}]

Every item in that array is a specific collection. The owner property is an object of type User. Users is an array of user_ids. I used to fill in the CoreData myself but now I want to switch to your powerful code. When primitively calling Sync.changes with the array given I get the following stack trace

2015-06-22 22:54:19.615 Grocli[11780:3544854] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x7ff821c5dcb0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key id.'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010dd31c65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010d9cabb7 objc_exception_throw + 45
    2   CoreFoundation                      0x000000010dd318a9 -[NSException raise] + 9
    3   Foundation                          0x000000010d40582a -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 226
    4   Foundation                          0x000000010d35eb23 -[NSObject(NSKeyValueCoding) valueForKey:] + 251
    5   Foundation                          0x000000010d35e914 -[NSArray(NSKeyValueCoding) valueForKey:] + 437
    6   Grocli                              0x000000010cd08bf7 -[NSManagedObject(Sync) sync_processToManyRelationship:usingDictionary:andParent:dataStack:] + 1095
    7   Grocli                              0x000000010cd083de -[NSManagedObject(Sync) sync_processRelationshipsUsingDictionary:andParent:dataStack:error:] + 606
    8   Grocli                              0x000000010cd0ac2c __78+[Sync changes:inEntityNamed:predicate:parent:inContext:dataStack:completion:]_block_invoke + 172
    9   Grocli                              0x000000010ccfdcc6 +[DATAFilter changes:inEntityNamed:localKey:remoteKey:context:predicate:inserted:updated:] + 1942
    10  Grocli                              0x000000010cd0a934 +[Sync changes:inEntityNamed:predicate:parent:inContext:dataStack:completion:] + 1748
    11  Grocli                              0x000000010cd09a99 __61+[Sync changes:inEntityNamed:predicate:dataStack:completion:]_block_invoke + 121
    12  Grocli                              0x000000010cd014ba __43-[DATAStack performInNewBackgroundContext:]_block_invoke + 58
    13  CoreData                            0x000000010cfd7a79 developerSubmittedBlockToNSManagedObjectContextPerform + 201
    14  libdispatch.dylib                   0x0000000110a7c614 _dispatch_client_callout + 8
    15  libdispatch.dylib                   0x0000000110a636a7 _dispatch_queue_drain + 2176
    16  libdispatch.dylib                   0x0000000110a62cc0 _dispatch_queue_invoke + 235
    17  libdispatch.dylib                   0x0000000110a663b9 _dispatch_root_queue_drain + 1359
    18  libdispatch.dylib                   0x0000000110a67b17 _dispatch_worker_thread3 + 111
    19  libsystem_pthread.dylib             0x0000000110de9637 _pthread_wqthread + 729
    20  libsystem_pthread.dylib             0x0000000110de740d start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException

I am not quite sure, but I would guess, that the code fails when it comes to handling the array 'users' since there is no 'id' field given. So I basically have two questions: a) Do you think that this is the problem and b) Can I somehow tell your code that it should handle all items in the users array as IDs?

Foreign key relationship

Hi guys,

Really like this pod so far, nice work! I was wondering if foreign key relationship is supported. Say you have this json:

{ "project":
    {   "id": 12345,
        "name": "My Project",
        "category": 1,
        "teamMemberIDs": [1, 2, 3, 4]
    }
 }

where the category would be a relation to a category and the teamMemberIDs a relation to multiple users.

MagicalRecord integration

Hi. Did you try to combine your project with MagicalRecord?

I see, that it closely integrated with DATAStack and it seems, like quite bit different way to manage with Core Data, than Magical Record implementation.

MR use simple method:

    + (void)setupCoreDataStack;

for initialisation of DataStack)

Is there any way to integrate Sync with MagicalRecord?

Rethink API

I've been wondering what if we changed the API to something more simple.

New

// [Sync changes:notes entityName:@"Notes" dataStack:self.dataStack completion:nil];

+ (void)changes:(NSArray *)changes
     entityName:(NSString *)entityName
      dataStack:(DATAStack *)dataStack
     completion:(void (^)(NSError *error))completion;

Old

// [Sync processChanges:notes usingEntityName:@"Notes" dataStack:self.dataStack completion:nil];

+ (void)processChanges:(NSArray *)changes
       usingEntityName:(NSString *)entityName
             dataStack:(DATAStack *)dataStack
            completion:(void (^)(NSError *error))completion;

Terminating app due to uncaught exception 'NSRangeException'

"Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray'"

I got this error on my first launch on every new test device
schermata 2015-10-06 alle 23 52 41

this is my coredata model, and i've set id as hyper.isPrimaryKey.

Can you please help me?
Thank you

Integrate networking

I was thinking today, all the classes of the networking part are pretty similar if you use Apple's networking, so I was thinking in adding a method that let you just type Sync from that URL into this entityName, so the user, in the same ViewController for example, can just type:

[Sync changesFromURL:@"Whatever"
                                       inEntityNamed:@"Story"
                                           dataStack:dataStack
                                          completion:^(NSError *error) {
                                          }];

Then we would do all the requests and stuff! :)

JSON Mapping for child objects

My goal is to map JSON which looks like this..

[
  {
    "id": "1",
    "a": {
      "b": {
        "name": "hello"
      }
    }
  }
]

.. to an NSManagedObject that looks like this:

@property (nonatomic, retain) NSString * remoteID;
@property (nonatomic, retain) NSString * name;

.. So that I can sync it using your library (which is great by the way).

There's nothing useful at the 'a' and 'b' levels, so I don't really want to create relationships for them.

I tried putting:

hyper.remoteKey = a.b.name

.. into the xcdatamodel file, but no luck.

Any ideas? I suppose I could pre-process the NSDictionary before I pass it into Sync?

Improve documentation or include a FAQ

  • Document using hyper.primaryKey in addition to hyper.remoteKey.
  • Document how uniquing works (many-to-many, one-to-many)
  • Document why a parent relationship is needed for recursive entities
  • Make F.A.Q.

Sync + Predicates

Sync provides a predicate method to filter which methods would be synced for example providing a predicate for startTime > now only should sync elements that start in the future and ignore all the other elements that don't apply to this predicate.

1.0.2 causes crash

I had Sync working with version 1.0.1, then updated to 1.0.2 today and now my app crashes with the below. I reverted back to 1.0.1, and it works again.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSDictionary initWithObjects:forKeys:]: count of objects (4) differs from count of keys (0)'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000103907c65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000105472bb7 objc_exception_throw + 45
    2   CoreFoundation                      0x0000000103850210 -[NSDate isEqual:] + 0
    3   CoreFoundation                      0x00000001038628e4 +[NSDictionary dictionaryWithObjects:forKeys:] + 52
    4   DATAFilter                          0x000000010337033e +[DATAFilter changes:inEntityNamed:localKey:remoteKey:context:predicate:inserted:updated:] + 702
    5   Sync                                0x00000001033a29cc +[Sync changes:inEntityNamed:predicate:parent:inContext:dataStack:completion:] + 1484
    6   Sync                                0x00000001033a3ef2 -[NSManagedObject(SyncPrivate) sync_processToManyRelationship:usingDictionary:andParent:dataStack:] + 1986
    7   Sync                                0x00000001033a335e -[NSManagedObject(SyncPrivate) sync_processRelationshipsUsingDictionary:andParent:dataStack:error:] + 558
    8   Sync                                0x00000001033a45b5 -[NSManagedObject(SyncPrivate) sync_processToOneRelationship:usingDictionary:andParent:dataStack:error:] + 1365
    9   Sync                                0x00000001033a35fd -[NSManagedObject(SyncPrivate) sync_processRelationshipsUsingDictionary:andParent:dataStack:error:] + 1229
    10  Sync                                0x00000001033a2cbc __78+[Sync changes:inEntityNamed:predicate:parent:inContext:dataStack:completion:]_block_invoke + 172
    11  DATAFilter                          0x0000000103370816 +[DATAFilter changes:inEntityNamed:localKey:remoteKey:context:predicate:inserted:updated:] + 1942
    12  Sync                                0x00000001033a29cc +[Sync changes:inEntityNamed:predicate:parent:inContext:dataStack:completion:] + 1484
    13  Sync                                0x00000001033a1c69 __61+[Sync changes:inEntityNamed:predicate:dataStack:completion:]_block_invoke + 121
    14  DATAStack                           0x000000010337d76a __43-[DATAStack performInNewBackgroundContext:]_block_invoke + 58
    15  CoreData                            0x0000000103469a79 developerSubmittedBlockToNSManagedObjectContextPerform + 201
    16  libdispatch.dylib                   0x0000000106939614 _dispatch_client_callout + 8
    17  libdispatch.dylib                   0x00000001069206a7 _dispatch_queue_drain + 2176
    18  libdispatch.dylib                   0x000000010691fcc0 _dispatch_queue_invoke + 235
    19  libdispatch.dylib                   0x00000001069233b9 _dispatch_root_queue_drain + 1359
    20  libdispatch.dylib                   0x0000000106924b17 _dispatch_worker_thread3 + 111
    21  libsystem_pthread.dylib             0x0000000106ca6637 _pthread_wqthread + 729
    22  libsystem_pthread.dylib             0x0000000106ca440d start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException

Sync with NSFetchedResultsController

Hi there! It's me again :)
I definitely want to write your documentation, but right now my code starts falling apart. So here is the problem:

I have the following CoreData Model set up:
bildschirmfoto 2015-07-17 um 19 40 29

Now I want to add a picture locally by doing the following (I am using old code that I wrote when I did not use Sync yet):

So I basically get an image, that I want to add to the Photo Items

func gotimage(image:UIImage){
  let col = GrocliCollectionManager.sharedInstance.getDefaultWriteCollection()

  let uid = createUniqueFileUIDForCollection(col)
  //create subdir if needed
  GrocliFileHandler.sharedInstance.createFolderForCollection(col)
  let filename = (col.remoteID + "/" + uid + ".jpg")
  let ok = self.writeImage(image, to: filename)


  if(ok){
      let photoDic = [
          "id" : uid,
          "collectionId" : col.remoteID,
          "fileurl"   : filename,
          "isBought"  : false
      ]
      var photo = GrocliCollectionManager.sharedInstance.createNonExistingPhoto(photoDic)


      if(photo != nil){
          photo?.isDownloaded = true
          photo?.isUploaded = false
          col.photos = col.photos.setByAddingObject(photo!)
          photo?.collectionId = col.remoteID
          //println(GrocliFileHandler.sharedInstance.createFileName(photo!))
          var error:NSError?
          //photo?.managedObjectContext?.save(&error)
          (UIApplication.sharedApplication().delegate as! AppDelegate).dataStack?.persistWithCompletion({ () -> Void in
              if(GrocliLoginHandler.sharedInstance.loggedIn as Bool){
                  GrocliAPIHandler.sharedInstance.uploadPicture(photo!)
              }
          })
          if(error != nil){
              GrocliErrorHandler.handleCoreDataSaveError(error!)
          }

      }
  }
}

The createNonExistingPhoto(dict:NSdictionary) function basically just take a JSON array and creates a CoreDAta PhotoItem on the AppDelegate's Datastack's mainContext.

You see, that I tried different version to save and tested those. They work. Now the uploading takes place in the APIHandler's uploadPicture. When the uploading was successful, I get a server response which contains the new ID for the photo which I need to set. I do this in the completionHandler

if let success: AnyObject = jsonDictionary!["success"]{
                      if(success as! Bool){
                          println(jsonDictionary)
                          if let payload = jsonDictionary!["payload"] as? NSDictionary{
                              println(payload)
                              let file = payload["file"] as! NSDictionary
                              photo.remoteID = file["id"] as! String
                              photo.isUploaded = true

                          }
                          var err:NSError?
                          //photo.managedObjectContext?.save(&err)

                          (UIApplication.sharedApplication().delegate as! AppDelegate).dataStack!.persistWithCompletion({ () -> Void in
                              if(err != nil){
                                  GrocliErrorHandler.handleCoreDataSaveError(err!)
                              }else{
                                  //self.getCollections()
                              }
                          })


                      }

                  }

For example I got a file Dictionary that looked likes this
file = { fileurl = "dd432a4135acb80e/D4CE12234E9A4AFEB5FD.jpg"; id = 1a309871d7618851; isBought = 0; };
Again this does work. I can successfully save this photo. Now I use a NSFetchedResultsController on the Photos that I set up like this:

func photoItemFetchRequest()->NSFetchRequest{

    var request = NSFetchRequest(entityName: "GrocliPhotoItem")

    request.predicate = NSPredicate(format: "deletedOnServer == NO && collection != nil", argumentArray: ["isDownloaded","deletedOnServer", "collection"])
    // TODO Gekaufte nicht anzeigen
    var sortDesc = NSSortDescriptor(key: "collection.remoteID", ascending: true)
    var ownColDesc = NSSortDescriptor(key: "collection.ownCollection", ascending: false)
    //var boughtSortDes = NSSortDescriptor(key: "isBought", ascending: true)
    request.sortDescriptors = [ownColDesc,sortDesc]
    return request
}

func getFetchedResultsController() -> NSFetchedResultsController {
    //TODO cache löschen wegtun
    NSFetchedResultsController.deleteCacheWithName("collectionCache")
    var fetchedResultsController = NSFetchedResultsController(fetchRequest: photoItemFetchRequest(), managedObjectContext: self.appDelegate!.dataStack!.mainContext, sectionNameKeyPath: "collection.remoteID", cacheName: "collectionCache")
    return fetchedResultsController
}

Like I said: everything seems to be working fine. If I now pull all images from the server I get the following json Response
[{ collectionId = dd432a4135acb80e; fileurl = "dd432a4135acb80e/D4CE12234E9A4AFEB5FD.jpg"; id = 1a309871d7618851; isBought = 0; }, { collectionId = dd432a4135acb80e; fileurl = "dd432a4135acb80e/E2A376033B294FC5A005.jpg"; id = 7b298fbb38ba3a65; isBought = 0; }, { collectionId = dd432a4135acb80e; fileurl = "dd432a4135acb80e/CB26B84FC46241CEA84C.jpg"; id = ae6c424bd4665b9c; isBought = 0; }, { collectionId = dd432a4135acb80e; fileurl = "dd432a4135acb80e/D3113522D516495A9F5F.jpg"; id = 719b416f0cc3fa70; isBought = 0; }]
The ID and the filePath of the first item actually exist in the database since I save the file Dictionary (see above). I use sync in the following way

let firstItem = photoArray.first
    if (firstItem != nil && firstItem!["id"] != nil) {
        let id = firstItem!["collectionId"] as? String // find out which collection to update
        if(id != nil){

            if let collection = self.getCollection(id: id!){
                println(photoArray)
                Sync.changes(photoArray, inEntityNamed: "GrocliPhotoItem", parent: collection, dataStack: self.appDelegate!.dataStack!, completion: { (error) -> Void in
                    if(error == nil){
                        GrocliFileDownloader.sharedInstance.downloadAllFilesForCollection(collection)
                    }else{
                        GrocliErrorHandler.handleFilesSyncError(error)
                    }
                })
            }
        }
    }

This seems to work, too.
Nonetheless the fetchedResultsController fails instantly afterwards with
erminating app due to uncaught exception 'NSRangeException', reason: '*** -[_PFArray objectAtIndex:]: index (3) beyond bounds (3)'

Is it possible, that Sync first updates and adds items and deletes items in a second save afterwards? Then the fetchedResultsController would call the controllerDidChangeContent function right after the first save and it would report more items that it really has when it saves all the deletes right afterwards. When it is then asked for the items that don't exist anymore, it crashes. Does that make sense? The other question then is: Why is the picture not updated? I suggest, that it does not delete/update properly, because if it is the very first item, my fetchedResultsController contains the same item two times. Thanks in advance! I hope everything is going well.

Best regards,

Pascal

Improve errors for missing primary keys

When a primary key is missing Sync just crashes with a localKey parameter not found message.

Ideally the error should say "Local primary key 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.

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

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

[Question] Is it possible to only add/update records but not deleting unpresented?

Assuming I'm building a Twitter client. I only want to fetch new tweets and save them. Old tweets not presented during a syncing should remain the same. I know I can use predicate to exclude all existing tweets to prevent them from deleted. But is there an option to not delete unpresented tweets?
If this is not a feature Sync would target, is there any alternative library to do so?

Thanks!

Need help for Data Model

Hello,

I need some help for creating the Data Model based on this JSON :

{
"posts": [
    {
      "id": "",
      "title": "",
      "author": {
        "id": "",
        "name": ""
      },
      "related": {
        "id": "",
        "name": ""
      },
      "original_post": {
              "id": "",
              "title": "",
              "author": {
                "id": "",
                "name": ""
              },
              "related": { ... },
              "original_post": { ... }
       }
    }]
}

Thanks a lot !

No primary key causes crash

I noticed while attempting to integrate Sync into a separate project, that if I don't have either a remoteID property (and corresponding "id" property in the JSON) or hyper.isPrimaryKey specified for a property on that entity, that the app crashes with an EXC_BAD_ACCESS on line 148 of Sync.m. It appears that the issue is that both remoteID and localKey are nil. I then added an "id" property to every object in my JSON and a matching remoteID property to each entity in my model, and it works. Is this intentional? There are cases where a primary key isn't always desired for every entity.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.