Swift NSURL,エラー処理について googleBooks API実装
エラー処理において、API接続のときなどによく利用します。
エラー処理というのを、簡単に説明します。
まず、値にnilが入力されるとプログラムが落ちてしまいます。
APIでデータ取得時などは、アドレスの違いや、サーバーのエラー、レスポンスの解析不具合、レスポンスの遅延など、エラーが起こりうる要素(nilが入る可能性)がてんこ盛りです。
今回は、まずURLの読み込みについて流れを説明します。
まずURLのロードによりURLへアクセスします。
iOSのアプリケーションは、ずっと待っています。
データやエラーが到着するとそれを処理します。
URLSessionのインスタンスを作成すると、サーバーにデータをアップロードしたり、キャッシュやCookieを利用したりできます。
セッションを利用するときに、クロージャーを利用すると、サーバーからデータ帰ってくるのを待たずに、他の処理をすることができ便利です。
データが帰ってきたときに、クロージャが実行されます。
便利ですね。クロージャーについてわからない方は、こちら
いよいよNSURL
urlセッションタスクはNSDataオブジェクトを使用してデーたを送受信します。
これがいいのは、データをバックグラウンドアップロードをサポートしてくれるところ。
つまり、プログラムの実行を、データ帰ってくるまで待たなくて良い。
これを非同期という。
非人情ではない。非人情は情けのないひどい人の事を言うが、プログラムの非同期はとても良いのだ。
プログラムが途中で止まらないので、、、
具体的には、
転送が正常に終了するか、エラーが発生したときに完了ハンドラブロックを呼び出す。
データが受信され、転送が完了すると、セッションのデリゲートのメソッドを呼び出します。
今回はこの記事のソースを実装してみようと思う。
How to parse JSON in Swift using NSURLSession - Stack Overflow
で結果がこれ
ソースの中にコメントで記載していきます。
//エラーが出たらfalse なかったらtrueを返すだけの関数にしました。 func searchBooks(keyword:String)->Bool{ //日本語入力はエンコードしないとだめです。 //検索キーワードをURLエンコードする guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else{ // 空だったらエラー return false } //エンドポイントへのリクエスト パラメーターをq=で渡している。 //リクエストURL作成 guard let req_url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(keyword_encode)") else{ return false } //先程のurlをURLRequest型へ、ここ結構ハマりました。 let request = NSMutableURLRequest(url: req_url) as URLRequest //ここはアップルのリファレンスを参照① let task = URLSession.shared.dataTask(with: request){ (data, response, error) in //中身をチェック print(keyword) print(keyword_encode) print(req_url) do{ let data = try Data(contentsOf: req_url) let searchResult = try JSONDecoder().decode(SearchResut.self, from: data) //searchResultに帰ってきたのを一つ一つ詰め込む。 for item in searchResult.items{ //ここでAPIを分解。分解している構造体は参照② let volumeInfo = item.volumeInfo let title = volumeInfo.title let description = volumeInfo.description let bookInfo = BookInfo(title: title, description: description) self.bookList.append(bookInfo) } //ここはtableViewをリロードしないといけない、しかしメインキューでtableViewのリロードが行なわれるので、メインキューを呼び出して実行。 DispatchQueue.main.async { self.tableView.reloadData() } }catch{ print("エラーが出ました") } } //ここで非同期なので、値が返却されるまで止まっているので、動かしてあげないといけない。 task.resume() return true
参照①
https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
このように記載してある。
completionHandlerとは何らかの処理要求が発生したときに起動されるプログラムの事。
Swiftではざっくりクロージャと考えて良い。
このハンドラは次のパラメーターがある。引数だね。
data
サーバーから帰ってくるデータ
response
HTTPヘッダーやステータスコードなどの応答メタデータを提供するオブジェクト。 HTTPまたはHTTPS要求を行う場合、返されるオブジェクトは実際にはHTTPURLResponseオブジェクトです。
error
要求が失敗した理由を示すエラーオブジェクト。要求が成功した場合はnil。
つまりこの関数はcompletionHandler以下にクロージャを書いてあげる。→なんという大雑把な。
//クロージャーは{ではじまり、引数を( )でくくって最初に記載できる。 in のあとに実装するコードを書く。 { (data, response, error) in
でクロージャーに、@escapingが関数定義で指定されている。これはどういう意味かと言うと
1-クロージャーがプロパティとして保存される(強参照される)
または
2-クロージャーがメソッド内ですぐに実行されない(非同期である)
今回は2番の非同期であります。
つまり値がいつかえってくるか、保証しないと言うこと。
参照②APIの取り出し
struct SearchResut: Codable { let kind:String let items:[Item] } struct Item: Codable{ let volumeInfo:VolumeInfo } struct VolumeInfo: Codable{ let title:String let description:String }
GoogleBook APIからのレスポンスはこれ
{ "kind": "books#volumes", "totalItems": 81, "items": [ { "kind": "books#volume", "id": "Aj5KbxLeXq0C", "etag": "GQfnr14OKQI", "selfLink": "https://www.googleapis.com/books/v1/volumes/Aj5KbxLeXq0C", "volumeInfo": { "title": "Population", "subtitle": "Special Report on Institutional Population 14 Years Old and Over, Characteristics of Inmates in Penal Institutions and in Institutions for the Delinquent, Defective, and Dependent", "authors": [ "United States. Bureau of the Census. 16th Census, 1940" ], "publishedDate": "1943", "industryIdentifiers": [ { "type": "OTHER", "identifier": "STANFORD:36105022635309" } ],