如何让SFSpeechRecognizer在各UIViewController中复用?
如何复用SFSpeechRecognizer并优化多VC场景的代码
嘿,这个问题我太熟了!你现在已经有了基础的语音转文本代码,但要在多个VC里复用,最靠谱的方式是把语音识别的逻辑封装成一个独立的管理器类,用单例模式来保证全局只有一个实例(毕竟SFSpeechRecognizer没必要重复创建多个),同时把所有和语音识别相关的细节都隐藏在这个管理器里,让各个ViewController只需要调用简单的API就能使用功能。
推荐的设计:单例模式封装语音识别管理器
这种方式的核心是把SFSpeechRecognizer、识别请求、任务这些核心对象都放在一个全局可访问的管理器里,统一处理权限、识别回调、状态管理,各个VC只需要和这个管理器交互,不用自己实现代理逻辑。
第一步:创建SpeechRecognitionManager类
import Foundation import Speech import AVFoundation class SpeechRecognitionManager: NSObject, SFSpeechRecognizerDelegate { // 单例实例,全局唯一 static let shared = SpeechRecognitionManager() // 语音识别核心对象 private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "zh-CN"))! // 改成你需要的语言 private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? private var recognitionTask: SFSpeechRecognitionTask? private let audioEngine = AVAudioEngine() // 用来向外部传递识别结果的回调 var onRecognitionResult: ((String) -> Void)? // 传递错误信息的回调 var onRecognitionError: ((Error) -> Void)? private override init() { super.init() speechRecognizer.delegate = self // 请求权限 requestAuthorization() } // 请求语音识别和麦克风权限 private func requestAuthorization() { SFSpeechRecognizer.requestAuthorization { authStatus in DispatchQueue.main.async { switch authStatus { case .authorized: print("语音识别权限已授权") case .denied: print("用户拒绝了语音识别权限") case .restricted: print("设备不支持语音识别") case .notDetermined: print("权限未确定") @unknown default: break } } } AVAudioSession.sharedInstance().requestRecordPermission { granted in if !granted { print("麦克风权限未授权") } } } // 开始录音识别 func startRecording() throws { // 如果有正在进行的任务,先取消 if let recognitionTask = recognitionTask { recognitionTask.cancel() self.recognitionTask = nil } let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) recognitionRequest = SFSpeechAudioBufferRecognitionRequest() let inputNode = audioEngine.inputNode guard let recognitionRequest = recognitionRequest else { fatalError("无法创建识别请求") } recognitionRequest.shouldReportPartialResults = true // 开始识别任务 recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in var isFinal = false if let result = result { // 把识别结果通过回调传给外部 self.onRecognitionResult?(result.bestTranscription.formattedString) isFinal = result.isFinal } if error != nil || isFinal { self.audioEngine.stop() inputNode.removeTap(onBus: 0) self.recognitionRequest = nil self.recognitionTask = nil if let error = error { self.onRecognitionError?(error) } } } let recordingFormat = inputNode.outputFormat(forBus: 0) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in self.recognitionRequest?.append(buffer) } audioEngine.prepare() try audioEngine.start() } // 停止录音识别 func stopRecording() { if audioEngine.isRunning { audioEngine.stop() recognitionRequest?.endAudio() } } // 代理方法:当识别器可用性变化时调用 func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) { if !available { print("语音识别器不可用") stopRecording() } } }
第二步:在ViewController中使用管理器
现在任何一个VC都可以轻松调用语音识别功能,不用重复写代理和权限逻辑:
import UIKit class FirstViewController: UIViewController { @IBOutlet weak var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() // 设置识别结果的回调 SpeechRecognitionManager.shared.onRecognitionResult = { [weak self] text in DispatchQueue.main.async { self?.textView.text = text } } // 设置错误回调 SpeechRecognitionManager.shared.onRecognitionError = { error in print("识别出错:\(error.localizedDescription)") } } @IBAction func startButtonTapped(_ sender: UIButton) { do { try SpeechRecognitionManager.shared.startRecording() sender.setTitle("停止识别", for: .normal) } catch { print("无法开始录音:\(error.localizedDescription)") } } @IBAction func stopButtonTapped(_ sender: UIButton) { SpeechRecognitionManager.shared.stopRecording() sender.setTitle("开始识别", for: .normal) } // 销毁时清空回调,避免内存泄漏 deinit { SpeechRecognitionManager.shared.onRecognitionResult = nil SpeechRecognitionManager.shared.onRecognitionError = nil } }
其他可选设计模式
如果你不想用单例(比如担心测试不便),也可以用依赖注入模式:
- 把SpeechRecognitionManager作为参数传递给每个需要的ViewController
- 比如在VC的初始化方法里传入管理器实例,或者通过属性设置
- 这种方式更适合单元测试,但日常开发中单例已经足够简洁高效
注意事项
- 别忘了在
Info.plist中添加两个权限描述:NSSpeechRecognitionUsageDescription:说明为什么需要语音识别权限NSMicrophoneUsageDescription:说明为什么需要麦克风权限
- 一定要在VC销毁时清空回调闭包,避免内存泄漏(上面的代码已经包含了这一步)
内容的提问来源于stack exchange,提问作者Pranavan SP
相关产品推荐
相关产品推荐

