142
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

世界一わかりやすくクラスとインスタンスを説明する

はじめに

クラスやインスタンスの説明は、世に多くありますが、たいてい難しい単語並びすぎて、初学者にはとっかかりにくいと感じます。
本記事は、具体例を多く入れて、掴んでもらうことを目的として書きました。

例ではSwiftを用いています。

エンジニアの方々へ

記事を読むと、静的変数は?などツッコミはありますが、大きくはずれない範囲で、わかりやすさを重視したつもりです。が、大きくずれている点があれば教えて下さい
僕の解釈が間違っている場合もあるかもしれません。間違いだけでなくても、よりわかりやすい説明とか、お気づきの点は、是非コメントで教えていただけると嬉しいです
(正直、現状では全然世界一分かりやすくないですが、せっかくQiitaにはコメント機能もあるので、OpenSourceでわかりやすい記事を作りたい、、、)

クラスの基礎

一旦構造体はおいておいて、クラスだけにフォーカスを当てて説明します。

プロパティ、メソッド

Swiftでは、クラスも構造体も、変数と関数のまとまりです。プログラムでは、

class クラス名 {
    // 内容(変数、関数の定義)
}

として定義します

クラス内で定義された変数のことを「プロパティ」、関数のことを「メソッド」と呼びます。

インスタンス

クラスは必要な要素と種類を並べた、テンプレートのようなものです。

例えば、Twitterアプリを想像してみてください。各ツイートは、「投稿者」「内容」「投稿日時」というデータが必要です。(他にもあるが)これをまとめて管理したい、こういう時にクラスが便利です。

今回、ツイートのデータのクラスは、以下のように定義しましょう。

class TweetData {
    var userName: String!
    var content: String!
    var postedDate: NSDate!
}

これら3つの変数(userName, content, postedDate)はひとまとまりのデータであることは想像つくと思います。クラスや構造体を使うと、これらをまとめて一つの変数に代入することができるようになります。

クラスが「テンプレート」のようなもの、というのはつまり、クラス自体は「どんなデータをもつか」を書いただけであって、実際に使うときには、このテンプレートに値を入れたものが重要なのである、ということです。(それぞれに具体的な値を入れてはじめて「値」となる)

userName = "_ha1f"
content = "大雨まじか"
postedDate = NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 14, minute: 51, second: 20, nanosecond: 0)!

「インスタンス」とは、実際に上に書いたような値を入れたデータのことです。

例えば、画面上に10このツイートが表示されているなら、そこには10このインスタンスがあり、各インスタンスにそれぞれ、ユーザー名、内容、投稿日時が保持されています

実際にこれをSwiftで書くときには、

var tweet1 = TweetData()
tweet1.userId = "_ha1f"
tweet1.content = "大雨まじか"
tweet1.postedDate = NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 14, minute: 51, second: 20, nanosecond: 0)!

というふうに書きます。中の変数へは.プロパティ名と書くことでアクセスできて、まるでインスタンス名.プロパティ名という名前の変数があるかのように、値を代入したり、読んだりできます。

このクラス名()という操作(今回ならTweetData())が、「インスタンス生成」という操作であり、テンプレートをコピーして、新たな変数群の箱を作る操作です。
まとめて1つのTweetData型の変数となっているので、普通の変数と同様に、関数に渡したりできます。

複数あれば、

var tweet1: TweetData = TweetData()
tweet1.userId = "_ha1f"
tweet1.content = "大雨まじか"
tweet1.postedDate = NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 14, minute: 51, second: 20, nanosecond: 0)!

var tweet2: TweetData = TweetData()
tweet2.userId = "_ha1f"
tweet2.content = "インスタントコーヒー、粉はあんなにいい匂いするのにコーヒーにしてみると微妙なんだよな"
tweet2.postedDate = NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 10, minute: 14, second: 40, nanosecond: 0)!

という風に、2つのインスタンスを生成することになる。

もし「インスタンス」という概念がなければ、ツイートごとに変数名をつけたりしなければいけないが、クラスを使うことで、簡単に、またわかりやすくなるのがわかると思う。

全部まとめてTweetData型の変数であり、他の変数と同様に、関数の引数・返り値にしたり、配列に挿入したりできる。

以下Arrayの例

var tweets: [TweetData] = []
tweets.append(TweetData())
tweets.append(TweetData())

抽象化

さきほど、ツイートデータで必要なデータは〜みたいな感じで、いわば、実際のデータから必要な要素を考えました。

これを「抽象化」と呼びます。

具体的なのもの(目に見えるツイートデータ)を抽象化(共通点を抜き出す)して、クラスを作るわけですね
ここでいう共通点というのは、どんな要素を持っているか、ということです。

もちろん、抽象化の結果は一つではありません。何かしらの共通の要素とか、プログラムに必要な要素を捉えることを抽象化と呼びます。

イニシャライザ

上の例では、インスタンスを生成したあとで、プロパティに値を設定していますが、これだと長いし、設定するのを忘れるかもしれないですよね?

これを、うまく解決する方法があります。これが「イニシャライザ」です

イニシャライザとは、インスタンス生成するときに呼ばれる、initという名前の特別な関数です

例えば、以下のようにイニシャライザを定義する。

class TweetData {
    var userId: String!
    var content: String!
    var postedDate: NSDate!

    init(userId: String, content: String, postedDate: NSDate) {
        self.userId = userId
        self.content = content
        self.postedDate = postedDate
    }
}

ここで、init(userId: String, content: String, postedDate: NSDate) {}がイニシャライザになります

今まで、あるインスタンスのプロパティにアクセスするときには、インスタンス.プロパティ名と書いていたが、クラス内のメソッドからプロパティにアクセスするには、self.プロパティ名と書く。(selfという変数にインスタンスが保存されているような感じ)

と、以下のようにしてインスタンスを生成できるようになります

var tweet1 = TweetData(userId: "_ha1f", content: "大雨まじか", postedDate: NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 14, minute: 51, second: 20, nanosecond: 0)!)

これは、インスタンス生成してから値をセットしていたものと、ほぼ同じ意味を表しますが、イニシャライザはインスタンス生成時に必ず呼ばれるので、userIdなどを非オプショナル型にしてもエラーにならないです!(オプショナルがわからない場合は読み飛ばしてください、今回の話の肝ではないので)

実際、さっきイニシャライザを何も定義していなかった時は、

init() {
    self.userId = nil
    self.content = nil
    self.postedDate = nil
}

というイニシャライザが自動的に内部で生成されていた(Swiftでいうデフォルトイニシャライザ)ので、TweetData()という記述ができていただけです。

イニシャライザは、引数が違えば複数生成することもできます。

class TweetData {
    var userId: String!
    var content: String!
    var postedDate: NSDate!
    init() {
        self.userId = nil
        self.content = nil
        self.postedDate = nil
    }
    init(userId: String, content: String, postedDate: NSDate) {
        self.userId = userId
        self.content = content
        self.postedDate = postedDate
    }
}

TweetData.init()としてもエラーにはならず、本来func init(){}と書くべきところかもしれないが、イニシャライザの「リテラル」としてこのように書きます。

イニシャライザを複数定義した例としては、CGRectをみるとわかりやすいと思います

CGRect.png

短形を保存するためのクラス(実際は構造体ですが、この文脈では同じだと思って問題ありません)で、実際の内部の定義では.x.y.width.heightですが、それを設定するために、イニシャライザは左上座標と右下座標だったり、左上の座標を保持するCGPointとサイズを保持するCGSizeから作ったりできるようになっています

メソッド

関数は、引数に基づいて処理を行う。引数があるインスタンスのプロパティに依存するとか、プロパティに対して操作をしたいとか、そういう場合は、クラス内にメソッドを作ると楽になります。

例えば以下は、相対時間(〜〜秒前とか、〜〜分前とか)を取得するメソッドを追加した例です

class TweetData {
    var userName: String!
    var content: String!
    var postedDate: NSDate!

    func getRelativeTime() -> String {
        let interval = Int(self.postedDate.timeIntervalSinceNow)
        if interval < 60 {
            return "\(interval)秒前"
        } else {
            return "\(interval / 60)分前"
        }
    }
}

実際には〜〜時〜〜分〜〜秒、みたいなデータだけを保存していても、self.でインスタンスのプロパティにアクセスできるので、これを用いて上のように相対時間を計算するメソッドを追加すれば、どのインスタンスに対しても相対時間で表示したいときは、

print(tweet1.getRelativeTime())
print(tweet2.getRelativeTime())

のようにできます。

プロパティへの操作としては、

class TweetDataRepository {
    var data = [TweetData]()
    function fetch() {
        data = // ここでリクエストとか投げて最終的に代入
    }
}

のような感じです。

メソッドの呼び出し方法も、プロパティと同様に、インスタンス.関数名とかきます。

構造体

struct TweetData {
    var userId: String!
    var content: String!
    var postedDate: NSDate!

    init(userId: String, content: String, postedDate: NSDate) {
        self.userId = userId
        self.content = content
        self.postedDate = postedDate
    }

    func getRelativeTime() -> String {
        let interval = Int(self.postedDate.timeIntervalSinceNow)
        if interval < 60 {
            return "\(interval)秒前"
        } else {
            return "\(interval / 60)分前"
        }
    }
}

とかけば構造体として定義できます(記述の違いはclassstructかだけ)

構造体も変数と関数のまとまりのテンプレートであり、インスタンスを生成して値を使います。

では、何が違うのでしょうか?

クラスと構造体

Swiftでの、クラスと構造体の最も大きな違いは、値渡し(call by value)か参照渡し(call by reference)か、という点です
(変数に代入するときに、値を入れるか、参照を入れるか)

TweetDataがクラスであるとき、

var tweet1 = TweetData()
tweet1.userId = "_ha1f"

var tweet2 = tweet1
tweet2.userId = "aritaku03"

print(tweet1.userId) // -> aritaku03

予想した通りの結果でしょうか?

クラスの場合、インスタンスを新しく作らない限り、箱が共有されます。これを「参照渡し」と呼びます

メリットとしては、メモリの使用量が少ない点が挙げられますが、デメリットとしては、予期しないところで値を書き換えられる危険があります。

構造体の場合は、同じプログラムを実行すると、_ha1fが表示されます。
構造体の場合、自動的にすべての値がコピーされます。見た目はわかりやすいですが、大きなデータをコピーしまくってしまう危険があります。

値渡しと参照渡しの違いとして、他にも以下のように、定数に対する挙動が異なります。

クラスなら、

let tweet = TweetData(userId: "_ha1f", content: "大雨まじか", postedDate: NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 14, minute: 51, second: 20, nanosecond: 0)!)
tweet.userId = "aritaku03"// OK
tweet = TweetData()// error

のように、インスタンス自体を置き換えることはできませんが、プロパティを編集することはできます。
これも、クラスは変数に「参照」を保持しているためです。
参照とは、「どのインスタンスか」ぐらいのイメージです。(C言語やってる人はポインタといえばわかるかもれません)
箱自体を置き換えることはできませんが、参照先の中身は守られていません。

一方、構造体だと、

let tweet = TweetData(userId: "_ha1f", content: "大雨まじか", postedDate: NSCalendar(identifier: NSCalendarIdentifierGregorian)!.dateWithEra(1, year: 2016, month: 6, day: 21, hour: 14, minute: 51, second: 20, nanosecond: 0)!)
tweet.userId = "aritaku03"// error
tweet = TweetData()// error

というようにどちらもエラーになります。

構造体だと中身も含めてひとつなので、値すら置き換えることができません。
これも直感的だと思います。

letの定義自体はどちらも同じです。

Swiftでは、Int、String等は構造体です。UIView等はクラスです。このような挙動を試してみると理解が深まると思います。

継承

新しくクラスを作るときに、全部書き換えるのではなくて、別のクラスの機能を引き継いで、一部追加したり書き換えたりして作ることもできます。これを「継承」と呼びます。

class クラス名: 継承するクラス {
}

例えば、

class Tweet: TweetData {
    var tweetId: Int
}

みたいにすると、

.tweetId
.userId
.content
.postedDate

という4つのプロパティを持ったクラスができる。もちろんメソッドも引き継がれている。

継承元のクラスをスーパークラス、継承して作られた新しいクラスを「(スーパークラスの名前)のサブクラス」という表現をする

override(書き換え)

クラスを継承したときに、メソッドを書き換えることもできる。これをオーバーライドと呼ぶ。

class Tweet: TweetData {
    override func getRelativeTime() -> String {
        return "この関数は俺が乗っ取った!!"
    }
}

このままオーバーライドすると本当に全て書き換えられてしまうので、

class Tweet: TweetData {
    override func getRelativeTime() -> String {
        let tmp = super.getRelativeTime()
        return tmp + "この関数は俺が乗っ取った!!"
    }
}

super.と書いて、オーバーライドする前の関数(スーパークラスの関数)を呼び出すことができる

継承ってどう使うの?

こう聞いてもあまり実感がわかなかったと思います。

でも、iPhoneアプリを作ったことのある人なら、少しわかるのではないでしょうか?

UIViewControllerは、真っ白な画面を表示するのに必要な処理が書かれたクラスです。
iPhoneプログラムを開発するときは、このクラスを一旦継承して、必要な書き換え・書き足しすることで、様々なアプリケーションを作っているのですね

class ViewController: UIViewController {
    override func viewDidLoad() {
        self.view.backgroundColor
    }
}

こんな風に、プログラムの再利用を非常にしやすくなる、というのが、継承のメリットの一つです。

また、

var a: UIViewController = ViewController()

とかもできて、これはアップキャストと呼ばれます。

値自体はViewControllerクラスのインスタンスですが、UIViewController型の変数に代入できています。ViewControllerクラスは、UIViewControllerのサブクラスなので、必要なプロパティを持っているため、実現できます。

逆の操作はダウンキャストといって、Swiftでは、

var a: ViewController = UIViewController() as! ViewController

のように、キャスト演算子asを使うとかけます。
※これは、失敗する可能性がある(本来持っていなければいけないプロパティを持っていない場合など)ので、as!またはas?を使う

オブジェクト指向の基礎概念

難しい概念ですが、少しずつ。

パソコンはオブジェクトです。キーボードもオブジェクトで、画面もオブジェクトです。複数のオブジェクトが、それぞれ自らの役割を果たして(つまり、画面はCPUからの指示通りに画面に表示する、キーボードは押されたボタンに従ってUSBで信号を送る、など)、ほかのオブジェクトと繋がることによって、パソコンというオブジェクトが出来上がっています。さらに言えば、電子部品というオブジェクトを組み合わせていろんな回路というオブジェクトをつくり、それらを組み合わせてどんどん大きなオブジェクトが出来ます。

オブジェクトの中身は、外側から見れば、あまり重要ではありません。キーボードの中身はパンタグラフ式やメンブレン式など色々ありますが、「押したら適切な信号が送られる」ことがキーボードの役割です。
この入出力のことを「インターフェース」と呼びます。インターフェースだけを知っていれば、オブジェクトを使う(ともに働く)事ができます。
このように、中身を隠蔽することを、「カプセル化」と呼びます。

パソコンの仕組みを知らなくても、キーボードを押したら文字が入力されるとか、それが画面に反映されるとか、それを知っていれば使える。

これがクラスとどう関係するのか?
インスタンスがオブジェクトなんです!

まず、Twitterとか、本当はめちゃくちゃ複雑にできてるんですよ。でも、難しいところは隠蔽して、あるクラスを提供してくれているので、それを使う(関数の中身は分からなくても、関数名さえわかれば呼び出せる)と、簡単に使うことができるようになります。

さらに、継承が上手く働きます。

あるオブジェクトと別のオブジェクトとの関連を決める必要があります。新しくクラスを作るとこの関連もなくなりますが、継承すると、維持されたまま(アップキャストすれば使える)です!
UIViewControllerにaddSubViewしたときは、UIViewを継承したクラスのみ追加できますが、これは常にUIViewにダウンキャストしているため、UIImageViewとか、UIPickerViewとかもaddできています

おわりに

挙動的なところから、「クラス」と「インスタンス」というものについて、説明しました。
どれも抽象的なもので、わかりにくいと思いますが、大体つかめましたでしょうか・・・?

今回は、クラスについて「変数をまとめて扱う」ことに焦点を当てましたが、オブジェクト指向での立ち位置的には、そこはあまり本質ではないのかもしれません。ただ、けっこう書いて理解できるところも大きいと思うので、あまりまだまだ学ぶべきことは多いと思いますが、本記事がその理解の足しになると信じています。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
142
Help us understand the problem. What are the problem?