事情是這樣的, 有一段代碼,精簡(jiǎn)之后如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSError * error;
[self fetchSyncError:&error];
NSLog(@"%@", error);
}
//1.該方法是一個(gè)耗時(shí)的同步方法房官,但不考慮阻塞UI線程的問(wèn)題
//2.error是一個(gè)傳出參數(shù)
- (void)fetchSyncError:(NSError **)error {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//do something
*error = [NSError errorWithDomain:@"" code:110 userInfo:nil];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
運(yùn)行代碼趾徽,然后就崩潰了。翰守。孵奶。我xxx,什么鬼(其實(shí)走到這一步已經(jīng)調(diào)試了許久了)
蜡峰,然后發(fā)現(xiàn)問(wèn)題出現(xiàn)在NSError
上了袁。簡(jiǎn)而言之,*error = [NSError errorWithDomain:@"" code:110 userInfo:nil];
這里對(duì)*error
進(jìn)行了賦值湿颅,但離開(kāi)dispatch
作用域后就被銷(xiāo)毀了载绿,所以error
就變成了野指針,在外部打印error
時(shí)便崩潰了肖爵。
通過(guò)查詢(xún)資料,發(fā)現(xiàn)在ARC中臀脏,編譯器對(duì)引用傳值做了一些隱藏的動(dòng)作劝堪,參考《Transitioning to ARC Release Notes》,其中介紹了如果按照上面的代碼,編譯器會(huì)自動(dòng)重寫(xiě)成如下方式:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//如果引用傳值參數(shù)沒(méi)有聲明為_(kāi)_autoreleasing揉稚,那么編譯器會(huì)自動(dòng)重新申請(qǐng)一個(gè)__autoreleasing屬性的臨時(shí)變量
NSError * error;
NSError * __autoreleasing tmp = error; //編譯器生成的臨時(shí)變量
[self fetchSyncError:&tmp];
error = tmp;
NSLog(@"%@", error);
}
//1.該方法是一個(gè)耗時(shí)的同步方法秒啦,但不考慮阻塞UI線程的問(wèn)題
//2.error是一個(gè)傳出參數(shù)
//3.參數(shù)聲明為_(kāi)_autoreleasing類(lèi)型
- (void)fetchSyncError:(NSError * __autoreleasing *)error {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//do something
*error = [NSError errorWithDomain:@"" code:110 userInfo:nil];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
到這里,知道了error
其實(shí)是一個(gè)__autoreleasing
屬性的變量搀玖,經(jīng)過(guò)測(cè)試余境,發(fā)現(xiàn)error
對(duì)象在dispatch_async
之后打印便會(huì)崩潰,所以推測(cè)在dispatch_async
中有一個(gè)內(nèi)部的autoreleasepool
灌诅。于是乎芳来,簡(jiǎn)化一下代碼,如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSError * __autoreleasing error;//我們直接自己聲明為_(kāi)_autoreleasing猜拾,替編譯器省一步操作
[self fetchSyncError:&error];
NSLog(@"%@", error);
}
- (void)fetchSyncError:(NSError * __autoreleasing *)error {
@autoreleasepool {
*error = [NSError errorWithDomain:@"" code:110 userInfo:nil];
}
}
運(yùn)行一下代碼即舌,發(fā)現(xiàn)果然還是出現(xiàn)相同的錯(cuò)誤。如此挎袜,該問(wèn)題的原因就是__autoreleasing類(lèi)型的error對(duì)象在出了autoreleasepool之后顽聂,就自動(dòng)釋放了肥惭,error變成了野指針。
那么紊搪,該怎么辦呢蜜葱?我們只要保證一個(gè)對(duì)象不是autorelease不就可以了嘛,那我們?cè)赼utoreleasepool外申請(qǐng)一個(gè)臨時(shí)變量,代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSError * __autoreleasing error;//我們直接自己聲明為_(kāi)_autoreleasing耀石,替編譯器省一步操作
[self fetchSyncError:&error];
NSLog(@"%@", error);
}
- (void)fetchSyncError:(NSError * __autoreleasing *)error {
NSError *tmp;
@autoreleasepool {
tmp = [NSError errorWithDomain:@"" code:110 userInfo:nil];
}
*error = tmp;
}
再次運(yùn)行牵囤,OK!
以上問(wèn)題的出現(xiàn)娶牌,主要有兩點(diǎn)奔浅,一是對(duì)于引用傳參,參數(shù)為__autoreleasing
屬性诗良;二是有些方法會(huì)自帶autoreleasepool
汹桦,像子線程內(nèi)部,字典或數(shù)組的block枚舉方法(- (void)enumerateObjectsUsingBlock:
- (void)enumerateKeysAndObjectsUsingBlock:
)等鉴裹。
下面兩篇參考資料推薦看一下舞骆,寫(xiě)的更專(zhuān)業(yè)更深入,包括這兩篇文章所參考的文章径荔,也有許多值得學(xué)習(xí)鉆研的地方督禽。
參考資料:
探索子線程autorelease對(duì)象的釋放時(shí)機(jī)
結(jié)合訪問(wèn)Out Parameters出現(xiàn)EXC_BAD_ACCESS的例子,反編譯匯編解讀__autoreleasing