When come to circular reference, it comes with risk of memory leaking, by not using a weak
keyword. For instance :-
Memory leak without using weak
class Human {
deinit {
print("bye bye from Human")
}
init(_ pet: Pet) {
self.pet = pet
}
let pet: Pet
}
class Pet {
deinit {
print("bye bye from Pet")
}
var human: Human?
}
print("start of scope")
if true {
let pet = Pet()
let human = Human(pet)
pet.human = human
print("going to end of scope")
}
print("end of scope")
/*
Output:
start of scope
going to end of scope
end of scope
*/
No memory leak by using weak
class Human {
deinit {
print("bye bye from Human")
}
init(_ pet: Pet) {
self.pet = pet
}
let pet: Pet
}
class Pet {
deinit {
print("bye bye from Pet")
}
weak var human: Human?
}
print("start of scope")
if true {
let pet = Pet()
let human = Human(pet)
pet.human = human
print("going to end of scope")
}
print("end of scope")
/*
Output:
start of scope
going to end of scope
bye bye from Human
bye bye from Pet
end of scope
*/
In CoreData, when setup 2 entities with one-to-many relationship, it is recommended to have inverse relationship too. Hence, CoreData will generate the following class with circular reference.
extension NSHolidayCountry {
@nonobjc public class func fetchRequest() -> NSFetchRequest<NSHolidayCountry> {
return NSFetchRequest<NSHolidayCountry>(entityName: "NSHolidayCountry")
}
@NSManaged public var code: String
@NSManaged public var name: String
@NSManaged public var holidaySubdivisions: NSOrderedSet
}
extension NSHolidaySubdivision {
@nonobjc public class func fetchRequest() -> NSFetchRequest<NSHolidaySubdivision> {
return NSFetchRequest<NSHolidaySubdivision>(entityName: "NSHolidaySubdivision")
}
@NSManaged public var code: String
@NSManaged public var name: String
@NSManaged public var holidayCountry: NSHolidayCountry?
}
(One to many relationship found in entity NSHolidayCountry)
(One to One inverse relationship found in entity NSHolidaySubdivision)
NSHolidaySubdivision
is having inverse relationship to NSHolidayCountry
.
However, such inverse relationship is not marked as weak
, based on CoreData generated class.
I was wondering, does this come with a memory leak risk? Should I, add a weak
keyword manually in entity NSHolidaySubdivision's holidayCountry
?
2
Answers
As per the docs state at Breaking Strong References Between Objects, a strong reference cycle is actually created in such cases. In order to break those, you should manually fault the objects again when no longer needed:
Although this question asks about CoreData specifically, the underlying issue is more generic, so I’ll try to answer as such:
weak
references are one way to handle retain cycles. It’s typically the best way to handle them, because in most cases you have a conceptual ownership relation that leads to the cycle. Like in your example, although you set up both objects individually (i.e.Pet
is not created insideHuman
‘s initializer), the names alone imply that the human is the owner of the pet, quite literally and conceptually.This is not the general case for systems like Core Data. Although in your example you could conceptually claim that
NSHolidaySubdivision
"belongs to"NSHolidayCountry
, this is not necessarily the case for any object graph (i.e. every data model is different! 😃).It would be tedious to have to define that explicitly for all different entities, but there is an easier solution anyway:
Remember that the
NSManagedObject
s are, well managed. TheNSManagedObjectContext
takes care of all necessary tasks that need to be performed in regards to their lifetime. It has to do that anyway, because unlike other types, they are tied to the actual store, i.e. if an object is to be deleted it is not sufficient to simply deinit its instance, it needs to be deleted from the underlying store as well. In fact, it may well be possible that an object may be deinited (because it goes out of scope and has no relationships with others), but the underlying store should not be modified! Lifetime is simply much more complicated.So while the context takes care of all the various things it needs to do to be able to persist and load the object graph from the store, it (most likely) also takes care of the properties that represent entity relationships. In fact, it needs the circular relationships to be able to properly figure out which other objects are affected.
This is especially true because we have deletion rules: A relationship becoming
nil
indicates that the object was deleted from a store bysave()
ing a context if the deletion rule is "Nullify". Aweak
property could also becomenil
simply because the object it points to has gone out of scope somewhere else. So you need the context to manage the object graph anyway,weak
does not help you, but its lack also does not lead to a memory leak.Disclaimer: I come to these conclusions via long time usage of the framework, I am not working for Apple and do not have insight into Core Data’s actual implementation.
However, I do know what is necessarily, in principle, to achieve what Core Data does, so I dare say my educated guess here can’t be all that wrong.