cv.cm

コーディング · プログラミング言語

Swift入門チュートリアル(初心者向け)

約11分で読めます

結論

Swiftは、AppleがiPhone、iPad、Mac、Apple Watch、Apple TV向けのアプリのために作った、高速で安全なコンパイル型言語で、LinuxやWindowsでも動作します。print("Hello, world") を hello.swift に保存して swift hello.swift で実行します。let の定数と var の変数、型推論、値がないかもしれない場合のオプショナル、値のセマンティクスを持つ構造体と列挙型、プロトコル、do/try/catch によるエラー処理が中心的な内容です。

JSONフォーマッタを開く

Swiftとは何か、何に使われるか

Swiftは、Objective-Cに代わるモダンな言語として2014年にAppleが発表し、2015年にオープンソース化されました。プロジェクトはswift.orgにあります。Appleプラットフォーム向けアプリの主要言語で、通常はSwiftUIやUIKitフレームワークと組み合わせて使います。サーバーサイドのコード(たとえばVaporフレームワーク)やコマンドラインツールにも使われます。この言語は、よくあるバグを防ぐよう設計されています。変数は初期化が必須で、配列は範囲をチェックし、nil は明示的に扱う必要があります。

Swiftのインストールと Hello, world の実行

Macでは、App StoreからXcodeをインストールするか、Xcodeのコマンドラインツールを入れます。LinuxとWindowsでは、swift.orgからツールチェーンをダウンロードします。swift --version で確認します。

print("Hello, world")
swift hello.swift
# Hello, world
swiftc hello.swift -o hello   # compile to a binary
./hello

大きなプロジェクトでは、swift package init --type executable を実行してから swift run を実行します。Xcodeのプレイグラウンドも、気軽に試せる方法です。

定数、変数、基本の型

定数には let、変数には var を使います。let を優先しましょう。一度も変更されない var には、コンパイラが警告を出します。型は推論され、Swiftは数値型どうしを暗黙的に変換しません。

型例備考
Intlet n = 42最近のプラットフォームでは64ビット
Doublelet x = 3.14デフォルトの浮動小数点型
Boollet ok = truetrue または false
Stringlet s = "hi \(name)"Unicodeテキスト。値型
Characterlet c: Character = "é"ユーザーが1文字と認識する単位
[Int], [String: Int][1, 2], ["a": 1]配列とディクショナリ
let name = "Ada"
var count = 7
count += 1
let avg = Double(count) / 2   // explicit conversion
print("\(name) has \(count) items, avg \(avg)")  // Ada has 8 items, avg 4.0
print(7 / 2, 7 % 2)          // 3 1

文字列リテラル内の \(expression) は、値を埋め込みます(文字列補間)。

制御構文

let score = 72
if score >= 90 {
    print("A")
} else if score >= 70 {
    print("B")                 // B
} else {
    print("C")
}

for i in 0..<3 {
    print(i, terminator: " ")  // 0 1 2
}
print()

var n = 10
while n > 1 { n /= 2 }
print(n)                       // 1

let day = "SAT"
switch day {
case "SAT", "SUN":
    print("weekend")           // weekend
default:
    print("weekday")
}

switch は網羅的である必要があり、デフォルトでは次のケースに落ちず、範囲(case 90...100:)、タプル、列挙型のケースにマッチできます。

関数とクロージャ

引数には、呼び出し側が書く引数ラベルがあり、呼び出しが文章のように読めます。ラベルを省略するには _ を使います。クロージャは、波括弧で書く無名関数です。

func greet(_ name: String, with greeting: String = "Hello") -> String {
    return "\(greeting), \(name)!"
}

print(greet("Lin"))                  // Hello, Lin!
print(greet("Lin", with: "Hi"))      // Hi, Lin!

let nums = [3, 1, 2, 5]
let squares = nums.map { $0 * $0 }
let evens = nums.filter { $0 % 2 == 0 }
let sorted = nums.sorted(by: >)
print(squares, evens, sorted)        // [9, 1, 4, 25] [2] [5, 3, 2, 1]

$0 はクロージャの最初の引数を指します。クロージャが最後の引数のときは、括弧の後ろに書けます(トレイリングクロージャ構文)。

配列、ディクショナリ、セット

var fruits = ["apple", "pear"]
fruits.append("fig")
print(fruits.count, fruits[0], fruits.contains("fig"))  // 3 apple true

var ages: [String: Int] = ["ann": 31, "bob": 25]
ages["cy"] = 40
for (person, age) in ages.sorted(by: { $0.key < $1.key }) {
    print(person, age)                                  // ann 31, bob 25, cy 40
}
print(ages["zed", default: 0])                          // 0

let tags: Set = ["b", "a", "b"]
print(tags.count)                                       // 2

コレクションは値型です。配列を別の変数に代入すると、独立したコピーが作られます(実際のコピーは、どちらかが変更されたときまで遅延されます)。let の配列は、まったく変更できません。

特徴的な機能:オプショナル

値がないかもしれない場合は、String? と書くオプショナル型になります。アンラップするまで String としては使えないため、コンパイラが nil の場合の処理を強制します。

let input = "42"
let number: Int? = Int(input)      // nil if input is not a number

if let number {                     // unwrap: runs only when not nil
    print("got \(number)")          // got 42
}

func firstWord(_ text: String) -> String? {
    text.split(separator: " ").first.map(String.init)
}

guard let word = firstWord("hello world") else {
    fatalError("no words")
}
print(word)                                  // hello
print(firstWord("")?.uppercased() ?? "none") // none

if let と guard let は安全にアンラップし、?.(オプショナルチェーン)は値がある場合にだけメソッドを呼び出し、?? はデフォルト値を与えます。強制アンラップ value! は値が nil のときにクラッシュするので、あまり使わないようにしましょう。省略形の if let number にはSwift 5.7以降が必要です。

構造体、クラス、列挙型、プロトコル

構造体と列挙型は値型で、クラスは継承を持つ参照型です。Swiftのコードでは、クラスの階層よりも、構造体とプロトコル(インターフェース)が好まれます。

protocol Shape {
    func area() -> Double
}

struct Rect: Shape {
    var width: Double
    var height: Double
    func area() -> Double { width * height }
}

enum Circle: Shape {
    case unit
    case radius(Double)
    func area() -> Double {
        switch self {
        case .unit: return Double.pi
        case .radius(let r): return Double.pi * r * r
        }
    }
}

let shapes: [any Shape] = [Rect(width: 2, height: 3), Circle.radius(1)]
let total = shapes.reduce(0) { $0 + $1.area() }
print((total * 100).rounded() / 100)   // 9.14

構造体には、メンバーワイズイニシャライザ(Rect(width:height:))が自動的に用意されます。列挙型のケースは、.radius(Double) のように関連値を持てます。

エラー処理

失敗しうる関数には throws を付けます。呼び出し側は、do/catch ブロックの中で try を使うか、try? でエラーを nil に変換します。

enum AgeError: Error {
    case notANumber(String)
    case negative
}

func parseAge(_ text: String) throws -> Int {
    guard let age = Int(text) else { throw AgeError.notANumber(text) }
    guard age >= 0 else { throw AgeError.negative }
    return age
}

do {
    print(try parseAge("41"))          // 41
    print(try parseAge("abc"))
} catch AgeError.notANumber(let text) {
    print("not a number: \(text)")     // not a number: abc
} catch {
    print("other error: \(error)")
}

let maybe = try? parseAge("-3")        // nil
print(maybe as Any)                    // nil

小さな完成プログラム:単語の出現頻度

import Foundation

let args = CommandLine.arguments
guard args.count == 2 else {
    print("usage: swift words.swift FILE")
    exit(1)
}

do {
    let text = try String(contentsOfFile: args[1], encoding: .utf8).lowercased()
    let words = text.split { !$0.isLetter && $0 != "'" }
    var counts: [Substring: Int] = [:]
    for word in words {
        counts[word, default: 0] += 1
    }
    for (word, n) in counts.sorted(by: { $0.value > $1.value }).prefix(5) {
        print(String(n).padding(toLength: 6, withPad: " ", startingAt: 0) + word)
    }
} catch {
    print("cannot read \(args[1]): \(error.localizedDescription)")
    exit(1)
}

swift words.swift notes.txt で実行します。Foundation はファイルの読み込みや文字列のヘルパーを提供し、Appleプラットフォームと、LinuxおよびWindowsのツールチェーンで利用できます。

初心者がよくやる間違い

  • let の定数や、let の構造体のプロパティを変更しようとする。値を変更する必要があるなら var を使います。
  • 構造体がオブジェクトのように共有されると期待する。コピーを変更しても元は変わりません。同一性を共有したいならクラスを使います。
  • s[0] のように、String を整数でインデックス指定する。s.first、s.prefix(3)、String.Index の値を使います。
  • Int と Double を変換せずに算術演算で混ぜる。これはコンパイルエラーです。
  • throwする呼び出しの前に try を付け忘れる、またはエラー時にクラッシュする try! を使う。

次のステップ

docs.swift.orgの公式ガイド兼リファレンス「The Swift Programming Language」と、swift.orgのドキュメントハブを読みましょう。アプリ開発では、XcodeでAppleのSwiftUIチュートリアルに沿って進めます。その後、Swiftの並行処理(async/await とアクター)、ジェネリクス、依存関係を管理するSwift Package Managerを学びます。

よくある質問

SwiftはAppleのプラットフォーム専用ですか?

いいえ。Swiftはオープンソースで、LinuxやWindowsでも動作し、サーバーサイドのコードやツールにも使われます。ただし、SwiftUIやUIKitといったアプリのフレームワークはApple専用です。

SwiftとObjective-Cのどちらを学ぶべきですか?

Swiftを学びましょう。新しいアプリにAppleが推奨している言語で、必要なときには既存のObjective-Cのコードも呼び出せます。

Swiftの構造体とクラスの違いは何ですか?

構造体は値型で、代入や受け渡しのときにコピーされます。クラスは参照型なので、すべての変数が1つのインスタンスを共有します。クラスは継承とデイニシャライザもサポートします。

型の後ろの疑問符は何を意味しますか?

Int? はオプショナルで、Int か nil のどちらかを保持します。値を使う前に、たとえば if let でアンラップする必要があります。

コーディング講座の一覧