Swift Guard Statement利用ケース



## Guard?

Early Exitというタイトルで説明されてますが、guardに記述したコードが実行できる条件を満たしていれば、guard以降のコードが引き続き実行され、満たしてなければelseのブロックが実行されます。elseのブロックの最後は、return、break、continueなどを記述し、関数やメソッド等の実行コンテキストから抜ける必要があります。
if letと異なるのは、評価結果をブロック外で使えることと処理フローが明確で可読性が上がるというところですかね。

クロージャーの中で使えば、guardの条件に一致したら評価結果を受け取り、不一致の場合はデフォルト値を受け取るという実装も出来ます。


```guard.swift
func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return
    }

    print("Hello \(name)!")

    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }

    print("I hope the weather is nice in \(location).")
}

greet(["name": "John"])
// prints "Hello John!"
// prints "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// prints "Hello Jane!"
// prints "I hope the weather is nice in Cupertino."
```

下記のGolangのベストプラクティスのように、ネストを避けるために、エラー等で処理を継続出来ない場合は関数やメソッドからすぐに抜けるというパターンはよくあるので、積極的に使っていけそうです。

http://talks.golang.org/2013/bestpractices.slide#4

```errors.go
func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
    err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
    if err != nil {
        return
    }
    size += 4
    n, err := w.Write([]byte(g.Name))
    size += int64(n)
    if err != nil {
        return
    }
    err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
    if err == nil {
        size += 4
    }
    return
}
```

## UICollectionViewLayoutの利用例

`UICollectionViewFlowLayout`は、セルのサイズやセル間のスペースを固定値でプロパティにセットすることもできるし、`UICollectionViewDelegateFlowLayout`でdelegateによって`indexPath`や`section`の位置に応じた値を返すことも可能です。
カスタマイズしたレイアウトで、コレクションビューのサイズ計算や個々のセルサイズを計算に利用したい場合、先にdelegateメソッドが定義されていればそこから値を取得して、未定義であればプロパティにセットされた値を取得するというようなケースで`guard`が使えるを思ったのでテストしてみました。

```[Class]CustomCollectionViewLayout.swift
class CustomCollectionViewLayout : UICollectionViewFlowLayout {
	var minimumLineSpacing: CGFloat = 0.0
	var minimumInteritemSpacing: CGFloat = 0.0
	var itemSize: CGSize = CGSizeMake(50, 50)
}
```

カスタマイズしたコレクションビューレイアウトに下記のメソッドを追加します。

```[Extension]CustomCollectionViewLayout.swift

extension CustomCollectionViewLayout {

    func minimumLineSpacingForSectionAtIndex(section: Int) -> CGFloat {
        guard let collectionView = self.collectionView, delegate = self.delegate else {
            return 0.0
        }
        guard let spacing = delegate.collectionView?(collectionView, layout: self, minimumLineSpacingForSectionAtIndex: section) else {
            return self.minimumLineSpacing
        }
        return spacing
    }

    func minimumInteritemSpacingForSectionAtIndex(section: Int) -> CGFloat {
        guard let collectionView = self.collectionView, delegate = self.delegate else {
            return 0.0
        }
        guard let spacing = delegate.collectionView?(collectionView, layout: self, minimumInteritemSpacingForSectionAtIndex: section) else {
            return self.minimumInteritemSpacing
        }
        return spacing
    }

    func insetForSectionAtIndex(section: Int) -> UIEdgeInsets {
        guard let collectionView = self.collectionView, delegate = self.delegate else {
            return UIEdgeInsetsZero
        }
        guard let inset = delegate.collectionView?(collectionView, layout: self, insetForSectionAtIndex: section) else {
            return self.sectionInset
        }
        return inset
    }

    func sizeForItemAtIndexPath(indexPath: NSIndexPath) -> CGSize {
        guard let collectionView = self.collectionView, delegate = self.delegate else {
            return CGSizeZero
        }
        guard let size = delegate.collectionView?(collectionView, layout: self, sizeForItemAtIndexPath: indexPath) else {
            return self.itemSize
        }
        return size
    }

}
```

やっていることは、

1. `collectionView`と`delegate`がない場合は、delegateメソッドを呼べないので0(それと等価なもの)を返す
2. delegateメソッドから値を取得出来ない場合は、guardのelseブロックが実行されるのでプロパティ値を返す
3. 〃取得出来た場合は、定数に値が格納されているのでそれを返す

複数条件を並べて記述できるようですが、if letと異なり1番目の評価結果を次の条件に利用できないようです。

あとは、ViewController側で必要なdelegateメソッドを実装した場合、そのメソッドが返した値を優先的に使うように出来ます。



```CollectionViewController.swift
    // MARK: UICollectionViewDelegateFlowLayout

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        return UIEdgeInsetsZero
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
        return 2.0
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
        return 2.0
    }
```