![見出し画像](https://assets.st-note.com/production/uploads/images/94017422/rectangle_large_type_2_a2f7f532bfd34f693904134e41b66a54.png?width=1200)
iOSエンジニアを目指すプログラミング初心者のほとんどが躓くクロージャを徹底的に細かく分解して解説していく (練習問題あり、回答のみ有料)
私について
コンピュータサイエンスの博士号を取得後10年ほどSEをしつつ、3年ほど前から個人でiOSアプリ開発の個人指導をしています。
これまで未経験であれば7人ほどiOSアプリエンジニアとして内定が出るぐらいまで教育しました。
また、半年ほど都内のプログラミング専門学校で講師をしています。
こちらのnoteは授業で話した内容を基に再編成したものとなっています。
内容について
ここに書かれている内容はiOS開発などで使われるSwift言語を前提にしていますが厳密に仕様や定義に基づいて解説しているものではありません。
初心者に対して正確性より分かりやすさを重視した内容となっており、現場で10年以上SEをしていて「大雑把にこういう理解で困ったことはない」ぐらいのものと思って下さい。
正確な内容を知りたい方はこちらの公式ドキュメントをご参照ください。
用語について
このnoteではfuncで定義されている処理を全て関数と表記します。
また以下の式のように=以降に記載されている{から}までを名前のない関数と表記します。
let closure = { value in print(value) }
非同期処理
少し話が概念的になりますが、クロージャの利用例である非同期処理について先に説明します。
非同期処理とは
プログラムは本来、ソースコードを上から下に順番に実行されます。
これを同期処理と言います。
![](https://assets.st-note.com/img/1671024395444-xyZVjrkidv.png?width=1200)
しかし、昨今のプログラミングでは単純に上から下に実行できないことがあります。
例えば世界の裏側にあるサーバーと通信して受け取ったデータを画面に表示する場合は以下のようになります。
![](https://assets.st-note.com/img/1671026192719-NdSNO6lvrQ.png?width=1200)
②の部分を実行した後に③を実行したくても、サーバーからデータを取得するまで時間がかかるためすぐには実行できません。
「②の処理が終わるのを待ってから③以降を動かせばいいじゃないか」と思うかもしれませんが、そうなるとアプリのプログラム自体がそこで止まって待つことになってしまうため、仮にサーバーからデータが取得できるまで10秒かかると、その10秒間、アプリはユーザの操作を受け付けなくなってしまいます。
それを防ぐために②以降の処理は一旦無視して、スマホアプリとしてはさらに別のプログラム(ユーザがタッチ操作した時の処理や画面をスクロールした時の処理)を実行していきます。
そして、サーバーからデータが返ってきたタイミングで無視していた部分のソースコードを実行するという動きになります。
![](https://assets.st-note.com/img/1671272444497-50rIbX4AHv.png?width=1200)
これが上から下に実行されない非同期処理の概念的な流れとなります。
現実の世界の非同期処理
「なんでコンピュータの世界はわざわざこんなややこしい仕組みを導入してるんだ?」と思われる方がいるかもしれませんが、現実の世界でも非同期処理があります。
それはコンビニのレジで会計をしている時です。
以下の図のようにコンビニでお弁当を温める時、店員は温め終わるのを待たずに次の処理、次の処理と続けています。
そして温め終わると、お弁当をお客様に渡すという残りのタスクを実行します。
![](https://assets.st-note.com/img/1671293557893-QGv761I3SC.png?width=1200)
もしこれが非同期処理ではない場合(つまり同期処理)だと以下のようになります。
![](https://assets.st-note.com/img/1671294103065-sRsmhwFARh.png?width=1200)
おそらくお客様はイライラしてそのコンビニを利用してくれなくなると思います。
こうならないようにコンピュータも待ち時間の長い処理は他に任せて次の処理を実行していくようにできています。
補足
実は最新仕様ではクロージャを使わなくてもサーバー通信の非同期処理を実現できる書き方ができたのですが、これは最新仕様のため、既存のプロジェクトのソースはクロージャで書かれてることがまだ多いかと思います。
クロージャとは
Swiftに限らず他の言語でも同様の機能があり、ラムダ式や無名関数と呼ばれています。
初心者が躓く原因でもありますが、大雑把に言葉で説明するとクロージャとは
「名前のない関数」を「変数/定数」に代入する機能
と、なります。
とは言え、いきなりでは何のことは分からなくて普通ですので、見慣れた関数からクロージャへの書き換えを次節からやっていきます。
関数からクロージャへの書き換え
関数からクロージャへの書き換えを説明します。
準備として、valueという名前の定数に10を代入しておき、IntとStringを受け取りStringを返す関数をmethodという名前で定義しておきます。
// valueという定数に10を代入
let value = 10
// Int型のinputValueとString型のiputStrを
// パラメータとして受け取り
// Stringを返す関数を
//methodという名前で定義する
func method(inputValue: Int, inputStr: String) -> String {
return "\(inputValue)と\(inputStr)が入力されました"
}
まずvalueに代入している値と関数の名前であるmethodを消します。
// valueに代入する値を消す
let value =
// 関数の名前を消す
(inputValue: Int, inputStr: String) -> String {
return "\(inputValue)と\(inputStr)が入力されました"
}
名前のなくなった関数をvalueに代入します。
// valueに名前のない関数を代入する
let value = (inputValue: Int, inputStr: String) -> String {
return "\(inputValue)と\(inputStr)が入力されました"
}
個人的な理想として↑のこの状態が完成系なのですがSwiftの言語仕様上で何か都合が悪いことがあったのかここから書き換えが必要です。
元の { があったところを in に書き換え、関数の先頭に { を付け足します。
// クロージャ
let value = { (inputValue: Int, inputStr: String) -> String in
return "\(inputValue)と\(inputStr)が入力されました"
}
これでクロージャとしてビルドが通る形となります。
まさに最初に説明した通りクロージャとは
「名前のない関数」を「変数/定数」に代入する
ということができるようになっただけとなります。
「だから何なんだ?」という疑問が湧いて当然ですが、まずは「クロージャとはこういう仕様なんだな」と頭に入れてもらえたら十分です。
練習問題1
クロージャの書き方に慣れる課題です。
次の関数をクロージャに書き換えましょう。
練習問題はplaygroundで実行可能です。
playgroundの使い方はこちらの情報を参考にしてください。
回答は最後にあります。
import Foundation
func add(value1: Int, value2: Int) -> Int {
return value1 + value2
}
クロージャを実行する
改めて関数とクロージャを並べてみます。
// 関数
func method(inputValue: Int, inputStr: String) -> String {
return "\(inputValue)と\(inputStr)が入力されました"
}
// クロージャ
let value = { (inputValue: Int, inputStr: String) -> String in
return "\(inputValue)と\(inputStr)が入力されました"
}
こちらのvalueに代入されているのは名前のない関数です。
改めて書きますが、つまりvalueは関数なんです。
関数である以上、当然methodと同じように実行できます。
関数の実行
// 関数
func method(inputValue: Int, inputStr: String) -> String {
return "\(inputValue)と\(inputStr)が入力されました"
}
// 関数を実行
method(inputValue: 10, inputStr: "文字列です")
クロージャの実行
// クロージャ
let value = { (inputValue: Int, inputStr: String) -> String in
return "\(inputValue)と\(inputStr)が入力されました"
}
// クロージャを実行
value(10, "文字列です")
Swiftの場合は入力するパラメータも含めて関数の名前となるため実行時にinputValueやinputStrも記載します。
クロージャは実行時にinputValueやinputStrなどのパラメータを記載しなくてもよい仕様です。
これでクロージャの宣言の仕方と実行の仕方が分かったかと思います。
書き方が違うだけで関数と同じと思ってもらえばいいです。
クロージャをパラメータに持つ関数
それでは非同期処理を実現するためにクロージャをどのように使うのか説明していきます。
そのためにはまず、クロージャをパラメータに持つ関数について説明する必要があります。
おさらいですがクロージャとは以下のように関数を代入した「変数/定数」のことです。
![](https://assets.st-note.com/img/1671351353473-NNjxv4BDt9.png?width=1200)
そして、関数は以下のように「定数」をパラメータとして受け取ることができます。
![](https://assets.st-note.com/img/1671351337614-y5f2hpvZ4p.png?width=1200)
ということは、以下のように関数はクロージャをパラメータとして受け取ることができることになります。
![](https://assets.st-note.com/img/1671377448461-gnUfdl5a7F.png?width=1200)
ただし、関数のパラメータとして定義するにはIntやStringといった型を指定する必要があります。
上の図でいうと(clousure: のあとの空白の部分です。
ではクロージャの型とはなんでしょうか?
クロージャの型
何度も書いたようにクロージャとは「名前のない関数」が代入された「変数/定数」のことであり、すなわち「クロージャは関数」のことです。
そして関数とは「何が代入されて何が出力されるのか」ということが大まかな型になります。
以下の場合は「Intが入力されてVoidが出力される」のがclosure定数に代入されている名前のない関数の型となります。
// 入力された数値に10を足すクロージャ
let closure = { (inputValue: Int) -> Void in
let v = 10
print(v + inputValue)
}
closureのあとに省略された型をしっかり明記すると以下のようになります。
// 入力された数値に10を足すクロージャ
let closure: ((Int) -> Void) = { (inputValue: Int) -> Void in
let v = 10
print(v + inputValue)
}
closure:のあとの((Int) -> Void)がこの定数の型であり、同じ型の関数を代入できることになります。
型としては(Int) -> Voidだけで指定できているのですが、文法の都合上、全体を()で囲う必要があります。
構造が二重の入れ子になっているため非常にややこしいです。
「クロージャの定数の型は((Int) -> Void)」であり、そこに代入される「名前のない関数の入力のパラメータがInt型で戻り値がVoid型」です。
練習問題2
型に合わせて正しくクロージャを定義する課題です。
次のclousreという定数に、名前のない関数を代入して実行できるようにしましょう。
関数の中身自体は何でも構いません。
練習問題はplaygroundで実行可能です。
playgroundの使い方はこちらの情報を参考にしてください。
回答は最後にあります。
import Foundation
let closure: ((String, Int) -> String) =
nilを許容するクロージャ
SwiftにはOptional型という仕様があります。
普通Int型は何かしら数値を代入しなければならずいわゆるnil(他の言語でいうNULLと思ってください)にはできません。
それと同様に ((Int) -> Void)型もnilを代入することはできません。
// nilを許容していないのでエラーとなる
let value: Int = nil
// nilを許容していないのでエラーとなる
let closure: ((Int) -> Void) = nil
nilを許容するInt型としてOptional<Int>もしくはInt?として型を定義できます。
同様にクロージャも以下のようにnilを許容させることができます。
// nilを許容しているのでエラーとならない
// あとから何か名前のない関数を代入するのでvarで定義
var value: Int? = nil
// nilを許容しているのでエラーとならない
// あとから何か名前のない関数を代入するのでvarで定義
var closureOrNil: ((Int) -> Void)? = nil
そしてoptionalなクロージャを実行する時は以下のように()の前に?を書きます。
closureOrNil?(10)
ではこのクロージャを実行したら何が出力されるでしょうか?
実はこのクロージャ、Intを受け取りVoidを返す名前のない関数を代入する型として定義はしていますが、肝心の関数が代入されていないため実行しても何も起きません。
以下のように、何かしらの名前のない関数を代入するからこそ、関数の中身が実行されます。
// 変数を定義
var closureOrNil: ((Int) -> Void)? = nil
// 変数を実行 = nilを実行
closureOrNil?(10)
// 出力 --> "何も起きない"
// 名前のない関数を代入
closureOrNil = { (value: Int) in
print("入力したの\(value)だね")
}
// 変数を実行 = 変数に代入された名前のない関数を実行
closureOrNil?(10)
// 出力 --> 入力したの10だね
// 別の名前のない関数を代入
closureOrNil = { (value: Int) in
let v = value * value
print("計算結果は\(v)だね")
}
// 変数を実行 = 変数に代入された名前のない関数を実行
closureOrNil?(10)
// 出力 --> 計算結果は100だね
クロージャをパラメータにもつ関数を定義する
クロージャの型が理解できれば、クロージャをパラメータにもつ関数の定義もできるようになります。
例えば「『入力のパラメータがInt型で戻り値がVoid型の関数』を受け取るクロージャをパラメータにもつmethodという関数」なら次のようになります。
func method(closure: ((Int) -> Void) ) -> Void {
// クロージャを実行
closure(10)
}
他の例として「『入力のパラメータがInt型とString型のふたつがあり戻り値がVoid型の関数』を受け取るクロージャをパラメータにもつmethodという関数」なら次のようになります。
func method(closure: ((String, Int) -> Void) ) -> Void {
// クロージャを実行
closure("クロージャを実行だ", 10)
}
「『入力のパラメータがなくて戻り値がString型の関数』を受け取るクロージャをパラメータにもつmethodという関数」なら次のようになります。
func method(closure: (() -> String) ) -> Void {
// クロージャを実行
let str = closure()
//クロージャの実行結果を出力
print(str)
}
「『入力のパラメータがなくて戻り値がInt型の関数』を受け取るクロージャと、『入力のパラメータがInt型で戻り値がない関数』を受け取るクロージャみたいに、ふたつのクロージャをパラメータにもつmethodという関数」なら次のようになります。
func method(closure1: (() -> Int), closure2: ((Int) -> Void) ) -> Void {
// クロージャ1を実行
let value = closure1()
// クロージャ2を実行
closure2(value)
}
「String型のパラメータと、『入力のパラメータがなくて戻り値がInt型の関数』を受け取るクロージャをパラメータにもつmethodという関数」なら次のようになります。
func method(url: String, closure: (() -> Int)) -> Void {
// 入力したurlを出力
print(url)
// クロージャを実行
let value = closure()
}
このようにクロージャをパラメータに持つ関数の定義ができます。
ただし、関数は定義しただけでは意味がなく実行しなければいけません。
当たり前のように思えるかもしれませんが、初学者の方は「関数の中でクロージャを実行している」ため、つい「この関数も実行していると勘違い」してしまいがちでした。
クロージャをパラメータにもつ関数を実行する
「クロージャをパラメータにもつ関数を実行する」と聞いてどんどんややこしく感じますが、これまでもInt型をパラメータとして受け取る関数を「定義して」「実行して」いたかと思います。
関数を定義します。
//Int型をパラメータとして受け取る関数を定義
func method(value: Int) {
print(value)
}
関数を実行します。
//関数を実行
method(value: 10)
// 出力 ---> 10
これと同様にクロージャをパラメータとして受け取る関数を「定義して」「実行する」必要があります。
クロージャを受け取る関数を定義します。
// Int型とクロージャをパラメータとして受け取る関数を定義
func method(value: Int, closure: ((String) -> Void)) {
if value < 0 {
closure("マイナスが入力されました")
} else {
let str = "\(value)が入力されました"
closure(str)
}
}
関数を実行します。
// 関数に入力する値
let inputValue1 = 10
let closure1 = { (str: String) -> Void in
print("入力されたものは以下のようになっています")
print(str)
}
// 関数を実行
method(value: inputValue1, closure: closure1)
// 出力 --->
// 入力されたものは以下のようになっています
// 10が入力されました
methodはそのままで渡すクロージャを変更することで出力を変えることができます。
// 関数に入力する別の値
let inputValue2 = -10
let closure2 = { (str: String) -> Void in
print("input value is ....")
print(str)
}
// 関数を実行
method(value: inputValue2, closure: closure2)
// 出力 --->
// input value is ....
// マイナスが入力されました
クロージャをパラメータにもつ関数を書く際の省略記法
クロージャをパラメータにもつ関数の実行の仕方が分かりましたが、Swiftの場合は省略記法があります。
例えば以下のようにInt型を受け取る関数を実行する時にわざわざ定数に数値を代入するでしょうか?
func method(value: Int) {
let v = value * value
print(v)
}
let inputValue = 10 // わざわざinputValueなんて用意する??
method(value: inputValue)
おそらくほとんどの人が10を直接代入して関数を実行するかと思います。
func method(value: Int) {
let v = value * value
print(v)
}
// 関数の実行時に直接パラメータを代入する
method(value: 10)
クロージャをパラメータに持つ関数も同様です。
以下のようにわざわざclosureという定数に代入せず、
func method(closure: ((Int) -> Void)) {
let v = Int.random(in: 0...100) // 0~100をランダムに返す
closure(v)
}
// こんなふうに定数に渡す必要もなく
let closure = {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
method(closure: closure)
直接、関数の実行時に名前のない関数を渡します。
func method(closure: ((Int) -> Void)) {
let v = Int.random(in: 0...100) // 0~100をランダムに返す
closure(v)
}
// methodの実行時に直接、名前のない関数を渡す
method(closure: {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
})
ただし、これだと名前のない関数が長すぎて、どこまでがmethodなのか分かりづらくなってしまいます。
そのためSwiftでは以下のようにclosure部分をmethod()の後ろに外出しできるようになっています。
// ()の後ろにclosureに代入される名前のない関数を書ける
method() {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
見慣れないと何が起きているのか分からなくなりますが、書き方を変えただけで、やっていることはさきほどと変わりません。
Swiftの場合はここからさらに省略記法があります。
methodがclosure以外にパラメータがない場合、method()の()すら省略できます。
method {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
さらに、methodに直接代入している場合、((Int) -> Void)という型なことが分かっているため、IntもVoidも省略できます。
method {(value) in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
さらに、((Int) -> Void)のIntのように入力がひとつであれば(value)の()も省略できます。
method { value in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
さらに、valueすら省略が可能で、その場合、名前のない関数内ではパラメータは先頭から$0, $1とアクセスできます。
今回の場合valueしかパラメータがないため$0だけとなります。
そして in より前に何も記載がない場合は in も省略可能です。
method {
guard $0 < 50 else {
print("値が小さすぎます")
return
}
print($0)
}
どこまで省略するべきなのかプロジェクトごとに違ってくるとは思いますがXcodeを使用しているとおそらく最も短い書き方にサジェストされるかと思います。
改めて書きますが、以下は全て実行していることは同じで書き方が違うだけです。
関数の定義
func method(closure: ((Int) -> Void)) {
let v = Int.random(in: 0...100) // 0~100をランダムに返す
closure(v)
}
実行
let closure = {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
method(closure: closure)
method(closure: {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
})
method() {(value: Int) -> Void in
guard value < 50 else {
print("値が小さすぎます")
return
}
print(value)
}
method {
guard $0 < 50 else {
print("値が小さすぎます")
return
}
print($0)
}
省略記法に惑わされず、元のmethodの定義を確認し、何に何が代入されているのか追えるようになってください。
練習問題3
クロージャをパラメータにもつ関数を正しく呼び出す課題です。
次のmethodという関数を実行して10が出力されるようにして下さい。
ただしmethodの処理内容を書き換えてはいけません。
練習問題はplaygroundで実行可能です。
playgroundの使い方はこちらの情報を参考にしてください。
回答は最後にあります。
import Foundation
func method(closure: ((Int) -> Void)) {
let value = 10
closure(value)
}
練習問題4
クロージャ以外にパラメータをもつ関数を正しく呼び出す課題です。
次のmethodという関数を実行して15が出力されるようにして下さい。
ただしmethodの処理内容を書き換えてはいけません。
練習問題はplaygroundで実行可能です。
playgroundの使い方はこちらの情報を参考にしてください。
回答は最後にあります。
import Foundation
func method(input: Int, closure: ((Int) -> Void)) {
let value = 10
closure(value + input)
}
練習問題5
クロージャを受けとる関数の中で正しくクロージャを関数として実行する課題です。
次のmethodの中身でcompletionを実行し、inputに代入された文字列が空なら「空です」、そうでなければ「文字があります」と出力するようにしましょう。
練習問題はplaygroundで実行可能です。
playgroundの使い方はこちらの情報を参考にしてください。
回答は最後にあります。
import Foundation
func method(input: String, completion: ((String) -> Void)) {
guard !input.isEmpty else {
return
}
}
API通信による非同期処理を実現するためにクロージャを利用する
これまでの集大成としてようやくAPI通信をしてそれをクロージャで受け取った非同期処理の説明をしていきます。
Swiftの場合API通信にはURLSessionというクラスのdataTask(with: completionHandler)という関数を利用します。
パラメータの付与やhttpメソッドの指定など大まかな使い方についてはこちらを確認してください。
このnoteではあくまでクロージャの利用法について焦点をあててソース内のコメントで解説してあります。
今回利用するのはこちらの住所検索APIです。
7桁の郵便番号を入力すると住所の情報が返ってきます。
この例で、「Appleが用意したクロージャをパラメータとして受け取るdataTask(with: completionHandler)という関数の利用」と「apiというクロージャをパラメータとして受け取る関数の定義の仕方」を学べます。
import Foundation
// URLSession.shared.dataTaskを使ってサーバーと通信するメソッド
func api(zipcode: String, closure: @escaping ((String) -> Void)) {
print("[1] api関数の処理を開始")
let url = URL(string: "https://zipcloud.ibsnet.co.jp/api/search?zipcode=\(zipcode)")!
let request = URLRequest(url: url)
// URLSession.shared.dataTaskメソッドがcompletionHandlerという名前のクロージャをパラメータに持っている
// それに渡すclosure
// api通信が終わればこのclosureに代入された名前のない関数が実行される
let dataTaskCompletionClosure = { (data: Data?, response: URLResponse?, error: Error?) in
print("[2] dataTaskCompletionClosureに代入した名前のない関数の処理を開始")
guard let data = data else {
closure("通信失敗")
return
}
let jsonStr = String(data: data, encoding: .utf8)!
print("[3] api関数実行時に渡しているclosure定数に代入された名前のない関数を実行する")
closure(jsonStr)
}
print("[4] api通信するためのメソッドdataTaskにdataTaskCompletionClosureを代入")
let task = URLSession.shared.dataTask(with: request, completionHandler: dataTaskCompletionClosure)
print("[5] resumeメソッドを実行すると通信が始まる")
task.resume()
print("[6] 通信が終わるのを待たずにメソッドを抜ける")
}
// api関数の実行時に渡す定数
let zipcode = "1006117"
let inputClosure = { response in
print("[7] inputClosure定数に代入されている名前のない関数の処理を開始")
print(response)
}
print("[8] api関数の実行前に行う処理")
api(zipcode: zipcode, closure: inputClosure)
print("[9] api関数の実行後に行う処理")
要所要所にコードの上から順にprintで [1]~[9]が記述されています。
あくまで「コードの上から順に番号を振った」だけで「実行される順ではない」ことに注意してください。
コードがどういう順で実行されていくか処理を追う時の参考にしてください。
実は、もう新たに解説することはなく、これまで説明した内容で処理を追うことができます。
@escapingという記述がありますがここで深く解説はしません。
optionalじゃないクロージャをパラメータにする時には付けるぐらいに理解してください。
上記の例は省略記法を利用せずに書きました。
実務ではこれが省略記法によって記述され以下のようになっていることが多いと思います。
import Foundation
// URLSession.shared.dataTaskを使ってサーバーと通信するメソッド
func api(zipcode: String, closure: @escaping ((String) -> Void)) {
print("[1] api関数の処理を開始")
let url = URL(string: "https://zipcloud.ibsnet.co.jp/api/search?zipcode=\(zipcode)")!
let request = URLRequest(url: url)
print("[4] api通信するためのメソッドdataTaskにdataTaskCompletionClosureを代入")
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
print("[2] dataTaskCompletionClosureに代入した名前のない関数の処理を開始")
guard let data = data else {
closure("通信失敗")
return
}
let jsonStr = String(data: data, encoding: .utf8)!
print("[3] api関数実行時に渡しているclosure定数に代入された名前のない関数を実行する")
closure(jsonStr)
}
print("[5] resumeメソッドを実行すると通信が始まる")
task.resume()
print("[6] 通信が終わるのを待たずにメソッドを抜ける")
}
// api関数の実行時に渡す定数
let zipcode = "1006117"
print("[8] api関数の実行前に行う処理")
api(zipcode: zipcode) {
print("[7] inputClosure定数に代入されている名前のない関数の処理を開始")
print($0)
}
print("[9] api関数の実行後に行う処理")
何が省略されたのか分からない方はもう一度クロージャをパラメータにもつ関数を書く際の省略記法をご確認ください。
練習6
最後の実践的な練習問題です
さきほどの郵便番号APIを実行するmethodがあります
completionという名前のクロージャを受け取るパラメータの型を記載してサーバーからのデータを受け取れるように実行してください
inputは7桁の数字(郵便番号 123-4567)が入ります
変更すべき箇所は[変更箇所]とコメントがしてあります
例とは違い通信エラーや不正なパラメータがきた場合の記述が必要となっています。
また、非同期処理が完了した時に実行するクロージャをcompletionという名前にすることが多いですが、通例というだけで言語仕様としては名前はなんでも構いません。
import Foundation
func method(input: String, completion: @escaping /* [変更箇所] completionの型を記載してください */) {
let urlStr = "https://zipcloud.ibsnet.co.jp/api/search"
let urlStrWithParameter = urlStr + "?zipcode=\(input)"
guard let url = URL(string: urlStrWithParameter) else {
print("不正な入力です")
/* [変更箇所] nilをcompletionの引数として実行してください */
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
// dataTaskメソッド自体がクロージャのパラメータをもち通信が終わったら
// { data, response, error in ... } という名前のない関数が実行される
let dataTask = session.dataTask(with: urlRequest) { data, response, error in
if let error = error {
print("\(error): エラーです")
/* [変更箇所] nilをcompletionの引数として実行してください */
return
}
guard let data = data else {
print("データがありません")
/* [変更箇所] nilをcompletionの引数として実行してください */
return
}
guard let responseStr = String(data: data, encoding: .utf8) else {
print("サーバーから受け取ったデータをstringに変換できませんでした")
/* [変更箇所] nilをcompletionの引数として実行してください */
return
}
/* [変更箇所] responseStrをcompletionの引数として実行してください */
}
dataTask.resume()
}
// [変更箇所]
// inputにこのパラメータを渡し
// completionで通信結果を受け取るパラメータを渡して受け取った文字列を出力してください
method(input: "1000014")
inputが正しい場合の出力
{
"message": null,
"results": [
{
"address1": "東京都",
"address2": "千代田区",
"address3": "永田町",
"kana1": "トウキョウト",
"kana2": "チヨダク",
"kana3": "ナガタチョウ",
"prefcode": "13",
"zipcode": "1000014"
}
],
"status": 200
}
最後に
長い解説となりましたが最後までお読みいただきありがとうございました。
非同期処理という難しい概念を、クロージャという難しい仕組みで実現しているので、自分の基準では1回読んで書いてすぐ理解できることではありません。
1~2週間考え込んでも分からなくて普通ぐらいの気持ちで学んでもらってもいいかと思います。
ですが、昨今のプログラムはサーバーと通信することが大前提ですので、非同期処理は必須な知識ですので諦めずに学んでほしいです。
自分自身も何十回も数ヶ月も書いてようやく理解できたような気がするぐらいになった覚えがあります。
読んでくれた方にとって、このnoteが自分が学んだ時より効率良く学べる手段になってくれていたら幸いです。
よろしければスキやSNSで拡散や回答の購入などしていただけると励みになります。
回答
各問題の回答例ですが有料となっておりますのでご理解たいだけると幸いです。
変数名などの違いはもちろんあるためあくまでひとつの例と思って下さい。
基本的には同じ出力となれば合っています。
ここから先は
¥ 100
この記事が気に入ったらサポートをしてみませんか?