LoginSignup
3
5

More than 3 years have passed since last update.

Swift 選択肢を広げるプチ引き出しまとめ

Last updated at Posted at 2019-10-05

はじめに

任せてもらったタスクをこなす中、求められた要件を満たすだけがエンジニアではないと日々感じてます。

  • 出来るだけ無駄をなくす
  • 出来るだけ読みやすく書く
  • 出来るだけリスクヘッジする

これらを加味したベストなゴールに向けて、実装アイディアを取捨選択するためには知識と引き出しが必要と思います。
なので、引き出しとして頭の片隅に入れとこ系のプチ知識をメモっていく。アイデア入手次第更新する個人的外部ストレージ。

増やせ選択肢、無くせアンチパターン、目指せLGTM。

💡実装アイデア系

:Equatable - オブジェクト同士を比較

特徴

  • オブジェクト同士は比較できない
  • class, struct 等にEquatableを付与することで可能になる
  • ないとそもそもstudentA == studentBのようなオブジェクト比較ができない

ついでにfunc ==(lhs: Foo, rhs: Foo)というたまに見る呪文も覚えよう。

用途

  • オブジェクト同士を比較したい時 ( Equatableだけ必要 )
  • オブジェクト同士のプロパティを比較したい時( Equatable と lhs/rhsメソッドが必要 )

サンプル

上記を満たしたい時

Sample 1 : オブジェクトだけ比較したい場合

struct Person: Equatable {
  let name: String
  let job: String
}

~

let tim = Person(name: "Tim Cook", job: "IT")
let timClone = Person(name: "Tim Cook", job: "IT")
let steve = Person(name: "Steve Jobs", job: "IT")

tim == steve // false  > job は同じだが name が違う
tim == timClone // true > name & job が同じ

Sample 2 : オブジェクト同士のプロパティを比較したい時

一部無視して一部比較したい時にlhs & rhsを使用して、比較メソッドをカスタムする。

struct Person: Equatable {
  let name: String
  let job: String
}

~

let tim = Person(name: "Tim Cook", job: "IT")
let timClone = Person(name: "Tim Cook", job: "IT")
let steve = Person(name: "Steve Jobs", job: "IT")

let sherlock = Person(name: "Sherlock Holmes", job: "Detective")

// name は違ってもいいから job が同じか比較したい
// lhs : Left Hand Side - rhs : Right Hand Side で覚えよう
static  func == (lhs: Student, rhs: Student) -> Bool {
  return lhs.job == rhs.job
}

tim == steve // true > name が違うが比較に含んでない
tim == sherlock // false > name は無視してるが job が違う

@ discardableResult - 返り値使わない場合の警告を防ぐ

特徴

  • 返り値があるメソッドで返り値を使用しないと警告がでる
  • これをメソッドにつけてあげると、警告が出なくなる

用途

  • 返り値を渡したい場合・void両方の機能を持たせたい場合の両方の機能をメソッドが持っている時

サンプル

  • cellForRowAt で セル生成メソッド作りたい ( return型 )
  • didSelectRowAt 選択されたものだけ画像の非表示したい( Void )
  • 2人で1つにしたい

流れとしては、選択されたセルが入る変数が一致してるかどうかで画像表示のスイッチにする

var items = [many cells] // セル達格納配列
var selectedItem: Item? //選択されたアイテム格納用

// 普段ならCustomCellクラス内で定義して cell.configure()とかで呼ぶが、VC内でメソッド定義してワンクッション置く
// 中で実質セル生成するメソッドcell.set()等をコール
@discardableResult
func configureCell(cell: CustomCell, at indexPath: IndexPath) -> UITableViewCell {
  let item = items[indexPath.row]
  cell.set(title: item.title, isSelected: selected == item)
  // isSelected:
  // selectedItemは初期はnilなので、cellForRowAtで呼ばれるうちはfalse
  // didSelectRowAtで呼ばれるとselectedItemに選択セルが格納されるのでtrue

  return cell
}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // セル生成してそのままリターン
        return configureCell(cell: tableView.dequeueReusableCell(for: indexPath), at: indexPath)
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 選択されたセルを格納
        selected = item
        if let cell = tableView.cellForRow(at: indexPath) as? CustomCell {
            // 選択されたセルを渡す
            configureCell(cell: cell, at: indexPath)
        }
}

class CustomCell: UITableViewCell, CellReusable {
    ~~~
    func set(title: String, isSelected: String) {
        titleLabel.text = title
        additionalImage.isHidden = isSelected //選択されたら画像非表示
    }
}

Tuple - バラバラのオブジェクトをペアで所持する

特徴

  • バラバラの型でもペアで組める
  • 一時的な構造体のまとまりを作れる

用途

上記みたいなことをしたい時

サンプル

出来るだけバラバラにしておきたくないものをまとめる(構造体まで作りたくない)

let name = "John"
let id = 23
var student1 = (name: name, id: id) // String + Int とか型違いでもペア可能

~

print(student1.name) // John
print(studnet1.id) // 23

protocol - 空protocolの準拠でクラスをグルーピング

特徴

  • 準拠させると該当クラスをグループ化できる

用途

  • Bool判定を行い、不要クラス排除/必要クラス引き抜き したい場合

サンプル

  • 該当するViewControllerのみ引き抜いて処理したい
// 空プロトコル、グルーピングしたいクラスに付与
protocol GroupingProtocol {}

class AViewController: UIViewController, GroupingProtocol {...}
class BViewController: UIViewController {...}
class CViewController: UIViewController, GroupingProtocol {...}

~

func validateVC(vc: UIViewController) -> UIViewController? {
  var validatedVC: UIViewController?

  if vc is GroupingProtocol { // if is でprotocolに準拠してるか判定
    validatedVC = vc
  }
  // true ならGroupingProtocolが付いてるVC、falseならnilをリターン
  return validatedVC
}

~

guard let vc = validateVC(vc: AViewController()) else { return }
// 空プロトコルが準拠してれば後続処理実行、違えばスキップ
// ...
// ...

Iteration - forEach / map / flatMap / .compactMap の使いわけ

特徴

  • 配列要素に対して繰り返し処理したい場合
  • forはほぼ使わないと思われる
  • forEach / map / flapMap はそれぞれ使い所がある

用途

  • .forEach
    • for inの簡略バージョン
    • 要素加工せず 、要素を使って何かして新しい配列を作成する必要がないとき
  • .map
    • 要素に対して処理して新しい配列を返したい時
    • ターゲットがオプショナルならオプショナル、そうじゃなないならノンオプショナル型を返す
  • .flatMap

    • 要素に対して処理して新しい配列を返したい時
    • 2次元目の配列要素に処理、その配列を消し要素を一列にして返す
    • [[1,2], [3, 4]].flatMap{ $0 + [99]} → [1, 2, 99 3, 4, 99]
  • .compactMap

    • 配列要素のnilを排除した配列を返したい時
    • nilが含まれている可能性がある配列に処理かけて安全性を得たい時

flatMapは細かい比較が面倒なので詳細は以下あたりを
参考:
https://qiita.com/shimesaba/items/1a89cb5600454f91cc67

なお、現在nil掃除のケースは.flatMapではなく.compactMap推奨となっています。

サンプル

////// forEach
// 要素操作はしない
// 要素を使用して何かをする
var nums = [1, 2, 3] 

nums.forEach { num in
    print(num) //[1, 2, 3]
}

////// map
// 要素操作する
// 新しい配列生成する必要ある
var nums = [1, 2, 3] 

let newNums = nums.map { $0 + 1 }
print(newNums) //[2, 3, 4]

////// flatMap
// 要素操作する
// 新しい配列生成する必要ある
// 2次元配列を操作して、1次元配列(一列)にしたい
let twoDimensionalNums: [[Int]] = [[1, 2], [3, 4]]

let oneDimensionalNums = twoDimensionalNums.flatMap {$0 + [99]}
print(oneDimensionalNums) //[1, 2, 99, 3, 4, 99]

////// compactMap
// 要素操作する
// 新しい配列生成する必要ある
// 要素に含まれるnil(入っている可能性)、該当dataTypeを排除したい

// nil排除
var nums = [1, 2, nil]

let numsRemovedNil = nums.compactMap { $0 } 
print(numsRemovedNil) //[1, 2]

// 要素がオプショナルの場合、nilを排除して安全性を保つ
var nums: [Int?] = [1, 2] // ブラックボックスなのでnilが入っている場合があるかも?

let numsRemovedNil = nums.compactMap { $0 } // 念の為nil排除
print(numsRemovedNil) //[1, 2] nil無しが保証

// 返還後にエラーになる部分を排除
var numsStr: [String] = ["1", "2", "Tom"]

let numsInt = numsStr.compactMap { Int($0) } // StringをIntにキャスト、”Tom”はできないので排除
print(numsInt) //[1, 2]

protocol extention - 必ず実装する訳ではないメソッド等も管理する

9/10/19

特徴

  • protocolを持たせたクラスは基本的にprocotol内に記述されたものを書く必要がある
  • protocol extensionを用いて空処理のデフォルト実装を与えると、記述内容を制御できる

用途

大枠他の場所では○○実装するけど、このクラスだけ○○を適用したくない、
という場合にprotocolでまとめつつ一部実装内容を制御したい時とか。

サンプル

bottomパートにいくつかボタンを設置するのでprotocolでまとめるが、一部のVCには特定のボタンを表示させない。


// 実装したいボタン群を記述
protocol BottomButtonsDelegate {
    func tappedBackButton()
    func tappedLIlkeButton()
    func tappedNextButton()
}

// defaultで空を定義しておく
extension BottomButtonsDelegate {
    func tappedBackButton() {}
    func tappedLIlkeButton() {}
    func tappedNextButton() {}
}

// Buttonsの設定とか色々しているカスタムクラス
class CustomButtons: UIView {

    weak var delegate: BottomButtonsDelegate?
    ~~~
}



// delegate 先 (全部実装したい)
class FirstViewController: UIViewController, BottomButtonsDelegate {

    let customButtons = CustomButtons()

    ~~~

    customButtons.delegate = self

    ~~~

    //  protocol BottomButtonsDelegate に記載してあるもの全て記述

    func tappedBackButton() {
        print("tap back button")
    }

    func tappedLIlkeButton() {
        print("tap like button")
    }

    func tappedNextButton() {
        print("tap next button")
    }
}



// delegate 先 (一部実装したくない)
class SecondViewController: UIViewController, BottomButtonsDelegate {

    let customButtons = CustomButtons()

    ~~~

    customButtons.delegate = self

    ~~~

    //  protocol BottomButtonsDelegate に記載してあるもの全て記述

    func tappedBackButton() {
        print("tap back button")
    }

    /*  これだけ実装したくない

    func tappedLIlkeButton() {
        print("tap like button")
    }

    */

    func tappedNextButton() {
        print("tap next button")
    }
}

終わりに

都度更新でござい。

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5