如何使 Core Data 的 ManagedObjectContext.ExecuteFetchRequest 同步而不是异步

How to make Core Data's ManagedObjectContext.ExecuteFetchRequest Synchronous and not Asynchronous

下面附上我的代码。

给我带来问题的行是 let fetchRequest = try moc.executeFetchRequest(fetchRequest) as! [AppSettings] 似乎是异步加载,但我希望它同步加载,以便我可以确保它正确检查用户名记录。

我该怎么做?

我知道它是异步加载的,因为当我不断地启动和停止程序时,它会在大约 80% 的时间找到实体,随机地在 20% 的时间找不到实体。由于没有其他东西在改变实体(因为我只是不断地启动和停止程序),所以当我使用命令

时,代码是异步的 运行 是有道理的
guard let appSettingsArrayItem = fetchRequest.first where fetchRequest.count>0 else {
                print ("no entities found...")
                return false
            }

有时找不到任何实体。

检查登录功能

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)")
        }


    }

整个 LoginViewController 包括检查登录功能

import UIKit
import CoreData

class LoginViewController: UIViewController, UITextFieldDelegate {

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

    var isLoggedIn = false

    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!")
            isLoggedIn = true

            self.performSegueWithIdentifier("loginSegue", sender: self)
        }
    }

    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("prepare 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("Is the user already logged in?")
                if(isLoggedIn){
                    print("Detected as YES")
                    return true
                }
                print("Detected as NO, so checking username and password fields next...")

                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://distribution.tech/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
//                    
//                    {
//                        "companyid": 3,
//                        "userid": 2,
//                        "tokenid": "804febae26ddbd0292b3d2c66b30afd5028d5ba9",
//                        "status": 1,
//                        "roleId": 1,
//                        "role": "super_admin",
//                        "isdev": 0
//                    }

                    //Save to disk using our own method, as COREDATA is unreliable!

                    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
    }


}

托管对象在 DataController 中创建,其文件位于下方。

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)")
            }
        }
    }
}

实体图像引用和有时可能发生的控制台错误

实体和控制台在大多数情况下确实找到实体时的图像参考

ManagedObjectContext.ExecuteFetchRequest 已经同步运行,但看起来您正在后台优先级线程中异步设置持久存储协调器。

如果这个获取请求在应用程序启动时立即发生,并且您一遍又一遍地执行,则有时可能无法完成设置。

好的,上面的答案是正确的,所以我所做的是创建一个新的项目单一视图,选择核心数据选项,并将其 AppDelegate 中的代码复制到我自己的 AppDelegate 上以获得正确的 CoreData Init 代码,等等一种在项目终止时正确保存上下文的方式等等。代码如下所示。

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        // Saves changes in the application's managed object context before the application terminates.
        self.saveContext()
    }

    // MARK: - Core Data stack

    lazy var applicationDocumentsDirectory: NSURL = {
        // The directory the application uses to store the Core Data store file. This code uses a directory named "com.distribution.tech.Test" in the application's documents Application Support directory.
        let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
        return urls[urls.count-1]
    }()

    lazy var managedObjectModel: NSManagedObjectModel = {
        // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
        let modelURL = NSBundle.mainBundle().URLForResource("AppSettings", withExtension: "momd")!
        return NSManagedObjectModel(contentsOfURL: modelURL)!
    }()

    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
        // Create the coordinator and store
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("AppSettings.sqlite")
        var failureReason = "There was an error creating or loading the application's saved data."
        do {
            try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
        } catch {
            // Report any error we got.
            var dict = [String: AnyObject]()
            dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
            dict[NSLocalizedFailureReasonErrorKey] = failureReason

            dict[NSUnderlyingErrorKey] = error as NSError
            let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
            // Replace this with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
            abort()
        }

        return coordinator
    }()

    lazy var managedObjectContext: NSManagedObjectContext = {
        // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
        let coordinator = self.persistentStoreCoordinator
        var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = coordinator
        return managedObjectContext
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        if managedObjectContext.hasChanges {
            do {
                try managedObjectContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
                abort()
            }
        }
    }

}

执行此操作时,关键是要更改对您自己的 xcdatamodeld 的引用,否则这将不起作用。在我的例子中,它是根据我以前的工作将这一行更改为正确的 sqlite。

let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("AppSettings.sqlite")

这行...

let modelURL = NSBundle.mainBundle().URLForResource("AppSettings", withExtension: "momd")!

这是 xcdatamodeld 文件的实际名称。

希望这对和我有同样问题的人有所帮助。哦......还有苹果,如果你正在阅读这篇文章......请在未来为基于选项卡的项目添加 'core data' 选项......而不仅仅是单一视图。