【问题标题】:How to make Core Data's ManagedObjectContext.ExecuteFetchRequest Synchronous and not Asynchronous如何使核心数据 ManagedObjectContext.ExecuteFetchRequest 同步而不是异步
【发布时间】:2016-03-25 16:12:36
【问题描述】:

下面附上我的代码。

给我带来问题的行是 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)")
            }
        }
    }
}

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

实体和控制台大部分时间找到实体时的图像参考

【问题讨论】:

  • 基本上强烈建议不要为结果提供与获取请求相同的变量名称。 executeFetchRequest 确实同步工作。
  • 它是同步的。如果不是,会有一些机制让您知道它何时完成(委托、阻止、通知......)。
  • 任何想法为什么它有时会得到实体,有时如果它是同步的,有时不会?真的很困惑可能是什么原因造成的。
  • @vadian 我什至没有注意到我使用了相同的变量名很好的捕获,想知道这是否会导致任何奇怪的错误,只是为了更好的形式而改变。

标签: ios core-data asynchronous swift2 synchronous


【解决方案1】:

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

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

【讨论】:

  • 有没有办法确保它与持久存储协调器同步?我用于 datacontroller 的代码是直接从 Apple 复制的,你认为这是为什么?会有意义。
  • 你可以让你的初始化函数接受一个完成处理程序,并在完成设置后从块内部调用处理程序。
  • 如果你不介意你能给我举个例子吗?对此还是陌生的。 (即使只是指向一个在线)
【解决方案2】:

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

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 文件的实际名称。

希望这对和我有同样问题的人有所帮助。哦...如果您正在阅读本文,还有苹果...请在未来为基于选项卡的项目添加“核心数据”选项...而不仅仅是单一视图。

【讨论】:

    猜你喜欢
    • 2010-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-23
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    相关资源
    最近更新 更多