Swift

#17
TIOBE#25
PYPL#11
GitHub#22
RedMonk#11
IEEESpectrum#21
JetBrains#16
プログラミング言語iOSmacOSAppleモバイル開発型安全SwiftUI

プログラミング言語

Swift

概要

SwiftはAppleが開発したiOS/macOSアプリ開発のための現代的なプログラミング言語です。

詳細

Swiftは2014年にAppleによって発表された、iOS、macOS、watchOS、tvOSアプリケーション開発のための現代的なプログラミング言語です。Objective-Cの後継として設計され、安全性、性能、表現力を重視した言語です。型安全性、自動メモリ管理、nil安全性などの機能により、より安全で保守しやすいコードの記述が可能です。2015年にオープンソース化され、Apple以外のプラットフォームでも使用できるようになりました。SwiftUIの導入により、宣言的なUI開発が可能となり、iOS開発の効率が大幅に向上しています。現在では、App Storeの多くのアプリがSwiftで開発されており、iOS/macOSアプリ開発の標準言語として確立されています。

書き方の例

Hello World

// 基本的な出力
print("Hello, World!")

// 複数の値を出力
print("Hello,", "Swift!")

// 変数を使った出力
let message = "こんにちは、Swift!"
print(message)

// 文字列補間
let name = "太郎"
let age = 25
print("私の名前は\(name)で、\(age)歳です。")

// 複数行の文字列
let multiline = """
これは複数行の
文字列です。
Swift らしい書き方ですね。
"""
print(multiline)

// デバッグ出力
debugPrint("これはデバッグ出力です")

// ファイル名と行番号を含む出力
print("エラーが発生しました", #file, #line)

// PlaygroundとiOSアプリでの基本構造
import Foundation

// iOSアプリの場合(UIKit)
/*
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        print("Hello from iOS app!")
    }
}
*/

// SwiftUIアプリの場合
/*
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
    }
}
*/

変数とデータ型

// 変数と定数
var mutableValue = 42        // 変更可能な変数
let immutableValue = 100     // 変更不可能な定数

// 型注釈(型を明示的に指定)
var explicitInt: Int = 50
var explicitString: String = "Hello"

// 基本データ型
let integer: Int = 42
let floatingPoint: Double = 3.14159
let singlePrecision: Float = 2.71828
let boolean: Bool = true
let character: Character = "A"
let string: String = "Swift Programming"

// 型推論
let inferredInt = 123        // Int型として推論
let inferredDouble = 3.14    // Double型として推論
let inferredString = "Hello" // String型として推論

// 数値の表現
let binary = 0b1010          // 二進数
let octal = 0o755           // 八進数
let hexadecimal = 0xFF      // 十六進数
let scientific = 1.25e2     // 指数表記

// Optional型(nil許容型)
var optionalString: String? = "値があります"
var nilValue: String? = nil

// Optional binding(安全な値取り出し)
if let unwrappedString = optionalString {
    print("値: \(unwrappedString)")
} else {
    print("値はありません")
}

// Guard文によるOptional handling
func processOptional(_ value: String?) {
    guard let unwrappedValue = value else {
        print("値がありません")
        return
    }
    print("処理中: \(unwrappedValue)")
}

// Nil coalescing operator
let defaultValue = optionalString ?? "デフォルト値"
print("値: \(defaultValue)")

// 配列
var fruits = ["りんご", "バナナ", "オレンジ"]
let numbers: [Int] = [1, 2, 3, 4, 5]
var emptyArray: [String] = []

// 配列の操作
fruits.append("いちご")
fruits.insert("ぶどう", at: 0)
fruits.remove(at: 1)

// 辞書(Dictionary)
var person = [
    "name": "田中太郎",
    "age": "30",
    "city": "東京"
]

// 型を明示した辞書
let scores: [String: Int] = [
    "数学": 85,
    "英語": 92,
    "理科": 78
]

// Set(重複なしコレクション)
var uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]
uniqueNumbers.insert(3) // 重複は追加されない

// タプル
let coordinates = (x: 10.0, y: 20.0)
let httpStatus = (statusCode: 200, description: "OK")

// タプルの要素にアクセス
print("X座標: \(coordinates.x)")
print("ステータス: \(httpStatus.statusCode)")

// 型エイリアス
typealias Point = (x: Double, y: Double)
typealias UserInfo = [String: String]

let point: Point = (x: 5.0, y: 10.0)
let user: UserInfo = ["name": "山田", "email": "[email protected]"]

// 型の確認
print(type(of: integer))     // Int
print(type(of: string))      // String
print(type(of: fruits))      // Array<String>

// 型変換
let stringNumber = "123"
let convertedNumber = Int(stringNumber) ?? 0
let numberAsString = String(integer)

print("文字列から数値: \(convertedNumber)")
print("数値から文字列: \(numberAsString)")
print("配列: \(fruits)")
print("辞書: \(person)")
print("セット: \(uniqueNumbers)")
print("タプル: \(coordinates)")

関数とクロージャ

// 基本的な関数
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

// 単一式の関数(returnを省略可能)
func multiply(_ a: Int, _ b: Int) -> Int {
    a * b
}

// 引数ラベル付きの関数
func greet(person name: String, from hometown: String) -> String {
    return "Hello, \(name) from \(hometown)!"
}

// デフォルト引数
func createUser(name: String, age: Int = 0, email: String = "") -> [String: Any] {
    return [
        "name": name,
        "age": age,
        "email": email
    ]
}

// 可変長引数
func sum(_ numbers: Int...) -> Int {
    return numbers.reduce(0, +)
}

// inout引数(参照渡し)
func swapValues(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

// 関数を返す関数
func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var total = 0
    return {
        total += incrementAmount
        return total
    }
}

// 関数を引数に取る関数
func processArray(_ array: [Int], using operation: (Int) -> Int) -> [Int] {
    return array.map(operation)
}

// 関数の使用例
print(add(5, 3))
print(multiply(4, 6))
print(greet(person: "田中", from: "東京"))

let user1 = createUser(name: "山田")
let user2 = createUser(name: "佐藤", age: 25, email: "[email protected]")

print("合計: \(sum(1, 2, 3, 4, 5))")

var x = 10
var y = 20
swapValues(&x, &y)
print("交換後: x=\(x), y=\(y)")

// クロージャ
let incrementByTwo = makeIncrementer(incrementAmount: 2)
print("インクリメント: \(incrementByTwo())")
print("インクリメント: \(incrementByTwo())")

// クロージャの様々な書き方
let numbers = [1, 2, 3, 4, 5]

// 完全な形
let doubled1 = numbers.map({ (number: Int) -> Int in
    return number * 2
})

// 型推論を使用
let doubled2 = numbers.map({ number in
    return number * 2
})

// 単一式のクロージャ
let doubled3 = numbers.map({ number in number * 2 })

// 引数名の省略
let doubled4 = numbers.map({ $0 * 2 })

// トレイリングクロージャ
let doubled5 = numbers.map { $0 * 2 }

// 高階関数の例
let evens = numbers.filter { $0 % 2 == 0 }
let total = numbers.reduce(0) { $0 + $1 }
let strings = numbers.map { "数値: \($0)" }

print("倍数: \(doubled5)")
print("偶数: \(evens)")
print("合計: \(total)")
print("文字列: \(strings)")

// エスケープクロージャ
func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // 非同期処理をシミュレート
        Thread.sleep(forTimeInterval: 1)
        DispatchQueue.main.async {
            completion("データを取得しました")
        }
    }
}

// 使用例(実際のアプリでは非同期で実行される)
// fetchData { result in
//     print(result)
// }

// オートクロージャ
func logIfDebug(_ message: @autoclosure () -> String, isDebug: Bool = true) {
    if isDebug {
        print("DEBUG: \(message())")
    }
}

logIfDebug("重要な情報: \(Date())")

構造体とクラス

// 構造体の定義
struct Person {
    var name: String
    var age: Int
    let id: String = UUID().uuidString
    
    // 計算プロパティ
    var description: String {
        return "\(name) (\(age)歳) - ID: \(id)"
    }
    
    // プロパティ監視
    var email: String = "" {
        willSet {
            print("メールアドレスを \(newValue) に変更予定")
        }
        didSet {
            print("メールアドレスを \(oldValue) から \(email) に変更しました")
        }
    }
    
    // イニシャライザ
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    // メソッド
    func introduce() -> String {
        return "私の名前は\(name)で、\(age)歳です。"
    }
    
    // mutating メソッド(構造体のプロパティを変更)
    mutating func haveBirthday() {
        age += 1
        print("\(name)さん、\(age)歳のお誕生日おめでとう!")
    }
    
    // 静的メソッド
    static func createUnknownPerson() -> Person {
        return Person(name: "Unknown", age: 0)
    }
}

// クラスの定義
class Vehicle {
    var brand: String
    var model: String
    var year: Int
    
    // 指定イニシャライザ
    init(brand: String, model: String, year: Int) {
        self.brand = brand
        self.model = model
        self.year = year
    }
    
    // 便利イニシャライザ
    convenience init(brand: String, model: String) {
        self.init(brand: brand, model: model, year: 2024)
    }
    
    func description() -> String {
        return "\(year) \(brand) \(model)"
    }
    
    // デイニシャライザ
    deinit {
        print("\(description()) が解放されました")
    }
}

// 継承
class Car: Vehicle {
    var numberOfDoors: Int
    
    init(brand: String, model: String, year: Int, numberOfDoors: Int) {
        self.numberOfDoors = numberOfDoors
        super.init(brand: brand, model: model, year: year)
    }
    
    override func description() -> String {
        return "\(super.description()) - \(numberOfDoors)ドア"
    }
    
    func startEngine() {
        print("\(brand) \(model) のエンジンを始動しました")
    }
}

// プロトコルの定義
protocol Drawable {
    func draw()
    var area: Double { get }
}

protocol Colorable {
    var color: String { get set }
}

// プロトコルの実装
struct Circle: Drawable, Colorable {
    var radius: Double
    var color: String
    
    var area: Double {
        return Double.pi * radius * radius
    }
    
    func draw() {
        print("半径\(radius)\(color)い円を描画します")
    }
}

// 列挙型
enum Direction {
    case north, south, east, west
    
    func description() -> String {
        switch self {
        case .north: return "北"
        case .south: return "南"
        case .east: return "東"
        case .west: return "西"
        }
    }
}

// 関連値を持つ列挙型
enum Result<T> {
    case success(T)
    case failure(Error)
    
    func getValue() -> T? {
        switch self {
        case .success(let value):
            return value
        case .failure(_):
            return nil
        }
    }
}

// 使用例
print("=== 構造体とクラスの使用例 ===")

// 構造体の使用
var person1 = Person(name: "田中太郎", age: 25)
print(person1.introduce())
print(person1.description)

person1.email = "[email protected]"
person1.haveBirthday()

let unknownPerson = Person.createUnknownPerson()
print(unknownPerson.introduce())

// クラスの使用
let vehicle = Vehicle(brand: "Toyota", model: "Prius")
print(vehicle.description())

let car = Car(brand: "Honda", model: "Civic", year: 2023, numberOfDoors: 4)
print(car.description())
car.startEngine()

// プロトコルの使用
let circle = Circle(radius: 5.0, color: "赤")
circle.draw()
print("面積: \(circle.area)")

// 列挙型の使用
let direction = Direction.north
print("方向: \(direction.description())")

let successResult: Result<String> = .success("データを正常に取得しました")
let failureResult: Result<String> = .failure(NSError(domain: "NetworkError", code: 404, userInfo: nil))

if let value = successResult.getValue() {
    print("成功: \(value)")
}

// 値型と参照型の違い
var person2 = person1  // 構造体は値型なのでコピーされる
person2.name = "山田花子"

print("person1: \(person1.name)")  // "田中太郎"
print("person2: \(person2.name)")  // "山田花子"

let car1 = car  // クラスは参照型なので同じインスタンスを参照
car1.brand = "Nissan"

print("car.brand: \(car.brand)")   // "Nissan"
print("car1.brand: \(car1.brand)") // "Nissan"

エラーハンドリング

// エラーの定義
enum ValidationError: Error {
    case emptyName
    case invalidAge(Int)
    case invalidEmail(String)
    case networkError(code: Int, message: String)
}

// エラーの詳細情報を提供
extension ValidationError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .emptyName:
            return "名前が空です"
        case .invalidAge(let age):
            return "無効な年齢です: \(age)"
        case .invalidEmail(let email):
            return "無効なメールアドレスです: \(email)"
        case .networkError(let code, let message):
            return "ネットワークエラー (\(code)): \(message)"
        }
    }
}

// throwsキーワードを使った関数
func validateUser(name: String, age: Int, email: String) throws -> [String: Any] {
    // 名前の検証
    guard !name.isEmpty else {
        throw ValidationError.emptyName
    }
    
    // 年齢の検証
    guard age >= 0 && age <= 150 else {
        throw ValidationError.invalidAge(age)
    }
    
    // メールアドレスの検証
    let emailRegex = #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"#
    guard email.range(of: emailRegex, options: .regularExpression) != nil else {
        throw ValidationError.invalidEmail(email)
    }
    
    return [
        "name": name,
        "age": age,
        "email": email,
        "id": UUID().uuidString
    ]
}

// ネットワーク処理をシミュレートする関数
func fetchDataFromServer() throws -> String {
    let shouldSucceed = Bool.random()
    
    if shouldSucceed {
        return "サーバーからデータを取得しました"
    } else {
        throw ValidationError.networkError(code: 500, message: "Internal Server Error")
    }
}

// リトライ機能付きの関数
func fetchDataWithRetry(maxAttempts: Int = 3) throws -> String {
    var attempts = 0
    
    while attempts < maxAttempts {
        attempts += 1
        
        do {
            let data = try fetchDataFromServer()
            print("試行 \(attempts) で成功")
            return data
        } catch {
            print("試行 \(attempts) 失敗: \(error)")
            
            if attempts == maxAttempts {
                throw error
            }
            
            // 少し待ってからリトライ
            Thread.sleep(forTimeInterval: 0.1)
        }
    }
    
    // この行には到達しないが、コンパイラエラーを避けるため
    throw ValidationError.networkError(code: 999, message: "Unexpected error")
}

// do-catch文による例外処理
print("=== エラーハンドリング ===")

// 正常なケース
do {
    let user = try validateUser(name: "田中太郎", age: 30, email: "[email protected]")
    print("ユーザー作成成功: \(user)")
} catch ValidationError.emptyName {
    print("エラー: 名前が空です")
} catch ValidationError.invalidAge(let age) {
    print("エラー: 無効な年齢 - \(age)")
} catch ValidationError.invalidEmail(let email) {
    print("エラー: 無効なメール - \(email)")
} catch {
    print("予期しないエラー: \(error)")
}

// 各種エラーケースのテスト
let testCases = [
    ("", 25, "[email protected]"),           // 空の名前
    ("山田花子", -5, "[email protected]"),     // 無効な年齢
    ("佐藤次郎", 30, "invalid-email"),          // 無効なメール
    ("高橋一郎", 25, "[email protected]")   // 正常なケース
]

for (name, age, email) in testCases {
    do {
        let user = try validateUser(name: name, age: age, email: email)
        print("✅ ユーザー作成成功: \(user["name"] ?? "Unknown")")
    } catch let error as ValidationError {
        print("❌ 検証エラー: \(error.localizedDescription)")
    } catch {
        print("❌ その他のエラー: \(error)")
    }
}

// try?とtry!の使用
print("\n=== try? と try! ===")

// try? - 失敗した場合にnilを返す
let optionalUser1 = try? validateUser(name: "テストユーザー1", age: 25, email: "[email protected]")
if let user = optionalUser1 {
    print("✅ オプショナルユーザー作成成功: \(user)")
} else {
    print("❌ ユーザー作成に失敗しました")
}

let optionalUser2 = try? validateUser(name: "", age: 25, email: "[email protected]")
print("空の名前での結果: \(optionalUser2 == nil ? "nil" : "値あり")")

// try! - 失敗した場合にクラッシュ(使用時は注意が必要)
// let forcedUser = try! validateUser(name: "強制ユーザー", age: 25, email: "[email protected]")
// print("強制的な結果: \(forcedUser)")

// defer文 - スコープ終了時に必ず実行される
func processFile() {
    print("ファイル処理を開始")
    
    defer {
        print("ファイル処理のクリーンアップ")
    }
    
    defer {
        print("ファイルをクローズ")
    }
    
    do {
        let data = try fetchDataFromServer()
        print("データ処理: \(data)")
    } catch {
        print("ファイル処理中にエラー: \(error)")
    }
    
    print("ファイル処理のメイン処理完了")
}

processFile()

// Result型を使ったエラーハンドリング
func validateUserAsResult(name: String, age: Int, email: String) -> Result<[String: Any], ValidationError> {
    do {
        let user = try validateUser(name: name, age: age, email: email)
        return .success(user)
    } catch let error as ValidationError {
        return .failure(error)
    } catch {
        return .failure(.networkError(code: 999, message: "Unknown error"))
    }
}

let result = validateUserAsResult(name: "結果ユーザー", age: 28, email: "[email protected]")

switch result {
case .success(let user):
    print("✅ Result成功: \(user["name"] ?? "Unknown")")
case .failure(let error):
    print("❌ Result失敗: \(error.localizedDescription)")
}

// リトライ処理の例
print("\n=== リトライ処理 ===")
do {
    let data = try fetchDataWithRetry(maxAttempts: 3)
    print("✅ リトライ成功: \(data)")
} catch {
    print("❌ リトライ最終失敗: \(error)")
}

SwiftUIの基本

// SwiftUIは宣言的UIフレームワーク
// 以下は基本的なSwiftUIの例

import SwiftUI

// 基本的なView
struct ContentView: View {
    @State private var name: String = ""
    @State private var age: String = ""
    @State private var showAlert = false
    @State private var message = ""
    
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                // ヘッダー
                Text("ユーザー登録")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                
                // 入力フィールド
                VStack(spacing: 15) {
                    TextField("名前を入力", text: $name)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    TextField("年齢を入力", text: $age)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                }
                .padding(.horizontal)
                
                // ボタン
                Button(action: registerUser) {
                    Text("登録")
                        .foregroundColor(.white)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.blue)
                        .cornerRadius(10)
                }
                .padding(.horizontal)
                .disabled(name.isEmpty || age.isEmpty)
                
                Spacer()
            }
            .navigationTitle("アプリ")
            .alert("結果", isPresented: $showAlert) {
                Button("OK") { }
            } message: {
                Text(message)
            }
        }
    }
    
    private func registerUser() {
        guard let ageInt = Int(age) else {
            message = "有効な年齢を入力してください"
            showAlert = true
            return
        }
        
        // バリデーション関数を使用(上記で定義したもの)
        do {
            let user = try validateUser(name: name, age: ageInt, email: "\(name.lowercased())@example.com")
            message = "ユーザー '\(user["name"] ?? "Unknown")' を登録しました"
            showAlert = true
            
            // フィールドをクリア
            name = ""
            age = ""
        } catch let error as ValidationError {
            message = "登録エラー: \(error.localizedDescription)"
            showAlert = true
        } catch {
            message = "予期しないエラーが発生しました"
            showAlert = true
        }
    }
}

// リスト表示のView
struct UserListView: View {
    @State private var users: [[String: Any]] = []
    
    var body: some View {
        NavigationView {
            List {
                ForEach(users.indices, id: \.self) { index in
                    UserRowView(user: users[index])
                }
                .onDelete(perform: deleteUsers)
            }
            .navigationTitle("ユーザー一覧")
            .onAppear(perform: loadUsers)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("追加") {
                        addSampleUser()
                    }
                }
            }
        }
    }
    
    private func loadUsers() {
        // サンプルユーザーの読み込み
        users = [
            ["name": "田中太郎", "age": 30, "email": "[email protected]"],
            ["name": "山田花子", "age": 25, "email": "[email protected]"],
            ["name": "佐藤次郎", "age": 35, "email": "[email protected]"]
        ]
    }
    
    private func addSampleUser() {
        let newUser = [
            "name": "新規ユーザー\(users.count + 1)",
            "age": Int.random(in: 20...60),
            "email": "user\(users.count + 1)@example.com"
        ] as [String : Any]
        
        users.append(newUser)
    }
    
    private func deleteUsers(offsets: IndexSet) {
        users.remove(atOffsets: offsets)
    }
}

// ユーザー行のView
struct UserRowView: View {
    let user: [String: Any]
    
    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                Text(user["name"] as? String ?? "Unknown")
                    .font(.headline)
                
                Text("\(user["age"] as? Int ?? 0)歳")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                
                Text(user["email"] as? String ?? "")
                    .font(.caption)
                    .foregroundColor(.blue)
            }
            
            Spacer()
            
            Image(systemName: "person.fill")
                .foregroundColor(.gray)
        }
        .padding(.vertical, 4)
    }
}

// アプリのエントリーポイント
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                ContentView()
                    .tabItem {
                        Image(systemName: "person.badge.plus")
                        Text("登録")
                    }
                
                UserListView()
                    .tabItem {
                        Image(systemName: "list.bullet")
                        Text("一覧")
                    }
            }
        }
    }
}

// Preview(Xcodeでのプレビュー用)
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

// カスタム修飾子(Modifier)の例
struct PrimaryButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .foregroundColor(.white)
            .padding()
            .background(Color.blue)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

extension View {
    func primaryButtonStyle() -> some View {
        modifier(PrimaryButtonStyle())
    }
}

// 使用例
// Button("テストボタン") { }
//     .primaryButtonStyle()

print("SwiftUIのコード例を定義しました。実際のXcodeプロジェクトで動作します。")

バージョン

バージョン リリース日 主な新機能
Swift 5.9 2023-09 Macros, if-else expressions, Parameter packs
Swift 5.8 2023-03 Function back deployment, Collection downcasts
Swift 5.7 2022-09 Regex literals, Generic protocols
Swift 5.6 2022-03 Type placeholders, ConcurrentValue protocol
Swift 5.5 2021-09 Async/await, Actors, Structured concurrency
Swift 5.4 2021-04 Multiple variadic parameters, Result builders
Swift 5.3 2020-09 Multi-pattern catch clauses, @main attribute

参考ページ

公式ドキュメント

学習リソース

開発ツール・フレームワーク