NSFetchedResultsController crashing when creating it, objc_exception_throw - ios

I am receiving a objc_exception_throw when creating my NSFetchedResultsController. I am relatively new to iOS programming so I am still getting the hang of debugging iOS crashes.
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
lazy var parentContext: NSManagedObjectContext? = {
if let managedObjectContext = self.appDelegate.managedObjectContext {
return managedObjectContext
}
else {
return nil
}
}()
lazy var fetchedResultsController: NSFetchedResultsController = {
let request = NSFetchRequest()
let entity = NSEntityDescription.entityForName("GameDate", inManagedObjectContext: self.parentContext!)
request.entity = entity
let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.parentContext!, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
override func viewDidLoad() {
super.viewDidLoad()
performFetchFromDb()
setupTableView()
}
I am getting the crash on this line:
let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.parentContext!, sectionNameKeyPath: nil, cacheName: nil)
Does this crash have something to do with my context?

I was missing an NSSortDescriptor in my fetch request. This fixed it:
lazy var fetchedResultsController: NSFetchedResultsController = {
let request = NSFetchRequest()
let entity = NSEntityDescription.entityForName("GameDate", inManagedObjectContext: self.parentContext!)
request.entity = entity
let gameTimeSort = NSSortDescriptor(key: "gameTime", ascending: false)
request.sortDescriptors = [gameTimeSort]
let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.parentContext!, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()

Related

Unable to infer complex closure return type; add explicit type to disambiguate

Does anybody know how I can solve this error that I'm getting? The error is received on the first line in the following chunk of code:
let fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Message")
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
return frc
}()
Try adding the return type in the closure like this code:
let fetchedResultsController: NSFetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Message")
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
return frc
}()
The error message is a bit misleading, the problem is that you did
not specify the generic placeholder type for the variable.
You can either add an explicit return type to the closure, as #Mukesh
suggested, in that case the type annotation on the variable is not
needed:
let fetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in
// ...
return frc
}()
Or fully specify the type of the variable, then the closure return
type is inferred automatically:
let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> = {
// ...
return frc
}()
Two issues:
As mentioned in the other answers you have to specify the generic type, in your case the ideal type is the concrete NSManagedObject subclass.
The declaration of an NSFetchedResultsController requires at least one sort descriptor.
let fetchedResultsController: NSFetchedResultsController<Message> = {
let fetchRequest = NSFetchRequest<Message>(entityName: "Message")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "somekey", ascending: true]
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
return frc
}()

NSFetchedResultsController with privateQueueConcurrencyType - changes do not call delegate methods

I'm unable to get my NSFetchedResultsControllerDelegate methods called when setting the NSFetchedResultsControllerDelegate up with a concurrencyType value of privateQueueConcurrencyType.
Consider this example:
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Note> = {
// Initialize Fetch Request
let fetchRequest: NSFetchRequest<Note> = Note.fetchRequest()
// Add Sort Descriptors
let sortDescriptor = NSSortDescriptor(key: "updatedAt", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Initialize Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
Where the coreDataManager class has this:
private(set) lazy var managedObjectContext: NSManagedObjectContext = {
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
return managedObjectContext
}()
If I call fetchedResultsController.managedObjectContext.delete(note) and then save, the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath method is called and I can respond to the deletion of the note object.
If I change the code above to be:
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Note> = {
// Initialize Fetch Request
let fetchRequest: NSFetchRequest<Note> = Note.fetchRequest()
// Add Sort Descriptors
let sortDescriptor = NSSortDescriptor(key: "updatedAt", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let managedObjectContext = NSManagedObjectContext(concurrencyType: .concurrencyType`)
managedObjectContext.persistentStoreCoordinator = self.coreDataManager.managedObjectContext.persistentStoreCoordinator
// Initialize Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
Which specifically changes the managed object context 'usage' from using self.coreDataManager.managedObjectContext to a new version that should work on a background thread, the delegate methods are never called when items are deleted.
I fetch once in viewDidLoad
This works fine when using the 'main' managed object, self.coreDataController.managedObjectContext
I am more or less using the code available at https://github.com/bartjacobs/ExploringTheFetchedResultsControllerDelegateProtocol to demonstrate this - as I stated above please adjust the fetchedResultsController method to use a different NSManagedObjectContext
So my main question is - how do I get the NSFetchedResultsControllerDelegate methods to be called when using this type of NSManagedObjectContext?
The fetchedResultsController is monitoring the context that you just created and that context is not processing updates. If you want this context to change you have to set automaticallyMergesChangesFromParent = true for the context.
As John told NSFetchedResultsController is monitoring only associated managed context like
lazy var fetchedResultsController: NSFetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in
let request = NSFetchRequest<NSFetchRequestResult>.init(entityName: "Repository")
let descriptors = [NSSortDescriptor.init(key: "name", ascending: true), NSSortDescriptor.init(key: "name", ascending: true)]
request.sortDescriptors = descriptors
let resultsController = NSFetchedResultsController.init(fetchRequest: request, managedObjectContext: CoreDataManager.shared.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
resultsController.delegate = self
return resultsController
}()
Inherit your .mainQueueConcurrencyType from .privateQueueConcurrencyType like below code
lazy var managedObjectContext: NSManagedObjectContext = {
let context = NSManagedObjectContext.init(concurrencyType: .mainQueueConcurrencyType)
context.parent = self.privateQueueManagedObjectContext
context.automaticallyMergesChangesFromParent = true
return context
}()
lazy var privateQueueManagedObjectContext: NSManagedObjectContext = {
let context = NSManagedObjectContext.init(concurrencyType: .privateQueueConcurrencyType)
context.persistentStoreCoordinator = self.persistentStoreCoordinator
return context
}()
Dont forget to add context.automaticallyMergesChangesFromParent = true in your maincontext to notify background operations on privateQue context.
I hope this will work

Ios Swift3 : lazy var fetchrequestcontroller showing an error

I was using a lazy var in table view controller to get the data from CoreData. It was working good in swift2. But When I upgraded to Swift3, Its showing an error as below. Kindly help me to change for Swift3
Error : Cannot convert value of type 'Error' to specified type 'NSFetchedResultsController'
lazy var fetchedResultsController: NSfetchedResultsController =
{
let fetchRequest = NSFetchRequest(entityName: "EvtIvtTbl")
let sortDescriptor = NSSortDescriptor(key: "bym_kol", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let fetchedResultsController = NSfetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
In Swift 3.0, NSFetchedResultsController and NSFetchRequest want type parameters. Assuming that EvtIvtTbl is your NSManagedObject subclass, your code should look like this:
lazy var fetchedResultsController: NSFetchedResultsController<EvtIvtTbl> = {
let fetchRequest = NSFetchRequest<EvtIvtTbl>(entityName: "EvtIvtTbl")
let sortDescriptor = NSSortDescriptor(key: "bym_kol", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let fetchedResultsController = NSFetchedResultsController<EvtIvtTbl>(fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()

How to initialize a NSFetchedResultsController in iOS 10 Swift 3

I can't get my NSFetchedResultsController initialized in iOS 10 using Swift 3 within CoreDataTableViewController from AECoreDataUI.
let request = NSFetchRequest<NasaPicture>(entityName:"NasaPicture")
request.predicate = Predicate(format: "isPagedResult == YES")
request.sortDescriptors = [SortDescriptor(key: "date", ascending: false)]
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
Compiler is complaining that
"Cannot convert value of type NSFetchedResultsController<NasaPicture> to expected type NSFetchedResultsController<_>"
The controller is now using generic type for swift, but it is not inferring the entity type correctly. I've tried explicitly:
fetchedResultsController = NSFetchedResultsController<NasaPicture>(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
No luck either.
Thanks!
NSFetchRequest is now a generic. NSFetchedResultsController is a generic too. Therefore, when declaring variables, you have to use a generic declaration, e.g.:
var fetchedResultsController: NSFetchedResultsController<NasaPicture>?
when we need to declare variable, then we have to use a generic declaration
please check below code for NSFetchedResultsController in swift 3..
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
// MARK: - NSFetchedResultsController
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<UserExistenceOnXMPPCD> = {
let fetchRequest = NSFetchRequest<UserExistenceOnXMPPCD>(entityName: "UserExistenceOnXMPPCD")
fetchRequest.sortDescriptors = [
NSSortDescriptor(key: "name", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:CoreDataController.sharedInstance.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
try! fetchedResultsController.performFetch()
fetchedResultsController.delegate = self
if let quotes = fetchedResultsController.fetchedObjects {
if quotes.count > 0 {
print(quotes.count)
}
}
return fetchedResultsController
}()
// MARK: - NSFetchedResultsController delegate methods
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
}
Simply call the function:
private func setupFetchedResultsController() {
let context = NSManagedObjectContext.mr_default()
let fetchRequest = NSFetchRequest<Month>(entityName: "Month")
let dateDescriptor = NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [dateDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: "year", cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
tableView.reloadData()
}

Load more in bottom UITableView using NSFetchedResultsController

I want to load more cells when user in the last cell. I use CoreData and I get data from it. I use this answer to do what I want, but it didn't work. What problem can be there? Also I have one more question. I don't know why working only first implementation.
First implementation
var fetchedResultsController: NSFetchedResultsController {
let moc = DatabaseController.sharedInstance.managedObjectContext
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Coctail", inManagedObjectContext: moc)
fetchRequest.entity = entity
fetchRequest.fetchLimit = 10
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: nil,
cacheName: nil)
aFetchedResultsController.delegate = self
do {
try aFetchedResultsController.performFetch()
} catch {
fatalError("\(error)")
}
return aFetchedResultsController
}
Second Implementation
func initilizeFetchedResultsController() {
let moc = DatabaseController.sharedInstance.managedObjectContext
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Coctail", inManagedObjectContext: moc)
fetchRequest.entity = entity
fetchRequest.fetchLimit = 10
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: nil,
cacheName: nil)
aFetchedResultsController.delegate = self
do {
try aFetchedResultsController.performFetch()
} catch {
fatalError("\(error)")
}
fetchedResultsController = aFetchedResultsController
}
Hard to tell why the first solution but the second doesn't without seeing the other code that would have used the fetchedResultsController or called initilizeFetchedResultsController().
The reason you're not getting more data is that you've hardcoded your fetchLimit to 10. No matter what you do, you'll only ever get 10 results.
You'll need to change the fetchLimit to get more data. Check out this answer -- it is similar to what you'll need to do: https://stackoverflow.com/a/6786931/5605379

Resources