はじめに
任せてもらったタスクをこなす中、求められた要件を満たすだけがエンジニアではないと日々感じてます。
- 出来るだけ無駄をなくす
- 出来るだけ読みやすく書く
- 出来るだけリスクヘッジする
これらを加味したベストなゴールに向けて、実装アイディアを取捨選択するためには知識と引き出しが必要と思います。
なので、引き出しとして頭の片隅に入れとこ系のプチ知識をメモっていく。アイデア入手次第更新する個人的外部ストレージ。
増やせ選択肢、無くせアンチパターン、目指せ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")
}
}
終わりに
都度更新でござい。