2017-11-18 2 views
0

나는 한 달 동안 나를 괴롭히는 애매한 버그를 수정하는 데 도움이 필요합니다.스위프트 EXEC_BAD_ACCESS는 AVAudioMixerNode에 설치된 탭으로 작성된 배열에 액세스 할 때 throw됩니다.

내가보기에 코드가 많지만 문제의 근원 인 것으로 보이는 메소드는 installReadTapOnPadMixer() 및 updateVolumeData() 메소드입니다.

저는 스위프트에 샘플러 앱이 있습니다. 내 ViewController 중 하나에서 사용자는 "패드"를 누를 수 있으며로드 된 사운드가 재생됩니다.

enter image description here

문제는 한 번에 내 애플 던져 동안이다 : 저는 현재 패드를 누를 때 소리의 현재 볼륨 패드가 착색하는 방법을 밝게 영향을 미치지 않도록이 설정이

스레드 1 : EXC_BAD_ACCESS (코드 = 1, 주소 = 0x55555555555550)

또는 예외는 다음과 같습니다 경우입니다 :

스레드 1 : EXC_BAD_ACCESS (코드 = 1, 주소 = 0x9c9c3170)

내 패드의 각 모델면은 AVAudioUnitVarispeed 노드의 배열에 연결된 AVAudioPlayerNodes의 배열로 구성됩니다. 각각의 varispeed 노드는 하나의 AVAudioMixerNode에 연결되며,이 AVAudioMixerNode는 AVAudioEngine의 주 믹서 노드에 연결됩니다.

그래서 다시, 그건 :

AVAudioPlayerNode [] -> AVAudioUnitVarispeed [] -> AVAudioMixerNode -> 메인 믹서 노드입니다.

모델 측면의 주어진 패드의 현재 볼륨을 얻으려면 체인의 각 믹서 노드에 탭이 설치되어 있어야합니다. 그래서 같이 : 내 "MasterSoundMod"클래스에서 플레이()를 호출 할 때

/** UI 개선, 플레이어 노드의 현재 재생 볼륨을 얻는다 */

private func installReadTapOnPadMixer(bankNumber: Int, padNumber: Int) 
{ 
    guard(_soundCollection[bankNumber]![padNumber]?.tapInstalled == false) else{ return; } 

    // https://miguelsaldana.me/2017/03/18/how-to-create-a-volume-meter-in-swift-3/ 
    _padMixerArray[bankNumber]![padNumber]?.installTap(onBus: 0, bufferSize: 1024, format: _outputFormat) 
    { 
     (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) in 
      let dataptrptr = buffer.floatChannelData!; 
      let dataptr = dataptrptr.pointee; 
      let datum = dataptr[Int(buffer.frameLength) - 1]; 
      self._padMixerVolumeDataArray[bankNumber][padNumber] = fabs(datum); 
    } 

    _soundCollection[bankNumber]![padNumber]?.tapInstalled = true; 
} 

그런 다음 나는 타이머를 시작합니다 과 같이 탭에서 데이터를 읽기 위해 :

/** 게임 패드의 현재의 사운드 설정 */

.SC에 전달 내 선택기 함수에 다음
func play(bankNumber: Int, padNumber: Int, preview: Bool) 
{ 
    if(!_isConnected && !_routeChanging) 
    { 
     if((_soundCollection[bankNumber]![padNumber]) != nil && (_soundCollection[bankNumber]![padNumber])!.isLoaded) 
     {  

      if(!(_soundCollection[bankNumber]![padNumber]?.playerNodeArray[(_soundCollection[bankNumber]![padNumber]?.currentPlayIndex)!]!.engine?.isRunning)!) 
      { 
       startMod(); // start the AVAudioEngine and update a few state variables 
      } 

      // [[PadModel]] 
      (_soundCollection[bankNumber]![padNumber])!.play(); 
     } 
    } 

    // the timer must not be scheduled for any preview or any blank pad 
    if(!preview) 
    { 
     _volumeDataTimerArray[bankNumber][padNumber] = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateVolumeData(timer:)), userInfo: (bankNumber, padNumber), repeats: true); 
    } 
} 

heduledTimer() 내가 가진 :

/** 여기에서 현재 터치 패드의 현재 볼륨을 통과 소유 노래, 까지 다음 패드 모델에 해당하는 PadView 객체에 선을 은 현재 재생 */

@objc func updateVolumeData(timer: Timer) 
{ 
    let userInfo = timer.userInfo as! (Int, Int); 

    _delegate!.passCurrentVolumeToPadView(bankNumber: userInfo.0, padNumber: userInfo.1, volume: _padMixerVolumeDataArray[userInfo.0][userInfo.1]); // <- EXEC_BAD_ACCESS 

예외는 _delegate!를 호출 할 때 발생합니다.passCurrentVolumeToPadView() 또한, 난과 같이 정지() 메소드 내 타이머를 무효화 :

/**) (은행 및 패드 번호 */

func stop(bankNumber: Int, padNumber: Int) 
{ 
    _releaseTimerArray[bankNumber][padNumber] = Timer() 

    var preFadeVolume: Float = 0.0; 

    if(_padMixerArray[bankNumber]![padNumber] != nil){ preFadeVolume = (_padMixerArray[bankNumber]![padNumber]?.volume)!; } 

    // no player node manipulation while audio route change is in progress. 
    if(!_isConnected && !_routeChanging) 
    { 
     if((_soundCollection[bankNumber]![padNumber]) != nil && (_soundCollection[bankNumber]![padNumber])!.isLoaded) 
     { 
      _releaseTimerArray[bankNumber][padNumber] = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(self.fadeout(timer:)), userInfo: (bankNumber, padNumber, preFadeVolume), repeats: true); 
     } 

     _volumeDataTimerArray[bankNumber][padNumber]?.invalidate(); 
    } 

    // for good measure..... 
    //  this DOES need to be here! 
    if(_volumeDataTimerArray[bankNumber][padNumber] != nil && (_volumeDataTimerArray[bankNumber][padNumber]!.isValid)) 
    { 
     _volumeDataTimerArray[bankNumber][padNumber]?.invalidate(); 
    } 
} 

정지에 해당하는 패드 재생을 중지 메서드는 불쾌한 클릭 소리를 피하기 위해 페이드 아웃되는 사운드를 실행하도록 타이머를 예약하지만 이는이 질문의 일부가 아닙니다.

나는이 문제를 해결하기 위해 몇 가지 방법을 시도했다. 나는 원래 단일 타이머와 단일 _volumeData 변수를 사용하고있었습니다. 이러한 멤버를 모두 배열로 승격하면 예외가 throw되는 빈도가 크게 줄어 듭니다.

때때로 앱을 실행하면 5 분 동안 패드를 누르기 만하면 예외가 표시되지 않습니다. 다른 경우 앱을 실행하면 잠깐 기다려야 만 예외가 발생합니다.

이제 installTapOnPadMixer()의 installTap() 호출에서 코드 블록이 내 메인 스레드 외부에서 실행됩니다.

예외를 일으키는 것에 대한 나의 순진 가설은 내 _padMixerVolumeDataArray []가 읽는 중 동시에 쓰여지고 있다는 것입니다. 다시 말하지만, 이것은 순진합니다.

문제를 조금 더 살펴본 후에 EXEC_BAD_ACCESS에 대한 합의는 출시 된 개체에 액세스하려고 할 때 발생하는 것으로 보입니다. 그래서 아마도 더 정확한 설명은 해당 타이머가 무효화 된 후에 updateVolumeData() 메서드의 코드가 호출된다는 것입니다.

다시 살펴보아야 할 코드가 많지만 문제의 근원 인 것으로 보이는 메소드는 installReadTapOnPadMixer() 및 updateVolumeData() 메소드입니다.

통찰력이있는 몸에 많은 감사를드립니다!

+0

는 예외 중단 점을 추가하고 프로젝트를 실행, 그것은 것 충돌이 일어나는 라인에서 멈추십시오. 그건 당신이 문제를 격리 도움이 될 것입니다 – user1046037

답변

0

글쎄, 나는 루키 실수를 저질렀다. 이전에 내 솔루션을 찾는 질문의 배열은 이렇게 선언되었습니다 :

개인 게으른 var _padMixerVolumeDataArray : [[Float?]?]!! = [];

이 잘못된 선언문은 내 앱에서 다양한 메모리 누수 문제를 해결하려고 시도한 것에 대한 응답으로 작성되었습니다.

exc_bad_access가 표시되지 않고 선언문을 변경하고 55 분 동안 패드를 매시간 후 문제가 해결되었다는 결론에 도달했습니다. 새 선언 :

private var _padMixerVolumeDataArray : [[Float]] = [];

나는이에서 게으른 키워드를 제거 할 생각이있어 : http://blog.swilliams.me/words/2015/03/31/tracking-down-an-exc-bad-access-with-swift/

문제가 해결 될 때까지 나는 찾지 못했습니다 또 다른 비슷한 질문 : Why does my @lazy property crash, but if I make it non lazy it works?

관련 문제