I developing swift application which included Core data and Alamofire. I'm struggling to solve this issue for almost a day.
Below is a screen shot after app is crashing and it happens randomly. I know this information might not enough, please mention if anyone need more details. At least guide me to track this issue.
EDIT:
Its mainly crashing after this method reload tableview function.
func reloadTable(){
print("reloadTable called")
DispatchQueue.main.async {
let appDel:AppDelegate = (UIApplication.shared.delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest < NSFetchRequestResult>(entityName: "Jars")
request.returnsObjectsAsFaults = false;
let sortDescriptor = NSSortDescriptor(key: "order", ascending: true)
let sortDescriptors = [sortDescriptor]
request.sortDescriptors = sortDescriptors
self.navigationController!.view.backgroundColor = UIColor.white
self.jarlist = NSArray()
do{
self.jarlist = try context.fetch(request) as NSArray
print("Check 11")
}catch{
}
self.ArrayPopulator("")
print("Check 12")
self.JarsTable.reloadData()
print("Check 13")
}
}
So looking at your stack trace it looks like you are using your managed context on thread 2.
A NSManagedObjectContext cannot be used on multiple threads, you must have one for every thread (queue) that you want to use it on. It is only safe to pass object identifiers between threads.
Can you double check that the objects your are wanting to dispose of in your NSOperation were retrieved on the same operation queue that you are trying to remove them on?
You will want to pass only the ids to the operation and have it rehydrate them from it's own managed object context.
EDIT: Looking closer this appears to be an internal queue managed by CoreData, so possibly this is not your doing?
Related
I am at the stage of optimizing my core data code,
I am making a messaging app, and I only want to load 50 messages at a time.
However, looking at the timeprofile Data below, the fetchrequest loads all the messages at once. Afterwards, it loads all the messages in batches of 50 without even scrolling. So it's like not even doing anything.
Any help would be greatly appreciated.
lazy var fetchedResultsControler: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Mesages")
fetchRequest.fetchBatchSize = 20
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
fetchRequest.predicate = NSPredicate(format: "user.id = %#", self.friend!.id!)
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
override func viewDidLoad() {
super.viewDidLoad()
do {
try fetchedResultsControler.performFetch()
} catch let err {
print(err)
}
self.fetchData = fetchedResultsControler.fetchedObjects as! [Mesages]}
This is the CoreData time profiling data:
That's correct behavior, per the NSFetchRequest documentation:
When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but no more than batchSize objects’ data will be fetched from the persistent store at a time. The array returned from executing the request will be a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.
If, instead, you want explicit control of the fetch grouping, use a combination of fetchLimit and fetchBatchSize instead. But at that point you're fighting the features of NSFetchedResultsController.
What are you really trying to do with the fetchData array?
OverView
I continue to run into issues with adding multiple values to my Core Data entity. All i need to do is simply add 6 string-value items from a text field into Core Data. Specific examples/critique of my code would be very appreciated, as i am nearing mental break down with this issue.
The Issue
The first time i ran this, i tested it by saving only the first line (the product name) to core data and then printing it off. It worked perfect. After that, i tried the same method for all of them, and then tried printing. My program would set a breakpoint next to the "entity1.setValue(three, forKey: "serialNo")."
I also get a message in the debugger area that says (lldb).
If i try to step through the breakpoint, everything just prints out as 'nil'.
CODE
#IBAction func saveButton(sender: AnyObject) {
let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObjectForEntityForName("UsedInfo", inManagedObjectContext:context) as NSManagedObject
let one = pickerTextField.text
let two = modelName.text
let three = serialNo.text
let four = YOM.text
let five = engineHours.text
let six = locationOfMachine.text
entity1.setValue(one, forKey: "product")
entity1.setValue(two, forKey:"modelName")
entity1.setValue(three, forKey:"serialNo")
entity1.setValue(four, forKey:"yom")
entity1.setValue(five, forKey:"engineHours")
entity1.setValue(six, forKey:"location")
print(entity1.valueForKey("product"))
print(entity1.valueForKey("modelName"))
print(entity1.valueForKey("serialNo"))
print(entity1.valueForKey("yom"))
print(entity1.valueForKey("engineHours"))
do {
try context.save()
}
catch {
print("error")
}
}
I am using uicollectionview to display some photos and I have a button that allows the user to delete the selected photo.
It works perfectly unless I try to delete the last photo in the array of photos being used to populate the uicollectionview. Ie if there are 5 photos then there will be a problem if a user removes the 5th photo but not the 1st, 2nd, 3rd or 4th. When I try to delete the 5th photo it crashes on reloadData() with the following error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 4 from section 0 which only contains 4 items before the update'
I don't understand why this would happen... The error even mentions "attempt to delete" but I never actually told it I was deleting anything. I just changed the source and then asked it to update. Also in the error message it says the section only contains 4 items before the update when their were actually 5 photos. Why?
A little bit more info about what I'm doing and how (Using Swift)...
I've got a ProgressPhotoSheetClass from which I have instantiated the object progressPhotoSheet
this progressPhotoSheet object has an array of photos and the photos can have priorities such as photo id, title etc
in my number of items in section I use
var numPics: Int = progressPhotoSheet.progressPhotos.count
return numPics
in my cellForItemAtIndexPath I use
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoHolder
cell.photoTitle.text = progressPhotoSheet.progressPhotos[indexPath.row].photoName
...etc..
When deleting the item I use
progressPhotoSheet.deletePhoto(progressPhotoSheet.progressPhotos[indexPath.row].photoId)
which deletes the photo from the device and from my core data and then reloads progressPhotoSheet.progressPhotos from using the modified core data so it is now exactly how it was before but without the deleted photo.
I then call
self.collectionView.reloadData()
Which should update the UICollectionView for the new data
I could understand if it felt there was a mismatch between what should be in the collection view and what is in the datasource if I were using
self.collectionView.deleteItemsAtIndexPaths(indexPaths)
because that would be saying ignored to get them to match we need to delete one item - here there is a possibility something could mismatch.. But surely using self.collectionView.reloadData() it doesn't matter what changes were made it should just look at what data is there now and update the UICollectionView accordingly....
So my question is... Why am I getting this error and what should I do to fix things so I don't get it?
Edit to include more info
Here is my telephoto Code
func deletePhoto(photoId: Int) {
// Set up Core Data Managed Object Context
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
// Fetch correct photo
let fetchRequest = NSFetchRequest(entityName: "CDProgressPhoto")
fetchRequest.predicate = NSPredicate(format: "photoId = %#", String(photoId))
// Save
if let fetchResults = managedContext.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
if fetchResults.count != 0{
// Will only be one photo with this photo id
var photo = fetchResults[0]
photo.setValue(true, forKey: "toDelete")
// Save the object
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
}
}
// Reload from core data
self.loadPhotoSheetFromCoreData()
}
self.loadPhotoSheetFromCoreData() then empties progressPhotoSheet.progressPhotos before getting the new data from core data... Code below...
private func loadPhotoSheetFromCoreData() {
if(self.hasPhotoSheet()) {
// Clear existing photos
self.progressPhotos = []
// Set up Core Data Managed Object Context
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "CDProgressPhoto")
let predicate1 = NSPredicate(format: "photoSheetId == %#", String(self.sheetId))
let predicate2 = NSPredicate(format: "toDelete == %#", false)
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false) as NSSortDescriptor]
var predicatesArray: [NSPredicate] = [predicate1, predicate2]
//predicatesArray.append(predicate1)
request.predicate = NSCompoundPredicate.andPredicateWithSubpredicates(predicatesArray)
let existings = managedContext.executeFetchRequest(request, error: nil)
let existingPhotos: [CDProgressPhoto] = existings as! [CDProgressPhoto]
// for each photo make a ProgressPhoto object and add to progress photos array
for photo in existingPhotos {
var newPhoto: ProgressPhoto = ProgressPhoto()
newPhoto.photoSheetId = Int(photo.photoSheetId)
newPhoto.photoId = Int(photo.photoId)
newPhoto.photoName = photo.photoName
newPhoto.date = Int(photo.date)
newPhoto.url = photo.url
newPhoto.filename = photo.filename
newPhoto.height = Float(photo.height)
newPhoto.width = Float(photo.width)
newPhoto.selected = false
self.progressPhotos.append(newPhoto)
}
}
}
As you can see the photo isn't actually deleted at this point I just set a toDelete flag to true and then only re load items where toDelete is set to false. The photos are deleted later asynchronously depending on network connection etc because they are also stored on a server for use on the main website.
Have you tried calling invalidateLayout() on the collectionView? That might help incase your view is empty i.e. 0 elements are present.
I am using the code below to fetch the objects in an array. In my program, I have an array displayed in a tableview and when a cell is tapped, it leads to another array displayed in a tableview. The user can add cells in both of these tableviews. What is happening is that when I create new rows in my second tableview, go back, and tap the same cell that got me there, I notice that the objects I created are not there (they were reassigned to another cell). I believe that the problem lies in the line: routines = results. What I think is happening, is that when I tap back in my second view, the line routines = results is called again, and because results is by nature unordered, it messes up the order of my previously established routines array.
var routines = [NSManagedObject]()
override func viewWillAppear(animated: Bool) {
self.navigationItem.leftBarButtonItem = self.editButtonItem()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "Routine")
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [NSManagedObject]?
if let results = fetchedResults {
routines = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
I agree with the comment from Jonathan. You are just making work for yourself holding onto everything in arrays. Use the tools that are designed to work with both Core Data and UITableView controllers and you will have a much easier (and more maintainable) time.
I've managed to sort the data received from my core data alphabetically, and now I want put the usernames in sections for each letter. I understand that the easiest way to do this is by using NSFetchedResultsController, but I can't figure out how to use this (very few tutorials covering Swift).
So my code looks like this:
override func viewDidAppear(animated: Bool) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Message")
let en = NSEntityDescription.entityForName("Message", inManagedObjectContext: context)
let fetchRequest = NSFetchRequest(entityName: "Message")
let sortDescriptor = NSSortDescriptor(key: "username", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
myList = context.executeFetchRequest(fetchRequest, error: nil) as [Model]
tv.reloadData()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return myList.count
}
I was hoping someone could shed some light over the NSFetchedResultsController and help me get on my way.
If I'm not completely wrong, the initialization looks something like this, although I can't figure out the "cache name":
let resultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: myList, sectionNameKeyPath: "username", cacheName: <#String?#>)
Any suggestions on how to proceed would be appreciated.
You can set up the fetched results controller easily by copying and tweaking the code from the Xcode template. (Create a new project, Master-Detail, check "Use Core Data", copy from MasterViewController.)
The following is not the ideal solution, but I think it is appropriate for your level of experience. When you add the username attribute to the Message entity object, also add another attribute with the first letter. Then use the name of this new attribute as the sectionNameKeyPath parameter when creating the fetched results controller.
Don't worry too much about the cache parameter. You can just put any string there, such as "Root", or even pass nil to not use cache which is also fine in most use cases.