Как использовать сильные и слабые с одноуровневыми объектами (каждый из которых может быть родительским) в Objective-C ARC?

У меня есть два объекта Objective-C, которые так или иначе связаны друг с другом. Вы можете думать об этом как о двусторонних отношениях. С ARC я понимаю, что родитель должен содержать строгую ссылку на свой дочерний объект, в то время как дочерний объект содержит слабую ссылку, указывающую на его родителя. Но что, если родитель может быть любым объектом? Или если объекты "братья и сестры"?

Допустим, у меня есть класс Person, и я хотел бы создать два объекта, свойства брата которых указывают друг на друга.

@implementation Person

@property (strong, nonatomic) Person *brother;

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

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

Не приведет ли это к сохранению цикла?

Вот еще один сценарий: допустим, у меня есть класс назначений и класс персонала.

@implementation Appointment

@property (strong, nonatomic) Staff *staff;


@implementation Staff

@property (strong, nonatomic) NSArray *appointments;

На просмотр персонала, я могу хотеть показать все назначения сотрудника. Поэтому я мог бы создать все мои объекты, например, так...

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];

Не приведет ли это к циклу сохранения, поскольку все ссылки сильны?

Наконец, рассмотрим следующий сценарий: допустим, я изменил свойство персонала Appointment на слабое.

@implementation Appointment

@property (weak, nonatomic) Staff *staff;

Это может решить проблему для моего второго (описанный выше сценарий), но что, если я создаю новую встречу и хочу присоединить нового сотрудника, а затем передать объект в другое место для обработки?

+ (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?
}

Поскольку мое свойство персонала в Appointment сейчас слабое, есть ли вероятность того, что оно будет установлено равным нулю при запуске сборки мусора (поскольку нет сильных ссылок на объект персонала)?

РЕДАКТИРОВАТЬ: Как dasblinkenlight объяснил, app.staff объект будет по-прежнему существовать как местный staff переменная (от buildAppointment) все еще в стеке. Однако, что если бы у меня было:

+ (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?
}

Мои коллеги, кажется, справились с этим, используя два свойства: одно сильное, а другое слабое:

@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;
}

Затем при установке свойства персонала они будут использовать setStaffStrong или setStaffWeak в зависимости от ситуации. Однако это выглядит очень глупо - наверняка есть более элегантное решение? Как бы вы построили свои классы для обработки моих сценариев выше?

PS: мои извинения за длинный вопрос; Я пытался объяснить это как мог.:)

1 ответ

Решение

Одно общее замечание о сильных и слабых ссылках: сильные ссылки обозначают собственность, в то время как слабые ссылки обозначают связь. Когда ни один из объектов не владеет другим, обычно есть другие объекты, которым они принадлежат, или они оба имеют сильные ссылки из локальных переменных.

Не будет Person класс] привести к сохранению цикла?

Да, потому что Person владеет своим братом, но не должен: это должно быть слабым свойством. Это должно быть хорошо, потому что должен быть другой объект (список всех людей, словарь, упорядочивающий людей по имени или что-то подобное), который владеет всеми Person объекты. Пока Person объект находится в этой коллекции людей, он не будет выпущен.

Не [Appointment а также Staff] привести к сохранению цикла, так как все ссылки сильны?

Правильно, это то, что должно произойти. NSArray сохраняет объекты, которые входят в него, замыкая цикл в цикле сохранения. Обратите внимание, что вы не можете сделать NSArray слабый, чтобы разорвать этот цикл: он должен быть Staff что становится слабым, а не appointments,

Наконец, рассмотрим этот сценарий: допустим, я меняю Appointment"s staff собственность на weak, Это может решить проблему для моего второго (сценарий выше), но что, если я создаю новую встречу и хочу присоединить сотрудника, а затем передать объект в другое место для обработки?

Там нет проблем с этим: переменная стека staff имеет сильную ссылку на него, поэтому он не будет выпущен.

Потому что мой staff свойство Appointment теперь слабое, есть ли вероятность того, что оно будет установлено равным nil при запуске сборки мусора (поскольку нет сильных ссылок на объект staff)?

ARC не является системой сбора мусора: сохранение и освобождение происходят в определенные, детерминированные моменты времени. staff не будет выпущен, потому что Staff *staff = [Staff new]; сильный по умолчанию.

РЕДАКТИРОВАТЬ: отредактированный пример действительно выпустит Staff объект. Однако такой пример необычен, потому что вряд ли ваш createAppointment сделал бы новый экземпляр Staff, Скорее, он получит существующий экземпляр из некоторого реестра, содержащего всех сотрудников, например:

+ (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 это класс, который управляет (и владеет) всем Staff объекты, сохраняя их в массиве, словаре или другой коллекции. Все остальные ссылки на Staff объекты (кроме временных ссылок на переменные стека) должны быть слабыми. Таким образом, удаление участника из реестра также освободит его от всех объектов назначения, в которых он может участвовать.

Мои коллеги, кажется, справились с этим, используя два свойства: одно сильное, а другое слабое.

Если вы думаете, что это взлом, вы на 100% правы. Сильная и слабая проблема становится очевидной, когда вы принимаете решение о том, кто что знает; вам не нужно придумывать код, который обещает какой-то серьезный кошмар обслуживания для его решения.

Другие вопросы по тегам