Defer AVSpeechSynthesisVoice init to first speak() call
AVSpeechSynthesisVoice(language:) triggers a malloc double-free on iOS 26 simulators when deserializing voice metadata during app launch. Move voice resolution from init() to first speak() so the framework call happens after the app is fully initialized. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,14 +4,20 @@ import AVFoundation
|
|||||||
@MainActor
|
@MainActor
|
||||||
final class SpeechService {
|
final class SpeechService {
|
||||||
private let synthesizer = AVSpeechSynthesizer()
|
private let synthesizer = AVSpeechSynthesizer()
|
||||||
private let spanishVoice: AVSpeechSynthesisVoice?
|
private var spanishVoice: AVSpeechSynthesisVoice?
|
||||||
|
private var voiceResolved = false
|
||||||
private var audioSessionConfigured = false
|
private var audioSessionConfigured = false
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
spanishVoice = AVSpeechSynthesisVoice(language: "es-ES")
|
// AVSpeechSynthesisVoice can trigger a malloc double-free on
|
||||||
|
// iOS 26 simulators when deserializing voice metadata. Defer
|
||||||
|
// voice resolution to first use so the crash doesn't happen
|
||||||
|
// during app launch.
|
||||||
|
spanishVoice = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func speak(_ text: String) {
|
func speak(_ text: String) {
|
||||||
|
resolveVoiceIfNeeded()
|
||||||
configureAudioSession()
|
configureAudioSession()
|
||||||
synthesizer.stopSpeaking(at: .immediate)
|
synthesizer.stopSpeaking(at: .immediate)
|
||||||
let utterance = AVSpeechUtterance(string: text)
|
let utterance = AVSpeechUtterance(string: text)
|
||||||
@@ -27,6 +33,12 @@ final class SpeechService {
|
|||||||
synthesizer.stopSpeaking(at: .immediate)
|
synthesizer.stopSpeaking(at: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func resolveVoiceIfNeeded() {
|
||||||
|
guard !voiceResolved else { return }
|
||||||
|
voiceResolved = true
|
||||||
|
spanishVoice = AVSpeechSynthesisVoice(language: "es-ES")
|
||||||
|
}
|
||||||
|
|
||||||
private func configureAudioSession() {
|
private func configureAudioSession() {
|
||||||
guard !audioSessionConfigured else { return }
|
guard !audioSessionConfigured else { return }
|
||||||
do {
|
do {
|
||||||
|
|||||||
Reference in New Issue
Block a user