Joseph Astrahan
Joseph Astrahan

Reputation: 9072

Does Core Data Use Threading By Default?

I was thinking of using coreData to store my username and password for persistence from the users. While testing I noticed that when I quickly close the program after it saves the data and relaunch it to check if the data was persisted that it would sometimes say nothing was there, and then when I relaunch the app again then it would be there. The longer I waited the more likely when I relaunched the app that the persisted data would appear.

I was adding coreData to an existing project so I created a controller called DataController.swift and copied the suggested code from apple. That code is below.

import UIKit
import CoreData

class DataController: NSObject {
    var managedObjectContext: NSManagedObjectContext
    override init() {
        // This resource is the same name as your xcdatamodeld contained in your project.
        guard let modelURL = NSBundle.mainBundle().URLForResource("AppSettings", withExtension:"momd") else {
            fatalError("Error loading model from bundle")
        }
        // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
        guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
            fatalError("Error initializing mom from: \(modelURL)")
        }
        let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
        self.managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        self.managedObjectContext.persistentStoreCoordinator = psc
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
            let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
            let docURL = urls[urls.endIndex-1]
            /* The directory the application uses to store the Core Data store file.
            This code uses a file named "DataModel.sqlite" in the application's documents directory.
            */
            let storeURL = docURL.URLByAppendingPathComponent("AppSettings.sqlite")
            do {
                try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
            } catch {
                fatalError("Error migrating store: \(error)")
            }
        }
    }
}

My LoginViewController.swift is below:

import UIKit
import CoreData

class LoginViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var usernameField: UITextField!
@IBOutlet weak var passwordField: UITextField!

let moc = DataController().managedObjectContext

@IBAction func SignUpButtonPressed(sender: UIButton) {
    print("sign up")
}

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

func textFieldShouldEndEditing(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}


override func viewDidLoad() {
    super.viewDidLoad()

    let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
    view.addGestureRecognizer(tap)

    print("view loaded, check if already signed in here")

    let loggedIn = checkIfLoggedInAlready() //checks database to see

    if(loggedIn){
        print("was logged in!")
    }
}

func checkIfLoggedInAlready() -> Bool{
    let fetchRequest = NSFetchRequest(entityName: "AppSettings")
    //let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) //Deletes ALL appsettings entities

    do {

        let fetchRequest = try moc.executeFetchRequest(fetchRequest) as! [AppSettings]

        guard let appSettingsArrayItem = fetchRequest.first where fetchRequest.count>0 else {
            print ("no entities found...")
            return false
        }

        guard let username = (appSettingsArrayItem as AppSettings).username else{
            print ("username not found")
            return false
        }

        print("number Of AppSetting Entities =\(fetchRequest.count)")
        print(username)

        //The following code deletes ALL the entities!
        //try moc.persistentStoreCoordinator!.executeRequest(deleteRequest, withContext: moc)

        //To delete just '1' entry use the code below.

        //moc.deleteObject(appSettingsArrayItem)
       // try moc.save()//save deletion change.

        print("deleted particular entity item")

        return true
    } catch{
        fatalError("bad things happened \(error)")
    }


}

func dismissKeyboard() {
    //Causes the view (or one of its embedded text fields) to resign the first responder status.
    view.endEditing(true)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
    print("prepar seque")
}

func displayErrorMessage(errorMessage: String){
    print("show error console with Error:"+errorMessage)
    let alert = UIAlertController(title: "Error", message: errorMessage, preferredStyle: UIAlertControllerStyle.Alert)
    alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
    self.presentViewController(alert, animated: true, completion: nil)
}

override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
    switch(identifier){
        case "loginSegue":

            print("loginSegue Check")

            guard let password = passwordField.text!.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet()) where !password.isEmpty else {
                displayErrorMessage("Password can not be empty!")
                return false
            }

            guard let username = usernameField.text!.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet()) where !username.isEmpty else{
                displayErrorMessage("Username can not be empty!")
                return false
            }

            let url = "http://mywebsite.com/restapi/v1/userlogin?email="+username+"&password="+password
            print(url)

            let json = JSON(url:url)
            print(json)

            if(json["status"].asInt==1){

                let entity = NSEntityDescription.insertNewObjectForEntityForName("AppSettings", inManagedObjectContext: moc) as! AppSettings

                entity.setValue(username, forKey: "username")
                entity.setValue(password, forKey: "password")
                entity.setValue(json["tokenid"].asString, forKey: "token")
                entity.setValue(json["roleid"].asInt, forKey: "roleid")
                entity.setValue(json["role"].asString, forKey: "role")
                entity.setValue(json["companyid"].asInt , forKey: "companyid")
                entity.setValue(json["isdev"].asInt, forKey: "isdev")

                //save token and other details to database.
                do {
                    try moc.save()
                    print("saved to entity")
                }catch{
                    fatalError("Failure to save context: \(error)")
                }

                //token
                //roleid int
                //role
                //companyid int



                return true //login succesfull
            }else{
                displayErrorMessage("Incorrect Username or Email")
                return false//failed
            }

    default:
        displayErrorMessage("Unknown Error Related To Segue Not Found")
    }
  return false //if it gets to this point assume false
}
}

To test my code you would just uncomment the part that deletes the entity (//moc.deleteObject(appSettingsArrayItem) ) in the function checkIfLoggedInAlready().

In short though the answer to this question is a simple yes or no, I have a hunch its threaded and that is why the delay matters. I think its threaded because of this line in the DataController.swift

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0))

but not sure if that is what is affecting it. The code that creates the entity is done in shouldPerformSegueWithIdentifier of LoginViewController.swift

Upvotes: 0

Views: 107

Answers (1)

Stefan Salatic
Stefan Salatic

Reputation: 4513

Not by default. All of the NSManagedObjects are bound to NSManagedContext which has it's own queue. If you want concurrency, you need to create another NSManagedContext with private queue. You can read about concurrency in Core Data over here:

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Concurrency.html

Also, never use dispatch_async for Core Data. Each context has it's own peformBlock which run on it's queue.

Upvotes: 3

Related Questions