🐕

Swift文法入門

2024/11/24に公開

目次


はじめに

Swiftは、Appleが開発したモダンなプログラミング言語で、iOSやmacOSのアプリ開発に使用されます。
現在私は現場ではKotlinを用いたAndroid開発を主に担当していますが、iOSエンジニア不足なことやもともとiOS開発をやりたかったこともあり、本記事では、Swiftの基本文法を例を交えてまとめました。
これからSwiftを学びたい初心者の方や私と同じようにAndroidだけでなくiOS開発もできるようになりたいと思っているの役に立てればと思います。

変数と定数

Swiftでは値を変更可能な変数と変更不可な定数を使い分けます。

// 変数
var age = 25
age = 30 // 変更可能

// 定数
let name = "太郎"
name = "次郎" // エラー: 定数は変更できません

ベストプラクティス

  • 変数はvar、定数はletで定義します。コードの可読性や安全性を高めるために、基本的には定数を使い必要な場合のみ変数を使用するようにしましょう。

型推論と型の明示

Swiftでは型推論によって型を自動的に判定しますが、必要に応じて型を明示することもできます。

// 型推論
var number = 100 // Int型と推測
var message = "こんにちは" // String型と推測

// 明示的な型指定
var preciseNumber: Float = 12.34

ベストプラクティス

  • 型を明示する場合、コロン:を使用します。型の明示はコードの意図を明確にしたいときや型エラーを防ぎたい時に有効です。

型変換と文字列補間

異なる型を扱う場合は明示的に型変換を行います。
また文字列補完を使えば簡単に変数を文字列に埋め込むことも可能です。

// 型変換
let height = 170
let description = "身長は " + String(height) + " cm です"

// 文字列補間
let weight = 60
print("体重は \(weight) kg です")

ベストプラクティス

  • 型変換にはString()Int()などを使います。文字列補間は、バッククオート付きの\(変数名)で記述します。

条件分岐

条件に応じて異なる処理を行います。

if文

// 書き方
if 条件1 {
   条件1の処理
}
else if 条件2 {
   条件2の処理
}
// 例
let score = 85
if score >= 90 {
    print("優秀")
} else if score >= 70 {
    print("合格")
} else {
    print("不合格")
}

switch文

// 書き方
switch 条件式 {
case1:1の処理
case2:2の処理
default:
    それ以外の処理
}

// 例
let grade = "B"
switch grade {
case "A":
    print("最高評価")
case "B":
    print("良い評価")
default:
    print("評価なし")
}

guard

guardは条件が満たされなかった場合にすぐに処理を中断します。そのためエラーチェックや入力確認を行う際に役立ちます。
guardの後のコードブロック内で、条件を満たさない場合の処理(else)を記述し、その後にreturnbreakを使って処理を終了させます。
guardを使うことで条件を満たす場合の処理をフラットに書けるため、可読性が向上します。

// 書き方
guard 条件 else {
    条件に一致しなかった時の処理
    return
}
条件に一致した時の処理

// 例
var age = 17

func drink() {
    // guardを使用して条件が満たされない場合は早期リターン
    guard age >= 20 else {
        print("酒が飲めない")
        return
    }
    print("酒を飲む")
}

drink()

ベストプラクティス

  • 条件分岐は読みやすく、必要最小限のロジックで記述しましょう。
    具体的には、条件を簡潔に表現し、不要なネストを避けることでコードの可読性が向上します。

オプショナル型

値が存在するかわからない場合にオプショナル型を使います。
オプショナル型とはnilを入れることができるデータ型のことをいい、オプショナル型にすることをラップするといいます。またオプショナル型から既存の型を取り出すことをアンラップするといいます。

// 書き方
let 定数名: Optional<> = 型に対応する値
// シンタックシュガーの書き方
let 定数名:?

// 例
var optionalValue: Int? = 42
print(optionalValue) // Optional(42)

強制的アンラップ

値にnilが入っていても関係なしにアンラップする方法。nil以外が入っている場合にのみ使います。
書き方はオプショナル型の変数に!をつけるだけです。

print(optionalValue!) // 42

オプショナルバインディング

ifguardを使いオプショナル型の値がnilかどうかで処理を分ける方法。

if let value = optionalValue {
    print("値は \(value) です")
} else {
    print("値はありません")
}

ベストプラクティス

  • 強制アンラップ(!)は極力避け、必ず値が存在することが保証できる時のみに使用する。
  • オプショナルバインディング(if letguard let)を使い安全にアンラップする。
  • デフォルト値を設定する場合は??を使用する。

配列と辞書

配列

複数の値を格納できるデータ構造のこと。

var fruits = ["リンゴ", "バナナ", "みかん"]
print(fruits) // ["リンゴ", "バナナ", "みかん"]
print(fruits[1]) // バナナ
// 配列.append(追加する要素)
fruits.append("ブドウ")
print(fruits) // ["リンゴ", "バナナ", "みかん", "ブドウ"]

空配列

// 書き方
var 変数名 = [配列の型]()
var emptyArray = [String]()
print(emptyArray) // []

辞書

キーと値のペアを持つ複数の値を格納できるデータ構造のこと。

辞書から値を取り出すときはオプショナル型になるので注意が必要です。

// 書き方
var 変数名 = [
    キー:,
    キー:,
    キー:]

var user = ["name": "太郎", "age": "25"]
print(user["name"] ?? "名前なし") // 太郎
// 辞書の名前[付け加えたいキー] = 付け加えたい値 
user["gender"] = "man"
print(user!) // ["name": "太郎", "age": "25", "gender": "man"]

空の辞書

// 書き方
var 変数名 = [キーの型: 値の型]()
var emptyDictionary = [String: Int]()
print(emptyDictionary) // [:]

ベストプラクティス

  • 配列: 型推論に任せずに、型を明示することでコードの可読性を向上。
  • 辞書: 値を取得する際は??でデフォルト値を設定し、nilを考慮した処理をする。

繰り返し処理

配列や範囲を使った繰り返し処理。

// 書き方
for 変数 in シーケンス {
    繰り返したい処理
}
// シーキンスとは複数の値で段階的に処理できるものをいいます。配列や数値範囲などがこれにあたります。
let animals = ["犬", "猫", "鳥"]
for animal in animals {
    print(animal) // 犬・猫・鳥がそれぞれ出力
}

範囲演算子

範囲演算子は閉区間と半開区間に分けることが可能です。

// 閉区間
for i in 1...10 {
    print(i) // 1から10まで出力
}
// 半開区間
for i in 1..<10 {
    print(i) // 1から9まで出力
}

ベストプラクティス

  • 範囲演算子を使う際は必要に応じてstrideでステップを指定します。
  • 配列でのforループではenumeratedを使いインデックスと値を取得します。
// ステップ指定
for i in stride(from: 1, through: 10, by: 2) {
    print(i) // 1, 3, 5, 7, 9
}

// インデックスと値を取得
let animals = ["犬", "猫", "鳥"]
for (index, animal) in animals.enumerated() {
    print("\(index): \(animal)")
}

関数

// 基本形
func 関数名(引数: 引数の型) -> 戻り値の型 {
    return 戻り値
}

// 引数と戻り値がある関数
func greet(name: String) -> String {
    return "こんにちは、\(name)さん!"
}
print(greet(name: "太郎")) // こんにちは、太郎さん!

// 引数なし・戻り値なしの関数
func sayHello() {
    print("Hello, World!")
}
sayHello() // Hello, World!

クロージャ

簡略化した名前のない関数のこと。1度しか使わないなどわざわざ定義するまでもない場合などに使います。

// 基本形
{ (引数: 引数の型) -> 戻り値の型 in 戻り値 }
// 例
let Hello = { (name: String) -> String in
	return "こんにちは、\(name)さん"
}
print(Hello("太郎")) // こんにちは、太郎さん

// 引数を省略した例
let Hello = { () -> String in
	return "こんにちは、太郎さん"
}
print(Hello) // こんにちは、太郎さん

// 戻り値も省略した例
let Hello = { print("こんにちは、太郎さん") }
Hello() // こんにちは、太郎さん

ベストプラクティス

  • 関数名や引数名は呼び出す時に読めるように自然な文章を心掛け設計します。
  • クロージャや引数に持つ関数は、@escapingを適切に活用します。
// 良い命名例
func greet(user name: String) -> String {
    return "こんにちは、\(name)さん!"
}
print(greet(user: "太郎"))

// クロージャを引数に持つ例
func performTask(completion: @escaping () -> Void) {
    DispatchQueue.global().async {
        print("非同期処理中...")
        DispatchQueue.main.async {
            completion()
        }
    }
}
performTask {
    print("完了しました")
}

クラスと構造体

オブジェクト指向プログラミングをサポートするための仕組みです。

クラス

クラスはインスタンスを作ることで使用することが可能になります。

インスタンスとはクラス(設計図)を元にして作った実体のことです。

動物クラスから犬や猫のインスタンスを使って使えるようにする。と考えていただけるとイメージがつきやすいかと思います。

クラスには変数・定数・関数を書き込むことが可能であり、変数や関数のことをプロパティ、関数のことをメソッドと言います。

class Animal {
    var age = 2
    let kind = "犬"
    func bark() {
        print("\(age)歳の\(kind)が吠えています!")
    }
}

var dog = Animal()
print(dog.age) // 2
print(dog.king) // 犬
dog.bark() // 2歳の犬が吠えています!

構造体

値をまとめておくためのものでクラスとほぼ同じです。

struct Animal {
    var age = 5
    var kind = "鳥"
    func bark() {
        print("\(age)歳の\(kind)が吠えています!")
    }
}

var bird = Animal()
bird.bark() // 5歳の鳥が吠えています!

// 配列にする場合の基本形
var 配列名: [構造体] = [
    構造体(),
    構造体()
]
var animals: [Animal] = [
    Animal(age: 10, kind: "パンダ"),
	Animal(age: 5, kind: "カンガルー"),
	Animal(age: 7, kind: "ヘビ"),
    Animal(age: 8, kind: "ライオン")
]
animals[1].bark() // 5歳のカンガルーが吠えています!

イニシャライザ

インスタンスの値に応じて値を変えるにはイニシャライザを使用します。
イニシャライザとはインスタンスを使う時にプロパティに初期値を与えるもののことをいいます。

クラスも構造体も書き方はほぼ同じです。

// クラスの基本形
init(プロパティ1:, プロパティ2:) {
    self.プロパティ1 = プロパティ1
    self.プロパティ2 = プロパティ2
}
// 例
class Animal {
    var age: Int
    var kind: String
    func bark() {
        print("\(age)歳の\(kind)が吠えています!")
    }
    
    init(age: Int, kind: String) {
        self.age = age
        self.kind = kind
    }
}

var cat = Animal(age: 3, kind: "猫")
cat.bark() // 3歳の猫が吠えています!
// 構造体の基本形
init(プロパティ1:, プロパティ2:) {
    self.プロパティ1 = プロパティ1
    self.プロパティ2 = プロパティ2
}
// 例
struct Animal {
    var age = 3
    var kind = "猫"
    func bark() {
        print("\(age)歳の\(kind)が吠えています!")
    }

    init(age: Int, kind: String) {
        self.age = age
        self.kind = kind
    }
}

var cat = Animal(age: 3, kind: "猫")
cat.bark() // 3歳の猫が吠えています!

ベストプラクティス

  • クラスはオブジェクト志向の継承が必要な場面で使用します。
  • 構造体は軽量なデータ構造に適しています。
  • 値型(構造体)の不変性を保つため、letprivate修飾子を活用します。

プロトコル

共通の振る舞いを定義する仕組みです。

具体的にはクラスや構造体の実装を保証するものであり、定義したプロパティやメソッドは必ず実装しなければいけません。

プロトコル実装時はプロパティやメソッドは中身は書かずに、構造体の中で実装するのがポイントです。

// 基本形
protocol プロトコル名 {
    var プロパティ名:{ get }
    func メソッド名()
}

{ get } とすることでプロパティを定数にすることができます。
{ get set } の場合は変数です。

// 例
protocol Animal {
    var name: String { get }
    func sound()
}

struct Bird: Animal {
    var name: String
    func sound() {
        print("\(name) がさえずっています!")
    }
}

let sparrow = Bird(name: "スズメ")
sparrow.sound() // ススメがさえずっています!

ベストプラクティス

  • プロトコルを活用して共通の振る舞いを明確に定義します。
  • プロトコルのデフォルト実装をextensionで提供し、必要な場面でオーバーライド可能にします。
protocol Greetable {
    var name: String { get }
    func greet()
}

extension Greetable {
    func greet() {
        print("こんにちは、\(name)さん!")
    }
}

struct User: Greetable {
    var name: String
}

let user = User(name: "太郎")
user.greet() // こんにちは、太郎さん!

エラー処理(do-catch)

エラー処理をする時の構文でエラーが出るかもしれない関数などを呼び出す時に使います。
エラーをキャッチして処理を分岐することができるので安全なエラーハンドリングが可能です。

// 基本形
do {
    try エラーが出る可能性がある関数
} catch {
    エラーが出た時の処理
}

// 例
var canConnectServer = false
// エラーを投げる可能性がある関数にはthrowsをつける。
func connectServer() throws {
    if canConnectServer {
        print("サーバーと接続できました")
    }
    else {
        throw NSError()
    }
}

func getData() {
    do {
        try connectServer()
        print("データを取得")
    } catch {
        print("エラー")
    }
}
getData() // エラー

ベストプラクティス

  • カスタムエラー型を定義し、明確なエラーハンドリングを行う。
  • 複数のエラーを区別して処理する際はcatchを使い分ける。

継承

クラスのプロパティやメソッドを引き継いで新しいクラスを作ることです。

敬称を使うことで共通の機能を基本クラスにまとめ、サブクラス(派生クラス)で特有の機能を追加することできます。

// 基本形
class クラス名: 継承元クラス {

}

// 例
// 基本クラス: Animal
class Animal {
    var name: String = "動物"
    
    func eat() {
        print("\(name)は食べています。")
    }
}

// 派生クラス: Dog
class Dog: Animal {
    func bark() {
        print("\(name)はワンワンと吠えています。")
    }
}

// 派生クラス: Bird
class Bird: Animal {
    func fly() {
        print("\(name)は空を飛んでいます。")
    }
}

// インスタンスを作成して使う
var dog = Dog()
dog.name = "犬"
dog.eat()    // 犬は食べています。
dog.bark()   // 犬はワンワンと吠えています。

var bird = Bird()
bird.name = "鳥"
bird.eat()   // 鳥は食べています。
bird.fly()   // 鳥は空を飛んでいます。

ベストプラクティス

  • 継承を使う場面では、適切にfinalを使用してクラスのオーバーライドを制御する。
  • 必要以上に継承を濫用せず、構成を優先する。
class Animal {
    var name: String
    init(name: String) {
        self.name = name
    }
    func eat() {
        print("\(name)が食べています")
    }
}

final class Dog: Animal {
    func bark() {
        print("\(name)がワンワンと吠えています")
    }
}

型キャスト(as)

インスタンスの型を他の型として扱うものです。

  • アップキャスト: 継承元のクラスとして扱う(常に成功する)
  • ダウンキャスト: 継承先のクラスとして扱う(失敗の可能性あり)

アップキャストはasを使い、ダウンキャストはas!またはas?を使用します。

var data: Any = "Hello, world!"
// as! を使った強制ダウンキャスト(失敗するとクラッシュ)
print(data as! String) // Hello, world!
print(data as! Int) // 実行時エラー

// as? を使った安全なダウンキャスト(成功時はオプショナル型で返る)
print(data as? String) // Optional("Hello, world!")
print(data as? Int) // nil

// 別の例で値を変更
data = 42
print(data as? Int) // Optional(42)
print(data as? String) // nil

ベストプラクティス

  • as?を使い安全に型キャストする。
  • 強制キャスト(as!)は失敗時のリスクが高いため極力避ける。
let data: Any = "Swift"
if let text = data as? String {
    print("文字列: \(text)")
} else {
    print("文字列ではありません")
}

enum

関連する値をグループ化して扱いやすくするための構文でありコードの可読性と安全性が向上します。

// 例
enum CompassPoint {
    case north
    case south
    case east
    case west
}

var directionToHead = CompassPoint.west

// switch文で列挙型のケースを使う
switch directionToHead {
case .north:
    print("北へ進む")
case .south:
    print("南へ進む")
case .east:
    print("東へ進む")
case .west:
    print("西へ進む") // 西へ進む
}

ベストプラクティス

  • rawValueや関連値を活用して柔軟性を向上させる。
  • 必要に応じて列挙型にメソッドを定義して動作をカプセル化させる。
enum CompassPoint: String {
    case north = "北"
    case south = "南"
    case east = "東"
    case west = "西"
    
    func description() -> String {
        return "方向は \(self.rawValue) です"
    }
}

let direction = CompassPoint.north
print(direction.description()) // 方向は 北 です

最後に

現場ではたまにUIkitを用いたiOSは開発を行うことはあるのですが、そこまで重ためのタスクではなかったため改めてアウトプットの意味も込めSwiftの文法をまとめてみましたが、個人的な所感ではKotlinよりも体感的で理解しやすい言語だと思いました!Android開発からスタートしたのミスかもしれません(笑)

初めての投稿のため至らぬ点があったとは思いますが、内容に誤りなどありましたらコメントいただけたらと思います!

参考文献

https://www.swiftlangjp.com/

GitHubで編集を提案

Discussion