Поточная безопасная ленивая инициализация на iOS - PullRequest
6 голосов
/ 22 октября 2011

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

@property (retain) UIViewController *myController

...

@synthesize myController = _myController;


...


- (UIViewController *)myController
{
    if (!_myController) {                                 // Evaluation
        _myController = [[MyViewController alloc] init];  // Object Creation
    }
    return _myController;
}

Это работает, но это не потокобезопасно, и если более одного потока оценят значение true до создания объекта, у меня будет утечка памяти.Одно из решений, которое я пробовал, состоит в том, чтобы @synchronized код, но я не уверен, что правильный способ сделать это.

Кажется, это работает, (lockForMyController - простая NSString), но это делает этот разделкод гораздо медленнее:

 - (UIViewController *)myController
{
    @synchronized(self.lockForMyController){
        if (!_myController) {
            _myController = [[MyViewController alloc] init];
        }
    }
    return _myController;
}

Мне было интересно, есть ли какой-нибудь другой способ получить ленивое инициализированное, поточно-ориентированное свойство?

1 Ответ

9 голосов
/ 22 октября 2011

Это решение работает

Обратите внимание, что это решение работает только при первом обращении к myController в фоновом потоке.Он будет заблокирован при вызове в главном потоке.

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

- (UIViewController *)myController
    if (_myController == nil) {
        dispatch_sync(dispatch_get_main_queue(), ^ { if (_myController == nil) _myController = [[MyViewController alloc] init]; });
    }
    return _myController;
}

Здесь, даже если несколько потоков исполняют блок, выполнениеблока сериализуется в основной поток, и только один MyViewController может быть создан.

Вы не увидите здесь снижения производительности, если объект не равен nil.

Поскольку свойство неявно атомарно, это означает, что в установщике значение будет автоматически освобождено.Это должно сделать его подходящим для смешивания с вашим пользовательским получением, поскольку оно автоматически высвобождает любые изменения значения _myController.

http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW2

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

dispatch_sync (dispatch_get_main_queue (), ^ {self.myController = {newValueOrNil}});

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

Это решение не работает

Вы хотите использовать gcd.

http://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_once

Смотрите этот пост о синглетонах.Я знаю, что вам не нужен синглтон, но это демонстрирует, как использовать метод.Вы можете легко адаптировать его.

Создать синглтон, используя dispatch_once из GCD в Objective C

...