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.
Note: This is a modernized fork of 3lvis/Sync, updated for Swift 6 with full concurrency support. The original project was maintained from 2014 to 2021.
- Automatic mapping of
camelCaseorsnake_caseJSON into Core Data - Thread-safe saving with Swift Concurrency (
async/await) - Diffing of changes: inserted, updated, and deleted objects (automatically purged)
- Auto-mapping of relationships (one-to-one, one-to-many, and many-to-many)
- Smart updates: only updates
NSManagedObjects if server values differ from local ones - Uniquing: one Core Data entry per primary key
- CRUD convenience methods for fetch, insert, update, and delete
- Swift 6.0+
- iOS 18.0+ / macOS 15.0+
- Xcode 16.0+
Add Sync to your project via SPM. In Xcode, go to File > Add Package Dependencies and enter:
https://github.com/3lvis/Sync
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/3lvis/Sync", from: "7.0.0")
]Replace your Core Data stack with an instance of DataStack:
let dataStack = DataStack(modelName: "MyModel")try await dataStack.sync(json, inEntityNamed: "User")
// New objects inserted, existing objects updated, missing objects deletedWith a predicate filter:
let yesterday = Date.now.addingTimeInterval(-24 * 60 * 60)
let predicate = NSPredicate(format: "createdAt > %@", yesterday as NSDate)
try await dataStack.sync(json, inEntityNamed: "User", predicate: predicate)With a parent relationship:
try await dataStack.sync(noteChanges, inEntityNamed: "Note", parent: user)// Fetch
let user: User? = try dataStack.fetch(userId, inEntityNamed: "User")
// Insert or Update
let user: User = try dataStack.insertOrUpdate(changes, inEntityNamed: "User")
// Update
let user: User? = try dataStack.update(userId, with: changes, inEntityNamed: "User")
// Delete
try dataStack.delete(userId, inEntityNamed: "User")Sync requires your entities to have a primary key for diffing. By default, it uses id from JSON and id (or remoteID) from Core Data.
You can mark any attribute as the primary key by adding sync.isPrimaryKey with value true in the Core Data model's User Info.
Attributes map automatically between snake_case JSON and camelCase Core Data:
first_namein JSON maps tofirstNamein Core Data- Reserved words are prefixed with the entity name (
typebecomesuserType) - Acronyms are normalized (
user_idmaps touserID)
Use sync.remoteKey in User Info to customize the mapping for any attribute.
Sync automatically maps relationships using nested JSON objects or simplified ID references:
// Full object
{ "id": 6, "name": "Shawn", "notes": [{ "id": 0, "text": "Hello" }] }
// Simplified (ID-only, requires pre-synced data)
{ "id": 6, "name": "Shawn", "notes_ids": [0, 1, 2] }Convert NSManagedObject back to JSON:
let json = user.export()
// Keys automatically converted to snake_case
// Without relationships
let json = user.export(using: .excludedRelationships)Detailed documentation is available in the docs folder:
- Core Data Stack
- DataStack
- Primary Key
- Attribute Mapping
- Relationship Mapping
- JSON Export
- PropertyMapper
- FAQ
Sync 7.0 is a major update that modernizes the library for Swift 6 with full concurrency support. Here's what you need to know:
The minimum deployment targets have been raised significantly:
- iOS 18.0+ (was iOS 8.0)
- macOS 15.0+ (was macOS 10.10)
- watchOS and tvOS targets have been removed
If your app still supports older OS versions, continue using Sync 6.x.
CocoaPods and Carthage are no longer supported. Migrate to Swift Package Manager:
- Remove Sync from your Podfile/Cartfile
- Add the SPM dependency (see Installation)
All completion-handler APIs are deprecated. Replace them with their async equivalents:
// Before (Sync 6.x)
dataStack.sync(json, inEntityNamed: "User") { error in
if let error = error {
print("Error: \(error)")
}
}
// After (Sync 7.x)
do {
try await dataStack.sync(json, inEntityNamed: "User")
} catch {
print("Error: \(error)")
}// Before
Sync.changes(json, inEntityNamed: "User", dataStack: dataStack) { error in
// ...
}
// After
try await Sync.changes(json, inEntityNamed: "User", dataStack: dataStack)The deprecated completion-handler methods are still available to ease migration, but will be removed in a future release.
The CompatibleOperationOptions enum and compatibleChanges methods have been removed. If you were calling Sync from Objective-C code, you will need to create a Swift wrapper or migrate that code to Swift.
The library is compiled with Swift 6 strict concurrency. Key types conform to Sendable where appropriate. If you encounter concurrency warnings when interacting with Sync APIs, ensure your data is being passed safely across isolation boundaries.
Sync is available under the MIT license. See the LICENSE file for more info.
Sync was originally created and maintained by Elvis Nunez from 2014 to 2021, with contributions from nearly 30 developers across 130 releases.
