Skip to content

spnkr-labs/CoreDataPlus

 
 

Repository files navigation

CoreDataPlus

Lightweight Active-record-ish pattern.

☁️ Works great with, or without, CloudKit.

✅ Manage your NSManagedObjectContext(s) however you want.

👨‍💻 Works with automatic or manual NSManagedObject code generation.

Documentation & Guides

Go to documentation


Installation

import CoreDataPlus

// An existing NSManagedObject
public class Drawing: NSManagedObject { }

extension Drawing: ManagedObjectPlus {
                   
}

You can also import just the features you want to use by replacing ManagedObjectPlus with one or more of the available ManagedObject* protocols:

extension Drawing: ManagedObjectDeletable,
                   ManagedObjectCountable,
                   ManagedObjectSearchable,
                   ManagedObjectFindOrCreateBy {
                   
}

Example App

Open Example App/Example App.xcodeproj.

Documentation

Predicate

Adds a new, more concise, initializer for NSPredicate. Aliased as Predicate.

You can type

Predicate("color = %@ and city = %@", "Blue", city)

Instead of

NSPredicate(format:"color = %@ and city = %@", argumentArray:["Blue", city])

empty()

Returns a new, non-nil, NSPredicate object that will match every single row in the database.

This is especially helpful with:

  1. The @FetchRequest property wrapper. Using a nil predicate can cause unexpected behavior if you dynamically assign or edit the predicate at runtime.
  2. Other Core Data quirks around nil predicates and updating when you do fancy stuff.

Before: @FetchRequest(sortDescriptors: [], predicate: nil) After: @FetchRequest(sortDescriptors: [], predicate: Predicate.empty())

NSManagedObject Extensions

findOrCreate

Finds an instance of the NSManagedObject that has a column (property) matching the passed value. If it doesn't exist in the database, creates one, and returns it.

let d = Drawing.findOrCreate(column: "my-column", value: "123456789", context: viewContext)
d.timestamp = Date()

The idea is that each object has a unique, deterministic, human-readable, identifier. So if you have a User with an id of 123, you can do let user = User.findOrCreate(id: "123") whenever and wherever you need access to the user object.

Shortcut:

If your xcdatamodel has a String property called id, you can use this shortcut:

let d = Drawing.findOrCreate(id: "123", context: viewContext)

Tip: If you're using SwiftUI, adding a String column id plays nicely with Identifiable

findButDoNotCreate

Same as findOrCreate, but returns nil if there is no object in the database.

if let d = Drawing.findButDoNotCreate(id: "10001", context: viewContext) {
    d.timestamp = Date()
    d.title = "My title"
}

countFor

Gets a count of objects matching the passed Predicate

let count = Drawing.countFor(Predicate("someField = %@", true), context: viewContext)

searchFor

Gets all objects matching the passed Predicate

let results = Drawing.searchFor(Predicate("foo = %@", "bar"), context: viewContext)

print("\(results.count) objects found")

for drawing in results {
    dump(drawing)
}

destroyAll

Removes all objects from Core Data. Deletes them from the context.

Drawing.destroyAll(context: viewContext)

You can also specify a predicate to only delete some items:

let withSpanishLanguage = Predicate("languages contains %@", es)
Country.destroyAll(matching: withSpanishLanguage)

Remember to save the context after deleting objects.

Logging

By default, log messages do not appear. To take action when there's a problem, use CoreDataPlus.Logger.configure(...)

CoreDataPlus.Logger.configure(logHandler: { message in
    print("A log message from the data layer: \(message)")
})

Recommended: set the logHandler in your AppDelegate. Example below:

// Your app's main .swift file:
import SwiftUI

@main
struct MyFancyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}


// A new file called AppDelegate.swift
import Foundation
import UIKit
import CoreDataPlus

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        CoreDataPlus.Logger.configure(logHandler: { message in
            print("A log message from the data layer: \(message)")
        })
        
        return true
    }
}

Automatic Context Management

Optional. If you specify a view context and a background context using CoreDataPlus.setup(...), you can simplify passing of NSManagedObjectContext objects.

For example, Book.findOrCreate(id:"123", context: foo) becomes Book.findOrCreate(id:"123") and Author.findOrCreate(id:"123", context: bar) becomes Author.findOrCreate(id:"123", using: .background). See the included example app for more examples.

Quick Start with SwiftUI

You can pass the viewContext variable as the context for all this library's methods.

Most SwiftUI quickstart guides use .environment(\.managedObjectContext, foo) on the view, along with

Main app file:

import SwiftUI

@main
struct MyFancyApp: App {
    let persistenceController = PersistenceController.shared // from the default Xcode new project setup
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

ContentView file:

import SwiftUI
import CoreData
import CoreDataPlus

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    var body: some View {
        VStack {
            Button("Hello", action: {
                Drawing.destroyAll(context: self.viewContext)
            })
        }
    }
}

Todo

  • Unit tests
  • Option to disable automatic saving when using destroyAll
  • Add docs on NSPersistentContainer agnosticity
  • Have an idea? Open an issue!

About

Lightweight Active-record-ish pattern for Core Data.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Swift 97.8%
  • Shell 1.7%
  • Ruby 0.5%