SwiftでのHTTP GETの非同期処理の書き方がわかりません。


# 質問

Swift での`HTTP GET`の「非同期処理」の書き方がわかりません。

## やりたいこと

APIにアクセスして、データをダウンロードできたタイミングでTextに表示させたいです。

`httpGet()`で取得した`Data()`を戻り値としたいのですが、やり方がわかりません。

## エラー

Voidのクロージャーの中で`return data`しており、そのエラーが出ています。

**エラーメッセージ:**

[code]

    Unexpected non-void return value in void function
    
[/code]

**ソースコード:**

[code]

    import SwiftUI
    import Foundation
    
    class API {
        public static func httpGet(_ urlString: String) -> Data?{
            guard let url = URL(string: urlString) else { return nil }
            
            var request = URLRequest(url: url)
            
            request.httpMethod = "GET"
    
            let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                if let error = error {
                    print(error)
                }
                
                guard let data = data else { return }
                
                return data;
            })
            
            task.resume()
            
            return nil
        }
        
    }
    
    class SampleAPI : API{
        public static func getData() -> String? {
            let urlString = "https://api.isevenapi.xyz/api/iseven/6/"
                    
            if let data = super.httpGet(urlString) {
                let dataString = String(data: data, encoding: .utf8)
                return dataString
            } else {
                return nil
            }
        }
    }
    
    struct ContentView : View {
        @State var text: String = "Hello"
        
        var body : some View {
            VStack {
                Text(self.text)
                
                Button(
                    action: {
                        self.showData()
                    },
                    label: {
                        Text("Button")
                    }
                )
            }
        }
        
        func showData() {
            if let text = SampleAPI.getData() {
                print(text)
                self.text = text
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
[/code]




> Swift でのHTTP GETの「非同期処理」の書き方がわかりません。

最も一般的なのは、自前の完了ハンドラを導入する形でしょう。

こんな感じになります。

[code]

    import SwiftUI
    
    class API {
        public static func httpGet(_ urlString: String, completion: @escaping(Data?)->Void){
            guard let url = URL(string: urlString) else {
                completion(nil)
                return
            }
            
            var request = URLRequest(url: url)
            
            request.httpMethod = "GET"
            
            let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                if let error = error {
                    print(error)
                    completion(nil)
                    return
                }
                
                guard let data = data else {
                    completion(nil)
                    return
                }
                
                completion(data)
            })
            
            task.resume()
        }
    }
    
    class SampleAPI : API{
        public static func getData(completion: @escaping(String?)->Void) {
            let urlString = "https://api.isevenapi.xyz/api/iseven/6/"
            
            super.httpGet(urlString) { data in
                if let data = data,
                   let dataString = String(data: data, encoding: .utf8) {
                    completion(dataString)
                } else {
                    completion(nil)
                }
            }
        }
    }
    
    struct ContentView : View {
        @State var text: String = "Hello"
        
        var body : some View {
            VStack {
                Text(self.text)
                
                Button(
                    action: {
                        self.showData()
                    },
                    label: {
                        Text("Button")
                    }
                )
            }
        }
        
        func showData() {
            SampleAPI.getData { text in
                if let text = text {
                    print(text)
                    self.text = text
                } else {
                    print("Got nil")
                }
            }
        }
    }
    
[/code]

完了ハンドラを使うパターンは、ほんの少し発想を変え、呼び出し方法も変えないといけないので、最初は大変難しく感じられるかもしれませんが、慣れれば書き換えはそれほど大変ではなく、何よりも現時点では最も一般的な方法と言えるでしょう。

* * *

もしSwift5.5(Xcode 13)を使用することができる(今の所ターゲットをiOS15世代以降にする必要がある)なら、Swift
5.5で導入されたasync/awaitを使うのが簡単でしょう。まだ時期尚早かと思うので、ここには詳しくは書きませんが、beta版のXcode
13を導入済みなら、色々試してみられると良いでしょう。




> Swift でのHTTP GETの「非同期処理」の書き方がわかりません。

最も一般的なのは、自前の完了ハンドラを導入する形でしょう。

こんな感じになります。

[code]

    import SwiftUI
    
    class API {
        public static func httpGet(_ urlString: String, completion: @escaping(Data?)->Void){
            guard let url = URL(string: urlString) else {
                completion(nil)
                return
            }
            
            var request = URLRequest(url: url)
            
            request.httpMethod = "GET"
            
            let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                if let error = error {
                    print(error)
                    completion(nil)
                    return
                }
                
                guard let data = data else {
                    completion(nil)
                    return
                }
                
                completion(data)
            })
            
            task.resume()
        }
    }
    
    class SampleAPI : API{
        public static func getData(completion: @escaping(String?)->Void) {
            let urlString = "https://api.isevenapi.xyz/api/iseven/6/"
            
            super.httpGet(urlString) { data in
                if let data = data,
                   let dataString = String(data: data, encoding: .utf8) {
                    completion(dataString)
                } else {
                    completion(nil)
                }
            }
        }
    }
    
    struct ContentView : View {
        @State var text: String = "Hello"
        
        var body : some View {
            VStack {
                Text(self.text)
                
                Button(
                    action: {
                        self.showData()
                    },
                    label: {
                        Text("Button")
                    }
                )
            }
        }
        
        func showData() {
            SampleAPI.getData { text in
                if let text = text {
                    print(text)
                    self.text = text
                } else {
                    print("Got nil")
                }
            }
        }
    }
    
[/code]

完了ハンドラを使うパターンは、ほんの少し発想を変え、呼び出し方法も変えないといけないので、最初は大変難しく感じられるかもしれませんが、慣れれば書き換えはそれほど大変ではなく、何よりも現時点では最も一般的な方法と言えるでしょう。

* * *

もしSwift5.5(Xcode 13)を使用することができる(今の所ターゲットをiOS15世代以降にする必要がある)なら、Swift
5.5で導入されたasync/awaitを使うのが簡単でしょう。まだ時期尚早かと思うので、ここには詳しくは書きませんが、beta版のXcode
13を導入済みなら、色々試してみられると良いでしょう。

