這個問題是我在做人臉識別、活體認(rèn)證的功能時遇到的一個bug。
問題現(xiàn)象:
測試同學(xué)在測試人臉識別時偶爾出現(xiàn)程序Crash,經(jīng)過初步診斷發(fā)現(xiàn)問題時出在第三方SDK,當(dāng)時因為項目進度比較急,就直接把奔潰日記、代碼截圖拋給了第三方對接的同學(xué)。
結(jié)果經(jīng)第三方同學(xué)說他們測試沒發(fā)現(xiàn)奔潰問題,說后面處理了就告訴我,然后就沒有然后了...重現(xiàn)問題:
一段時間過后,測試小同學(xué)又來向我反饋這個問題時,我發(fā)誓我一定要解決這個bug。(補充:測試是個男同學(xué)?。?br> 于是我又打開項目調(diào)試了好多次,終于復(fù)現(xiàn)了bug,找到了那段坑爹的代碼,仔細(xì)的研究了一番,終于找到了問題!坑爹代碼如下:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
//音頻播放器
@property(nonatomic,strong)AVAudioPlayer * audioPlayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//業(yè)務(wù)場景:
//攝像頭不斷獲取圖像,然后每張圖像進行異步比對,同時不斷將結(jié)果返回代理,然后進行語音操作提示。
dispatch_queue_t currentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 1; i<4; i++) {
dispatch_async(currentQueue, ^{
NSLog(@"播放次數(shù)--- %d ---",i);
[self playBackgroundSound:i];
});
}
}
//播放音頻
- (void) playBackgroundSound:(NSInteger )num {
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: @"test" ofType: @"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
@synchronized (_audioPlayer) {
NSLog(@"%@",[NSThread currentThread]);
if ([_audioPlayer isPlaying]) {
[_audioPlayer stop];
}
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error:nil];
NSLog(@"次數(shù)- %ld ---播放器:%@ ",num,_audioPlayer);
[_audioPlayer play];
NSLog(@"次數(shù)- %ld ---播放器:%@ ",num,_audioPlayer);
}
}
-
分析問題:
第三方SDK開發(fā)小同學(xué),為了避免并發(fā)隊列異步執(zhí)行時出現(xiàn)資源競爭問題,特地好心地給這個任務(wù)加了個鎖。
@synchronized (_audioPlayer) {
//這是一個坑爹的初始化,切換音頻內(nèi)容必須進行一個初始化?。?!
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error:nil];
}
然而問題恰恰就出現(xiàn)在這里,小同學(xué)千不該萬不該把 _audioPlayer對象作為了鎖對象。
- 鎖對象_audioPlayer 初始化是在鎖的內(nèi)部,會導(dǎo)致多個任務(wù)進入鎖內(nèi)執(zhí)行;
- 初始化是什么:給一個對象分配存儲空間,并返回這個對象(新指針地址)?。?!也就是鎖對象_audioPlayer會一直變!
- 當(dāng)任務(wù)1初始化返回一個 A指針賦值給_audioPlayer,正準(zhǔn)備發(fā)送"play"消息時,任務(wù)2也初始化一個B指針賦值給_audioPlayer,原來的A指針沒有變量持有,內(nèi)存即被收回。此時"A指針"變成了一個野指針,此時繼續(xù)向它發(fā)生"play"消息,程序自然會崩潰!
- 處理方案:
- 將鎖對象"_audioPlayer"換成"self"。(最簡單、效果最好)
- 去掉鎖,將異步換成同步執(zhí)行。