【これからiOS頑張りたい方向け】2年半iOSアプリ開発をしてハッとした瞬間まとめ

## はじめに
`iOS Advent Calendar 2020` 17日目です。:tada:

2年半くらいiOSアプリ開発してきてハッとした瞬間をまとめました。(iOSとかswiftに限った話じゃない学びもあるけど。)
がんばらなくても読めるけど、なんとなく勉強にもなる記事を目指しました。
タイトルに近い方が初歩的なやつです。
もし時間あればみていただけると嬉しいです。

## お品書き
* [返り値でBoolを返す時はそのBool自身を返せばいい](#返り値でboolを返す時はそのbool自身を返せばいい)
* [三項演算子を使うとif else がワンライナーで書ける](#三項演算子を使うと-if-else-がワンライナーで書ける)
* [var +=は計算型プロパティにできる。](#var-は計算型プロパティにできる)
* [ネストは早期returnで減らせる](#ネストは早期returnで減らせる)
* [2重否定はifでいい。](#2重否定はifでいい)
* [型が明確な時のinitializerは.initに省略できる](#型が明確な時のinitializerはinitに省略できる)
* [trailing closureは引数から省略できる](#trailing-closureは引数から省略できる)
* [enumとswitchを組み合わせて網羅性をチェックする](#enumとswitchを組み合わせて網羅性をチェックする)
* [Bool値が複数ある場合の場合分けはswitchとパターンマッチを使うと見やすくなる。](#bool値が複数ある場合の場合分けはswitchとパターンマッチを使うと見やすくなる)
* [var+forは大抵高階関数で書き換えられる](#varforは大抵高階関数で書き換えられる)
* [配列のmappingにはmapが使える](#配列のmappingにはmapが使える)
* [for文にifを挟む処理はfor+whereに書き換えできる。](#for文にifを挟む処理はforwhereに書き換えできる)
* [for+whereはfilter+forEachに書き換えできる。](#forwhereはfilterforeachに書き換えできる)
* [引数の変更を参照元に反映させたい時はinoutが使える](#引数の変更を参照元に反映させたい時はinoutが使える)
* [filter + first or lastはfirst(where: ) or last(where: )で書ける。](#filter--first-or-lastはfirstwhere--or-lastwhere-で書ける)
* [StackViewを使うと制約をグッと減らせる。](#stackviewを使うと制約をグッと減らせる)
* [StackView + ScrollViewでお手軽に拡張しやすいスクロール可能なレイアウトを構築できる。](#stackview--scrollviewでお手軽に拡張しやすいスクロール可能なレイアウトを構築できる)
* [見えているViewの裏のViewのタッチイベントを取得したい時はhittestが使える](#見えているviewの裏のviewのタッチイベントを取得したい時はhittestが使える)
* [同じ処理をアノテーション化して省略する時はPropertyWrapperが使える](#同じ処理をアノテーション化して省略する時はpropertywrapperが使える)

## 返り値でBoolを返す時はそのBool自身を返せばいい
### Before

```swift
var hoge: Bool {
    if fugaBool {
        return true
    } else {
        return false
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">


### After

```swift
var hoge: Bool {
    return fugaBool
}
```

今思えば当たり前なんですけどね。
〇〇がtrueの時trueを返さなきゃってなって〇〇自体が真偽値なことに気づいてなかったんでしょうね。

## 三項演算子を使うと if else がワンライナーで書ける
### Before

```swift
if fugaBool {
    hogeLabel.text = "fugaは真です"
} else {
    hogeLabel.text = "fugaは偽です"
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
hogeLabel.text = fugaBool ? "fugaは真です" : "fugaは偽です"
```

三項演算子すこ。

## `var += `は計算型プロパティにできる。
### Before

```swift
func presentHogeAlert() {
    var message = ""
    if validationA {
        message.append("Aがあかんかった")
    } else if validationB {
        message.append("Bがあかんかった")
    } else {
        message.append("あかんかった")
    }
    let alert = UIAlertController(title: "エラー", message: message, preferredStyle: .alert)
    alert.addAction(.init(title: "OK", style: .default))
    present(alert, animated: true)
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
func presentHogeAlert() {
    var message: String {
        if validationA {
            return "Aがあかんかった"
        } else if validationB {
            return "Bがあかんかった"
        } else {
            return "あかんかった"
        }
    }

    let alert = UIAlertController(title: "エラー", message: message, preferredStyle: .alert)
    alert.addAction(.init(title: "OK", style: .default))
    present(alert, animated: true)
}
```

計算型の方が返り値がわかりやすくてスッキリしますよね。

## ネストは早期returnで減らせる
### Before

```swift
func hoge() {
    if yagi {
        // 処理A
        if let mogu = mogu {
            // 処理B
        } else {
            if fuga {
                // 処理C
            } else {
                // 処理D
            }
        }
    } else {
        // 処理E
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
func hoge() {
    guard yagi else {
        // 処理E
        return
    }
    
    // 処理A

    guard let mogu = mogu else {
        if fuga {
            // 処理C
        } else {
            // 処理D
        }
        return 
    }

    // 処理B
}

```

もうちょっといい例を用意したかったです。
ネストが多くて長いだけで読むのが辛くなっちゃいますよね。


## 2重否定はifでいい。

### Before

```swift
guard !fugaBool else {
    // do something
    return
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">


### After

```swift
if fugaBool {
    // do something
    return
}
```

プロジェクトによってはguardで明示的にreturnを表現するためにあえて2重否定を好むところもあります。
真偽値一つならまだ分かるけどこれが`!fugaBool || mogu > 1 && yagi.isEmpty` みたいな条件になってきたら発狂しますよね。

## 型が明確な時のinitializerは`.init`に省略できる

### Before

```swift
view.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
view.frame = .init(
    origin: .init(
        x: 0,
        y: 0
    ),
    size: .init(
        width: 0,
        height: 0
    )
)
```

長い型名を省略できるので好き。
多用すると読みにくくなるので注意。


## `trailing closure`は引数から省略できる

### Before

```swift
func hogeChanged(fugaBool: Bool, hogeCompletionHandler: @escaping ((String) -> Void)) {
    guard fugaBool else { return }

    let hoge = getChangedHogeValue
    hogeCompletionHandler(hoge)
}

hogeChanged(fugaBool: true, hogeCompletionHandler: @escaping { [weak self] hoge in

    guard let self = self else { return }

    self.hogeLabel.text = hoge
})
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
func hogeChanged(fugaBool: Bool, hogeCompletionHandler: @escaping ((String) -> Void)) {
    guard fugaBool else { return }

    let hoge = getChangedHogeValue
    hogeCompletionHandler(hoge)
}

hogeChanged(fugaBool: true) { [weak self] hoge in

    guard let self = self else { return }

    self.hogeLabel.text = hoge
})
```

クロージャの引数名大抵長くなるから助かります。
読みづらくなる場合もあります。

## enumとswitchを組み合わせて網羅性をチェックする
例えばTabBarControllerのtabの種類をenumにするとか。
### Before

```swift
    override func viewDidLoad() {
        super.viewDidLoad()

        var viewControllers: [UIViewController] = []

        let hogeVC = HogeViewController())
        hogeVC.tabBarItem = UITabBarItem(title: "hoge", image: nil, tag: 1)
        viewControllers.append(hogeVC)

        let fugaVC = FugaViewController()
        fugaVC.tabBarItem = UITabBarItem(title: "fuga", image: nil, tag: 2)
        viewControllers.append(fugaVC)

        setViewControllers(viewControllers, animated: false)
    }
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
enum TabType: Int, CaseIterable {
    case hoge = 0
    case fuga = 1

    private var baseViewController: UIViewController {
        switch self {
        case .hoge:
            return HogeViewController()

        case .fuge:
            return FugaViewController()

        }
    }

    private var title: String {
        switch self {
        case .hoge:
            return "hoge"

        case .fuga:
            return "fuga"

        }
    }

    var tabItem: UITabBarItem {
        .init(title: title, image: nil, tag: self.rawValue)
    }

    var viewController: UIViewController {
        let viewController = baseViewController
        viewController.tabBarItem = tabItem
        return viewController
    }
}

// 使う時
override func viewDidLoad() {
    super.viewDidLoad()
    setViewControllers(TabType.allCases.map(\.viewController), animated: false)
}
```

caseを追加すれば他の設定値も自ずと追加が必要になるので漏れを防げます。

## Bool値が複数ある場合の場合分けはswitchとパターンマッチを使うと見やすくなる。
### Before
フラグが何個かあってtrueとfalseの組み合わせを表現したい時。

```swift
let hogeFlag: Bool
let fugaFlag: Bool

if hogeFlag && fugaFlag {
    // true true
} else if !hogeFlag && fugaFlag {
    // false true
} else if hogeFlag && !fugaFlag {
    // true flase
} else {
    // false false
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">


### After
```swift
switch (hogeFlag, fugaFlag) {
case (true, true):   break
case (false, true):  break
case (true, false):  break
case (false, false): break
}
```

switchの見やすさは異常。

## `var+for`は大抵高階関数で書き換えられる
### Before
```swift
var titles: [String] = []
for i in 0...10 {
    let title = "りんごが\(i)個あります。"
    titles.append(title)
}
titles.joined(separator: "\n")
return titles
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
```swift
(0...10).map {
    "りんごが\($0)個あります。"
}
.joined(separator: "\n")
```
https://qiita.com/shtnkgm/items/600009917d8e572e6780
こちらの記事が詳しいです。

## 配列のmappingにはmapが使える
### Before
```swift
struct Hoge {
    let aaa: String
    let iii: String
    let uuu: String
}

// Hogeの配列があります。
let hogeList = [Hoge]()
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
```swift
// 特定のプロパティのみ取り出し
let aaaList = hogeList.map(\.aaa)
// 別の型にmapping
struct Fuga {
    let aaa: String
    let eee: String
    let ooo: String
}
let fugaList = hogeList.map { Fuga(aaa: $0.aaa, eee: "", ooo: "") }
```

API叩いてデータ加工してViewに渡す過程で絶対必要になりますよね。

## for文にifを挟む処理は`for+where`に書き換えできる。
こういう動物達がいるとします。

```swift
protocol Animal {
    var name: String { get }
}

protocol Runable: Animal {
    func run()
}
extension Runable {
    func run() {
        print("run!!!")
    }
}

class Cat: Animal, Runable {
    var name: String = "猫"
}

class Dog: Animal, Runable {
    var name: String = "犬"
}

class Penguin: Animal {
    var name: String = "ペンギン"
}

let animals: [Animal] = [Cat(), Dog(), Penguin()]
```

### Before
```swift
for animal in animals {
    if animal is Runable {
        print("この動物は走れます！")
    }
    if let runableAnimal = animal as? Runable {
        runableAnimal.run()
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
```swift
for animal in animals where animal is Runable {
    print("この動物は走れます！")
    
    if let runableAnimal = animal as? Runable {
        runableAnimal.run()
    }
}
```

whereを使いこなしてるとなんかできる感出てきますよね。

## `for+where`は`filter+forEach`に書き換えできる。
先ほどの例から
### Before
```swift
for animal in animals where animal is Runable {
    print("\(animal.name)は走れます！")
    
    if let runableAnimal = animal as? Runable {
        runableAnimal.run()
    }
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After

```swift
animals.filter { $0 is Runable }.forEach {
    print("\($0.name)は走れます！")
    
    if let runableAnimal = $0 as? Runable {
        runableAnimal.run()
    }
}
```

高階関数すこ。
この例ならキャストしてcompactMapにかけた方が早いかも。

```swift
animals.compactMap { $0 as? Runable }.forEach {
    print("\($0.name)は走れます！")
    $0.run()
}
```

## 引数の変更を参照元に反映させたい時はinoutが使える
いわゆる参照渡しです。

### Before
```swift
var fuga = "fugaです。"

print(fuga) // fugaです。

func addHogeString() {
    fuga.append("\n")
    fuga.append("hogeを追加します")
}

addHogeString() 

print(fuga) // fugaです。\n hogeを追加します。
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
```swift
func addHogeString(strings: inout String) {
    strings.append("\n")
    strings.append("hogeを追加します")
}

var fuga = "fugaです。"

print(fuga) // fugaです。

addHogeString(strings: &fuga)

print(fuga) // fugaです。\n hogeを追加します。
```

この&を使った参照渡しの表現はCombineのassignでも使いますね。


## `filter + first or last`は`first(where: ) or last(where: )`で書ける。
### Before
```swift
animals.filter { !($0 is Runable) }.first // Penguin
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
```swift
animals.first { !($0 is Runable) } // Punguin
```

firstを使う方がパフォーマンスにも優れるとかなんとか。

## StackViewを使うと制約をグッと減らせる。
こんなViewを用意したいとします。(枠が見えやすいように色を変えています。)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/e0dc16a8-100d-f194-ae79-682ba913d6f5.png" width="250">

### Before
```swift
class ViewController: UIViewController {

    private lazy var hogeLabel: UILabel = {
        let label = UILabel()
        label.text = "hogehoge"
        return label
    }()
    
    private lazy var fugaLabel: UILabel = {
        let label = UILabel()
        label.text = "fugafuga"
        return label
    }()
    
    private lazy var moguLabel: UILabel = {
        let label = UILabel()
        label.text = "mogumogu"
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupLayout()
    }

    private func setupLayout() {
        [hogeLabel, fugaLabel, moguLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            $0.backgroundColor = .gray
            view.addSubview($0)
        }
        
        NSLayoutConstraint.activate([
            hogeLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 80),
            hogeLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
            hogeLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),

            fugaLabel.topAnchor.constraint(equalTo: hogeLabel.bottomAnchor, constant: 16),
            fugaLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
            fugaLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),

            moguLabel.topAnchor.constraint(equalTo: fugaLabel.bottomAnchor, constant: 16),
            moguLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
            moguLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
        ])
    }
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
```swift
class ViewController: UIViewController {

...

    private lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.spacing = 16
        stackView.axis = .vertical
        return stackView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupLayout()
    }

    private func setupLayout() {
        view.backgroundColor = .lightGray
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        
        [hogeLabel, fugaLabel, moguLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            $0.backgroundColor = .gray
            stackView.addArrangedSubview($0)
        }
        
        NSLayoutConstraint.activate([
            hogeLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 80),
            hogeLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
            hogeLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
        ])
    }
}
```

StackViewすこ。使いすぎるとパフォーマンス落ちるとの声もあるので注意。

## `StackView + ScrollView`でお手軽に拡張しやすいスクロール可能なレイアウトを構築できる。
こんな感じにスクロールしたい画面を作る時はUIScrollViewを使いますが、ScrollViewはその中身のViewのサイズによってスクロールが発生するかどうかが決まるので中身をViewを用意してあげないといけません。そのViewをStackViewにしてやれば、stackViewにaddArreangedSubViewするだけでサイズが変わってくれるので、Viewの高さ計算とかしなくても楽にスクロールが発生します。

* ScrollViewをSuperViewの全方向に0pxで制約を貼る
* ScrollViewにStackViewをaddSubView
* StackViewとFrameLayoutGuideを同じ横幅にする
* StackViewをContentLayoutGuideの上下左右に0pxで制約を貼る

後はstackViewにViewを追加していくだけでStackViewのサイズが画面サイズより大きくなった時にスクロールが発生します。

スクロールなし | スクロールあり
:--: | :--:
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/1423a76b-29de-2330-4b8f-8daf4e443a29.png" width="500"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/8ecf65da-a55b-5078-1b54-6626485b69e3.png" width="500" />


## 見えているViewの裏のViewのタッチイベントを取得したい時は`hittest`が使える
こういうScrollViewが重なった画面があるとします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/8663d5d1-eb4a-bd7e-6fc5-a2a21b5c88df.png" width="350">
画像は今開発中の岩の位置を記録するアプリの画面です。

https://github.com/kawano108/RockMap

### Before
そのままだと前面の縦ScrollViewがタッチイベントを吸収し、裏の横ScrollViewが反応しません。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/6306efab-6f53-41f5-4a71-6acca0d16d7d.gif" width="300">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">

### After
そこでhittestです。
こいつでnilを返すようにoverrideすればタッチイベントを無視できます。

```swift

final class HeaderIgnorableScrollView: UIScrollView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        
        if view == self,
           point.x < UIScreen.main.bounds.width && point.y < UIScreen.main.bounds.height * (9/16) {
            return nil
        }
        return view
    }
}
```

裏面の横ScrollViewもタッチできるようになりました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/0c0ee2de-f6a2-f73e-d3fb-3dd5f296eaf7.gif" width="300">

感動しました。

## 同じ処理をアノテーション化して省略する時は`PropertyWrapper`が使える
例えばKeychainへのsetとgetの処理。
### Before
こんな感じでgetとsetをしてたとします。

```swift
final class KeychainManager {
    
    /// キー
    struct Key {
        static let accessToken = "accessToken"
    }

    /// キーチェーンインスタンス
    static let keychain = Keychain(service: Bundle.main.bundleIdentifier ?? "")
    
    static var accessToken: String {
        get { get(key: Key.accessToken) ?? "" }
        set { set(key: Key.accessToken, value: newValue) }
    }
    
    private static func get<T>(key: String) -> T? {
        do {
            return try KeychainManager.keychain.get(key) as? T
            
        } catch {
            print(error.localizedDescription)
            assertionFailure("Keychainからのデータの取得に失敗しました。")
            return nil
        }
    }

    private static func set(key: String, value: String) {
        do {
            return try KeychainManager.keychain.set(value, key: key)
            
        } catch {
            print(error.localizedDescription)
            assertionFailure("Keychainへのデータの保存に失敗しました。。")
        }
    }
}

// 利用時
KeychainManager.accessToken = accessToken // set
let accessToken = KeychainManager.accessToken

```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a31f9bbe-4f90-b087-ad3a-15c97d00da17.png" width="150">


### After
propertyWrapperを使えばsetとかgetを書かなくてよくなります。

```swift
import KeychainAccess

@propertyWrapper
class KeychainStorage<T: LosslessStringConvertible> {

    private let key: String

    var keychain: Keychain {
        guard let identifier = Bundle.main.object(forInfoDictionaryKey: UUID().uuidString) as? String else {
            return Keychain(service: "")
        }
        return Keychain(service: identifier)
    }
    
    init(key: String) {
        self.key = key
    }

    var wrappedValue: T? {
        get {
            do {
                guard let result = try keychain.get(key) else { return nil }
                return T(result)
            } catch {
                print(error.localizedDescription)
                return nil
            }
        }
        set {
            do {
                guard let new = newValue else {
                    try keychain.remove(key)
                    return
                }
                try keychain.set(String(new), key: key)
            } catch {
                print(error.localizedDescription)
            }
        }
    }
}

final class KeychainDataHolder {
    
    private enum Key: String {
        case uid = "_accessToken"
    }

    static let shared: KeychainDataHolder = KeychainDataHolder()

    private init() {}

    @KeychainStorage(key: Key.accessToken.rawValue)
    var accessToken: String?

}

// 利用時
KeychainDataHolder.shared.accessToken = accessToken // set
let accessToken = KeychainDataHolder.shared.accessToken //get
```

keyとプロパティを増やせばsetとgetは書かなくてよくなりますね。

## 終わりに
読んでいただいてありがとうございました！
もっといっぱいあると思うんですけどいざ出そうとすると出てこないんですよね。
明日は@takashicoさんです！