Swiftで学ぶクラスの使い方!基本から応用までの10選

Swiftのクラスの基本と応用を学ぶイラストSwift
この記事は約18分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swiftを学び始めたばかりの方や、今まで関数だけでプログラミングをしてきた方に、クラスの魅力とその活用方法を紹介したいと思います。

クラスを使うことで、より高度なプログラミングやデータの管理が可能になり、アプリケーションの開発もスムーズになります。

この記事を読めば、Swiftのクラスを効果的に利用する技術を習得できます。

●Swiftのクラスとは

Swiftのクラスは、データや処理をまとめて管理するための設計図のようなものです。

クラスを使用することで、複雑なプログラムをシンプルに、かつ効率的に実装することができます。

○クラスの基本的な概念

クラスは、変数や関数を一つのまとまりとして扱うためのものです。

具体的には、ある特定の動作や性質を持つオブジェクトを生成するための「設計図」として利用されます。

例えば、動物の「犬」という種類を表現する場合、犬の特性や行動(例: 吠える、尾を振る)をクラス内に定義することができます。

○クラスの利点とは

  1. 再利用性:一度定義したクラスは、何度でもインスタンスとして生成して利用することができます。これにより、同じ動作やデータ構造を持つオブジェクトを簡単に再利用することができます。
  2. 保守性:クラスを利用することで、関連するデータや処理をまとめることができます。この結果、コードの変更や修正が容易になり、プログラムの保守性が向上します。
  3. 拡張性:既存のクラスを基に新しいクラスを作成することが可能です。この「継承」という機能を利用することで、すでに存在するクラスを基に新しい機能や特性を持つクラスを効率的に開発することができます。

これらの利点を活かすことで、プログラムの品質を向上させることが可能です。

特に、大規模なアプリケーション開発においては、クラスの利用はほぼ必須となります。

●Swiftのクラスの使い方

Swiftでのプログラミングにおいて、クラスは非常に中心的な役割を果たします。

しかし、その使い方を一から学ぶのはなかなか大変なもの。

そこで、ここではSwiftのクラスの基本的な使い方をサンプルコードとともにご紹介します。

○サンプルコード1:基本的なクラスの定義とインスタンス生成

まずは、最も基本的なクラスの定義と、そのクラスを元にしたオブジェクト(インスタンス)の生成方法を学びましょう。

// Dogというクラスを定義
class Dog {
    var name: String = "未設定"
    func bark() {
        print("\(name)がワンワンと吠える")
    }
}

// インスタンスの生成
let myDog = Dog()
myDog.name = "ポチ"
myDog.bark() // 出力:ポチがワンワンと吠える

このコードではDogというクラスを定義しています。

その中にnameというプロパティと、barkというメソッドがあります。

次に、Dogクラスのインスタンスを生成し、そのインスタンスのnameプロパティを”ポチ”に設定しました。

最後にbarkメソッドを呼び出すことで、犬が吠える様子を出力しています。

○サンプルコード2:プロパティとメソッドの活用

Swiftのクラスでは、変数のようなものを「プロパティ」として、関数のようなものを「メソッド」として持つことができます。

こちらでは、これらのプロパティとメソッドの基本的な使い方を解説します。

class Car {
    var color: String = "白"
    var speed: Int = 0

    func accelerate() {
        speed += 10
    }

    func brake() {
        speed -= 10
        if speed < 0 {
            speed = 0
        }
    }
}

let sportsCar = Car()
sportsCar.accelerate()
print(sportsCar.speed) // 出力:10

sportsCar.brake()
print(sportsCar.speed) // 出力:0

この例では、Carクラスにcolorspeedという2つのプロパティ、そしてacceleratebrakeという2つのメソッドを定義しています。

○サンプルコード3:イニシャライザのカスタマイズ

Swiftのクラスでは、インスタンスの生成時に初期値を設定するための特別なメソッド、イニシャライザが利用されます。

イニシャライザは、デフォルトで提供されるものだけでなく、自分でカスタマイズすることも可能です。

初心者の方にも理解しやすくするために、簡単な例を用いて説明します。

class Human {
    var name: String
    var age: Int

    // カスタマイズされたイニシャライザ
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let person = Human(name: "太郎", age: 25)
print("名前は\(person.name)で、年齢は\(person.age)歳です。")

このコードではHumanというクラスを定義し、その中にnameageというプロパティを持つイニシャライザをカスタマイズしています。

selfは、インスタンス自身を指すキーワードで、これによってプロパティとイニシャライザの引数とを区別しています。

○サンプルコード4:クラスの継承とオーバーライド

Swiftのクラスの魅力の一つは、既存のクラスをベースにして新しいクラスを作成することができる「継承」という機能です。

また、継承されたクラスのメソッドやプロパティを変更することを「オーバーライド」と言います。

具体的な例を用いて、継承とオーバーライドの使い方を見てみましょう。

class Animal {
    func sound() {
        print("この動物の鳴き声は特定されていません。")
    }
}

class Cat: Animal {
    // soundメソッドのオーバーライド
    override func sound() {
        print("ニャー")
    }
}

let genericAnimal = Animal()
genericAnimal.sound() // この動物の鳴き声は特定されていません。

let miko = Cat()
miko.sound() // ニャー

上記の例では、まずAnimalという基底クラスを定義しています。

その後、このAnimalクラスを継承したCatクラスを作成し、soundメソッドをオーバーライドしています。

このように、継承を用いると、新しいクラスを効率よく作成することができ、オーバーライドを利用すれば親クラスの機能をカスタマイズすることも可能です。

●Swiftのクラスの応用例

Swiftでのクラスの使い方を学んだ後、次に考えるべきはその応用例です。

プログラミング初心者にも分かりやすく、そして実践的な知識として役立つように、Swiftのクラスの応用的な使い方について詳しく解説していきます。

○サンプルコード5:プロトコルを用いたクラスの拡張

プロトコルはSwiftにおける一つの強力な機能であり、クラスや構造体、列挙型に特定の契約を結ぶ役割を持っています。

これを使用することで、異なるクラス間で共通のメソッドやプロパティを定義することが可能となります。

protocol Driveable {
    func drive()
}

class Car: Driveable {
    func drive() {
        print("車を運転する")
    }
}

let myCar = Car()
myCar.drive() // 車を運転する

このコードでは、Driveableというプロトコルを定義し、driveメソッドを持つことを強制しています。

その後、Carクラスでこのプロトコルを採用(adopt)し、実際のdriveメソッドを定義しています。

このように、プロトコルを使用することで、異なるクラスに共通のインターフェースを提供することができます。

○サンプルコード6:クラスのカプセル化

カプセル化とは、データやメソッドを一つの「カプセル」の中に閉じ込め、外部から直接アクセスできないようにするプログラミングの原則の一つです。

Swiftではprivatefileprivateなどのアクセス修飾子を用いて、カプセル化を実現します。

class BankAccount {
    private var balance: Int = 0

    func deposit(amount: Int) {
        if amount > 0 {
            balance += amount
            print("¥\(amount)を預けました。")
        } else {
            print("預ける金額が不正です。")
        }
    }

    func displayBalance() {
        print("残高は¥\(balance)です。")
    }
}

let account = BankAccount()
account.deposit(amount: 5000) // ¥5000を預けました。
account.displayBalance() // 残高は¥5000です。

上記の例では、BankAccountクラス内のbalanceというプロパティは外部から直接触れることができません。

これにより、不正な操作を防ぐことができます。

外部からの操作は、depositメソッドを通じてのみ行うことが可能です。

これにより、安全かつ予期しないエラーを防ぐことができます。

○サンプルコード7:デリゲートを使ったクラスのコミュニケーション

デリゲートはオブジェクト間のコミュニケーションを助けるデザインパターンの一つです。

Swiftでは、デリゲートはプロトコルと組み合わせて使用されることが多く、あるクラスや構造体が別のクラスや構造体に何らかのタスクを依頼する際に使用されます。

これにより、クラス間の疎結合を保ち、再利用やテストが容易になります。

下記のサンプルコードは、TaskExecutorというクラスがTaskDelegateというプロトコルを用いてタスクの完了を別のクラスに伝えるシナリオを表しています。

// デリゲートの定義
protocol TaskDelegate: AnyObject {
    func didCompleteTask(result: String)
}

class TaskExecutor {
    weak var delegate: TaskDelegate?

    func execute() {
        // 何らかの処理
        print("タスクを実行中...")
        // 処理完了後にデリゲートメソッドを呼び出す
        delegate?.didCompleteTask(result: "タスク完了")
    }
}

class ViewController: TaskDelegate {
    let taskExecutor = TaskExecutor()

    init() {
        taskExecutor.delegate = self
        taskExecutor.execute()
    }

    func didCompleteTask(result: String) {
        print(result)
    }
}

let viewController = ViewController() // タスクを実行中... タスク完了

このコードでは、TaskExecutorというクラスがタスクの実行を担当し、そのタスクが完了した際にTaskDelegateを通じてViewControllerに結果を伝えています。

デリゲートを用いることで、TaskExecutorViewControllerの関連性を低く保ちつつ、必要な情報の伝達が行われています。

○サンプルコード8:クロージャを活用したクラスの設計

クロージャは自己完結型の関数ブロックとして考えることができ、Swiftでは非常に強力なツールとして利用されます。

クラスの設計時に、特定のタスクを非同期に実行した後のコールバックとしてクロージャを利用することが一般的です。

下記のサンプルコードは、非同期のデータ取得タスクを実行し、その完了後にクロージャを用いて結果を返す例です。

class DataFetcher {
    func fetchData(completion: @escaping (String) -> Void) {
        DispatchQueue.global().async {
            // 何らかの非同期の処理
            sleep(2) // 2秒待機する
            let data = "取得したデータ"
            completion(data)
        }
    }
}

let fetcher = DataFetcher()
fetcher.fetchData { data in
    print(data) // 取得したデータ
}

上記のDataFetcherクラスでは、非同期にデータを取得するfetchDataメソッドが定義されており、このメソッドの引数としてクロージャを受け取っています。

データの取得が完了したら、このクロージャが呼び出され、取得したデータがそのクロージャの引数として渡されます。

○サンプルコード9:ジェネリクスを活用したクラスの拡張

ジェネリクスはSwiftの非常に強力な機能の一つで、型安全を保ったままでコードの再利用性を高めることができます。

クラスや関数、構造体など、さまざまな場所でジェネリクスを用いることができ、特定の型に依存せずに柔軟にコードを記述することが可能となります。

ジェネリクスの一般的な使用例としては、配列や辞書などのコレクション型が挙げられます。

しかし、ジェネリクスを活用して独自のクラスを拡張することもでき、その可能性は非常に広がります。

下記のサンプルコードは、ジェネリクスを使用してデータを保持するBoxクラスを定義しています。

class Box<T> {
    var value: T

    init(_ value: T) {
        self.value = value
    }

    func describe() {
        print("保持しているデータは \(value) です。")
    }
}

let intBox = Box(123)
intBox.describe() // 保持しているデータは 123 です。

let stringBox = Box("Swift")
stringBox.describe() // 保持しているデータは Swift です。

このコードではBoxクラスがジェネリクスを用いて定義されています。

Tはプレースホルダー型として使用され、実際にBoxクラスをインスタンス化する際に具体的な型が指定されるまで、その型は未定義となります。

このため、intBoxInt型、stringBoxString型のデータをそれぞれ保持することができるのです。

○サンプルコード10:クラスと構造体の違いと使い分け

Swiftにおけるクラスと構造体は、多くの面で似ています。

しかし、それぞれには独特の特徴があり、使用するシチュエーションによって選択をする必要があります。

クラスは参照型、構造体は値型として動作します。

この違いがもたらす振る舞いの差は、プログラミングの中で非常に重要です。

下記のサンプルコードでは、クラスと構造体の代入時の振る舞いの違いを表しています。

class MyClass {
    var number: Int = 0
}

struct MyStruct {
    var number: Int = 0
}

let classInstanceA = MyClass()
let classInstanceB = classInstanceA
classInstanceB.number = 100

print(classInstanceA.number) // 100

let structInstanceA = MyStruct()
var structInstanceB = structInstanceA
structInstanceB.number = 100

print(structInstanceA.number) // 0

クラスの場合、classInstanceBclassInstanceAの参照を持っているため、classInstanceBのプロパティを変更するとclassInstanceAのプロパティも変更されます。

しかし、構造体の場合、インスタンスがコピーされるので、structInstanceBのプロパティを変更しても、structInstanceAのプロパティは変わりません。

●クラスを利用する際の注意点と対処法

Swiftでクラスを利用する際、特に初心者の方が陥りやすいいくつかの問題点や注意点が存在します。

これらの問題を理解し、適切な対処法を知っておくことで、安全で効率的なコードを書く手助けとなります。

○メモリリークの予防と対策

メモリリークは、プログラムが確保したメモリ領域を解放せずに放置することで起こる問題です。

Swiftのクラスでは特に、強い参照サイクルが原因となってメモリリークが発生することがあります。

例として、2つのクラスTeacherStudentが相互に強い参照を持つ場合を考えます。

class Teacher {
    var student: Student?
}

class Student {
    var teacher: Teacher?
}

let teacher = Teacher()
let student = Student()

teacher.student = student
student.teacher = teacher

上記のコードでは、teacherstudentを強く参照し、そのstudentが逆にteacherを強く参照しています。

このような状態を強い参照サイクルと呼び、このサイクルが存在すると、インスタンスが不要になってもメモリから解放されず、メモリリークが発生します。

この問題を解決するためには、weakunownedといったキーワードを使用して参照を弱くすることが効果的です。

class Teacher {
    var student: Student?
}

class Student {
    weak var teacher: Teacher?
}

こうすることで、Studentteacherプロパティは弱い参照となり、強い参照サイクルを避けることができます。

○クラスの循環参照を避ける方法

前述のメモリリークの節で触れた強い参照サイクルは、クラスの循環参照とも呼ばれる問題です。

循環参照を避けるための最も一般的な方法は、前述したようにweakunownedキーワードを使用して参照を弱くすることです。

しかし、これらのキーワードをどのような場面で使用するかは、そのプロパティがnilとなる可能性があるかどうか、そしてオプショナル型として定義するか非オプショナル型として定義するかによって異なります。

  • weak:常にオプショナル型として定義され、nilとなる可能性がある場合に使用します。
  • unowned:非オプショナル型であり、nilとならないことが保証される場合に使用します。

これらのキーワードを適切に使用することで、クラス間の循環参照を効果的に避け、メモリ管理の問題を最小限に抑えることができます。

●Swiftのクラスのカスタマイズ方法

Swiftでプログラミングを行う際、クラスをカスタマイズすることで、より効率的かつ効果的なコードを書くことができます。

今回は、Swiftのクラスをカスタマイズする主要な2つの方法、すなわち「アクセス制御」および「拡張」について詳しく解説していきます。

○アクセス制御を用いたカスタマイズ

アクセス制御は、プロパティやメソッドがどの範囲からアクセス可能かを制御する機能です。

Swiftにはpublicinternalfileprivateprivateなどのアクセスレベルがあります。

例えば、下記のコードではMyClass内のnameプロパティをprivateとして定義しています。

これにより、このプロパティはMyClassの外からアクセスすることはできません。

class MyClass {
    private var name: String = "Default Name"

    func showName() -> String {
        return "My name is \(name)."
    }
}

このコードを実行すると、MyClassのインスタンスからshowNameメソッドは呼び出せますが、nameプロパティには直接アクセスすることはできません。

○拡張を用いてクラスに機能を追加

Swiftの「拡張(extension)」は、既存のクラスや構造体、列挙型に新しい機能を追加するための機能です。

拡張を使用すると、新しいプロパティやメソッドを追加することができます。

例として、Int型に平方と立方の計算の機能を追加してみます。

extension Int {
    var squared: Int {
        return self * self
    }

    var cubed: Int {
        return self * self * self
    }
}

このコードにより、既存のInt型の値に.squared.cubedを追加して、簡単に2乗や3乗の計算を行うことができます。

let value = 3
print(value.squared)  // 出力結果は "9"
print(value.cubed)    // 出力結果は "27"

まとめ

Swiftのクラスの使い方について、基本的な部分から応用的な部分まで詳しく解説してきました。

クラスはオブジェクト指向プログラミングの中心的存在であり、Swiftを学ぶ上で欠かせない知識です。

この記事を通して、クラスの基本的な定義方法やカスタマイズの方法、そして応用例を学ぶことができたと思います。

特に、Swiftにおけるアクセス制御や拡張といった機能は、コードの可読性や安全性、再利用性を高めるための重要なツールとなります。

これらの機能を理解し、適切に使用することで、効率的かつ安全なアプリケーションの開発が可能となります。

Swiftの学習を進める中で、クラスの使い方やその他の関連知識は常に念頭に置きながら、継続的に実践的なコーディングを行うことをおすすめします。

プログラミングは経験を積むことで上達しますので、今回学んだ知識をベースに、さらに多くのコードを書いてみることで、Swiftのクラスの使い方をより深く理解していきましょう。