Combine入門 | Combineを使ってネットワーク接続する方法

この記事では、Combineを使ってネットワークアクセスする方法を解説します。

Combineが初めての方やCombineの他の例については、次の記事もご覧ください。

関連記事

Combineはアップル純正の非同期処理を実装するためのフレームワークです。SwiftUIのバインディングなどでも使われています。重要なフレームワークです。 Combineを使った他の例については以下の記事をご覧ください。[…]

スポンサーリンク

DataTaskを実行するPublisherを作る

Combineを使ってネットワークアクセスするには、DataTask用のPublisherを作ります。Publisherを作るためのメソッドは以下の2つです。

func dataTaskPublisher(for: URL) -> URLSession.DataTaskPublisher

func dataTaskPublisher(for: URLRequest) -> URLSession.DataTaskPublisher

引数が異なる2つのメソッドです。最初のメソッドはURLを指定します。単純にGETでURLに接続してダウンロードしたいときは、こちらを使います。

2番目のメソッドはURLRequestを指定できます。URLRequestなら、HTTPのメソッドや送信するボディデータ、HTTPのヘッダーなど色々な指定できます。

この2つのメソッドがあれば、全てのケースに対応できるでしょう。但し、ダウンロードするファイルが巨大な場合には、プログレス処理ができないのと、メモリ不足になるので、URLSessionのDownloadTaskを単体で使った方が望ましいと思います。

受信する

サーバーのデータやレスポンス、エラーなどの受信は、sinkを使います。

func sink(receiveCompletion: @escaping ((Subscrivers.Completion<URLError>) -> Void),
receiveValue: @escaping (((data: Data, response: URLResponse)) -> Void)) -> AnyCancellable

sinkの戻り値はどこかに保持しておきます。

sinkは次のような形で使用します。

cancellable = urlSession.dataTaskPublisher(for: url)
.sink(receiveCompletion: { (failure) in
	switch failure {
		case .finished:
			// エラーなく完了できたときの処理
			break

		case .failure(let error: URLError):
			// HTTPレベルではないエラーが起きたときの処理
			break
	}
}, receiveValue: { (data: Data, response: URLResponse) in 
	// サーバーから取得したデータの処理
})

実行されるスレッドに注意

sinkの引数で渡した完了処理やデータの取得処理はどちらもメインスレッドではなく、サブスレッドで実行されます。GUIの更新など、メインスレッドでしかできないことを、やらないように注意しましょう。

サンプルコード

“GET”ボタンをタップすると、テキストフィールドに入力したURLからダウンロードするサンプルコードを作りました。

SwiftUIで書いているので、ContentView.swiftにコピー&ペーストで動作します。試してみてください。

import SwiftUI
import Combine

struct ContentView: View {
    class ContentViewContext {
        public var cancellable: AnyCancellable?
        
        init() {
            self.cancellable = nil
        }
    }
    
    @State private var urlString: String = "https://example.com/"
    @State private var logText: String = ""
    
    private var urlSession: URLSession = URLSession(configuration: .default)
    private var context: ContentViewContext = ContentViewContext()
    
    var body: some View {
        VStack {
            TextField("URL", text: $urlString)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .keyboardType(.URL)
            Button(action: {
                self.getFromServer()
            }) {
                Text("GET")
            }
            
            ScrollView(.vertical, showsIndicators: true) {
                Text("\(self.logText)")
                .frame(width: 400)
            }
            .frame(width: 400, height: 400, alignment: .top)
        }.padding()
    }
    
    func getFromServer() {
        guard let url = URL(string: self.urlString) else {
            return
        }
        
        self.logText = ""

        self.context.cancellable = self.urlSession.dataTaskPublisher(for: url)
            .sink(receiveCompletion: { (failure) in
                DispatchQueue.main.async {
                    switch failure {
                    case .finished:
                        self.logText += "Finished\n"
                        
                    case .failure(let error):
                        self.logText += "Failure: \(error.localizedDescription)\n"
                    }
                }
                 
            }, receiveValue: { (data: Data, response: URLResponse) in
                DispatchQueue.main.async {
                    if let httpURLResponse = response as? HTTPURLResponse {
                        self.logText += "HTTP STATUS: \(httpURLResponse.statusCode)\n\n"
                    }
                    
                    if let text = String(data: data, encoding: .utf8) {
                        self.logText += "\(text)\n"
                    }

                }
            })
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

スポンサーリンク
最新情報をチェックしよう!
>現役のプログラマーが書くプログラミング情報

現役のプログラマーが書くプログラミング情報

日々の開発の中での学びや分かったこと、調べたことなどを書いていくブログです。

CTR IMG