47436

[code]

        let request = SFSpeechURLRecognitionRequest(url:voiceFileURL)
        let semaphore = DispatchSemaphore(value: 0)
        let queue = DispatchQueue.global(qos: .default)
        queue.async {
            self.speechRecognizer.recognitionTask(with: request){ (result, error) in
            guard let result = result else {
                print("=============Recognition failed, so check error for details and handle it")
                return
            }
            if result.isFinal {
                self.recognitionText = (result.bestTranscription.formattedString)
                semaphore.signal()
                print("============A recognitionText : ",self.recognitionText)
            }
        }
        }
        semaphore.wait()
        print("============wait done ")
    
[/code]

recognitionTaskの終了をisFinalで調べてsemaphore.signal()を行いsemaphore.wait()を抜けたいのですが、semaphore.wait()で止まってしまいます。
なにか基本的に間違いを起こしているでしょうか？

* * *
[code]

    @IBAction func addButton(_ sender: UIBarButtonItem) {
    
        let todo = Todo(context: self.context)
        let voiceFile = audioInit()
    
        todo.voiceFileName = voiceFile
        todo.dateTime = Date()
        audioRecorder?.record(forDuration:10.0)
        print("=================:Sleep Start11")
        Thread.sleep(forTimeInterval: 11.0)
         print("=================:Sleep End11")
        todo.text = voiceRecognition(voiceFileName:voiceFile)
    
        self.todos.append(todo)
        (UIApplication.shared.delegate as! AppDelegate).saveContext()
    }
    
    
       func voiceRecognition(voiceFileName : String) -> String{
    
        let fileMgr = FileManager.default
        let dirPaths = fileMgr.urls(for: .documentDirectory,in: .userDomainMask)
        let voiceFileURL = dirPaths[0].appendingPathComponent(voiceFileName)
    
        let request = SFSpeechURLRecognitionRequest(url:voiceFileURL)
        let semaphore = DispatchSemaphore(value: 0)
        let queue = DispatchQueue.global(qos: .default)
        queue.async {
            self.speechRecognizer.recognitionTask(with: request){ (result, error) in
            guard let result = result else {
                print("=============Recognition failed, so check error for details and handle it")
                return
            }
            if result.isFinal {
                self.recognitionText = (result.bestTranscription.formattedString)
                semaphore.signal()
                print("============A recognitionText : ",self.recognitionText)
            }
        }
        }
        semaphore.wait()
        print("============wait done ")
        return(self.recognitionText)
    }
    
[/code]

ソースコードを関連箇所に変更しました。ご指摘通り@IBActionのなかでwaitしています。やりたいことは、addButtonが押されると音声を録音し、音声認識して結果をCoreDataに保存することです。

CoreDataの定義は以下です。  
CoreData Entitis : Todo  
Attribute Type  
・dateTime Date  
・text String  
・voiceFileName String


CC BY-SA 4.0
2018-08-10T08:30:31.937
47412
2018-08-11T01:43:58.393
2018-08-11T01:43:58.393
13972
21142
question
swift ios swift4
音声認識の結果が出てから次の処理に進みたい（DispatchSemaphoreを使っています


iOSではUIスレッドで「待つ」処理は禁止です。セマフォで`wait()`することも`sleep()`もこれに含まれます。

非同期処理の結果を待たずに使用する場合、発想を切り替えないといけません。それは

### 結果を関数の戻り値として返す

から

# 結果を含む通知(イベント)を起動する

と言う切り替えです。非同期処理というよりイベントドリブンですね。

ボタンが押されたら何かの処理をするなんていう時に、

[code]

    while !button.isPressed {
        sleep(100)
    }
    
[/code]

なんてやってたら「ひどい!」と思えるようになってください。(ちなみに仮想コードなので、コンパイルは通りませんが。)

* * *

「通知(イベント)を起動する」には、

  * `Notification`を使う(古い。新しいフレームワークではあまり使われない。)

  * `delegate`を使う(かなり古いが、今でもよく使われる。)

  * 完了ハンドラ(クロージャ)を使う(新しい。最近のフレームワークでは多い。)

と言った方法があります。肝心なのは **受け取った「結果」は受け取ったメソッド(やクロージャ)の中で使う** という点です。

`AVAudioRecorder`は録音完了の通知に`delegate`を使いますが、あなたは`delegate`を利用していないようです。

また、`SFSpeechRecognizer`は完了ハンドラを使いますが、あなたはクロージャの中で結果を使わずにそれを外に渡そうとしています。

* * *

1点目は`delegate`を設定し、適切にメソッドを実装してやるだけですが、2点目については「結果を戻り値として返す」ことから発想を転換してもらう象徴として、あなたのメソッドでも完了ハンドラバターンを使ってみましょう。

コードの関連する部分はこんな感じになります。

[code]

    //### view controllerを AVAudioRecorderDelegate に適合させておく
    class MyViewController: UIViewController, AVAudioRecorderDelegate/*,...*/ {
    
        //...
    
        //### delegateのメソッドへ必要な情報を受け渡すためのプロパティ
        var todoToBeAdded: Todo?
    
        @IBAction func addButton(_ sender: UIBarButtonItem) {
    
            let todo = Todo(context: self.context)
            let voiceFile = audioInit()
    
            todo.voiceFileName = voiceFile
            todo.dateTime = Date()
            self.todoToBeAdded = todo
    
            audioRecorder?.delegate = self //### AVAudioRecorder の初期化時に設定しているなら不要
            //### この辺に「録音中」を示すUI更新を入れる
            audioRecorder?.record(forDuration:10.0)
            print("=================:Recording started")
        }
    
        //### AVAudioRecorderDelegate のメソッドを実装する。録音が完了したらこのメソッドが呼ばれる。
        func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
            guard let todo = self.todoToBeAdded else {
                print("=================:Unexpectedly invoked audioRecorderDidFinishRecording(_:successfully:)")
                return
            }
            self.todoToBeAdded = nil //### ローカル変数`todo`の中に保持され、完了ハンドラーの中にもキャプチャーされているのでお役御免
            if flag {
                print("=================:Recording finished successfully")
                voiceRecognition(voiceFileName: todo.voiceFileName) { (text, error) in
                    //### voiceRecognitionの結果は完了ハンドラの中で使う
                    guard let text = text else {
                        //### 音声認識がエラー終了した時の処理
                        print(error?.localizedDescription ?? "Unexpected error")
                        return
                    }
                    todo.text = text
                    self.todos.append(todo)
                    (UIApplication.shared.delegate as! AppDelegate).saveContext()
                }
            } else {
                print("=================:Recording failed")
                //### 録音が失敗終了した時の処理
            }
        }
    
        //### voiceRecognitionメソッドに完了ハンドラ引数を追加する。戻り値は要らない。
        func voiceRecognition(voiceFileName : String, completionHandler: @escaping (String?, Error?)->()) {
            print("=================:Recognition started")
            let fileMgr = FileManager.default
            let dirPaths = fileMgr.urls(for: .documentDirectory,in: .userDomainMask)
            let voiceFileURL = dirPaths[0].appendingPathComponent(voiceFileName)
    
            let request = SFSpeechURLRecognitionRequest(url:voiceFileURL)
            //### セマフォもキューも要らない
            self.speechRecognizer.recognitionTask(with: request){ (result, error) in
                guard let result = result else {
                    print("=============Recognition failed, so check error for details and handle it")
                    //### エラー終了したことを完了ハンドラで通知
                    completionHandler(nil, error)
                    return
                }
                if result.isFinal {
                    let text = result.bestTranscription.formattedString
                    print("============A recognitionText :", text)
                    //### 成功終了したことを完了ハンドラで通知
                    //### ここで完了ハンドラを呼ぶのも「結果をクロージャの中で使う」に入る
                    completionHandler(text, nil)
                }
            }
        }
    }
    
[/code]

Xcode
9.4.1でコンパイルが通ることを確かめただけなので、細部は修正する必要があるかもしれませんが、大枠としてはこれで動かせるはずです。お試しください。

* * *

余談ですが、ご質問を書かれる場合、プラットフォームを示すタグ`ios`を含められた方が良いでしょう。また言語タグは`swift4`だけでなく`swift`も入れておかれた方がより多くの方の目に止まる可能性が高まります。


CC BY-SA 4.0
2018-08-11T01:43:21.197
47436
2018-08-11T01:43:21.197
13972
47412
answer