Reputation: 717
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
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
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
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