Подтвердить что ты не робот

Почему мы должны отделять вызовы alloc и init, чтобы избежать взаимоблокировок в Objective-C?

Когда вы читали о потокобезопасных синглтонах, я нашел Thread safe instantiation singleton здесь на SO, и в принятом ответе этот код:

    sharedInstance = [MyClass alloc];
    sharedInstance = [sharedInstance init];

Зачем нам выделять методы alloc и init? Автор ответа написал:

А именно, если init выделенного класса вызывает вызов метода sharedInstance, он будет делать это до того, как будет установлена ​​переменная. В обоих случаях это приведет к тупиковой ситуации. Это один раз, когда вы хотите отделить alloc и init.

Может кто-нибудь, пожалуйста, объясните мне подробно, каковы преимущества этого разделения? Я не мог понять, что имел в виду автор. Мне действительно нужно отделять вызовы методов alloc и init, когда я создаю синглтон, даже если я делаю это в dispatch_once(), который является потокобезопасным??

4b9b3361

Ответ 1

@bbum post обновлено, чтобы упомянуть, что это решение не решает описанную проблему. Независимо от того, разделяете ли вы +alloc и -init или нет, эта проблема все еще существует.

Обоснование заключается в редактировании его сообщения, но для этого, dispatch_once() не reentrant. В этом случае это означает, что вызов dispatch_once() внутри блока dispatch_once() (т.е. Рекурсивно) приведет к тупиковой ситуации.

Так, например, если у вас есть следующий код для +sharedInstance:

+ (MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        sharedInstance = [[MyClass alloc] init]
    });

    return sharedInstance;
}

.. и MyClass -init метод прямо или косвенно также вызывает свой собственный метод класса +sharedInstance (например, возможно, какой-то другой объект, который MyClass -init распределяет вызовы до MyClass +sharedInstance), что будет означать, что вы пытаетесь вызвать dispatch_once изнутри самого себя.

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

Если вам требуется решение, обеспечивающее повторное размещение, вам нужно будет использовать что-то вроде NSRecursiveLock, что значительно дороже, чем dispatch_once, который не использует механизм блокировки.

РЕДАКТИРОВАТЬ: Рассуждение для разделения +alloc/-init в исходном ответе @bbum в соответствии с запросом:

Исходный код @bbum, опубликованный перед его редактированием, выглядит так:

+ (MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    if (sharedInstance) return sharedInstance;

    dispatch_once(&pred, ^{
        sharedInstance = [MyClass alloc];
        sharedInstance = [sharedInstance init];
    });

    return sharedInstance;
}

Обратите внимание на эту строку: if (sharedInstance) return sharedInstance;

Идея здесь заключается в том, что присвоение значения non-nil sharedInstance перед вызовом -init приведет к возврату существующего значения sharedInstance (возвращается из +alloc) до нажав вызов dispatch_once() (и избегая тупика) в случае, когда вызов -init приводит к рекурсивному вызову +sharedInstance, как обсуждалось ранее в моем ответе.

Однако это хрупкое исправление, потому что оператор if не является потокобезопасным.