Правильное управление addObserverForName:object:queue:usingBlock:

Я все еще новичок в блоках в target-c и задаюсь вопросом, правильный ли у меня этот псевдокод. Я не уверен, достаточно ли просто удалить наблюдателя или мне нужно вызвать removeObserver:name:object:

-(void) scan {
    Scanner *scanner = [[Scanner alloc] init];
    id scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:@"ScanComplete" 
                        object:scanner 
                        queue:nil 
                        usingBlock:^(NSNotification *notification){
                            /*
                             do something
                             */
                            [[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
                            [scanner release];
                        }];
    [scanner startScan];
}

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


person seanalltogether    schedule 29.12.2010    source источник


Ответы (4)


Объявите переменную scanComplete перед определением самого блока.

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

Что такое EXC_BAD_ACCESS? Ну, это исключение, которое выдается, когда вы пытаетесь получить доступ к несуществующей ссылке. Так что в вашем примере именно так.

Так что если вы объявите переменную перед самим блоком, то она должна работать:

-(void) scan {
    Scanner *scanner = [[Scanner alloc] init];
    __block id scanComplete;
    scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:@"ScanComplete" 
                        object:scanner 
                        queue:nil 
                        usingBlock:^(NSNotification *notification){
                           /*
                           do something
                           */
                           [[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
                           [scanner release];
                    }];
    [scanner startScan];
}
person Jacob Relkin    schedule 29.12.2010
comment
Вам нужен __block id scanComplete;, иначе он будет скопирован в блок и вы будете сливать обозреватели. - person hwaxxer; 15.04.2011
comment
В мире ARC комментарий об использовании __block во избежание захвата больше не актуален. Что действительно верно, так это то, что квалификатор __block устраняет фундаментальную проблему: когда блок определен, addObserverForName:... еще не возвращено, поэтому фиксируемое значение в лучшем случае равно nil (при работе в ARC, из-за его неявного автоматического обнуления при объявлении переменной) или undefined, обменивая один BAD_ACCESS на полностью неопределенное поведение, в худшем случае… - person danyowdee; 27.11.2011
comment
Удаление наблюдателя путем ссылки на локальную переменную внутри блока всегда было непристойным. Сохраните возвращенный токен наблюдателя (здесь scanComplete) как переменную экземпляра; в ARC это должна быть переменная экземпляра __weak, чтобы предотвратить цикл сохранения для себя. - person matt; 27.11.2011
comment
@мат Почему? Что плохого в желании полностью локализовать объем всех движущихся частей, связанных с блочными уведомлениями? - person CIFilter; 08.09.2012
comment
[scanner release]; должно быть вне блока - person user102008; 26.03.2013

Вы не должны отменять регистрацию в блоке регистрации. Вместо этого сохраните токен, возвращенный из addObserverForName (в данном случае ваш scanComplete), как переменную экземпляра или в коллекции, которая является переменной экземпляра, и отмените регистрацию позже, когда вы собираетесь прекратить существование (например, в dealloc). Что я делаю, так это сохраняю NSMutableSet с именем observers. Так:

id ob = [[NSNotificationCenter defaultCenter] 
     addObserverForName:@"whatever" object:nil queue:nil 
     usingBlock:^(NSNotification *note) {
        // ... whatever ...
}];
[self->observers addObject:ob];

А потом позже:

for (id ob in self->observers)
    [[NSNotificationCenter defaultCenter] removeObserver:ob];
self->observers = nil;
person matt    schedule 23.11.2011
comment
Если вам нужно однократное уведомление, я не понимаю, почему вы не можете отменить регистрацию в самом блоке. - person ipmcc; 03.11.2012
comment
Согласен с комментарием выше. Я думаю, что в этом ответе отсутствует причина. Я думаю, было бы полезно объяснить, почему вы не должны отменять регистрацию в блоке регистрации. Тем более, что собственная документация Apple для этого API включает рекомендацию отменить регистрацию в этом блоке... - person Daniel Galasko; 18.06.2018
comment
@DanielGalasko Вопрос был в том, как предотвратить сбой, и в моем ответе правильно указан способ сделать это. Принятый ответ решает проблему с другой стороны, объявление ob. Оба ответа являются правильными способами предотвращения сбоя. - person matt; 18.06.2018

Документ Apple об этом методе:

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

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
self.localeChangeObserver = [center addObserverForName:NSCurrentLocaleDidChangeNotification object:nil
    queue:mainQueue usingBlock:^(NSNotification *note) {

        NSLog(@"The user's locale changed to: %@", [[NSLocale currentLocale] localeIdentifier]);
    }];

Чтобы отменить регистрацию наблюдений, вы передаете объект, возвращаемый этим методом, в метод removeObserver:. Вы должны вызывать removeObserver: или removeObserver:name:object: перед освобождением любого объекта, указанного в addObserverForName:object:queue:usingBlock:.

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self.localeChangeObserver];
person likid1412    schedule 01.06.2015

Область действия блока не имеет разрешения на освобождение объекта сканера. Если вы не используете сборку мусора, удаление release и автоматическое освобождение сканера ([[[Scanner alloc] init] autorelease]) должно помочь.

Вы также должны иметь возможность безопасно переместить вызов removeObserver за пределы блока.

В случае EXC_BAD_ACCESS: ввод bt в окне консоли после сбоя приложения даст вам обратную трассировку и сообщит вам, где произошла ошибка.

person mralex    schedule 29.12.2010