please i have an problem with perform segue with identifier in table view didSelectRow method every time i tapped the cell the memory is increasing
the following is my code :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// firstly i need to check if edit button is true so i can select cell
if isShowToolBar {
// her for hold selected index
let cell = tableView.cellForRow(at: indexPath) as? MovieDownloadedTableViewCell
if let cell = cell {
cell.movieCheckMarkImageView.isHidden = false
cell.movieEmptyCircleImageView.isHidden = true
operationDocumentDirectoryObject.dictionaryHoldIndexCellForDisplayWhichCellSelected.updateValue(indexPath.row, forKey: indexPath.row)
// start hold URL
operationDocumentDirectoryObject.dictionaryForHoldURLSelected.updateValue(operationDocumentDirectoryObject.arrayOfMovieURL![indexPath.row], forKey: indexPath.row)
}// end the if let cell
}else{
// her for show the content folder
let cell = tableView.cellForRow(at: indexPath) as? MovieDownloadedTableViewCell
if let cell = cell {
if cell.fetchURL!.pathExtension == "" {
performSegue(withIdentifier: "ShowFolder", sender: indexPath.row)
}else{
// playing the video
performSegue(withIdentifier: "PlayingMovie", sender: cell.fetchURL!.lastPathComponent)
}// end the if for check path extenstion
}// end the if let cell
cell = nil
}// end the if for the isShowToolbar
}
the above method have memory leak in perform segue and cause increasing memory with the (if cell.fetchURL!.pathExtension == "") also make memory leak
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MoveFile" {
if let destination = segue.destination as? MoveMovieViewController {
destination.operationDocumentDirectoryObject.dictionaryForHoldURLSelected = self.operationDocumentDirectoryObject.dictionaryForHoldURLSelected
}
}else if segue.identifier == "ShowFolder" {
if let destination = segue.destination as? ShowContentFolderMovieViewController {
if let fetchIndex = sender as? Int {
destination.operationDocumentDirectory.folderName = self.operationDocumentDirectoryObject.arrayOfMovieURL![fetchIndex].lastPathComponent
}
}
}else if segue.identifier == "PlayingMovie" {
// make an object for the playing video view controller
if let destination = segue.destination as? PlayingMovieViewController {
if let movieName = sender as? String {
destination.operationDocumentDirectory.movieName = movieName
}
}
}// end the condition for the segue
}
although the deinit is call success in the view controller but i have still leaking and increasing memory
please help for what is my wrong code?
thank you very much
I suspect you have a strong reference cycle somewhere in your code - although, not in the bit that you have posted as far as I can tell. However, you say that your deinit is being called successfully. Did you check to see if the deinits of all of the view controllers you are segueing to are being called at the expected time (such as when you dismiss PlayingMovieViewController or ShowContentFolderMovieViewController)? Observing the xcode debug console for deinit print statements to check for the appropriate releases in memory is the best way to go. Your memory leak could be coming from somewhere else though. You should look out for strong references elsewhere in your code (maybe a delegate holding on to sender objects?).
Related
developing a little app for my comic collection encountered this issue:
in my second "add comic" VC I have a button and the func below, but I save TWICE entities in manged context (ate least, I think this is the issue)
for example if I have 2 comics yet shown in main VC tableview, go to "add comic VC" and save a third one, going back to main VC I'll print 3 objects with title, number etc but also print 2 new objects with no data as I had saved twice a manger context a "right one" and another one with same number of object but empty. If I keep adding a 4th comic, I'll get 6 complete comic + the 4th and more 6 "blank itmes" with default values "no title"
let kComicEntityName = "Comic"
func addingSingleComic(gotTitle: String, gotIssue: Int16, gotInCollection: Bool ) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: kComicEntityName, in: managedContext)!
let comicToAdd = Comic(entity: entity, insertInto: managedContext)
comicToAdd.comicTitle = gotTitle
comicToAdd.issueNumber = gotIssue
comicToAdd.inCollection = gotInCollection
do {
try managedContext.save()
} catch let error as NSError {
print("could not save. \(error), \(error.userInfo)")
}
print("new single comic crated: title: \(comicToAdd.comicTitle ?? "!! not title !!"), n. \(comicToAdd.issueNumber), owned?: \(comicToAdd.inCollection)")
}
in the main VC I use this to check items in core data
func asyncPrintEntities() {
self.asyncComicEntityArray.removeAll(keepingCapacity: true)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
let comicFetch : NSFetchRequest<Comic> = Comic.fetchRequest()
asyncFetchRequest = NSAsynchronousFetchRequest<Comic>(fetchRequest: comicFetch) {
[unowned self] (result: NSAsynchronousFetchResult) in
guard let AllComicEntityResult = result.finalResult else {
return
}
self.asyncComicEntityArray = AllComicEntityResult
//************************************
do {
self.asyncComicEntityArray = try managedContext.fetch(comicFetch)
if self.asyncComicEntityArray.count > 0 {
print("Ok! model is not empty!")
} else {
print("No entites availabe")
}
} catch let error as NSError {
print("Fetch error: \(error) description: \(error.userInfo)")
}
guard self.asyncComicEntityArray != nil else {return}
for comicFoundInArray in self.asyncComicEntityArray {
let entity = NSEntityDescription.entity(forEntityName: self.kComicEntityName, in: managedContext)!
var comicTouse = Comic(entity: entity, insertInto: managedContext)
// var comicTouse = Comic() //to be deleted since this kind of init is not allowed, better above insertInto
comicTouse = comicFoundInArray as! Comic
print("comic title: \(comicTouse.comicTitle ?? "error title"), is it in collection? : \(comicTouse.inCollection)")
}
self.MyTableView.reloadData()
//************************************
}
// MARK: - async fetch request 3
do {
try managedContext.execute(asyncFetchRequest)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
//end of function
}
In your addingSingleComic you create a new Comic here:
let comicToAdd = Comic(entity: entity, insertInto: managedContext)
Then you assign values to the object's properties.
Separately, in asyncPrintEntities, you also create new Comic objects here:
var comicTouse = Comic(entity: entity, insertInto: managedContext)
This time you do not assign values to the object's properties. They will have no title, etc, because you created them but never assigned a title. This line executes once for every object in asyncComicEntityArray, so if the array has two objects, you create two new objects that contain no data. You don't use comicToUse anywhere except in the one print, but it still exists in the managed object context and will still get saved the next time you save changes.
This is why you're getting extra entries-- because you're creating them in this line of code. It's not clear why you're creating them here. You just executed a fetch request, and then you immediately create a bunch of no-data entries which you don't use. It looks like that entire for loop could just be deleted, because the only thing it does is create these extra entries.
I found the following issue in my memory. I couldn't understand it.
error evaluating expression “(CAListenerProxy::DeviceAggregateNotification *)0x7cee3d60”: error: use of undeclared identifier 'CAListenerProxy'
error: expected expression
I used two notification centers - one for sending the object to another view and the other in another view to send the dictionary which contain objects after deleting one from it.
My code is :
// only for delegate method for the downloading videos
extension WebViewController {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// her i need to get the data from the movie which i download it so i can save it in the document directory
if let fetchDataFromDownloadFile = try? Data(contentsOf: location) {
// generate the fileName randamlly
let createFileName = UUID().uuidString
// generate object for save file
let operationDocumentDirectory = OperationDocumentDirectory()
operationDocumentDirectory.saveMovie(movieName: createFileName, data: fetchDataFromDownloadFile)
}// end the if let for the fetch data from download file
// her for fetch the download video object when i save it to set it to ni for free the memory
if let fetchURL = downloadTask.originalRequest?.url {
var fetchObject = operationObject?.dictionaryOfDownloadVideo?.removeValue(forKey: fetchURL)
// for stop the downloadtask when finish download
if fetchObject?.videoURL == downloadTask.originalRequest?.url {
downloadTask.cancel()
fetchObject = nil
}
// for update the badge after finish download movie
DispatchQueue.main.async {[weak self] in
if let mySelf = self {
// set badge for nil if the objects zero
if operationObject.dictionaryOfDownloadVideo?.count == 0 {
self?.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = nil
}else{
// if the object not zero update the badge
mySelf.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = "\(operationObject.dictionaryOfDownloadVideo!.count)"
}
}// end the if for myself
}// end the dispatchqueue.main
// update the data in table view
NotificationCenter.default.post(name: NOTIFICATION_UPDATE_TABLEVIEW, object: nil)
}// end the fetch url
}
// and this code in another view for updating table view when i currently downloading movie
extension MovieDownloadingViewController {
// data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? DownloadingTableViewCell
if let cell = cell {
cell.movieObject = arrayOfObjects?[indexPath.row]
cell.movieDeleteButton.tag = indexPath.row
cell.movieDeleteButton.addTarget(self, action: #selector(self.deleteCurrentDownloadingMovie(sender:)), for: .touchUpInside)
}
return cell!
}
func deleteCurrentDownloadingMovie(sender:UIButton){
displayAlertDeleteMovie(arrayOfObject: arrayOfObjects!, index: sender.tag)
}
func displayAlertDeleteMovie(arrayOfObject:[DownloadVideo],index:Int) {
let alertController = UIAlertController(title: "Delete Movie", message: "Do You Want Delete Movie \(arrayOfObject[index].videoName)", preferredStyle: .actionSheet)
let alertDelete = UIAlertAction(title: "Delete", style: .default) {[weak self] (alertAction:UIAlertAction) in
var fetchObjectMovie = self?.arrayOfObjects?.remove(at: index)
// set the notification for update the number of element in dict and array
NotificationCenter.default.post(name: NOTIFICATION_UPDATE_NUMBER_OF_ARRAY_DICT, object: fetchObjectMovie?.videoURL)
if fetchObjectMovie != nil {
fetchObjectMovie = nil
}
// update table view
// self?.tableView.reloadData()
// update the badge in the tab bar controller
if operationObject.dictionaryOfDownloadVideo?.count == 0 {
self?.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = nil
}else{
self?.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = "\(operationObject.dictionaryOfDownloadVideo!.count)"
}
}
let alertCancel = UIAlertAction(title: "Cancel", style: .cancel) { [weak self](alertAction:UIAlertAction) in
self?.dismiss(animated: true, completion: {})
}
alertController.addAction(alertDelete)
alertController.addAction(alertCancel)
present(alertController, animated: true, completion: nil)
}
please any help
thanks a lot
Is there a way to do a batch delete of all data stored in all of the entities in core data?
I read somewhere that in iOS 9 or 10 that apple introduced a way to do batch deletes, but I can't seem to find any good information on it.
Ultimately, I just need a function that goes through an entity, and deletes all of the data in it. Seems like it should be simple enough, but documentation/tutorials for it have proven exceedingly difficult to find.
Any thoughts?
Edit
I added the following code into an IBAction attached to a button:
#IBAction func clearAllData(_ sender: AnyObject) {
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PLProjects")
let request = NSBatchDeleteRequest(fetchRequest: fetch)
//get the data from core data
getPLData()
//reload the table view
tableView.reloadData()
}
This does not seem to work however. If I close down the project and reopen it, the data is still there. I am assuming this is also why the table view doesn't update, because the data is not actually being deleted.
You're thinking of NSBatchDeleteRequest, which was added in iOS 9. Create one like this:
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Employee")
let request = NSBatchDeleteRequest(fetchRequest: fetch)
You can also add a predicate if you only wanted to delete instances that match the predicate. To run the request:
let result = try managedObjectContext.executeRequest(request)
Note that batch requests don't update any of your current app state. If you have managed objects in memory that would be affected by the delete, you need to stop using them immediately.
To flesh out Tom's reply, this is what I added to have a complete routine:
func deleteAllRecords() {
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "CurrentCourse")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try context.execute(deleteRequest)
try context.save()
} catch {
print ("There was an error")
}
}
Declare the Method for getting the Context in your CoreDataManager
Class
class func getContext() -> NSManagedObjectContext {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
}
if #available(iOS 10.0, *) {
return appDelegate.persistentContainer.viewContext
} else {
return appDelegate.managedObjectContext
}
}
Call the above method from your NSManagedObject subClass:
class func deleteAllRecords() {
//getting context from your Core Data Manager Class
let managedContext = CoreDataManager.getContext()
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Your entity name")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try managedContext.execute(deleteRequest)
try managedContext.save()
} catch {
print ("There is an error in deleting records")
}
}
My managedObjectContext hierarchy is as follows: (PSC)<-(writerMOC -- private)<-(mainMOC -- main)<-(backgroundMOC -- private)
I have an NSManagedObject who "name" property is "Banana".
In the backgroundMOC, I get a reference to the object with backgroundMOC.objectWithID, change the NSManagedObject's "name" property to "Apple", and subsequently set it's "syncStatus" property to 1 (flagged for synchronization), then recursively save the moc's with the following routine:
func saveManagedContext(moc: NSManagedObjectContext, shouldSync: Bool = true, completion: (() -> Void)? = nil)
{
print("\nSaving managed object context...")
do {
try moc.save()
if let parentContext = moc.parentContext {
parentContext.performBlock {
self.saveManagedContext(parentContext, shouldSync: shouldSync, completion: completion)
}
}
else {
if shouldSync { SyncEngine.sharedInstance.synchronize(shouldPushUpdates: true) }
completion?()
}
print("Finished saving managed object context...")
} catch {
logger.error("\(error)")
}
}
Once the last moc is saved, a sync routine is called which does its work on the backgroundMOC, which queries the local store for all objects whose syncStatus is 1, again this fetch is called on the backgroundMOC.
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
This correctly returns the updated object in the array, however, that object's syncStatus property equals 0, and its "name" property is still set to "Banana".
This is really causing me headaches, I felt like i had totally understood how managedObjectContext blocks should work, but this has proven to be quite a puzzle.
UPDATE
Here's the code that prompts the update. This is called from the main thread when the cell is tapped.
func updateNameForCell(cell: UITableViewCell)
{
///gets the object id from the fetchedResultsController
guard let fruitMetaID = tableController.objectIDForCell(cell) else { return }
let backgroundMOC = CoreDataController.sharedInstance.newBackgroundManagedObjectContext()
backgroundMOC.performBlock {
do {
guard let fruit = (backgroundMOC.objectWithID(fruitMetaID) as? FruitMetaData)?.fruit else {
throw //Error
}
print(fruit.name) // "Banana"
fruit.name = "Apple"
fruit.needsSynchronization() //Sets syncStatus to 1
CoreDataController.sharedInstance.saveManagedContext(backgroundMOC)
}
catch {
//handle error
}
}
}
UPDATE AGAIN
Maybe I'm not creating the contexts right. Enlighten me please!
/// The parent to all other NSManagedObjectContexts. Responsible for writting to the store.
lazy var writerManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
}
return managedObjectContext
}()
lazy var mainManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.parentContext = self.writerManagedObjectContext
}
return managedObjectContext
}()
/// The context associated with background syncing..
func newBackgroundManagedObjectContext() -> NSManagedObjectContext
{
let backgroundManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
backgroundManagedObjectContext.performBlockAndWait {
backgroundManagedObjectContext.parentContext = self.mainManagedObjectContext
}
return backgroundManagedObjectContext
}
Holding onto child MOCs (children of the main context) is fraught with issues. I would recommend creating a new child (aka backgroundMOC) for each operation that you do.
Without seeing all of your code this looks like an issue with the child context getting out of sync.
Update
Assuming that your creation of the backgroundMOC sets the mainMOC as its parent then I wonder about the -objectWithID: and what it is returning.
I also wonder about your -performBlock: calls. In my head the threading looks fine but better to test. Try changing to -performBlockAndWait: just to test and see if there is a threading race condition. Not a permanent change but eliminates that part of the code as a source of the issue.
Before fetchRequest is called, you should reset context.
backgroundMOC.reset() // add this line
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
The reason is FruitMetaData is an object(or class) so changing one of its properties/Core Data attributes does not register as a change to the results array ... the object references in the array remain the same.
And NSFetchRequest still returns the same result(by using cache). When use context.reset().This tells the context in the extension to fetch new data every time and ignore the cache.
I have really big problems when i try to compile my PrepareForSegue function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "findMap" {
let MapViewController = segue.destinationViewController as UIViewController
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()!
let destinationTitle = filteredDepartments[indexPath.row].name
MapViewController.title = destinationTitle
} else {
let indexPath = self.tableView.indexPathForSelectedRow()!
let destinationTitle = departments[indexPath.row].name
MapViewController.title = destinationTitle
}
}
}
The error opens in the Thread section in the "trap"-row:
--> 0x2f6e18: trap
and the error code is as above:
--> Thread 1: EXC_BREAKPOINT(code=EXC_ARM_BREAKPOINT, subcode=0xdefe
I think the error is in this line:
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
Bur i don't know how to solve it , so please help me ...
It's hard to tell what your code is supposed to mean, but my guess is that you mean this:
if sender === self.searchDisplayController!.searchResultsTableView {
Notice the use of the triple-equals operator to mean "is the same object as".