magicalpanda / MagicalRecord

Super Awesome Easy Fetching for Core Data!
Other
10.8k stars 1.79k forks source link

xCode 6.3 and Swift 1.2: saveToPersistentStoreAndWait and NSFetchedResultsControllerDelegate #963

Closed andriyslyusar closed 9 years ago

andriyslyusar commented 9 years ago

I tried to migrate my project to xCode 6.3 with swift 1.2 support. But I start to experience strange issue:

  1. I am creating and updating new Core Data objects:
    private lazy var moc: NSManagedObjectContext = {
        return NSManagedObjectContext.MR_context();
    }()
  1. In code I create new objects in 'moc'
  2. As a final step
 moc.MR_saveToPersistentStoreAndWait()

In another place I have a UITableViewControoler with NSFetchedResultsControllerDelegate, but none of delegates fire after migration to xCode 6.3. Rolling back to swift 1.1 in xCode 6.1.1 everything works as expected.

Can anybody confirm the same behaviour? Or maybe any ideas why it can happen and possible workaround ?

Spokane-Dude commented 9 years ago

Workaround: stay with Objective-C! IMHO, Swift is not mature enough yet...

Andrey Slyusar wrote:

I tried to migrate my project to xCode 6.3 with swift 1.2 support. But I start to experience strange issue:

  1. I am creating and updating new Core Data objects:

private lazyvar moc: NSManagedObjectContext= { return NSManagedObjectContext.MR_context(); }()

  1. In code I create new objects in 'moc'
  2. As a final step

moc.MR_saveToPersistentStoreAndWait()

In another place I have a UITableViewControoler with NSFetchedResultsControllerDelegate, but none of delegates fire after migration to xCode 6.3. Rolling back to swift 1.1 in xCode 6.1.1 everything works as expected.

Can anybody confirm the same behaviour? Or maybe any ideas why it can happen and possible workaround ?

— Reply to this email directly or view it on GitHub https://github.com/magicalpanda/MagicalRecord/issues/963.

andriyslyusar commented 9 years ago

It is already to late to switch back to ObjC, too many code in swift to rewrite. It just working in xCode 6.1.1 without incremental updates is very painful, but without solving this issue I will need to rollback from swift 1.2 to 1.1.

tonyarnold commented 9 years ago

It would be wise to play with Swift 1.2, but not commit to it until later in the seed cycle for Xcode 6.3. The beta we have right now seems to have some glaring bugs, many of which Apple engineers have acknowledged they are working on fixing for Swift 1.2's actual release.

Keep an eye on this issue all the same — if it comes up later in the seeds of Xcode 6.3, I'll need to look at fixing it.

andriyslyusar commented 9 years ago

I try to reimplement similar logic without MagicRecord and delegate method is called. Swift 1.2 has incremental updates and this is a great benefit for pure swift project >300 classes. I will spent some time trying to find by myself or switch to stable xCode. Maybe you have any idea why it could happen?

class ViewController: UIViewController, UITableViewDataSource, NSFetchedResultsControllerDelegate {

    @IBOutlet weak var tableView: UITableView!

    var fetchedResultsController: NSFetchedResultsController!

    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate

    lazy var managedContext: NSManagedObjectContext = {
        return self.appDelegate.managedObjectContext!
    }()

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        let fetchRequest = NSFetchRequest(entityName: "Person")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]

        fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: appDelegate.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
        self.fetchedResultsController.delegate = self

        var error: NSError?
        fetchedResultsController.performFetch(&error)

        tableView.reloadData()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }

    // MARK: UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fetchedResultsController.fetchedObjects!.count
    }

    func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell

      let person = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
      cell.textLabel!.text = person.valueForKey("name") as! String?

      return cell
    }

    //Implement the addName IBAction
    @IBAction func addName(sender: AnyObject) {
        var alert = UIAlertController(title: "New name", message: "Add a new name", preferredStyle: .Alert)

        let saveAction = UIAlertAction(title: "Save", style: .Default) { (action: UIAlertAction!) -> Void in
            let textField = alert.textFields![0] as! UITextField
            self.saveName(textField.text)
            self.tableView.reloadData()
        }

        let cancelAction = UIAlertAction(title: "Cancel", style: .Default) { (action: UIAlertAction!) -> Void in
        }

        alert.addTextFieldWithConfigurationHandler {
          (textField: UITextField!) -> Void in
        }

        alert.addAction(saveAction)
        alert.addAction(cancelAction)

        presentViewController(alert, animated: true, completion: nil)
    }

    func saveName(name: String) {
        //2
        let entity =  NSEntityDescription.entityForName("Person", inManagedObjectContext: managedContext)

        let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)

        //3
        person.setValue(name, forKey: "name")

        //4
        var error: NSError?
        if !managedContext.save(&error) {
          println("Could not save \(error), \(error?.userInfo)")
        }
    }

    // ========================================
    // MARK: - NSFetchedResultsController
    // ========================================
    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        tableView.beginUpdates()
    }
}
davetroy commented 9 years ago

I found a possible cure for this.

1) Be sure that you have implemented -controllerDidChangeContent (even if empty), otherwise the fetchedResultsController track-changes will not be enabled (per docs).

2) Set the @objc directive on -controllerDidChangeContent and other NSFetchedResultsControllerDelegate methods, so the FRC can 'see' them (as it's an ObjC class).

I was having this problem and #2 above saved the day. Still fighting various other weird bugs, but that was by far the worst.

andriyslyusar commented 9 years ago

@davetroy Thanks for response.

  1. In my original implementation I has a -controllerDidChangeContent:
    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        if ignoreNextUpdates {
            ignoreNextUpdates = false
        } else {
            tableView.endUpdates()
        }
    }
  1. @objc could be a case, but I had the issue only in case I implement delegate methods as a part of private extension. I cannot verify it now for the reason we switch to xCode 6.2 and wait for official release of xCode 6.3 (we need to make beta build to iTunes)
BrianSemiglia commented 9 years ago

Adding @objc seemed to fix the issue for me as well. Thanks for the help. I was using Xcode 6.3, Swift 1.2.

andriyslyusar commented 9 years ago

Confirm. Thanks a lot for help. Does anybody now the reason we need to add @objc ?

carlodurso commented 9 years ago

It seems that this is a behavior change in Swift 1.2: methods in non-Objective-C-derived classes will no longer be implicitly marked @objc even if they match an Objective-C protocol. You can explicitly mark the methods with the @objc attribute if you don't want to extend NSObject. This is described in the Xcode 6.3 release notes at https://developer.apple.com/library/ios/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc6_release_notes.html#//apple_ref/doc/uid/TP40001051-CH4-SW3.

Source: http://stackoverflow.com/questions/29536314/fetched-result-controller-delegate-not-called-after-swift-1-2-xcode-6-3-update