見出し画像

iPhone アプリを自分でつくる 7.

クロージャ、 エラーハンドリング、 プロトコル、 エクステンション  

もう疲れたよ〜、というそこのあなた! 今回は休んでも大丈夫です。
次回からSwiftUI に入りますので、それまで休養をとってください。
今回の分は、いつかまた戻ってくるのを心待ちにしています。

クロージャ Closures

クロージャは名前のない関数とも呼ばれるものです。
変数に代入して使用します。
 var 変数 = { アクション内容 } のように使います。
使うときには 変数() のようにします。

let sayHello = {
    print("こんにちは!")
}
sayHello()             // 変数を () でイニシャライズして print()アクション を実行した

// コンソール表示
こんにちは!

クロージャは通常の関数と同じように引数を取ったり値を返すこともできます。

作り方は
var 変数 = { (引数ラベル: 引数の型) -> 返す型 in  
返す内容  
}
    
使い方は
変数(引数)
使うときに引数ラベルは記入しません。

下のケースでは返した値を print() でコンソール表示させています。

let sayHello = { (name: String) -> String in     // (name: String) -> String in  で Stringを受け取り、String を返す と明示
    return "こんにちは、 \(name)さん!"          // ※この場合return は省略できます。(※ 1行の場合)
}

print(sayHello("田中"))    // 使用するときは 引数ラベル は記入しない
                  // この場合はString を返すクロージャなので print() して使用しています。
// コンソール表示
こんにちは、 田中さん!

上記のクロージャを関数にしてみます

func helloTo(name: String) -> String {     // 関数をつくってみた
    return "こんにちは、 \(name)さん!"
}

let sayHello = helloTo(name: "田中")     

print(sayHello)

この関数では
func helloTo(name: String) -> String {  となっています。
これをクロージャにすると
{ (name: String) -> String in
となりますが、これは「引数と返す値の型を { の中にいれましたよ。この後の内容を見てくださいよ。」と理解していただいて良いと思います。

このようにクロージャは関数を使用しても同じようなことができます。
では、なぜわざわざ紛らわしい書き方のものを用意しているのか。
そのひとつは Swift が簡単に使えて効率的なクロージャを使用するメソッドを用意してくれているからです。 

次の例では Array型が持つ filter() メソッドでクロージャを使用しています。
今の段階では、こういうことができるんだ、という感じでみていただければよいのですが、配列 lines に続けて .filter メソッド、そこに続けてクロージャを省略形で記入しています。
このコードのイメージとしては配列の要素が$0 として 順番に入ってきて、"新幹線"を含まないもの( ! で contains() の反対を設定)して 最終的にtrue となった要素を配列にして返す、ということが行われます。
最初は戸惑いますが慣れてくると、「こう書けば、こういう結果がくるはずだ」という感じになってきます。
この例では一番省略した形を記載しましたが、フルの形で書かれたり、少し省略した形で書かれたりするので、見慣れておくためにこのあとで例を取り上げていきます。

let lines = ["山陽新幹線", "広電宮島線", "伊予鉄道高浜線"]

let localRail = lines.filter{ !$0.contains("新幹線") }    // クロージャを省略形で1行で記入した

print(localRail)                                         // contains() はBool値を返すメソッドです。filter はtrue となったものの配列を作成します。
                                                         // ! を最初につけて含まないものを true にしています
// コンソール表示
["広電宮島線", "伊予鉄道高浜線"]

ひとまず上の例を filter を使わずに書く場合を記入してみます。

let lines = ["山陽新幹線", "広電宮島線", "伊予鉄道高浜線"]

var foundLines = [String]()                 // 空配列を用意した
for line in lines {                         // for in 文で一つづつ条件を確認
    if !line.contains("新幹線") {
        foundLines.append(line)             // 条件に適合したらfoundLines に追加する
    } else {                                // この場合 else は必要ありませんが参考まで
    }
}
print(foundLines)

// コンソール表示
["広電宮島線", "伊予鉄道高浜線"]

このように書くのも良いと思います(特に最初は for in の書き方にまず慣れた方が良いと思います)が、クロージャを使用するメソッドもときには試しながら馴染んでいくのも良いです。
特に配列の持つメソッド、この filter 以外に map(この場合のmap は地図ではなくて配列要素を違う型に変えたり違う表現にしたりするメソッドです。)、sorted(順番の付け方をカスタマイズするときにクロージャをつけます) など使う場面はよくありますので、今後取り上げていきたいと思います。


次の例は filter メソッドをフルでクロージャを使用して記入した例です。
省略しないので メソッドは () を持ちます。 配列.filter(〜)
() の中に String を取りBool値を返すクロージャを書きます
(filterメソッドは配列の要素を順にクロージャに引き渡すよう作られています)
クロージャは dayとしてもらった値が hasPrefix("12月") かどうか、つまり要素の最初の文字が"12月"かどうかを true か false でfilter に返します。 
filter はtrue のときは自分がつくった空配列に追加していきます。
配列すべてを実行したらできた出来上がった配列を返します。
filter

クロージャ [Self.Element] -> Bool 型」 を引数に持ち、
 -> [Self.Element]   を返すメソッドです。
正確にはエラーを取る型なので少しややこしくなります。またSelf.ElementのSelf は自分の型自体のことです)

// ①

let offDay = ["11月20日", "11月21日", "11月30日", "12月5日", "12月6日"]

let offDecember = offDay.filter({ (day: String) -> Bool in          // filter() とフルの形
    return day.hasPrefix("12月")
})

print(offDecember)

// コンソール表示
["12月5日", "12月6日"]

② return で1行だけ返すコードは return を省略できます。

// ②

let offDecember = offDay.filter({ (day: String) -> Bool in
    day.hasPrefix("12月")                                      // return を省略した
})

③ (day: String) とありますが 配列の内容で引数の型はきまってきます。
  また Bool型を返すとありますが、filter は必ずBool 型を返すクロージャを持た
  ないと成り立たないので②は以下の省略形にできることになっています。

// ③
let offDecember = offDay.filter({ day in
    day.hasPrefix("12月")
})

クロージャが最後に来ている場合() を外すことができます。
     最後にあるクロージャを特別にtrailing closure トレーリング・クロージャといいます。

// ④ 

let offDecember = offDay.filter{ day in
    day.hasPrefix("12月")
}

残った引数ラベルを消す代わりに、$0 として内容を記入することができます。

// ⑤

let offDecember = offDay.filter {
    $0.hasPrefix("12月")
}

// 横に並べれば1行でも記入できます。
let offDecember = offDay.filter { $0.hasPrefix("12月") }

エラーハンドリング

エラーハンドリングは何らかのエラーが発生したときに端末をクラッシュさせずにエラーを受け入れて必要に応じた対応をさせるものです。

Swift での基本的なエラーハンドリングの方法
① エラーをカテゴリー分けしておく
② エラーを発生させる条件を関数にしておく
③ 関数を実行してエラー区分に応じてアクションを行う

この方法をログイン時のエラーチェックを例として作成します。
① エラーのカテゴリー分け
enum LoginError としてエラー区分を分けています。
: Error  とあるのは Error ルールに基づいて作られています、ということです。
このルールのことをプロトコルと言います。
クラスで継承のときの書き方と同じですが、継承は親クラスを引き継ぐもので、このプロトコルというのはこの型がどのようなルールで書かれているのかを示すものです。例えばInt 型であれば、Equatableプロトコル、Comparable プロトコルが設定してあり、これにより「等しい」、「比べる」などの記号を使うことができるようになっています。

case fieldError, invalidEmail, invalidPassword の3つの区分を作成しました。
なお Error プロトコルは特に書き方のルールを定めていないので自由に書けますが、Errorであるという宣言としてこのプロトコルを使用します。

enum LoginError: Error {
    case fieldError
    case invalidEmail
    case invalidPassword
}

② エラーを発生させる条件を関数にしておく
func login(〜) throws {  となっています。
今回はなにも返さない関数ですが、throwsError を発生させる可能性がある関数であることを表します。
もし func login(〜) throws -> String {  とすれば Stringを返す関数であるが Error となるかもしれない関数となります。

①で作った3つの区分それぞれの条件を記入しました。
もし emailAddress か password がカラの時
throw LoginError.fieldError としています。
この throw は「Error 区分に入れるよ」ということです。処理方法についてはこの後で記入します。
プログラミングではエラー区分に入れることを throw error、 スローエラー、とかエラーをスローする、エラーを投げるなどの表現もします。
2つ目の条件は !email.contains("@") || !email.contains(".") としました。
つまり、@と. の文字を含まなければ invalidEmailエラーに投げられる、
throw LoginError.invalidEmail です
3つ目は password.count < 8 としてパスワードは8文字以上でないとエラーにする設定にしました。
以上の条件をクリアすれば"OK"の文字をコンソール表示するようにしました。

func login(email: String, pass: String) throws {
    let emailAddress = email
    let password = pass
    
    if emailAddress.isEmpty || password.isEmpty {
        throw LoginError.fieldError
    }
    
    if !email.contains("@") || !email.contains(".") {
        throw LoginError.invalidEmail
    }
    
    if password.count < 8 {
        throw LoginError.invalidPassword
        
    } else {
        print("OK")
    }
}


③ 関数を実行してエラー区分に応じてアクションを行う

まず上記の関数をそのまま使ってみます。そうするとエラー表示で
throw の可能性がある関数を呼び出しているけど、 try が使われていないよ」とされました。

そこで 関数を呼び出す際に try をつけます。 try というのは、「実行してみる」ということですが、もしそこでエラーが発生した場合のエラー内容による分岐をcatch でキャッチ していきます。
この構文は頭に do {  をつけるので   do - catch 文とよばれます。
下記のようになります。

func checkLogIn(email: String, pass: String) {
    do {
        try login(email: email, pass: pass)
        
    } catch LoginError.fieldError {
        print("Please fill out the form.")
    } catch LoginError.invalidEmail {
        print("Please check your email account.")
    } catch LoginError.invalidPassword {
        print("Please check your password.")
    } catch {
        print("There was an error.")
    }
}

do { 
 try login(email: email, pass: pass)
}
として関数をよびだしています。
そしてエラーパターンによって catch していきます。
} catch LoginError.fieldError {
        print("Please fill out the form.")
}
のようにエラーパターンをキャッチして、必要なアクションをとっていきます。
そして最後
} catch {
        print("There was an error ")
}
としていますが、そこまでのパターンに当てはまらないエラーが発生した時はここでcatch して対応します。
このようにcatch はすべてのケースを網羅する必要があります。
(外国の方のYou tube を見ていると、この部分で Pokemon catch とよく言っていますがあちらでは「ポケモン・キャッチだぜ!」となるんですかね。 )

このログインチェックの関数を使用してみます。
checkLogIn() 関数がその中にある try login() 関数で得た結果から条件分岐してコンソール表示させます。

let myEmail = "sunny@email.com"
let myPassword = "xyz01abc"
checkLogIn(email: myEmail, pass: myPassword)

// コンソール表示 OK

myEmail の. ドットを削除して試してみます。 

let myEmail = "sunny@emailcom"
let myPassword = "xyz01abc"
checkLogIn(email: myEmail, pass: myPassword)

// コンソール表示 Please check your email account.

myPasswordを7文字でチェックしてみます。

let myEmail = "sunny@email.com"
let myPassword = "xyz01ab"
checkLogIn(email: myEmail, pass: myPassword)

// コンソール表示 Please check your password.

下記ご参考まで



プロトコル

プロトコルは型に適合させるルールです。
クラスの継承と書き方は同じですが、継承は親クラスを引き継ぐもので、このプロトコルというのはこの型がどのようなルールで書かれているのかを示すものです。
また継承は一つからしかできませんでしたが、ひとつの型に多くのプロトコルを適合させることができます。複数のプロトコルはカンマ区切りで加えていきます。

Swift の型のプロトコルはplayground でも簡単に調べることができます。
下の通り調べてみると非常に多くのプロトコルに適合していることがわかります。

protocol は自分でつくることもできます。
以下の例では Car プロトコルを作っています。
struct Truck は Car プロトコルに記入してある内容が必須となります。
なおプロトコルは特別な記入方法で書かれます。

var speedLimit: Int { get }    
 定数 かcomputedプロパティを設定します。
 もし { get  set } の場合は 変数か get set を使用したcomputedプロパティを設
 定します。
func driveSpeed(speed: Int) -> String  
 関数の後ろに { } がありませんが、この部分を使って関数を作りなさいというこ
 とです。

protocol Car {
    var speedLimit: Int { get }
    func driveSpeed(speed: Int) -> String
}


struct Truck: Car {
    var speedLimit = 80
    
    func driveSpeed(speed: Int) -> String {
        if speed <= speedLimit {
            return "Keep driving."
        } else {
            return "Stop! Limit over!"
        }
    }
}


let myTruck = Truck()

print(myTruck.driveSpeed(speed: 100))


// コンソール表示 Stop! Limit over!

エクステンション Extensions

データ型の持つパワーをアップさせることのできるものです。
具体的にはある型のもつメソッドの追加やイニシャライザを追加したり computed プロパティを追加したりできます。

例えば文字を逆から表示させる場合、次のステップ a, b, c が必要になります。

var myChara = "あいうえお"

var a = myChara.reversed()   //ReversedCollection<String>(_base: "あいうえお")
var b = Array(a)       //["お", "え", "う", "い", "あ"]
var c = String(b)      //おえういあ

もしこのステップがあちこちで必要となる場合にはひとつの方法は関数を作っておくことです。
使用する際は、var gyaku = gyakuyomi(text: myChara) とするだけですが、
関数の場所をどこにするか(static func にするか)など検討が必要です。

func gyakuyomi(text: String) -> String {
    let b = text.reversed()
    let c = Array(b)
    let d = String(c)
    return d
}

var myChara = "あいうえお"
var gyaku = gyakuyomi(text: myChara)    // おえういあ

もうひとつの方法はエクステンションを作っておくことです。
エクステンションはstruct やクラスなどと同じトップレベルに置きます。
エクステンションをプロジェクトのどこかにおいておけば、String の持つメソッドとして gyakukara() メソッドがプロジェクト名内で自由に使えるようになります。
使う際は var myGyaku = myChara.gyakukara() のように関数のときよりもさらにシンプルになります。

extension String {
    func gyakukara() -> String {
        let b = self.reversed()
        let c = Array(b)
        let d = String(c)
        return d
    }
}

var myChara = "あいうえお"
var myGyaku = myChara.gyakukara()  // おえういあ

上の例では関数を使用した場合と効果の違いがわかりにくかったですが、次の例はCollection プロトコルをエクステンションすることで広い範囲で効果が得られます。
Collection に適合する Array, Dictionary, Set はもちろん、String も適合しているので、これらすべての型で使える便利なものです。
computed プロパティを使用して isEmpty が false のときは true を返すようにしています。(これを考えるだけでもこんがらがります)

extension Collection {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

var myArray: [Int] = [1, 2, 3]
if myArray.isNotEmpty { print("Daa!") }  // Daa!

var myChara = "あいうえお"
if myChara.isNotEmpty { print("OK!") }  // OK!

通常であれば
if !myArray.isEmpty { print("Daa!") } 
のように ! を使用して「isEmpty の否定」をすることになりますが、勘違いしやすく何度も見返すことがあります。
でも、このエクステンションを置いておけば簡単に isNotEmpty で肯定の表現にすることができます。


まとめ

クロージャ
クロージャは名前のない関数とも呼ばれるもので、変数に代入して使用します。
省略形やそうでないもの、いろいろな形に見慣れていきます。

エラーハンドリング
Swift での基本的なエラーハンドリングの方法
① エラーをカテゴリー分けしておく
② エラーを発生させる条件を関数にしておく
③ 関数を実行してエラー区分に応じてアクションを行う

プロトコル
プロトコルは型に適合させるルールです。
ひとつの型に多くのプロトコルを適合させることができます。
プロトコルに適合することでパワーを持ちます。
(ルールに適合させてメソッドを使えるようにする)
Swift はオブジェクト指向のプログラム言語のひとつですが、プロトコル指向の言語であるとも言われます

エクステンション
データ型の持つパワーをアップさせることのできるものです。
ある型のもつメソッドを追加したり computed プロパティを追加したりできます。

お疲れ様です
ここまで記事を読んでくださりありがとうございます。
次回からSwiftUI に入っていきます。
え〜、まだSwift が全然理解できてないよ〜 とおっしゃるあなた、
い〜い〜んです。
今まで隠していましたが、はっきり言って私だってわかっておりません!
ただ何度も何度も失敗するうちに分かってくることがあります。
最初はこう動くはずだと思ってやっても9割失敗だったものが7割になって、5割になって、ああ、Swift はこうして欲しかったのね。と思う時があります。
プログラムを書いているととても面倒なことがあります。何度も何度も面倒なコードを書いていると、こ〜ゆ〜ふ〜にできたら良いのに、と思うことがあります。
そのときに、「そ〜いえば、エクステンションてあったけど使えるのかな」とか
「イーナムをセットすると分かりやすいのかな」とか思って使ってみると、また新たなエラーに遭遇します。
そしてそこには、「このメソッドは〜プロトコルに適合させないと使えないよ」などとXcode が上から目線で言ってきます。
でも、それは次の段階に進んでいるんです。
Swift は型に厳しい言語だと言われます。でも型に厳しいから早めにエラーに気づくことができます。だから安心して次に進み出していけます。
Swift を分かっていないと感じても SwiftUI でいろいろと試すうちに、「あ〜あの時のあれはこういうことを言っていたのね。」と、
振り返ると初めて見えることもあります。

自宅にて

次回第8回予定内容  SwiftUIの準備をする、です。
            Canvasに文字を表示させます。

この記事が気に入ったらサポートをしてみませんか?