Зачем NSError нужна двойная косвенность? (указатель на указатель) - PullRequest
46 голосов
/ 24 мая 2009

Эта концепция, кажется, беспокоит меня. Почему объекту NSError нужно, чтобы его указатель был передан методу, который модифицирует объект? Например, не передаст ли ссылка на ошибку то же самое?

NSError *anError;
[myObjc doStuff:withAnotherObj error:error];

и затем в doStuff:

 - (void)doStuff:(id)withAnotherObjc error:(NSError *)error 
 {
    // something went bad!
    [error doSomethingToTheObject];
 }

Почему вышеперечисленное не работает так, как работает большинство других шаблонов обмена сообщениями? Почему вместо этого мы должны использовать ошибку: (NSError **) error?

Ответы [ 5 ]

93 голосов
/ 24 мая 2009

Очень просто:

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

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

В случае NSError функция может захотеть создать новый объект NSError и передать вам указатель на этот объект NSError. Таким образом, вам нужно двойное косвенное обращение, чтобы указатель мог быть изменен.

77 голосов
/ 24 мая 2009

Шаблон NSError** используется, когда метод обычно возвращает какое-то значение, но вместо этого может потребоваться вернуть объект ошибки (типа NSError*) в случае сбоя. В Objective-C метод может возвращать только один тип объекта, но это тот случай, когда вы хотите вернуть два. В C-подобных языках, когда вам нужно вернуть дополнительное значение, вы запрашиваете указатель на значение этого типа, поэтому для возврата NSError* вам нужен параметр NSError**. Более реалистичным примером будет такой:

// The method should return something, because otherwise it could just return
// NSError* directly and the error argument wouldn't be necessary
- (NSArray *)doStuffWithObject:(id)obj error:(NSError **)error
{
  NSArray *result = ...;  // Do some work that might fail
  if (result != nil) {
    return result;
  } else {
    // Something went bad!
    // The caller might pass NULL for `error` if they don't care about
    // the result, so check for NULL before dereferencing it
    if (error != NULL) {
      *error = [NSError errorWithDomain:...];
    }
    return nil;  // The caller knows to check error if I return nil
  }
}

Если бы у вас был только параметр NSError* вместо NSError**, тогда doStuff никогда не сможет передать объект ошибки обратно его вызывающей стороне.

8 голосов
/ 28 января 2014

Старый вопрос, но все же я думаю, что стоит поставить это здесь -

Фактический виновник NSError . Если вы посмотрите на ссылку на класс, нет никаких методов установки для любого из его атрибутов, то есть домена, кода или userInfo. Таким образом, нет никакого способа, вы можете просто выделить и инициализировать NSError, передать его методу и затем заполнить информацию о переданном объекте NSError. (Если бы был метод установки, мы могли бы просто передать NSError * и сделать что-то вроде error.code = 1 в методе.)

Таким образом, в случае возникновения ошибки вы должны сгенерировать новый объект NSError в методе, и если вы делаете это, единственный способ передать его вызывающей стороне - получить аргумент NSError **. (По причине, изложенной в ответах выше.)

6 голосов
/ 24 мая 2009

Альтернативное изложение того, что сказал n8gray:

Поскольку вы не получаете объект для отправки сообщений; Вы создаете объект и возвращаете его. Как правило, вам нужен аргумент указателя на NSError * -вариант, потому что вы можете использовать оператор return только для одной вещи за раз, и вы уже используете его с NO.

0 голосов
/ 12 августа 2016

Я все еще не получил полную картину, прочитав все ответы выше. Непрофессиональное упражнение, которое я выполнил ниже, наконец помогло мне понять, что происходит. Просто поместите это на тот случай, если это поможет другим новичкам.

Предположим, у вас есть следующие

@interface Class X
-(void) methodX:(NSMutableArray *)array;
@end

В какой-то другой части кода у вас есть следующая последовательность

ClassX *objectX = [[ClassX alloc] init];
NSMutableArray *arrayXX = [@[@(1), @(2)] mutableCopy]; 
//What is stored in arrayXX is the address in the heap at which the NSMutableArray object starts, lets call this address ZZZ
//array starting at address ZZZ in the heap now contains NSNUmbers @1,@2
[objectX methodX:array]

Когда вы вызываете [objectX methodX:array], метод получает копию из array. Так как массив содержит адрес (то есть указатель), копия является особенной в том, что то, что получено, является другой переменной с адресом ZZZ в нем.

Итак, если methodX делает [array removeObjectAtIndex:0], то объект, начинающийся с адреса ZZZ, будет затронут (теперь содержит только один NSNUmber @ (2)). Поэтому, когда метод возвращается, исходный массив также затрагивается.

Предположим, что вместо methodX выполняется array = [@[@(2)] mutableCopy];, тогда на исходный массив это не влияет. Это потому, что вы не пошли в адрес ZZZ и что-то изменили. Вместо этого вы перезаписали ZZZ в копии , полученной методом, на другой адрес YYY. Адрес YYY - это начало объекта NSMUtableArray с одним элементом NSNUmber @ (2). Исходный адрес ZZZ по-прежнему содержит NSMUtableArray с двумя элементами. @ (1) и @ (2). Таким образом, при возврате метода исходный массив не изменяется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...