user7684436
user7684436

Reputation: 717

Swift Core Data Class method?

I'm currently learning Core Data and I have two view controllers that are using the same piece of code to get a users profile. The problem is that it's the same code copy and pasted and I would like to avoid this. I'm using the Managed Class approach to access the data and each controller has the following method:

var profileHolder: Profile!
    let profileRequest = Profile.createFetchRequest()
    profileRequest.predicate = NSPredicate(format: "id == %d", 1)
    profileRequest.fetchLimit = 1
    if let profiles = try? context.fetch(profileRequest) {
        if profiles.count > 0 {
            profileHolder = profiles[0]
        }
    }

    if profileHolder == nil {
        let newProfile = Profile(context: context)
        newProfile.id = 1
        newProfile.attempts = nil
        profileHolder = newProfile
    }
    profile = profileHolder

Profile is a var inside the controller: var profile: Profile! and I call the above inside viewWillAppear()

I know there's a cleaner approach and I would like to move this logic inside the class but unsure how to.

Thanks

Upvotes: 0

Views: 1182

Answers (3)

Thomas Paul
Thomas Paul

Reputation: 363

Create a class(CoreDataManager) that can manage core data operations.

import CoreData

class CoreDataManager:NSObject{
/// Application Document directory
lazy var applicationDocumentsDirectory: URL = {
    let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return urls[urls.count-1]
}()
/// Core data manager
static var shared = CoreDataManager()
/// Managed Object Model
lazy var managedObjectModel: NSManagedObjectModel = {

    let modelURL = Bundle.main.url(forResource: “your DB name”, withExtension: "momd")!
    return NSManagedObjectModel(contentsOf: modelURL)!
}()
/// Persistent Store Coordinator 
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
    var failureReason = "There was an error creating or loading the application's saved data."
    let options = [ NSInferMappingModelAutomaticallyOption : true,
                    NSMigratePersistentStoresAutomaticallyOption : true]
    do {
        try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options)
        persistanceStoreKeeper.sharedInstance.persistanceStorePath = url
    } catch {
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject
        dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject
        dict[NSUnderlyingErrorKey] = error as NSError
        let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        abort()
    }
    return coordinator
}()
/// Managed Object Context
lazy var managedObjectContext: NSManagedObjectContext = {

    let coordinator = self.persistentStoreCoordinator
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()
/// Save context
func saveContext () {
    if managedObjectContext.hasChanges {
        do {
            try managedObjectContext.save()
        } catch {
            let nserror = error as NSError
            NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
            abort()
        }
    }
}
}

Add the bellow function in your class.

func fetchProfile(profileId:String,fetchlimit:Int,completion: ((_ fetchedList:["Your model class"]) -> Void)){
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Your entity name")
    let predicate:NSPredicate = NSPredicate(format: "id = %@", profileId)
    fetchRequest.predicate=predicate
    fetchRequest.fetchLimit = fetchlimit
    do {
        let results =
            try CoreDataManager.shared.managedObjectContext.fetch(fetchRequest)
        let profileList:["Your model class"] = results as! ["Your model class"]
        if(profileList.count == 0){
            //Empty fetch list
        }
        else{
            completion(profileList)

        }
    }
    catch{
       //error
    }
}

replace "Your model class" according to your requirement.

You can call the function "fetchProfile" and you will get the result inside the completion block.

Upvotes: 0

Todanley
Todanley

Reputation: 508

 var profileHolder: Profile!

profileHolder here is force unwrapping optional value. And you are fetching from core data and assigning the value in viewWillAppear, which is risky as profileHolder would be nil and can trigger crash if you access it before viewWillAppear.

My suggestion would be:

var profileHolder: Profile 
{
    if let profiles = try? context.fetch(profileRequest),
       profiles.count > 0           
    {
       return profiles[0]
    }
    else
    {
       let newProfile = Profile(context: context)
       newProfile.id = 1
       newProfile.attempts = nil
       return newProfile
    }
}()

This will ensure profileHolder is either fetched or created when the view controller is initialised.

However this would not work if

context

is a stored property of viewController, in which case, do:

var profileHolder: Profile?

override func viewDidLoad() 
{ 
   if let profiles = try? context.fetch(profileRequest),
       profiles.count > 0           
    {
       return profiles[0]
    }
    else
    {
       let newProfile = Profile(context: context)
       newProfile.id = 1
       newProfile.attempts = nil
       return newProfile
    }
}

Upvotes: 1

Jake
Jake

Reputation: 2216

Here is the struct I created for a project I did that allows me to access my CoreData functions anywhere. Create a new empty swift file and do something like this.

import CoreData

// MARK: - CoreDataStack

struct CoreDataStack {

    // MARK: Properties

    private let model: NSManagedObjectModel
    internal let coordinator: NSPersistentStoreCoordinator
    private let modelURL: URL
    internal let dbURL: URL
    let context: NSManagedObjectContext
    let privateContext: NSManagedObjectContext

    // MARK: Initializers

    init?(modelName: String) {

        // Assumes the model is in the main bundle
        guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "momd") else {
            print("Unable to find \(modelName)in the main bundle")
            return nil
        }
        self.modelURL = modelURL

        // Try to create the model from the URL
        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            print("unable to create a model from \(modelURL)")
            return nil
        }
        self.model = model

        // Create the store coordinator
        coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)

        // create a context and add connect it to the coordinator
        //context.persistentStoreCoordinator = coordinator

        privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        privateContext.persistentStoreCoordinator = coordinator

        context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        context.parent = privateContext

        // Add a SQLite store located in the documents folder
        let fm = FileManager.default
        guard let docUrl = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
            print("Unable to reach the documents folder")
            return nil
        }

        self.dbURL = docUrl.appendingPathComponent("model.sqlite")

        // Options for migration
        let options = [NSInferMappingModelAutomaticallyOption: true,NSMigratePersistentStoresAutomaticallyOption: true]

        do {
            try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: options as [NSObject : AnyObject]?)
        } catch {
            print("unable to add store at \(dbURL)")
        }
    }

    // MARK: Utils

    func addStoreCoordinator(_ storeType: String, configuration: String?, storeURL: URL, options : [NSObject:AnyObject]?) throws {
        try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: dbURL, options: nil)
    }
}

// MARK: - CoreDataStack (Removing Data)

internal extension CoreDataStack  {

    func dropAllData() throws {
        // delete all the objects in the db. This won't delete the files, it will
        // just leave empty tables.
        try coordinator.destroyPersistentStore(at: dbURL, ofType:NSSQLiteStoreType , options: nil)
        try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: nil)
    }
}

// MARK: - CoreDataStack (Save Data)

extension CoreDataStack {

    func saveContext() throws {
        /*if context.hasChanges {
            try context.save()
        }*/
        if privateContext.hasChanges {
            try privateContext.save()
        }
    }

    func autoSave(_ delayInSeconds : Int) {

        if delayInSeconds > 0 {
            do {
                try saveContext()
                print("Autosaving")
            } catch {
                print("Error while autosaving")
            }

            let delayInNanoSeconds = UInt64(delayInSeconds) * NSEC_PER_SEC
            let time = DispatchTime.now() + Double(Int64(delayInNanoSeconds)) / Double(NSEC_PER_SEC)

            DispatchQueue.main.asyncAfter(deadline: time) {
                self.autoSave(delayInSeconds)
            }
        }
    }
}

Upvotes: 1

Related Questions