You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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的初始化方法里传入管理器实例,或者通过属性设置
  • 这种方式更适合单元测试,但日常开发中单例已经足够简洁高效

注意事项

  1. 别忘了在Info.plist中添加两个权限描述:
    • NSSpeechRecognitionUsageDescription:说明为什么需要语音识别权限
    • NSMicrophoneUsageDescription:说明为什么需要麦克风权限
  2. 一定要在VC销毁时清空回调闭包,避免内存泄漏(上面的代码已经包含了这一步)

内容的提问来源于stack exchange,提问作者Pranavan SP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:43:19