Swift 文法その6 - クラス -

クラスとは、特定のデータの値や振る舞いを変数(プロパティ)と関数(メソッド)によって定義したものです。たとえばユーザ登録が必要なサービスでユーザをクラスとして定義すると、

class User {
    var id: String?
    var name: String?
    var email: String?
    var createdAt: Date?

    func register(name: String?, email: String) {
        self.name = name
        self.email = email
        self.createdAt = Date()
    }
}

のような感じで書けます。いきなり例文を出してしまいましたが、順に説明していきます。

クラスの使い方

クラスは基本的にインスタンス化(実体化)して使用します。インスタンス化とは、簡単に言うとクラスで定義したものを特定の個体(よく「オブジェクト」と呼ばれます)に変換することです。クラスはよく「設計図」とも呼ばれますが、たとえば上記クラスは「ユーザはIDや名前、メールアドレスを持っていて、登録するとユーザが入力した情報がセットされて...」という、どのユーザにも共通したデータや振る舞いを定義したもので、このクラス自体は特定のユーザの情報を示すものではありません。

このクラス(設計図)を利用して、たとえばユーザAの情報をセットするには以下のようにします。

let user = User()
user.register(name: "test", email: "test@example.com")
print(user.name) // "test"
print(user.email) // "test@example.com"

User() の部分がインスタンス化している箇所で、これによってクラスの初期化処理が走り、1つの User インスタンスが返り値として user にセットされます。ではこの「初期化処理」についてどのように実行されるのかを見ていきます。

イニシャライザ

init(): インスタンス化する時に変数に初期値をセットしたい場合に定義するもので、コンストラクタとも呼ばれます。コンストラクタは引数の数や型が違えばいくつでも定義することができます。

init() {
    name = "test"
}

init(name: String) {
    self.name = name
}

たとえば let user = User() は引数に何も指定していないので init() が呼ばれ、name には必ず "test" という値がセットされます。もし let user = User(name: "hoge") とすると、init(name: String)  の方が呼ばれ、引数に渡した値が name にセットされます。

もしすでに定義した init() を呼んだ上で更に別の初期化処理を書きたい場合は convenience init() を使用します。

convenience init(name: String) {
    self.init()
    self.name = name
}

このようにクラスをインスタンス化する時にはイニシャライザが呼ばれ、イニシャライザは複数定義することができるのを覚えておきましょう。

変数(プロパティ)

クラスを定義する時に保持する値を宣言しますが、用途によっていろいろな書き方ができます。すべてこうしないといけない、というわけではないですが、変数の宣言の仕方を見るだけでどういう使われ方をするのかがぱっとわかったりするので、初めてそのクラスを見る人にも把握できるように変数はなるべく厳密に定義できるようにしましょう。

Optional / 非Optional の定義
Optional な型には ? をつけて宣言するだけで問題ないですが、非 Optional な型として宣言するには2つの方法があります。1つ目はすでに上記で紹介したイニシャライザで値をセットする方法、もう1つは変数の宣言と同時に値を代入する方法です。

宣言と同時に値を代入する

class User {
    var name: String = ""
}

イニシャライザで値を代入する

class User {
    var name: String

    init(name: String) {
        self.name = name
    }
}

var と let 
var は何度でも代入可能で、let は初期化時のみセット可能となります。また、letは「定数」なので必ず非 Optional 扱いになります。

get / set
プロパティの取得、設定時の処理を書きます。

class User {
    var name: String {
        get { return "xxx" }
        set {
            print(newValue)
        }
    }
    
    init(name: String) {
       self.name = name
   }
}

let user = User(name: "test")
print(user.name) // "xxx"

set するときに、セットした値は newValue で参照できます。
注意点として、

・get で自身の値は返さないこと。上記の例でいうと get { return name } としないこと。これは get がさらに呼ばれて無限ループします。
・set で自身のプロパティに値をセットしないこと。上記の例でいうと set { self.name = newValue } としないこと。これも set がさらに呼ばれて無限ループします。

get の使い方は、自分で値をセットするのではなく、何かデータを加工する必要がある場合に使用します。以下の totalPrice では 金額 x 消費税で合計金額を返しています。

class Product {
    var price: Double
    var taxRate: Double = 1.1
    var totalPrice: Double {
        get { return price * taxRate }
    }
    
    init(price: Double) {
        self.price = price
    }
}

let product = Product(price: 100)
print(product.totalPrice) // 110

set を宣言しない場合は get を省略して書くこともできます。

var totalPrice: Double { return price * taxRate }
※ 上記のように式が1行の場合は return も省略可能です

set の使い方は、プロパティに値がセットされたときに他のプロパティにも値をセットする場合に使用します。ただし、set は必ず get と一緒に定義する必要があります。上記の例を少し変え、totalPrice をセットした時に price をセットするようにしてみます。

class Product {
    var price: Double = 0
    var taxRate: Double = 1.1
    var totalPrice: Double {
        get { 
            return price * taxRate 
        }
        set { 
            self.price = newValue / taxRate
        }
    }
    init(totalPrice: Double) {
        self.totalPrice = totalPrice
    }
}

let product = Product(totalPrice: 110)
print(product.price) // 100

willSet / didSet
プロパティに値をセットする直前・直後に willSet / didSet が呼ばれます。didSet は自身のプロパティをセットし直すことも可能です。

class User {
    var name: String = "hoge" {
        willSet {
            print("willSet")
            print(newValue) // newValue でセットされようとしている値を参照できます
        }
        didSet {
            print("didSet")
            print(oldValue) // oldValue でセット前の値を参照できます
        }
    }
}let user = User()
user.name = "fuga"
// willSet
// fuga
// didSet
// hoge

Computed Property  Stored Property
プロパティのうち、セットされた値をそのまま返すのではなく、getter で値を加工して返したり、didSet で値をセットする時に値を加工してからセットするプロパティのことを Computed Property(計算型プロパティ)、 それ以外を Stored Property(格納型プロパティ) と言います。上記例文の Product クラスの totalPrice は計算型プロパティです 。

lazy
プロパティが参照された時に初期化処理をしたい場合につけます。

class Product {
    var price: Double = 0
    var taxRate: Double = 1.1
    lazy var totalPrice: Double = price * taxRate 
}

let product = Product()
product.price = 100
print(product.totalPrice) // 110

Product() で初期化した時に totalPrice にまだ値は入っていません。product.totalPrice で参照された時に初めて price * taxRate  が実行されます。

関数(メソッド)

クラスに関する機能を宣言します。以下は文頭で紹介した例文の関数です。

func register(name: String?, email: String) {
    self.name = name
    self.email = email
    self.createdAt = Date()
}

これはユーザ登録に関する処理をメソッドとして定義したものです。register 関数の( )内は「引数」と呼ばれ、以下のように呼び出す側のコードで任意の値を渡すことができます。

user.register(name: "test", email: "test@example.com")

また、関数を呼び出した側に何か値を返すこともできます。ここでは処理が成功したかどうかを Bool 値で返してみます。register() の右隣に -> Bool を追加します。

func register(name: String?, email: String) -> Bool {
    ...
    return true
}

シグネチャ
上記のように関数には「引数がある/ない」「返り値がある/ない」などのいくつかのパターンがあり、その組み合わせをシグネチャと呼びます。シグネチャが異なれば、たとえば同名のメソッドを複数定義することも可能ですが、プログラミング言語によってシグネチャの定義が異なるので、ここで Swift で使用できるシグネチャを確認しておきます。

class Item {
   func method() {}
   func method() -> Bool {
       return true
   }
   func method(a: String) {}
   func method(a: String, b: Int) {}
   func method(a: Int) {}
}

Swift のシグネチャはかなり細かく、上記の method という同名のメソッドを5つ定義しましたが、これはどれもエラーにならずに定義できます。これは、引数の数や型、返り値があるかどうかで違うメソッドとして Swift がきちんと区別してくれるからです。

引数名の省略
関数を呼び出す時に引数を省略したい場合は関数定義時の引数に _(アンダーバー) をつけます。

class User {
    ...
    func register(_ email: String) {
        ...
    }
}

let user = User()
user.register("test@example.com") // 引数名を省略できる

クラス変数・クラスメソッド

今まで紹介した変数や関数はインスタンス化してから利用するのを前提とした定義ですが、変数や関数に class または static をつけると、初期化せずにクラスのままで呼び出すことも可能です。

class Greeting {
    class var greeting: String {
        return "おはよう"
    }
    static var greeting2: String = "こんにちは"
  
    class func morning() {
        print(self.greeting)
    }
  
    static func noon() {
        print(self.greeting2)
    }
}

print(Greeting.greeting) // "おはよう"
Greeting.noon() // "こんにちは"

class と static の違いは以下のとおりです。

              オーバーライド    Struct/Enum   Stored Property
static              ✕                          ◯                      ◯
class              ◯                          ✕                       ✕

Struct や Enum はまた後日紹介します。

継承

他のクラスで宣言した変数や関数は、「継承」すると自クラスでも使用することができます。継承は以下のように行います。

class Parent {
    var name: String = "Hoge"
}

class Child: Parent {
    ...
}
​let ​child = Child()print(child.name) // "Hoge"

継承できるクラスは1つのみです。

オーバーライド
親クラスで定義した変数や関数は、子クラスで上書き(オーバーライド)することができます。上書きする変数や関数に子クラスで override をつけます。

class Parent {
    func method() -> String {
        return "hoge"
    }
}
class Child: Parent {
    override func method() -> String {
        return super.method() + "fuga"
    }
}

let child = Child()
print(child.method()) // "hogefuga"
親クラスで定義された変数や関数を子クラスから呼び出す時は上記のように super をつけます。

アクセス修飾子

変数や関数にアクセスする時に、どのファイルからアクセス可能にするかを定義することができます。Swfit には以下の5つの修飾子があります。

open: プロジェクト内の別のターゲットからもアクセス可能
public: プロジェクト内の別のターゲットからもアクセス可能、ただし継承、オーバーライドは不可
internal: プロジェクト内の同じターゲット内ならアクセス可能(デフォルト値)
fileprivate: 同じファイル内からのみアクセス可能
private: 同じクラス内からのみアクセス可能

変数で使用する場合は var/let の前、メソッドで使用する場合は func の前につけます。

また、変数の場合は getter / setter で違うアクセス修飾子を設定することもできます。以下は setter のみにアクセス修飾子を設定する例です。

private(set) var ...
fileprivate(set) var ...

おさらい

クラスでよく使われる文法について一気にご紹介しました。これでも全部ご紹介しきれてないのですが、抑えるべきところは網羅できたのではないかと思います。ただ、変数や関数をどこまで厳密に定義するべきかは会社や現場によって異なるのが正直なところなので(上司がどこまで厳しくコーディングルールを設定するかによります)、無理に全部覚えようとせず、少しずつ便利なものを取り入れる、という感じで吸収していきましょう。

次は構造体(Struct)についてご紹介します。

この記事が気に入ったらサポートをしてみませんか?