small medium large xlarge

Generic-user-small
03 Feb 2017, 13:55
Tobias Haehnel (6 posts)

The addition of child managed objects to “to many” relationships in the following code sample creates retain cycles.

PPRecipes/PPRecipes/ExportImportHandler.swift


  if relDesc.toMany {
    guard let childArray = childStructure as? [[String: AnyObject]] else {
      fatalError("To many relationship with malformed JSON")
    }
    var children = [NSManagedObject]()
    for child in childArray {
      let mo = createChild(childDict: child, entity: destEntity, moc: moc)
      children.append(mo)
    }
    object.setValue(children, forKey: name)
  } else {
    ...
  } ---

After the Import, the memory will not be released until the app starts again, or the imported root managed object will be deleted.

I tried at various points to turn managed objects into faults, resetting the import and parent contexts or using autoreleasepools, but all I get is errors or leaked memory.

There isn’t the memory issue, If I comment/ remove the to-many code part.

Can you please provide a solution to free the memory?!

Avatarsmall_pragsmall
03 Feb 2017, 18:31
Marcus S. Zarra (284 posts)

Assuming you are not holding onto references and Core Data is the only framework with a reference to the memory then the short answer is to not worry about it.

Core Data has its own internal reference management and it will release objects as needed when memory pressure increases to an unacceptable level. It receives level 1 memory warnings (your app only receives level 2 and 3) and it will respond before you application is notified of an issue.

If you want to force Core Data to release the memory then you need to reset the associated NSManagedObjectContext as well as remove any strong references to the parent object.

Generic-user-small
04 Feb 2017, 09:17
Tobias Haehnel (6 posts)

Yes, I want to force Core Data to release the memory. I reseted the importContext, after I saved it. The parent context has an unowned reference in the ImportOperation Class. And even If I reset the parentContext, the memory will not be released.

I think the strong reference cycles created while creating the to-many-relationship can’t be solved by Core Data. Therefor Apple has emphasised the use of refreshObject to turn Managed Objects into faults in it’s `Core Data Programming Guide - Breaking Strong References Between Objects

But If I use it after saving the importContext, it has no effect or before it produces errors.

And what I don’t understand, that the “unreleased memory” is transferred to the mainContext/parentContext, while the Importer Class and importContext is deallocated and the imported root object isn’t fetched or used then in the mainContext.

Avatarsmall_pragsmall
04 Feb 2017, 16:16
Marcus S. Zarra (284 posts)

First, forcing Core Data to unload all of the memory it is retaining is a bad idea. The entire reason why Core Data is holding onto that memory is to improve performance and responsiveness. If you unload the memory the performance will suffer; dramatically.

Having said that, here are a few points:

  • If you save the import context the data gets moved from the import context to the parent context, usually the view or UI context.
  • Forcing the view context to unload its memory requires tearing down the UI as the UI usually holds references as well
  • Even if you destroy the UI context it is possible that the NSPersistentStoreCoordinator will hold onto references in its cache; again for performance. Therefore, to achieve 100% memory reclaim you will need to tear down the entire Core Data stack.

Again, this is designed this way intentionally. Core Data handles its own memory. Unless you are seeing a leak in your code in the leaks Instrument it is best to let Core Data manage its memory.

With regard to the article you referenced, that discussion is the exact opposite of what you are trying to accomplish. That article discusses how to force Core Data to retain references that it otherwise wants to dispose of. You are trying to dispose of objects that Core Data is keeping.

Generic-user-small
06 Feb 2017, 14:52
Tobias Haehnel (6 posts)

So you mean:

  1. The/ Your code example of creating to-many-rel. doesn’t create retain cycles.
  2. Even if 1. does, Core Data knows how to solve these cycles internally

Is it correct?

Avatarsmall_pragsmall
06 Feb 2017, 15:26
Marcus S. Zarra (284 posts)

The code I presented in the book does not create retain cycles, correct.

If your code intentionally creates a retain cycle then your code needs to handle that. Otherwise, Core Data handles its own internal ownership and memory with regard to relationships (and primitive objects as well).

Generic-user-small
06 Feb 2017, 15:40
Tobias Haehnel (6 posts)

Regarding 1.

But when you have a many to many relationship. Then creates your code strong references to an objects children (children array) and for the inverse relationship also strong references, doesn’t it?

Avatarsmall_pragsmall
06 Feb 2017, 16:17
Marcus S. Zarra (284 posts)

When you have a many to many relationship between MO_A and MO_B there is a collection on either end. When you create that relationship, MO_A will have a reference to a collection and MO_B will have a reference to a different collection. Both of these collection objects will have references to the other managed object:

  • MO_A_collection will have a reference to MO_B
  • MO_B_collection will have a reference to MO_A

These collection objects are controlled and maintained by Core Data.

There is no circular reference between objects.

Do you have a specific code example from the book that you are questioning? It is easier to discuss this subject with code rather than in generalities.

Generic-user-small
06 Feb 2017, 19:53
Tobias Haehnel (6 posts)

Just the code sample from the first post. I applied your importer code and main/writer context principle to my datamodel. When I do an extreme import of a user profile with a 200MB file, the memory consumption increases from 21MB idle to 445MB top. After the import, again in idle, the memory consumption stays at 153MB.

When I delete the imported profile the memory consumption drops to 24MB.

If I uncomment the for-loop and setValue method in the to-many-relationship part, the memory consumption goes back down to 23MB directly after the import.

Avatarsmall_pragsmall
07 Feb 2017, 03:37
Marcus S. Zarra (284 posts)

That is a significant difference. Do you have a sample project that I can look at and tinker with?

Can you create one?

A) It would hopefully help me to help you find the issue; or B) Be the first step to a radar if it truly is an internal problem with Core Data.

Avatarsmall_pragsmall
07 Feb 2017, 20:05
Marcus S. Zarra (284 posts)

Received, I will look at it asap although it might not be for a couple of days.

You must be logged in to comment