在 iOS 10 中进行有效同步的异步调用的最佳方法

Posted

技术标签:

【中文标题】在 iOS 10 中进行有效同步的异步调用的最佳方法【英文标题】:Best way to make an asynchronous call effectively synchronous in iOS 10 【发布时间】:2017-02-08 18:08:07 【问题描述】:

为了更好地解释我的情况,我正在尝试制作一个应用程序,它会在按下按钮时播放 ping 噪音,然后立即继续录制和转录用户的声音。

对于 ping 声音,我使用系统声音服务,录制音频我使用 AudioToolbox,转录它我使用语音工具包。

我认为我的问题的症结在于异步系统声音服务播放功能的时机:

    //Button pressed function

    let audiosession = AVAudioSession.sharedInstance()

    let filename = "Ping"
    let ext = "wav"

    if let soundUrl = Bundle.main.url(forResource: filename, withExtension: ext)

        var soundId: SystemSoundID = 0
        AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId)

        AudioServicesAddSystemSoundCompletion(soundId, nil, nil, (soundid,_) -> Void in 
        AudioServicesDisposeSystemSoundID(soundid)
        print("Sound played!"), nil)

        AudioServicesPlaySystemSound(soundId)
    

    do
        try audiosession.setCategory(AVAudioSessionCategoryRecord)
        try audiosession.setMode(AVAudioSessionModeMeasurement)
        try audiosession.setActive(true, with: .notifyOthersOnDeactivation)
        print("Changing modes!")

    catch
        print("error with audio session")
    

    recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

    guard let inputNode = audioEngine.inputNode else
        fatalError("Audio engine has no input node!")
    

    guard let recognitionRequest = recognitionRequest else
        fatalError("Unable to create a speech audio buffer recognition request object")
    

    recognitionRequest.shouldReportPartialResults = true

    recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, delegate: self)

    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()
        delegate?.didStartRecording()

    catch
        print("audioEngine couldn't start because of an error")
    

当我运行这段代码时,它会记录声音并成功转录。但是从未播放过ping。我在其中的两个(非错误)打印语句按顺序触发:

    改变模式! 播放声音!

据我了解,没有播放 ping 声音的原因是,当它实际完成时,我已经将音频会话类别从播放更改为录制。为了验证这是真的,我尝试删除除了声音服务 ping 之外的所有内容,它会按预期播放声音。

所以我的问题是绕过 AudioServicesPlaySystemSound 调用的异步特性的最佳方法是什么?我已经尝试尝试将 self 传递给完成函数,这样我就可以让它在我的类中触发一个函数,然后运行记录块。但是,我无法弄清楚实际上如何将 self 转换为 UnsafeMutableRawPointer,以便它可以作为 clientData 传递。此外,即使我知道如何做到这一点,我也不确定这是否是一个好主意或该参数的预期用途。

或者,我可以依靠通知中心之类的东西来解决这个问题。但再一次,这似乎是一种非常笨拙的解决问题的方法,我以后会后悔的。

有谁知道处理这种情况的正确方法是什么?

更新:

根据 Gruntcake 的要求,这是我在完成块中访问 self 的尝试。

首先我创建一个 userData 常量,它是一个 UnsafeMutableRawPointer to self:

    var me = self
    let userData = withUnsafePointer(to: &me)  ptr in
        return unsafeBitCast(ptr, to: UnsafeMutableRawPointer.self)

接下来我在回调块中使用该常量,并尝试从中访问 self:

    AudioServicesAddSystemSoundCompletion(soundId, nil, nil, (sounded,me) -> Void in 
        AudioServicesDisposeSystemSoundID(sounded)
        let myself = Unmanaged<myclassname>.fromOpaque(me!).takeRetainedValue()
        myself.doOtherStuff()
        print("Sound played!"), userData)

【问题讨论】:

我希望它这么简单......但是我遇到的问题是我不能简单地在闭包中引用 self 。具体来说,如果我只是将 self.doOtherStuff() 放在那里,我会收到以下编译器错误:“不能从捕获上下文的闭包中形成 C 函数指针”。 AudioServicesAddSystemSoundCompletion 方法的最后一个参数称为“inClientData”,这有助于在文档中说“应用程序数据在调用时传递给回调函数”。类型是 UnsafeMutableRawPointer,这就是我最初做整个 userData 的原因 您尝试在完成块中调用 doOtherStuff() 是一种正确的方法(唯一的另一个是通知,这是仅有的两个选项)在这种情况下,复杂的是 Obj 的桥接-C 到 Swift 是必要的。这里的最后一个(不被接受的)答案对你有用吗***.com/questions/26823405/… @Mungbeans,谢谢!那成功了。在试图弄清楚这一点时,我实际上偶然发现了同样的问题。但我什至没有看过那个答案,因为评分低且状态不被接受……如果你想发表你的评论作为我问题的答案,我会接受。 【参考方案1】:

您尝试在完成块中调用 doOtherStuff() 是一种正确的方法(唯一的另一个是通知,这是仅有的两个选项)

在这种情况下,复杂的是从 Obj-C 到 Swift 的桥接是必要的。这样做的代码是:

let myData = unsafeBitCast(self, UnsafeMutablePointer<Void>.self)
AudioServicesAddSystemSoundCompletion(YOUR_SOUND_ID, CFRunLoopGetMain(), kCFRunLoopDefaultMode, (mSound, mVoid) in
        let me = unsafeBitCast(mVoid, YOURCURRENTCLASS.self)
        //me it is your current object so if yo have a variable like
        // var someVar you can do
        print(me.someVar)
    , myData)

学分:此代码取自此问题的答案,尽管它不是公认的答案:

How do I implement AudioServicesSystemSoundCompletionProc in Swift?

【讨论】:

以上是关于在 iOS 10 中进行有效同步的异步调用的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

阻塞非阻塞,同步异步总结

同步异步阻塞非阻塞 总结

同步与异步,阻塞与非阻塞

从同步异步阻塞非阻塞到5种IO模型

从同步异步阻塞非阻塞到5种IO模型

同步调用,异步回调和 Future 模式