現役プログラマのプログラミング教室@岡崎市康生町一隆堂ビル, Android/iOS(Swift/Kotlin)のアプリ開発、デジタル漫画制作を主軸事業としています。

Swift感を取り戻せ!Androidのkotlin/javaやった後の気持ち切り替え攻略メモ

最近のプログラマーで、同じ言語で生涯終わるなんてことは稀ではないでしょうか。
kotlin/AndroidJavaやった後に感覚をすぐに取り戻すための必須メモ。

guard
アンラップした変数をスコープ外で利用、またnilチェックのように使う

  guard let realm = self.realm() else {
      return
  }
  try! realm.write {
      // realmを使ったなんらかの処理
  }

ここでguardするのはnullを許容する?マークをつけるオプショナル型になります。
上記でrealmはスコープ外でも使えるのが特徴です。

Codable
Codableなプロパティで構成されていればCodableとなる
String, Int, Double, Date, Data, URLは既にCodable
(Array, Dictionary, Optionalも同様)

import Foundation
struct Coordinate: Codable {
    var lat: Double
    var lon: Double
    enum CodingKeys: String, CodingKey {
        case lat = "latitude"
        case lon = "longitude"
    }
}
let coordinate = try! JSONEncoder().encode(Coordinate(lat: 0, lon: 0))
enum
非常に便利
・イニシャライザ
・caseに引数を渡せる
・関数が使える

    enum Lang {
        case Jp
        case En
        init(){
            self = .En
        }
    }
    enum Fruits {
        //型は使わない
        case apple(price: Int)
        case orange(price: Int)
        //関数が利用できる
        func priceString(lang: Lang) -> String {
            switch self {
            case .apple(let price):
                return lang == .Jp ? "りんごは\(price)円" : "Apple is $\(price)"
            case .orange(let price):
                return lang == .Jp ? "みかん\(price)円" : "Orange is $\(price)"
            }
        }
    }
    var lang = Lang()
    override func viewDidLoad() {
        super.viewDidLoad()
        print("fruits: \(Fruits.apple(price: 100).priceString(lang: lang))")
        print("fruits: \(Fruits.orange(price: 150).priceString(lang: lang))")
    }
extension
クラス、構造体、列挙体などを拡張します。
Objective-Cのカテゴリより便利ですっきり。
またクラスの継承で同じクラスを拡張することが出来ます。
1.クロージャーを繰り返す拡張例

extension Int {
    func times(closure:() -> Void) {
        (0..<self).forEach{_ in
            closure()
        }
    }
}
5.times{ print("Excute Closure")

2.プロパティを拡張する例

extension String {
    var localized: String {
        return NSLocalizedString(self, comment: self)
    }
}
"Hello".localized

3.whereで制約をつける

extension Array where Element == Int {
    func sum() -> Int {
        return reduce(0, +)
    }
}
[1,2,3].sum()

4.プロトコルの拡張
大人数で作成する場合にはプロトコルで制約をつけることがよくあります
元を変更せずに拡張することが出来ます。

struct Adult: User {
    var name: String
    var email: String
    var age: Int
}
protocol User: Codable {
    var name: String { get }
    var email: String { get }
    var age: Int { get }
}
extension User {
    var json: Codable {
        guard let str = try! String(data: JSONEncoder().encode(self), encoding: .utf8) else {
            return "none"
        }
        return str
    }
}
 let adult = Adult(name: "太郎", email: "taro@gmail.com", age: 20)
 print(adult.json)

5.型の定義を分割する

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
}
タプル
型が違う値をまとめられます。
メソッドの戻り値やSwiftLintで引数が多くひっかかる場合などに便利。

var returnUser = ("Taro", 14, true)
switch
タプル

let month = 12
let day = 24
let date = (month, day)
switch date {
  case (12,24): print("クリスマスイブ")
  case (12,25): print("クリスマス")
  case (1,1): print("元旦")
  default : break
}

範囲

let value = 2.4
switch value {
case 0..<100:
    print("範囲内")
default:
    print("範囲外")
}

条件

let club = "grampus"
switch club {
case "grampus", "urawa":
    print("ユニフォーム色かぶり")
default:
    print("例外")
}
let location = (20, 60)
switch location {
case (let x, let y) where x == 20 && y == 44:
    print("敵が出現")
default:
    print("通常")
}
for/array/dictionary
・filter
文字通り条件指定して取り出したいときに便利

let dict = ["炭水化物": ["もち", "白米", "マーマレード"], "タンパク質": ["肉", "卵","大豆"]]
let filtered = dict.filter {
    $0.key.contains("炭水化物")
}

Dictionaryの場合の戻り値に注意
(swift3) Dictionaryでなくタプルで返却される
(swift4) Dictionaryで返却される
・map
要素に処理を加えたいときに便利

let src = [1,2,3,4,5]
let dest = src.map { $0 * 2 + 1 }
[3,5,7,9,11] ・reduce
要素の集計などに便利

let sum = [1, 2, 3, 4, 5].reduce(0) { res, num in
    return res + num
}

reduceの引数0は初期値、sumは15になる。.reduce(0, combine: *)などcombineをクロージャでなく*,+などにすることも可能
組み合わせて使うと効果を発揮

let info = ["Tokyo":14.0, "Osaka":17.0, "Aichi":15.0]
let avr = info.map { 1.8 * $0.1 + 32 } .reduce(0, combine: +) / Double(info.count)

for文で書いたほうが速いが数千件の簡単な処理でミリ秒の差程度なので、よっぽでなければ気にせず見やすいほうがよい。
・flatMap
2重の配列を1重にしたり、nilを除去します。

[[1, 2], [3]].flatMap { $0 }
→ [1, 2, 3]
mapとの比較
[[1, 2], [3]].map { $0 }
→ [[1, 2], [3]]

・for-in / forEach
for 変数 in 開始値 … 終了値
[1.2.3].forEach{ $0 }
・set
論理積が使えるのがメリットかな?
・array/dictionary
初期化について

let fruits = ["Apple", "Orange", "Grape"]
fruits.append("Pine")

・zip
2つの配列から辞書を作成

let names = ["Apple", "Orange", "Grape"]
let price = [100, 30, 40]
let zipValue = zip(names, count)
let dict = Dictionary(uniqueKeysWithValues: zipValue)
[“Grape”: 40, “Orange”: 30, “Apple”: 100] ・merge
文字通りマージできます。

var cart = [🍌: 1, 🍇: 2]
let otherCart = [🍌: 2, 🍇: 3]
cart.merge(otherCart, uniquingKeysWith: +)
closureなどのweak/@escaping
クロージャーなどでcompletionHandlerで非同期処理などする場合に、処理待ちされません。
そんなときはスコープから退避する@escapingをつけます。

 func asyncMethod(completion: @escaping () -> Void) {
      非同期処理{
          completionHandler()
      }
}
循環参照=たとえばクラスAとクラスBがお互いがインスタンスを強参照してしまうような状態。メモリーリークの代名詞w
@IBOutletのものにはweakをつけましょう(特別なことしない場合は問題ない気がしますが)
クロージャーのよくあるパターン
class Com {
    func send(success:((Void) -> Void)?) {
        success?()
    }
}
class ClassA {
    var com = Com()
    func sendData() {
        com.send({[weak self] () -> Void in
            if let _self = self {
                // 処理
            }
        })
    }
}

asyncMethod({
self.isFinish = true
})

lazy
値が代入されたときに初期化される

lazy var value: Int
struct
クラスとほぼ同じstructは値型であるとして使い分ける
継承が関係ある場合はクラス
getter/setter
通常のgetter/setterに差はないと思いますが、入力時に他の処理をさせたいだけであればwillSetやdidSetを使う
アクセスコントロール
準備中
protocol
準備中
命名規則
準備中
よく使うライブラリ
・alamfire
・RxSwift
・Realm
・SwiftLint