TypeScriptとC#をやってきたWebエンジニアがSwift Tourを読んでみたログ(重要項目抜き出してみた)

はじめましての人ははじめまして。そうでないひとはお久しぶりです。猫ロキP(@deflis/id:deflis55)です。

これまでWebのフロントエンドとバックエンド開発中心でお仕事をしてきたのですが、 最近iOSアプリ*1の開発を始めまして、Swiftを書くようになりました。

それにあたって、Swiftを勉強するのにSwift Tourを読んでみました、 ですが、Swift Tourはどちらかといえばプログラミング初学者向けに書かれているので、TypeScriptのような他言語に慣れ親しんだ人が読むにはちょっと冗長だなと思いました。

自分がSwift Tourを読み進める中で、特に重要なポイントを抽出してメモしていました。これらを整理し、他の人にも理解しやすい形で共有しておこうと思いました。

これまで自分がTypeScriptを主に書いていたので、このメモはTypeScriptとSwiftの類似点と違いに焦点を当てて書いています。

新しいUIフレームワークとしてSwiftUIが最近登場したのですが、これは比較的Reactに似ている宣言的UIで記述できるUIフレームワークです。 そのSwiftUIがiOS開発の中心になりつつあるので、Webフロントエンドを中心にやっている開発者は非常に取っつきやすくなっていると思います。 なので、このメモがWebフロントエンドを中心に書いている方々の参考になれば幸いです。

docs.swift.org swift-programming-language-jp.gitbook.io

Swift Tourを始める前に最初に文法を見た感想

CやJavaのように関数呼び出しの末尾にセミコロンを付けなければならない言語は多いですが、Swiftでは末尾にセミコロンを付けてはいけないです。 これは、すごくTypeScriptやScalaの影響を受けていると感じます。

また、iOS開発をするうえではあまり関係ないですが、main関数のようなものはなく、フラットな構造となっているのも特徴と言えます。 このあたりはNode.jsあたりの影響が強く出ているなと思います。

SwiftはObjective-Cよりも洗練されており、再構築されたことでより現代的かつ理解しやすい言語になっています。 iOS開発をこれから始める場合、Objective-Cを今更学ぶよりはSwiftを学方が圧倒的に良いでしょう。

シンプルな値(Simple Values)

Swiftにおける変数の宣言は var を使い、定数の宣言には letを使います。JavaScriptでは、それぞれ letconst に相当します。(なぜ、let の意味が入れ違ってしまったのか…。)

Swiftには最近の言語らしくちゃんと型推論があるので基本的に型を書くことはないですが、どうしても型を書きたいときは変数名のあとに型を宣言します。

let value: String = "文字列型!!!"

今時の言語っぽくテンプレートリテラルなどもあります。

let count = 100

let templateString = "count is \(count)"

let multiLineString = """
複数行の文字列も
今時の言語らしく書けます。
"""

配列や辞書型は [] で宣言します。

let arrayList = ["りんご", "ばなな"]

// DictinaryはJSと違って `{}` で宣言するわけではないので注意
let dict = [
  "key": "value",
]

// 空の宣言
let emptyArray: [String] = []
let emptyDictonary: [String: Int] = [:]

Swift Tourのこの項目にはnullについての記述がないですが、nullは nil で表します。

// オプショナルな値は `?` をつけて宣言
let emptyString: String? = nil
let nullableObject: Someting? = nil

// TypeScriptでいうnull合体演算子がある
let str = emptyString ?? "empty"

// オプショナルチェーンもちゃんとある
let accessOptionalValue = nullableObject?.str ?? "optional chain"

制御フロー(Control Flow)

Swiftにおける条件分岐は ifswitch があります。Scalaみたいに値が返ってくる優れものです。

// 条件に書くカッコは省略できます。
// ifの条件は必ず `Boolean` である必要があるので注意(例外については後述します)
let result= if score > 10 {
  "負け"
} else {
  "勝ち"
}

// breakは必要ない。この辺もScalaっぽい。
switch value {
case "case1":
  func1()
case "case2":
  func2()
default:
  // valueの値のケースがすべて書かれていればdefaultを書かないでもいいけど、
  // ケースが足りない場合はちゃんとコンパイルエラーになる
  func3()
}

ループは for-in while repeat-while の3種類があります。Swiftにおける for はいわゆる foreach に相当する構文で、 for(;;) 形式の構文はありません。(Range構文はあるのでそれで代用は出来る)

let scores = [1, 10, 100]

for score in scores {
  funcA(score)
}

// Range構文で for(;;) 風の処理も出来る
for i in 1...10 {
  funcB(i)
}

// whileは省略

iflet の組み合わせでオプショナルな値を扱うこともできます。 使ってみると結構便利で、頻出なので覚えておくと良いです。ほかの言語にも欲しい。

// こんな感じでオプショナルな値を閉じられたスコープの中に宣言できます
if let value = nullish?.someting {
  // nullじゃないときの処理
  value.someFunc()
} else {
  // nullだったときの処理
  anyting()
}

関数とクロージャ(Functions and Closures)

関数定義は func で行います。また、戻り値の型は -> で記述します。

func funcA(arg: String) -> String {
  return "引数は\(arg)"
}

// 返り値の型が `Void` のとき省略できる
func funcB(arg: Int) {
  print("引数は\(arg)")
}

関数の返り値をタプルみたいに複数指定することも出来ます。ラベルがついているのでラベルでもアクセスできます。

func calculateMinMax(list: [Int]) -> (min: Int, max: Int) {
  // ...省略
  return (min, max)
}

let minMax = caluculateMinMax([1, 2, 3, 4])
print(minMax.min)
print(minMax.1)

関数は第一級オブジェクトなので、関数を戻り値にしたり引数にしたり出来ます。

func funcX() -> ((Int) -> Int) {
  func funcXX(in: Int) -> Int {
    return in + 1
  }
  return funcXX
}

func funcY(f: ((Int) -> Int)) -> Int {
  return f(100)
}

そもそも関数はクロージャシンタックスシュガーなので、名前なしのクロージャも作れます。JavaScriptでいうところのラムダ式のような使い方もできます。

numbers.map({ (number: Int) -> Int in
  return number * number * number
})
// パラメータや戻り値の型は省略できる
numbers.map({ number in
  return number * number * number
})
// パラメータは `$x` でもアクセスできる。
numbers.sorted { $0 > $1 }

オブジェクトとクラス(Objects and Classes)

クラス定義はよく見る形です。イニシャライザ(コンストラクタ)は init 、自身(this)は self でアクセスします。

class SampleClass {
  let str1: String
  let str2: String

  // イニシャライザ
  init(str1: String, str2: String) {
    self.str1 = str1
    self.str2 = str2
  }

  func printMessage() {
    print("\(str1) is \(str2)")
  }

  // overrideもできる
  override func getMessage() {
     // ...
  }
}

計算プロパティの定義もできます。C#風の get / set です。

class SampleClass2 {
  var length: Double = 100

  // プロパティ定義
  var bodyLength {
     get {
       return length - 20
     }
     set {
       self.length = newValue + 20
     }
  }
}

列挙型と構造体(Enumerations and Structures)

列挙型Enumenum で定義します。クラス同様に拡張することが出来ます。rawValue で値を取ることも出来ます。

enum EventType: String {
  case click = "ClickEvent"
  case back = "BackEvent"

  // switch case で enum をいい感じにするのはありがちのやり方
  func description() {
    switch self {
    case .click:
      return "Click Event"
    default:
      return "Common Events"
    }
  }
}

// 推論できるときは型を省略できる
let event1: EventType = .click

// イニシャライザでも値を作れる
let event2: EventType = EventType.init(back)

また、構造体というものもあります。 struct で定義できます。使い方はほとんどクラスと同じなのですが、クラスが参照型なのに対して構造体は値型なので常にコピーされるという違いがあります。

Apple的には struct を主に使うことを推奨しているようです。

同時並行処理(Concurrency)

いわゆるasync/awaitが使えます。

func getUser(server: String) async {
  let user = await fetchUser(from: server)

  if user == nil {
    return await createUser(from: server)
  }

  return user
}

同期的なコードの中で非同期処理を書くには Task を使います。

func writeSync(str: String) async {
  Task {
    await writeAsync(str)
  }
}

呼び出し部分で async let と書くことで並列に実行でき、 await で値を取り出せます。が、使うことはあまりないので省略します。 また、タスクグループや actor の解説もありますがここも余り使わないので省略します。

プロトコルと拡張(Protocols and Extensions)

Objective-C由来でプロトコル protocol というのがあります。ほかの言語で言うところのいわゆるインターフェースみたいなものです。 classenumstructプロトコルに準拠できます。

protocol ExampleProtocol {
  var description: String { get }
  mutating func funcA()
}

また、extension で型の拡張をすることも出来ます。 近しいけどちょっと違う概念として、ScalaのtraitやC#の拡張メソッドを思い浮かべると少し近いかもしれません。

// ExampleClassを拡張する
extension ExampleClass {
  func extensionMethod() {
  }
}

// extensionを使うことで既存のクラスを特定のプロトコルに準拠させることも出来る
extension ExampleClass: ExampleProtocol {
  var description = "Description"
  mutating func funcA() {
    // anything...
  }
}

エラーハンドリング(Error Handling)

いわゆる他の言語で言うところの例外クラスに相当するものは Error プロトコルです。

enum PizzaError: Error {
  case pieceOfPizza
  case wholePizza
}

Javaみたいに例外を投げる関数には throws をつける必要があります。

func sendPizza throws {
  throw wholePizza
}

例外をスローする関数を実行するときは頭に tryをつけます。

例外処理は do-catch パターンで記述します。 catch 句に何もつけないと、エラー内容を error で取得できます。

do {
  try sendPizza()
} catch {
  print(error)
}

複数のエラーを識別することもできます。

do {
  try func()
} catch TofuError.onFire {
  print("📛")
} catch let tofuError as TofuError {
  print(tofuError)
} catch {
  print("nice catch!! \(error)")
}

頻出パターンとしてOptionalに変換してくれる try? もあります。これは例外が発生したときに nil を返します。

let result = try? func()

// エラー起こっても続行したいときの書き方
try? func()

いわゆる finally に相当する、例外が発生してもしなくても実行したい処理を書くには defer を使います。

// 冷蔵庫が空いているか
var fridgeIsOpen = false
// 冷蔵庫の中身
let fridgeContent = ["tofu", "soysource"]

func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }

    let result = fridgeContent.contains(food)
    return result
}

ジェネリクス(Generics)

<> で囲うとジェネリクスになります。

where をつけることによって細かい型束縛をすることも出来ます。

func funcA<T: Sequence, U: Sequence>(seqA: T, seqB: U)
 where T.Element: Equatable, T.Element == U.Element {
  // ...something
}

おわりに

今回は最近の他の言語をしっかり学んでいる人が初めてSwiftを学ぶときに詰まりそうなところを中心にSwiftの要注意ポイントをまとめました。

あとはこれを頭に入れつつSwiftUIを学べば大体iOS開発を始められるのではないでしょうか。

それでは、よいSwiftライフを!ではでは!