在编写Objective-C
代码时,很多时候会需要对错误进行处理,在OC
里使用的是NSError
。当我们编写一个方法时,比如进行一个网络请求,这个时候会有请求成功或请求失败两种情况。当请求失败时,我们会在方法中生成一个错误并告诉调用方。
在C语言里,返回数据有两种方式,一种是常用的return
返回,这是大部分情况下从函数返回数据的方式,但C语言里一个函数只能返回一个数据,也就是return
后面只能有一个指针或值(包括结构体)。如果想要返回多个数据时,有时会考虑使用参数返回的形式。
最典型的例子就是交换两个数值的函数:
int a = 1, b = 2;
swap(&a, &b);
void swap(int *a, int *b) {
int tmp = *a; *a = *b; *b = tmp; //或者直接*a ^= *b ^= *a ^= *b;
}
交换外部变量的方式很多,如指针、引用、位运算,但不能直接使用变量,这是因为形参和实参的区别,具体不用多说。此处想强调的是,不使用return
语句,返回两个交换后的变量值,可以使用这种参数指针的形式。
铺垫这些,是为了说明NSError
的使用情况。在OC中错误生成的常见形式是这样:
-(id)requestWithParameter:(id)obj error:(NSError *__autoreleasing *)error {
id result = ... //使用obj参数进行网络请求并返回
if (result != nil) {
return result;
} else {
if (error != NULL) {//判断调用方是否需要获取错误信息
*error = [NSError errorWithDomain:...]; //生成错误对象
}
return nil;
}
}
对比发现,交换指针的方法使用的是单指针作为参数,直接交换了指针指向的内容;而错误生成的案例中,错误参数使用的是指针的指针。
实际上,他们的本质是一样的。
我们先考虑这种情况:
-(id)requestWithParameter:(id)obj error:(NSError __autoreleasing *)error {
id result = ... //使用obj参数进行网络请求并返回
if (result != nil) {
return result;
} else {
if (error != NULL) {//判断调用方是否需要获取错误信息
error = [NSError errorWithDomain:...]; //生成错误对象
}
return nil;
}
}
上面的代码会发生什么?如果在上面代码的基础上在外部创建一个错误对象然后调用方法,最后打印:
NSError *error;
[self doSomethingWithObj:nil error:error];
NSLog(@"error: %@", error);
此时打印结果会是什么?运行一下会发现控制台输出:
error: (null)
也就是说方法中创建的新的NSError
实例并没有传递给外部的对象指针。其实分析一下可知道,error = [NSError errorWithDomain:...]; //生成错误对象
此处只是将新的实例指针分配给了error这个方法内的局部指针变量,而这个局部指针变量是外部指针变量的一个拷贝。
当方法调用结束,外部并没有对这个新实例的强引用,因此也就会被释放掉。同时外部的NSError
指针也无法指向这个新的对象。
这就好比那个指针交换数值的例子,将其转换成错误的值传递:
void swap(int a, int b) {
int tmp = a; a = b; b = tmp;
}
此处只是对局部变量a, b进行了交换,函数出栈后,这两个局部变量都被释放,而外部的变量值并不改变。
回到NSError
,为了能够将方法中创建的NSError
实例分配给外部的那个error
指针指向的地址,我们需要将外部指针变量存储的地址直接传递给方法进行值拷贝,而不是传递指针变量本身。这是因为如果传递指针变量本身的话,方法只会拷贝一个指针,虽然拷贝后的指针和外部的指针都指向同一个地址,但是指针本身的地址是不同的。
而如果传递&error
,即取error指针本身的地址,则是单纯的值拷贝(但实际情况略有区别,稍复杂一些,因为此处增加了__autorelease
关键字,将指针对象自动入池,这个过程会对指针地址做一些处理,导致拷贝的地址会有偏移),会保留这个指针的地址并在方法内部恢复指针,同时新建NSError
实例并取地址给这个指针。
类似的,我们可以写一个交换NSError
的方法来跟交换数值的方法进行对比理解:
- (void)swapError:(NSError **)a with:(NSError **)b {
NSError *tmp = *a;
*a = *b;
*b = tmp;
}
因此,我们可以得出一个结论,就是使用参数传递返回OC指针类型的对象时,指针的指针是一种比较方便的处理参数返回方式。
如有错误望不吝指正!