Может ли блок в Objective-C принимать нулевое значение в качестве параметра?

Следующий код загружает изображение и возвращает результат с блоком, чтобы я мог воспользоваться преимуществами асинхронных функций блоков и Grand Central Dispatch. Я обнаружил, что если изображение или объект ошибки равны нулю, я получаю ошибку EXC_BAD_ACCESS. Всегда ли будет вызывать ошибку, если значение параметра равно нулю?

Неудачная часть — это блок returnImage, который используется для возврата изображения в конце метода.

- (void)downloadImageWithBlock:(void (^)(UIImage *image, NSError *error))returnImage {
    dispatch_queue_t callerQueue = dispatch_get_current_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("Image Downloader Queue", NULL);
    dispatch_async(downloadQueue, ^{
        UIImage *image = nil;
        NSError *error;

        // get the UIImage using imageURL (ivar)

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Downloading: %@", imageURL);
        });

        NSURL *url = [NSURL URLWithString:imageURL];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        NSURLResponse *urlResponse = nil;
        NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];

        if (!error && response) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"Success!");
            });
            image = [UIImage imageWithData:response];
        }

        // image will be nil if the request failed

        dispatch_async(callerQueue, ^{
            returnImage(image, error);
        });
    });
    dispatch_release(downloadQueue);
}

Ниже приведена трассировка стека, которую я не знаю, как читать.

0x00002d20  <+0000>  push   %ebp
0x00002d21  <+0001>  mov    %esp,%ebp
0x00002d23  <+0003>  sub    $0x18,%esp
0x00002d26  <+0006>  mov    0x8(%ebp),%eax
0x00002d29  <+0009>  mov    %eax,-0x4(%ebp)
0x00002d2c  <+0012>  mov    -0x4(%ebp),%eax
0x00002d2f  <+0015>  mov    0x14(%eax),%eax
0x00002d32  <+0018>  mov    %eax,(%esp)
0x00002d35  <+0021>  movl   $0x3,0x4(%esp)
0x00002d3d  <+0029>  call   0x314c <dyld_stub__Block_object_dispose>
0x00002d42  <+0034>  mov    -0x4(%ebp),%eax
0x00002d45  <+0037>  mov    0x18(%eax),%eax
0x00002d48  <+0040>  mov    %eax,(%esp)
0x00002d4b  <+0043>  movl   $0x3,0x4(%esp)
0x00002d53  <+0051>  call   0x314c <dyld_stub__Block_object_dispose>
0x00002d58  <+0056>  mov    -0x4(%ebp),%eax
0x00002d5b  <+0059>  mov    0x1c(%eax),%eax
0x00002d5e  <+0062>  mov    %eax,(%esp)
0x00002d61  <+0065>  movl   $0x7,0x4(%esp)
0x00002d69  <+0073>  call   0x314c <dyld_stub__Block_object_dispose>
0x00002d6e  <+0078>  add    $0x18,%esp
0x00002d71  <+0081>  pop    %ebp
0x00002d72  <+0082>  ret    
0x00002d73  <+0083>  nopw   0x0(%eax,%eax,1)
0x00002d79  <+0089>  nopl   0x0(%eax)

person Brennan    schedule 23.05.2011    source источник
comment
Что затрудняет отладку этого кода, так это тот факт, что ошибка не отображается в строке кода, поэтому мне приходится комментировать фрагменты кода, чтобы попытаться определить, где происходит ошибка. Определить строку кода ошибки сложно.   -  person Brennan    schedule 24.05.2011


Ответы (2)


NSError *error; создает указатель на случайное/недопустимое место в памяти.

Если в блоке не возникает ошибки, он не присвоит новое (действительное) значение error. В результате, когда вы проверяете, является ли error нулевым, вы разыменовываете недопустимый указатель:

NSError *error; // invalid pointer
NSLog(@"%@", error); // crash -- dereferencing invalid pointer

Вы должны либо:

  1. Всегда присваивайте nil переменным ошибок, прежде чем передавать их методу такого рода.
  2. Обратитесь к документации метода, которая обычно сообщает вам, было ли переменной ошибки присвоено допустимое значение на основе возвращаемого значения метода.

Обновление: локальные переменные объекта теперь по умолчанию равны нулю в ARC.

person titaniumdecoy    schedule 24.05.2011
comment
+1, и я просто хочу подчеркнуть: Apple гарантирует, что методы Cocoa, косвенно возвращающие объекты NSError, будут иметь допустимый объект ошибки, если метод указывает на сбой своим возвращаемым значением. Однако они не гарантируют обратного: если метод указывает на успех, объект ошибки будет нулевым. Неправильно проверять ошибку, если прямое возвращаемое значение метода не указывает на сбой. - person jscs; 24.05.2011
comment
Спасибо Джейсон и Джош. Теперь это имеет смысл. Я почему-то думал, что это проблема GCD, когда это была просто проблема с локальной переменной. Я также устанавливаю значение imageURL, которое является ivar, в локальную переменную, чтобы это не стало проблемой в блоке. Теперь код работает довольно надежно. Я обнаружил, что мне нужно проверить код состояния HTTP, потому что отсутствие ошибки не означает, что я не получил 403 или 404 и т. д. Теперь мне нужно 200 для успеха. - person Brennan; 24.05.2011
comment
@Brennan Подводя итог: всегда сначала проверяйте возвращаемое значение. Если возвращаемое значение указывает на ошибку (например, это nil или NO), проверьте выходной параметр NSError. Присвоение nil переменной NSError и опора на нее ненадежны. - person ; 24.05.2011

Итак, пара моментов, которые могут помочь:

Вы не должны проверять ошибку или делать какие-либо предположения об ошибке, если ответ не равен нулю.

Если ответ не равен нулю, то ошибка не определена, и все же вы просите блок сохранить ошибку. Вы уверены, что это не ваша проблема?

Вы уверены, что returnImage() может обрабатывать nil (или неопределенный NSError *)?

person Firoze Lafeer    schedule 23.05.2011
comment
Как правило, нет смысла НЕ устанавливать указатели NSError на nil, хотя среда выполнения делает это за нас автоматически. @Brennan Отправьте трассировку стека и проверьте callerQueue. - person TheBlack; 24.05.2011
comment
Я добавил трассировку стека к вопросу. - person Brennan; 24.05.2011
comment
Установка для ошибки значения nil при ее объявлении теперь позволяет выполнять ее без исключений. Я не уверен, что здесь происходит. - person Brennan; 24.05.2011
comment
@TheBlack: среда выполнения не устанавливает для указателей NSError (или любых других) значение nil, если только они не являются переменными экземпляра, что здесь не так. - person omz; 24.05.2011
comment
@TheBlack, я не уверен, что вы подразумеваете под средой выполнения, которая делает это автоматически? Пожалуйста, объясните. @Brennan, я думаю, проблема в попытке [сохранить ошибку] ​​(или, возможно, выпустить?), Когда «ошибка» не была определена. - person Firoze Lafeer; 24.05.2011
comment
@omz Ты прав. Это было предположение, основанное на поведении переменных экземпляра. - person TheBlack; 24.05.2011
comment
Установка переменной ошибки на nil устранила проблему. Блок, вероятно, пытался сохранить неопределенное значение. Значение nil допустимо, но явно не неопределенное значение. - person Brennan; 24.05.2011