Question

I have two Objective-C objects that relate to each other in some way. You may think of this as a two-way relationship. With ARC, I understand that the parent should hold a strong reference to its child object, while the child holds a weak reference pointing back to its parent. But what if the parent can be either object? Or if the objects are "siblings"?

Let's say I have a Person class and I would like to instantiate two objects whose brother properties point to each other.

@implementation Person

@property (strong, nonatomic) Person *brother;

Person personA = [Person new];
Person personB = [Person new];

personA.brother = personB;
personB.brother = personA;

Wouldn't this lead to a retain cycle?

Here's another scenario: Let's say I have an Appointment class and Staff class.

@implementation Appointment

@property (strong, nonatomic) Staff *staff;


@implementation Staff

@property (strong, nonatomic) NSArray *appointments;

On a view staff, I may want to show all the staff member's appointments. Therefore I might instantiate all my objects like so...

Staff *bob = [Staff new];

Appointment *apptA = [Appointment new];
Appointment *apptB = [Appointment new];

apptA.staff = bob;
apptB.staff = bob;

bob.appointments = [NSArray arrayWithObjects:apptA, apptB, nil];

Wouldn't this lead to a retain cycle since all the references are strong?

Finally, consider this scenario: Let's say I change Appointment's staff property to weak.

@implementation Appointment

@property (weak, nonatomic) Staff *staff;

This may solve the issue for my second (above scenario), but what if I am creating a new appointment and I want to attach a new staff member, then pass the object somewhere else for processing?

+ (void)buildAppointment {
    Appointment *appt = [Appointment new];
    Staff *staff      = [Staff new];

    appt.staff = staff;

    [self saveAppointment:appt];
}

+ (void)saveAppointment:(Appointment *)appt {
    // Do something with appt here.

    // Will I still have appt.staff?
}

Because my staff property on Appointment is now weak, is there the potential for it to be set to nil when garbage collection runs (as there are no strong references to the staff object)?

EDIT: As @dasblinkenlight explained, the app.staff object would still exist as the local staff variable (from buildAppointment) is still on the stack. However, what if I had:

+ (void)createAndSaveAppointment {
    Appointment *appointment = [self createAppointment];

    [self saveAppointment:appointment];
}

+ (Appointment *)createAppointment {
    Appointment *appt = [Appointment new];
    Staff *staff      = [Staff new];

    appt.staff = staff;

    return appt;
}

+ (void)saveAppointment:(Appointment *)appt {
    // Do something with appt here.

    // Will I still have appt.staff?
}

My co-workers seem to have handled it by using two properties, one strong and the other weak:

@implementation Appointment

@property (strong, nonatomic) Staff *staffStrong;
@property (weak, nonatomic) Staff *staffWeak;

- (Staff *)staff {
    if (staffStrong != nil) {
        return staffStrong;
    }

    return staffWeak;
}

- (void)setStaffStrong:(Staff *)staff {
    staffStrong = staff;
    staffWeak   = nil;
}

- (void)setStaffWeak:(Staff *)staff {
    staffStrong = nil;
    staffWeak   = staff;
}

Then when setting the staff property, they would use the setStaffStrong or setStaffWeak as appropriate. However this seems very hacky — surely there is a more elegant solution? How would you build your classes to handle my above scenarios?

PS: My apologizes for the long question; I tried to explain it as best I could. :)

Was it helpful?

Solution

One general note on the strong vs. weak reference: strong references denote ownership, while weak references denote association. When neither object owns the other, there's usually other objects that own them, or they both have strong references coming from local variables.

Wouldn't [the Person class] lead to a retain cycle?

Yes, it would, because a Person owns his brother, but he shouldn't: this should be a weak property. It should be OK, because there should be another object (a list of all persons, a dictionary organizing persons by name, or something like it) that owns all of the Person objects. As long as a Person object is in that collection of persons, it's not going to be released.

Wouldn't [Appointment and Staff] lead to a retain cycle since all the references are strong?

Correct, that is what is going to happen. NSArray retains objects that go into it, closing the loop on the retain cycle. Note that you cannot make NSArray weak to break that cycle: it needs to be Staff that gets weak, not appointments.

Finally, consider this scenario: Let's say I change Appointment's staff property to weak. This may solve the issue for my second (above scenario), but what if I am creating a new appointment and I want to attach a staff member, then pass the object somewhere else for processing?

There's no problem with that: stack variable staff has a strong reference on it, so it wouldn't get released.

Because my staff property on Appointment is now weak, is there the potential for it to be set to nil when garbage collection runs (as there are no strong references to the staff object)?

ARC is not a garbage collection system: retains and releases happen at specific, deterministic time points. The staff is not going to be released, because Staff *staff = [Staff new]; is strong by default.

EDIT : The edited example will indeed release the Staff object. However, such example is unusual, because it is unlikely that your createAppointment would make a new instance of Staff. Rather, it would grab an existing instance from some registry containing all members of staff, like this:

+ (Appointment *)createAppointment {
    Appointment *appt = [Appointment new];
    // Registry gives you a reference to an existing Staff.
    // Since the object remains registered, that's not the last strong reference.
    Staff *staff      = [staffRegistry reserveMember];

    appt.staff = staff;

    return appt;
}

staffRegistry is a class that manages (and owns) all Staff objects, keeping them in an array, a dictionary, or some other collection. All other references to Staff objects (apart from temporary references by stack variables) should be weak. This way removing a member from the registry will also release him from all the appointment objects in which he may participate.

My co-workers seem to have handled it by using two properties, one strong and the other weak

If you are thinking it's a hack, you are 100% right. The strong vs. weak issue gets straightforward once you make a decision on who knows what; you don't need to come up with code that promises some serious maintenance nightmare to address it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top