快速使用协议和服务

时间:2018-07-26 13:44:19

标签: ios swift swift-protocols

我在我的迅速项目中实现了音频捕获服务,该服务应该处理音频记录并将其转换为文本。

AudioCaptureService

class AudioCaptureService: 

    // EXLCUDED A BUNCH OF SETUP CODE

    func record(textView: UITextView, microphoneButton: UIButton) {
        if audioEngine.isRunning {
            audioEngine.stop()
            recognitionRequest?.endAudio()
            microphoneButton.setImage(#imageLiteral(resourceName: "microphone-full-white").withRenderingMode(.alwaysOriginal), for: .normal)
            print("stopped recording...")
        } else if !audioEngine.isRunning, isRecordingEnabled{
            startRecording(textView: textView)
            print("start recording...")
            microphoneButton.setImage(#imageLiteral(resourceName: "microphone-red").withRenderingMode(.alwaysOriginal), for: .normal)
        }
    }

    fileprivate func startRecording(textView: UITextView) {

        if recognitionTask != nil {
            recognitionTask?.cancel()
            recognitionTask = nil
        }

        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(AVAudioSessionCategoryRecord)
            try audioSession.setMode(AVAudioSessionModeMeasurement)
            try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
        } catch {
            print("audioSession properties weren't set because of an error.")
        }

        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

        let inputNode = audioEngine.inputNode

        guard let recognitionRequest = recognitionRequest else {
            fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
        }

        recognitionRequest.shouldReportPartialResults = true

        recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in

            var isFinal = false

            if result != nil {

                textView.text = result?.bestTranscription.formattedString
                isFinal = (result?.isFinal)!
            }

            if error != nil || isFinal {
                self.audioEngine.stop()
                inputNode.removeTap(onBus: 0)

                self.recognitionRequest = nil
                self.recognitionTask = nil

                //self.microphoneButton.isEnabled = true
            }
        })

        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
            self.recognitionRequest?.append(buffer)
        }

        audioEngine.prepare()

        do {
            try audioEngine.start()
        } catch {
            print("audioEngine couldn't start because of an error.")
        }

        textView.text = ""

    }

}

ViewController

在一个单独的视图控制器中,我有一个麦克风按钮,可以触发此录制功能。当用户单击“麦克风”按钮时,该按钮应变为红色以指示其录音,然后用户输入的音频将显示在该视图控制器的textview中:

class PreviewController: UIViewController {

    var notesOpen = false
    let audioCaptureService = AudioCaptureService()

    let microphoneButton: UIButton = {
        let button = UIButton(type: .system)
        button.addTarget(self, action: #selector(handleRecord), for: .touchUpInside)
        button.setImage(#imageLiteral(resourceName: "muted-white").withRenderingMode(.alwaysOriginal), for: .normal)
        button.isEnabled = false
        return button
    }()

    lazy var notesView: UITextView = {
        let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
        let tv = UITextView()
        tv.layer.cornerRadius = 8
        tv.font = UIFont.boldSystemFont(ofSize: 12)
        tv.backgroundColor = .white
        tv.keyboardDismissMode = .onDrag
        return tv
    }()


    //EXCLUDED SOME BASIC SETUP CODE

    @objc func handleRecord() {
        print("record button pressed")
        toggleNotesView()
        audioCaptureService.record(textView: notesView, microphoneButton: microphoneButton)

}

问题

目前,我的实现可行,但是我怀疑它是否可以改进。我不认为我应该担心将textView和麦克风按钮传递到AudioCaptureService中吗?理想情况下,我想在没有AudioCaptureService的情况下将这些东西分开,这取决于传递给它的textView和按钮可以正常工作。

我正在阅读有关协议的信息,并认为这可能是一种解决方案,但是我似乎无法全神贯注于实现该协议的方式。

我以为我可以做类似的事情:

protocol AudioCaptureServiceDelegate {
    func record(textView: UITextView)
}

但是,然后谁将由PreviewController类作为委托?我对如何更好地实现代码有些困惑,任何建议都会有所帮助。

1 个答案:

答案 0 :(得分:0)

如果它只是输出委托,KISS会说在UITextView中符合,请参见下文。如果您要在委托中处理更多操作,则绝对应该进入PreviewViewController,因为UITextView仅显示文本。

由于音频!= View,有人建议将其放入某种控制器中,这值得争论。控制器是指控制PreviewViewController的东西,该控件随后在PreviewViewController上设置文本以在每次文本更改时显示。然后,recordButton通过PreviewViewControllers的委托到达控制器,该控制器随后处理记录,也许将记录存储在某个地方,等等。

总而言之,ViewController可以让其父控制器处理记录并显示文本,并根据父控制器在ViewController上设置的状态来更改recordButton。

// Variant 1
protocol AudioCaptureServiceOutputDelegate: class {
    func audioCaptureServiceOutputDelegate(outputChanged: String)
}
extension UITextView: AudioCaptureServiceOutputDelegate {
    func audioCaptureServiceOutputDelegate(outputChanged: String) {
        self.text = outputChanged
    }
}

// Variant 2
protocol AudioCaptureServiceOutputDelegate2: class {
    var text: String! { get set } 
}
extension UITextView: AudioCaptureServiceOutputDelegate2 {}

// Variant 3
protocol AudioCaptureServiceOutputDelegate3: class {
    var audioCaptureServiceOutputText: String { get set } 
}
extension UITextView: AudioCaptureServiceOutputDelegate3 {
    var audioCaptureServiceOutputText: String {
        get { return text }
        set { text = newValue }
    }
}

这三个功能都一样好。变体2稍差一些,因为Controller / ViewController实现具有随机名称text的变量可能有点太通用了。

一个控制器可能更喜欢1,因为它在get时应该返回什么?它可能已经存储了几条录音。

呼叫示例

func record(outputDelegate: AudioCaptureServiceOutputDelegate, microphoneButton: UIButton) {
    // ....
    startRecording(outputDelegate: outputDelegate)
    // ....
}

fileprivate func startRecording(outputDelegate: AudioCaptureServiceOutputDelegate) {
    // ....
    outputDelegate.audioCaptureServiceOutputDelegate(outputChanged: result?.bestTranscription.formattedString ?? "Error: result is nil.")
    // ....
    outputDelegate.audioCaptureServiceOutputDelegate(outputChanged: "")
}