8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クラスむずすぎ

Posted at

はじめに

 こんにちは、初心者です。
 SwiftとiOS(UIKit)を勉強してると、「クラス分かんないと詰むな〜〜(詰んだ)」って思うことが多々あったので、備忘録としてまとめを書きました
 間違いがないように頑張ります。初心者なので優しくしてください
 基本的にこの本と教わったことのまとめです↓

 親切すぎるiPhoneアプリ開発の本

プログラミングとメモリ

 クラスについて話す前に、この感覚を身につけておかないとオブジェクト指向について分からなくなってしまうので、抑えておきましょう
 

variable
var greeting = "Hello, Swift"

 

 ただの変数です。特に難しいことは何もありません
 この変数を書く時、人間目線で見ると「変数を定義した」と言えます
 ですが、コンピュータ目線で見ると、この変数を定義しているテキストはメモリのどこかにgreetingという名前の区画を確保し、その確保した区域に”Hello, Swift”という文字列を記録させるという命令になります
 

名称未設定 1 (44) (1).png
 
 ここで何を抑えておかなければいけないかというと、プログラミングとは、コンピューターのメモリに値や処理を記録させる行為であるということです

オブジェクト指向を使わないプログラミングの限界

 プログラミングとメモリについて抑えたので、次はどうしてクラスを使う必要性があるのかという話をしていきます

 例として、三匹の猫ちゃんの特徴をまとめたものをコードにしてきましょう
 

cat
//猫ちゃんの特徴
var catName1 = "ミケ"
var mikeAge = 3
var mikeBreed = "スコティッシュフォールド"
var mikeCoatColor = "白"

var catName2 = "タマ"
var tamaAge = 2
var tamaBreed = "マンチカン"
var tamaCoatColor = "茶色"

var catName3 = "チビ"
var tibiAge = 4
var tibiBreed = "アメリカンショートヘア"
var tibiCoatColor = "しましま"

 

 これを関数を使って、うまいことコンソールに表示してきます
 関数の説明はここでは省略します
 

cat
//猫ちゃんの特徴
var catName1 = "ミケ"
var mikeAge = 3
var mikeBreed = "スコティッシュフォールド"
var mikeCoatColor = "白"

var catName2 = "タマ"
var tamaAge = 2
var tamaBreed = "マンチカン"
var tamaCoatColor = "茶色"

var catName3 = "チビ"
var tibiAge = 4
var tibiBreed = "アメリカンショートヘア"
var tibiCoatColor = "しましま"

//猫ちゃんの特徴を紹介する関数
func catIntroduce(name:String, age:Int, catBreed:String, catColor:String) {
    print("\(name)\(age)才です")
    print("猫種は\(catBreed)です")
    print("毛色は\(catColor)です")
}
 
//ミケの特徴
catIntroduce(name: catName1,age: mikeAge,catBreed: mikeBreed,catColor: mikeCoatColor)

//タマの特徴
catIntroduce(name: catName2,age: tamaAge,catBreed: tamaBreed,catColor: tamaCoatColor)

//チビの特徴
catIntroduce(name: catName3,age: tibiAge,catBreed: tibiBreed,catColor: tibiCoatColor)


//実行結果
ミケは3才です
猫種はスコティッシュフォールドです
毛色は白です

タマは2才です
猫種はマンチカンです
毛色は茶色です

チビは4才です
猫種はアメリカンショートヘアです
毛色はしましまです

 
 書きました。おおむね皆さんが想像した特徴まとめになっているはずです
 ちゃんとミケとタマとチビの特徴ごとに分かれてるし、これでよくない?と思った人もいるのではないでしょうか
 なんでクラスを使う必要があるの?って

 
 まず、このコードの駄目ところです

 書くのがダルすぎる
 個人の感想かよって思ったかもしれませんが、このダルいというのは色々な問題を複合してます
 
 まず第一のダルいポイントは変数名考えるのめんどくさすぎ問題です

 いちいちミケとタマとチビに分けて変数名考えるのめんどい。名前も年齢も猫種も毛色もカテゴリ名でしかないので、いちいち独立した変数名で分けてる。めんどい。共通化したい。123でナンバリングしてるのもっと何とかならんの

 ここだけでダメなところが5個も出てきました

 
 第二のダルいポイントはカテゴライズしているように見えて全くしてない問題です
 皆さんはここを見て、どう思いましたか?
 

cat
//猫ちゃんの特徴
var catName1 = "ミケ"
var mikeAge = 3
var mikeBreed = "スコティッシュフォールド"
var mikeCoatColor = "白"

var catName2 = "タマ"
var tamaAge = 2
var tamaBreed = "マンチカン"
var tamaCoatColor = "茶色"

var catName3 = "チビ"
var tibiAge = 4
var tibiBreed = "アメリカンショートヘア"
var tibiCoatColor = "しましま"

 
 「ミケとタマとチビで分けてるんだな! ヨシ!」という風に見えますね
 分けているように見えているのは人間だけです
 

  コンピュータ側から見ると、「文字列か数字を格納してる変数が12個あるな」となります

 先ほど、人間が変数を定義すると言っている行為はメモリのどこかに〇〇という名前の区画を確保し、その確保した区域に変数の中身(数値もしくは文字列)を記録させるであると書きました
 つまり、コンピューター視点では、変数catName1、変数mikeAge、変数mikeBreed、変数mikeCoatColorは独立していて、それぞれの名前でメモリの中に区画を確保して、それぞれに変数の中身を記録させています
 
名称未設定 1 (44).png

 これの何が問題なのかというと、私たち人間がグループ化してあると思っているこの12個の変数は、コンピュータからすると全く何も関連性のない、独立した変数を羅列したものでしかないというわけです。
 人間が小賢しくmikeAgeとかtamaAgeとか頑張って区別していますが、コンピュータにとっては何の意味もない行為です

 
 なので、こんな問題が起こってしまいます

cat
//猫ちゃんの特徴
var catName1 = "ミケ"
var mikeAge = 3
var mikeBreed = "スコティッシュフォールド"
var mikeCoatColor = "白"

var catName2 = "タマ"
var tamaAge = 2
var tamaBreed = "マンチカン"
var tamaCoatColor = "茶色"

var catName3 = "チビ"
var tibiAge = 4
var tibiBreed = "アメリカンショートヘア"
var tibiCoatColor = "しましま"


//猫ちゃんの特徴を紹介する関数
func catIntroduce(name:String, age:Int, catBreed:String, catColor:String) {
   print("\(name)\(age)才です")
   print("猫種は\(catBreed)です")
   print("毛色は\(catColor)です")
}

//ミケの特徴
catIntroduce(name: catName3,age: mikeAge,catBreed: mikeBreed,catColor: tibiBreed)

//タマの特徴
catIntroduce(name: catName1,age: tamaAge,catBreed: tibiCoatColor,catColor: mikeBreed)

//チビの特徴
catIntroduce(name: catName2,age: tibiAge,catBreed: tibiBreed,catColor: tibiCoatColor)

//実行結果
チビは3才です
猫種はスコティッシュフォールドです
毛色はアメリカンショートヘアです

ミケは2才です
猫種はしましまです
毛色はスコティッシュフォールドです

タマは4才です
猫種はアメリカンショートヘアです
毛色はしましまです

 
 もうしっちゃかめっちゃかです。毛色はアメリカンショートへアってどういうことなんでしょう
 人間からすると、これは想定してない挙動です。いわゆるバグです
 ですが、コンピュータ側からすると正しく実行した結果です。
 引数nameに入っているべきなのはString型の値で、引数catBreedに入っているべきなのもString型の値です。それ以外の指定はありません。だから、エラーが起こらずにビルドが通ってしまいました

 
 では、どうしてこんな問題が起こるのでしょう?
 それはひとえに、それぞれの猫の特徴として定義したそれぞれの変数に、何の関連性を持たせていないからです

クラスとは

 catIntroduce関数で起こったバグは、どうすれば防ぐことができるのか
 それにはコンピュータ側にそれぞれの変数が関連のあるものだと教えてあげる必要があります

 この関連性をつける行為ということを、型を定義すると言います

 必要なデータ(プロパティ)、振る舞い(メソッド)をまとめて、一つの単位としてまとめる(カプセル化する)ことで、コンピュータにそれぞれの値が関連しているものだと伝えることができるのです

 
  クラスを用いる理由とは、クラスには型を独自に定義する機能があるからです
  では、実際に猫ちゃんの特徴をクラスで書いてみましょう

 クラスを作成する際は、一番大きい括りから定義するといいです
 例えば、初めに日本人というカテゴリーを作るよりも、人間という最も根本的なところでカテゴリーを作り、人間というカテゴリーの中の日本人、他にもアメリカ人、カナダ人……と派生していく方が自然ですね

Cat
class Cat {
	var name: String = "未設定"
	var age: Int = 0
	var catBreed: String = "未設定"
	var catColor: String = "未設定"
}

 
 Catクラスを定義したことによって、猫ちゃんの特徴を表していた四つの変数は関連づけられ、Catという型にまとめられました
 先ほど出てきたプロパティという用語は、この変数nameなどのことです

 名称未設定 1 (44) (2).png

 

 では、この作ったCatクラスはどう使えばいいのでしょうか
 print関数で出力させても、何も出てきません

スクリーンショット 2024-01-21 23.42.56.png

 
 クラスとはよく設計図に例えられるのですが、その通り、クラスそのものには実際に何かを処理したりする機能はありません
 クラスに書かれたもの利用するには、クラスに書かれた定義に従ったインスタンス(オブジェクト)を作成させる必要があります

 
 このことをしばしばインスタンス化と呼んだり、実体化と呼んだりもします
 では、実際にインスタンスを生成してみます

Cat

//ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

//タマ
//インスタンス(オブジェクト)を作成している
var brownCat = Cat()

//チビ
//インスタンス(オブジェクト)を作成している
var stripesCat = Cat()

 
名称未設定 1 (44) (3).png

オブジェクトとは

 いきなり出てきたオブジェクト(インスタンス)という概念ですが、いったい何なのでしょうか
 
 オブジェクトを作成するということは、クラスで定義されたプロパティやメソッドの値をまとめて、メモリ上に一つの区画として確保するという意味です

 従って、オブジェクトとはクラスによって定義されたプロパティやメソッドのまとまりということになります
 今回のCatクラスの場合は、一つのインスタンスを作成するごとにname、age、catBreed、catColorを記録するための区画が確保されているということです
 

 ところで、今回Catクラスのインスタンスを作るために利用した変数whiteCatには一体何が代入されているのでしょう?
 Catクラスのインスタンスがそのまま入っているのでしょうか?

 答えは参照値が代入されています

 参照値とは「〇〇クラスのインスタンスはメモリの⚫︎⚫︎ある」と教えてくれるもので、実際にインスタンスが代入されている訳ではありません。いわば住所のようなものです
 どうして変数にインスタンスを直接代入するのではなく、インスタンスの参照値が代入されているのかというと、クラスは参照型という仕様だからだと現時点では理解しておいてください。
 
名称未設定 1 (44) (4).png
 

 猫ちゃんの特徴をまとめるCatクラスを作りましたが、現状のままでは色々と不十分ですね
 ですので、作成したインスタンスのプロパティを変更して、それぞれの猫ちゃんの特徴を定義していきましょう

 

cat
class Cat {
	var name: String = "未設定"
	var age: Int = 0
	var catBreed: String = "未設定"
	var catColor: String = "未設定"
}

//プロパティを変更する
var whiteCat = Cat()
whiteCat.name = "ミケ"
whiteCat.age = 3
whiteCat.catBreed = "スコティッシュフォールド"
whiteCat.catColor = "白"

var brownCat = Cat()
brownCat.name = "タマ"
brownCat.age = 2
brownCat.catBreed = "マンチカン"
brownCat.catColor = "茶色"

var stripesCat = Cat()
stripesCat.name = "チビ"
stripesCat.age = 4
stripesCat.catBreed = "アメリカンショートヘア"
stripesCat.catColor "しましま"

 
 ちなみに、Xcodeでは参照値を代入している変数の後ろにドットを書くと、アクセスすることのできるプロパティを出してくれます。便利です
 
スクリーンショット 2024-01-22 22.59.19.png

 
 これで、ミケ、タマ、チビの三匹の特徴を定義することができました
 先ほどとは違い、コンピュータにも三匹の情報が別々のものであると教えられたので、catIntroduce関数で発生するバグも防げるようになりました
 

cat
class Cat {
   var name: String = "未設定"
   var age: Int = 0
   var catBreed: String = "未設定"
   var catColor: String = "未設定"
}

//プロパティを変更する
var whiteCat = Cat()
whiteCat.name = "ミケ"
whiteCat.age = 3
whiteCat.catBreed = "スコティッシュフォールド"
whiteCat.catColor = "白"

var brownCat = Cat()
brownCat.name = "タマ"
brownCat.age = 2
brownCat.catBreed = "マンチカン"
brownCat.catColor = "茶色"

var stripesCat = Cat()
stripesCat.name = "チビ"
stripesCat.age = 4
stripesCat.catBreed = "アメリカンショートヘア"
stripesCat.catColor = "しましま"


//猫ちゃんの特徴を紹介する
func catIntroduce(cat:Cat) {
   print("\(cat.name)\(cat.age)才です")
  print("猫種は\(cat.catBreed)です")
  print("毛色は\(cat.catColor)です")
}

//実行する
catIntroduce(cat: whiteCat)

catIntroduce(cat: brownCat)

catIntroduce(cat: stripesCat)

 
実際に実行させてみると、コンソールにはこう表示されます

スクリーンショット 2024-01-22 23.44.50.png

 catIntroduce関数の引数をCatクラスに指定してあるので、人間が引数を指定する必要はありません
 なので、人間が想定していない場所に、想定していない情報が紛れ込んでしまうということ自体を防ぐことができました
 
 これで想定外のバグが発生せず、さまざまな猫ちゃんに対応できるコードになった訳ですが、まだ問題点があります

 プロパティを設定するために4行書くのダルすぎ

Cat
//プロパティを変更する
var whiteCat = Cat()
whiteCat.name = "ミケ"
whiteCat.age = 3
whiteCat.catBreed = "スコティッシュフォールド"
whiteCat.catColor = "白"

 
 もちろん、この問題を解決してくれる機能があります

イニシャライザ

 何行もプロパティを書かなければいけないのは、Catクラスのプロパティの値を既に設定してしまっているからです

 この問題を一気に解決してくれるのが、イニシャライザというメソッドです
 イニシャライザはクラスのインスタンスが生成された時に呼び出されるメソッドで、プロパティの初期化をしてくれます
 クラス専用の引数みたいなものだと、すごくざっくりとした理解をしてます

 
 イニシャライザを使うとこうなります

Cat
class Cat {
  var name: String
  var age: Int
  var catBreed: String
  var catColor: String
  
  //イニシャライザ
  init(name: String, age: Int, catBreed: String, catColor: String) {
      self.name = name
      self.age = age
      self.catBreed = catBreed
      self.catColor = catColor
  }
}

var whiteCat = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")

 
イニシャライザの中にあるコードは、プロパティとイニシャライザの引数が同じものだと定義しています

名称未設定 1 (44) (5).png

 
 
 ところで、先ほどはこのようにインスタンスを生成しました

Cat
//ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

//タマ
//インスタンス(オブジェクト)を作成している
var brownCat = Cat()

//チビ
//インスタンス(オブジェクト)を作成している
var stripesCat = Cat()

名称未設定 1 (44) (6).png
 

 
 ですが、イニシャライザを定義した状態でインスタンスを生成するとき、Cat()ではエラーが出ます
 エラー文を読んでみると、「呼び出しのパラメータ 'name', 'age', 'catBreed', 'catColor' に引数がありません。」と書いてあります

スクリーンショット 2024-01-23 1.11.29.png

 
 イニシャライザの引数を指定せず、Cat()でインスタンスを生成したい場合、簡易イニシャライザ(convenienceイニシャライザ)を使用します

スクリーンショット 2024-01-23 1.20.39.png
 
 
 ちなみに、簡易イニシャライザの元になっているイニシャライザを指定イニシャライザ(Designatedイニシャライザ)と言います

クラスの派生

 先ほど、初めに日本人というカテゴリーを作るよりも、人間という最も根本的なところでカテゴリーを作り、人間というカテゴリーの中の日本人、他にもアメリカ人、カナダ人……と派生していく方が自然と言いました
 概念のカテゴリーが派生していくように、クラスも同じように派生していくことができます
 

 クラスを派生させるには、継承という機能を使います
 継承とは元のクラスの特徴を全て所持している新しいクラスを改めて定義することです
 継承元となるクラスをスーパークラス(親クラス)、継承先のクラスのことをサブクラス(子クラス) と言います
 
 では、実際に継承させてみましょう
  Catクラスから派生して、子猫の子クラスを作ります

Cat
//親クラス
class Cat {
    var name: String
    var age: Int
    var catBreed: String
    var catColor: String
    
	//イニシャライザ
    init(name: String, age: Int, catBreed: String, catColor: String) {
        self.name = name
        self.age = age
        self.catBreed = catBreed
        self.catColor = catColor
    }
}

//子クラス
class LittleCat: Cat {
    var personality:String
    
    init(name: String, age: Int, catBreed: String, catColor: String, personality:String) {
        self.personality = personality
        super.init(name: name, age: age, catBreed: catBreed, catColor: catColor)
    }
}

名称未設定 1 (44) (7).png

 イニシャライズに書いてある通り、Catクラスの子クラスであるLittleCatクラスはCatクラスのプロパティを使うことができます
 Catクラスを引数にしたCatIntroduce関数でも使うことができます
 whiteCatはLttleCatクラスを元に作られたインスタンスを参照しています

スクリーンショット 2024-01-23 23.13.13.png

override

 継承には親クラスのプロパティやメソッドを上書きする機能があります
 その機能のことをoverrideと言います
 overrideという英単語は「〜より優位に立つ」のような意味合いの言葉なので、単語の意味が分かると理解しやすいではないのでしょうか

 
 実際にoverrideしていきたいのですが、Catクラスに上書きできるようなものがないので、適当にメソッドを追加しておきます

Cat
//親クラス
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
   }
   
   func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
   }
}

 
 プロパティで設定した特徴を紹介するIntroduceメソッドを追加しました
 では、子クラスであるLittleCatクラスでIntroduceメソッドをoverrideしてみましょう

Cat
//親クラス
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
   }
   
   func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
   }
}

//子クラス
class LittleCat:Cat {
   var personality:String
   
   init(name: String, age: Int, catBreed: String, catColor: String, personality:String) {
       self.personality = personality
       super.init(name: name, age: age, catBreed: catBreed, catColor: catColor)
   }
   
   override func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
       print("\(personality)な性格の猫です")
   }
}

 
コンソールに表示するとこうなります
スクリーンショット 2024-01-24 0.52.40.png

extension

 ところで、今回はoverrideの実演をするために、仕方なくCatクラスを直接書き換えてメソッドを追加しましたが、あまりいい方法ではありません
 例えばアプリを作っている場合、Catクラスを参照している箇所が100箇所くらいあると仮定します。Catクラスを直接書き換えて何かしらのメソッドやプロパティを追加すると、その参照している100箇所で何かしらのバグが発生する可能性が生まれてしまいます。やめておきましょう
 
 
 ですが、どうしてもクラスにプロパティやメソッドを追加したい場合、extension(拡張) という機能を利用します

Cat
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
   }
}


//機能を拡張する
extension Cat {
   func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
   }
}

 
 extensionによって、継承を用いずにCatクラスにメソッドを追加することができました

 継承とextension、両方にメリットデメリットは存在します
 extensionは簡単にメソッドやプロパティを追加できますが、extensionで追加した機能は継承でオーバーライドすることはできません
 ですが、基本的に継承でなければならない場合を除き、単なる機能追加程度であれば、extensionを使用する方がコードや参照の関係が複雑にならないので、extensionがおすすめです

クラスでオブジェクト指向をしてみる

 ここまで長々とクラスについて書いてみましたが、正直なところ、クラスが持つ特徴や強みについて全てを書けた訳ではありません

ポリモーフィズム

 今回、例として三匹の猫ちゃんの特徴をまとめたCatクラスを作りました。そして、Catクラスを継承したLittleCatクラスも作りました
 ところで、猫は鳴きますよね
 猫ちゃんの鳴き声は可愛いので、鳴き声を出力するcryメソッドでも生やしてみましょう

Cat
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
   }
   	//鳴き声メソッド
   			func cry() {
   					print("にゃー")
   	}

}

//機能を拡張する
extension Cat {
   func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
   }
}


//子クラス
class LittleCat:Cat {
   var personality:String
   
   init(name: String, age: Int, catBreed: String, catColor: String, personality:String) {
       self.personality = personality
       super.init(name: name, age: age, catBreed: catBreed, catColor: catColor)
   }
   
   override func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
       print("\(personality)な性格の猫です")
   }
   
   //ここを追加
   //鳴き声メソッド
   	override func cry() {
   			print("みゃ〜")
   }		
}

 
 ところで、子猫がいたら成猫がいてもいいですよね。生やしましょう
 成猫の鳴き声も子猫とは違うと思うので変えます
 

Cat
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
   }

   	//鳴き声メソッド
   			func cry() {
   					print("にゃー")
   	}
}

//機能を拡張する
extension Cat {
   func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
   }
}


//子クラス
class LittleCat:Cat {
   var personality:String
   
   init(name: String, age: Int, catBreed: String, catColor: String, personality:String) {
       self.personality = personality
       super.init(name: name, age: age, catBreed: catBreed, catColor: catColor)
   }
   
   override func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
       print("\(personality)な性格の猫です")
   }
   
   //鳴き声メソッド
   	override func cry() {
   			print("みゃ〜")
   }		
}

//ここを追加
//子クラス
class MatureCat:Cat {
   	override func cry() {
   			print("にゃお")
   }
}

 
 せっかく鳴き声メソッドを作ったので実行してみましょう
 LittleCatクラスとMatureCatクラスをインスタンス化します

 Catクラス、LittleCatクラス、MutureCatクラスをそれぞれインスタンス化しました

Cat
class Cat {
  var name: String
  var age: Int
  var catBreed: String
  var catColor: String
  
  //イニシャライザ
  init(name: String, age: Int, catBreed: String, catColor: String) {
      self.name = name
      self.age = age
      self.catBreed = catBreed
      self.catColor = catColor
  }

  //鳴き声メソッド
  func cry() {
      print("にゃー")
  }
}

//機能を拡張する
extension Cat {
  func introduce() {
      print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
  }
}


//子クラス
class LittleCat:Cat {
  var personality:String
  
  init(name: String, age: Int, catBreed: String, catColor: String, personality:String) {
      self.personality = personality
      super.init(name: name, age: age, catBreed: catBreed, catColor: catColor)
  }

  //鳴き声メソッド
  override func cry() {
      print("みゃ〜")
  }
}

//子クラス
class MatureCat:Cat {
  override func cry() {
      print("にゃお")
  }
}

//インスタンスを作成
var whiteCat = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")
var brownCat = LittleCat(name: "タマ", age: 2, catBreed: "マンチカン", catColor:  "茶色", personality: "元気")
var stripesCat = MatureCat(name: "チビ", age: 4, catBreed: "アメリカンショートヘア", catColor: "しましま")

//鳴き声メソッドを実行
whiteCat.cry()
brownCat.cry()
stripesCat.cry()

 
このコードを実行すると、コンソールにはこのように出力されます

スクリーンショット 2024-01-25 1.06.51.png

 whiteCatもbrownCatもstripeCatも全て同じメソッドを実行しているはずなのに、実際に出力された結果がどれも異なっています
 これがポリモーフィズム(多態性) です
 
名称未設定 1 (44) (8).png

 どうして、同じメソッドを実行しているのに、結果が異なっているのでしょうか?
 それはwhiteCatインスタンス、brownCatインスタンス、striprCatインスタンスがどれも別々のクラスに属しているからです
 そして、Catクラスにあるcryメソッド、LittleCatクラスにあるcryメソッド、MatureCatクラスにあるcryメソッドの処理の内容が全て異なっており、同じメソッドを実行しても、得られる結果が異なったという訳です

参照型

 この記事の冒頭で、プログラミングとは、コンピューターのメモリに値や処理を記録させる行為であると書きました
 そして、このようにCatクラスのインスタンスを作りました

Cat
//ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

//タマ
//インスタンス(オブジェクト)を作成している
var brownCat = Cat()

//チビ
//インスタンス(オブジェクト)を作成している
var stripesCat = Cat()

名称未設定 1 (44) (9).png
 
 インスタンスを新たに作れば作るほど、メモリの中はインスタンスで埋まっていきます

 ですが、メモリは有限です
 インスタンスを作り続ければ、やがてメモリの中がインスタンスだらけになってしまい、いっぱいになってしまいます

 現状、この記事で作っているCatクラスとその子クラス達はいくつかのプロパティとメソッドしかないので、確保されるメモリの区画は小さいです
 しかし、動画などの重たいファイルを表示するというクラスを用いる場合、そのクラスのインスタンスを作るたびに動画ファイルも複製され、すぐにメモリがいっぱいになってしまいます

 
 メモリがいっぱいになるとどうなるのか
 あまり詳しくなくても、「なんか良くないことが起りそうだな……」と察しますね
 その通りで、プログラムが正常に動作しなくなるので、メモリをいっぱいにしてしまうことは出来る限り避けるべきです
 

 
 では、どうすればメモリがインスタンスでいっぱいになるという事態を防げるのでしょうか
 Swiftに関していえば、人間が特別に何か気をつけなければいけないことはありません

 何故かというと、Swiftではメモリがインスタンスで氾濫してしまうという事態を避ける設計をしており、使い終わったインスタンスを自動的にメモリから破棄してくれるからです

 
 Swiftには、この二つの仕組みが備わっています
名称未設定 1 (44) (10).png

 この二つの仕組みによって、人間がメモリの容量を気にする必要なく、プログラムを作ることができるのです
 ちなみに、1の仕組みのことをARC(Auto Reference Counting) と呼びます。2の仕組みを支えているのが参照型です
 実のところ、ARCのような仕組みはSwift独自のものではなく、オブジェクト指向のプログラミング言語であるJavaのガベージコレクションのことで、オブジェクト指向型の他のプログラミング言語にも備わっています
 

 
 まず、ARCの仕組みについて理解してきます
 ARC(Auto Reference Counting)とは、書いてあるとおり自動(Auto)で参照(Reference )をカウント(Counting)する機能です

 
 ARCはインスタンスが不必要になった時に、そのインスタンスをメモリから破棄してくれる機能なのですが、そもそもインスタンスが不必要になった時とはいつででしょうか?
 何をもって不必要と判断しているのでしょうか?

 ここで、自動(Auto)で参照(Reference )をカウント(Counting)する機能が必要になってきます
 
 
 ARCという機能はインスタンスに向けられた参照の回数をカウントします
 この参照のカウントが0になったとき、インスタンスは不要になったと判断されて、メモリから破棄されます

 ところで、何気なく参照参照と言ってきたのですが、そもそも参照とは一体何なのでしょう?
 オブジェクトの説明のでこう書いたことを覚えていますか

Cat
//ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

//タマ
//インスタンス(オブジェクト)を作成している
var brownCat = Cat()

//チビ
//インスタンス(オブジェクト)を作成している
var stripesCat = Cat()

 今回Catクラスのインスタンスを作るために利用した変数whiteCatには一体何が代入されているのでしょう?
 Catクラスのインスタンスがそのまま入っているのでしょうか?

 
 答えは参照値が代入されています
 参照値とは「〇〇クラスのインスタンスはメモリの⚫︎⚫︎ある」と教えてくれるもので、実際にインスタンスが代入されている訳ではありません。いわば住所のようなものです
 どうして変数にインスタンスを直接代入するのではなく、インスタンスの参照値が代入されているのかというと、クラスは参照型という仕様だからだと現時点では理解しておいてください

Cat
//ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

 
 このコードはインスタンスを作成するコードです。変数whiteCatにCatクラスの参照値を代入しています
 もっと正確に説明すると、Cat()という名前のメモリの区画にインスタンスがあるということを指し示している値を変数whteCatに代入しているというコードです

名称未設定 1 (44) (11).png

 
 つまり、参照とは参照値とのことで、インスタンスがメモリの〇〇にあるということを教えている回数を数えているのが、ARCということです
 参照値が登場する回数がARCの参照カウンターの回数であるとも思えてきましたね

 
 では、実際にARCでの参照カウンターの増減を、これまで書いてきたコードで見てみましょう
 分かりやすいように、deinitを追加しておきます
 あと、後々のためにwhiteCatをオプショナル型にしておきます
 

Cat
class Cat {
  var name: String
  var age: Int
  var catBreed: String
  var catColor: String
  
  //イニシャライザ
  init(name: String, age: Int, catBreed: String, catColor: String) {
      self.name = name
      self.age = age
      self.catBreed = catBreed
      self.catColor = catColor
  }
  
  //ここを追加
  deinit {
      print("\(name)が家から逃げました")
  }

  //鳴き声メソッド
  func cry() {
      print("にゃー")
  }
}

//機能を拡張する
extension Cat {
  func introduce() {
      print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
  }
}

var whiteCat: Cat? = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")

 initはインスタンスが生成される際に呼び出され、クラスのプロパティの初期化に利用するメソッドです
 それに対して、deinitはインスタンスがメモリから破棄される時に一度だけ呼び出されるメソッドです

名称未設定 1 (44) (12).png

 つまり、ARCによる参照カウンターが0になった時にだけ実行されるメソッドで、他のメソッドのように人間がコードによって呼び出しをする必要はありません
 今回は、このdeinieによって「"(name)が家から逃げました"」という文字列が出力されたタイミングがインスタンスがメモリから破棄されたタイミングということになります
 
 
 では、色々と試していきましょう
 まず、introduceメソッドを実行します
 deinitはまだ実行されておらず、「"(name)が家から逃げました"」という文字列はコンソールに出力されておりません

スクリーンショット 2024-01-26 17.20.51.png

 deinitを呼び出すためには、Catインスタンスが破棄されないといけません
 インスタンスが破棄されるには、インスタンスへの参照が全てなくなる必要があります
 
 現状、Catインスタンスへの参照はこの部分だけです

Cat
var whiteCat: Cat? = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")

 
 whiteCat?.introduceはCatクラスの中にあるメソッドを呼び出しているだけで、Catインスタンスへの参照ではありません

 
 
現状のコードでCatクラスへの参照をなくすには、whiteCat変数へnilを代入します
スクリーンショット 2024-01-26 22.29.19.png

 実行すると、コンソールには「"(name)が家から逃げました"」が表示されます

 実行のフローはこうなっています

 1. Catクラスのインスタンスを生成。変数whiteCatにCatインスタンスの参照値を代入する
 2. Catクラスのintroduceメソッドを呼び出す
 3. この時点で、「ミケは3才で、毛色はスコティッシュフォールドの白です」が出力される
 4. 変数whiteCatにnilを代入。変数whiteCatがCatインスタンスではなく、nilになるのでCatインスタンスへの参照がなくなる
 5. この時点で、「ミケが家から逃げました」が出力される
 6. 全ての処理が終わったので、結果がコンソールに表示される
 
 
 
 これにより、インスタンスへの参照がなくなれば、インスタンスがメモリから破棄されることは分かりました
 
 
 ですが、インスタンスを破棄する為には、毎回nilを代入しなければいけないのかと疑問に思いますね
 nilを代入しなくとも、ARCの参照カウンターが0になるタイミングはないのでしょうか?
 当然あります
 
 今回もdeinitのインスタンスが破棄された時に呼び出されるという性質と、initのインスタンスが生成された時に呼び出されるという性質を利用して、実験をしてみます

Cat
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
       
   			//ここを追加
       print("\(self.name)が家にきました")
   }
   deinit {
       print("\(name)が家から逃げました")
   }
}

//追加する
func introduce() {
   //インスタンスを生成
   let whiteCat = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")
   print("\(whiteCat.name)は家にいます")
   
}

introduce()
print("家に戻ってきました")

 
 色々と書き換えました
 まずinitの中に、「(self.name)が家にいます」という文字列を出力するprint関数を追加しました
 そして、introduce関数にCatクラスのインスタンスを生成します
 introduce関数を実行した後、「家に戻ってきました」という文字列を出力するという流れです

 
 では、実際に実行してみます
スクリーンショット 2024-01-27 1.06.25.png
 
 
 コンソールに3行目に、「ミケが家から逃げました」が出力されています
 参照値にnilを代入していないのにも関わらず、です
 これはいったいどういうことなのでしょうか? いったいどのタイミングでインスタンスへの参照が消えて、deinitが呼び出されたのでしょうか

 
 一つ一つ順に紐解いていきましょう
 実際のコードの行数とXcodeの行数の表示は異なっていますが、分かりやすいのでXcodeの左にある行数を採用します
 

 
 1. このコードを実行するために、Runボタンを押します
   その時点で、まず98行目にあるintroduce関数を呼び出しているintroduce()が実行されます

 2. introduce関数の中で、Catクラスのインスタンスが生成されます
    Catクラスのインスタンスへの参照カウントは+1されました

 3. Catクラスのインスタンスが生成された時点で、initが呼び出され、「(self.name)が家にいます」がprintによって出力されます

 4. introduce関数の中で、 print("(whiteCat.name)は家にいます"が実行されます
   introduce関数の実行が終わったので、98行めのintroduce()から99行目のprint("家に戻ってきました")に制御が移ります

 5. 98行目から99行目に処理の制御が移ったことにより、introduce関数内にある定数whiteCatがintroduce関数のスコープから抜けました
   この時点でCatインスタンスの参照カウントが−1されます
   参照カウントが0となったので、Catクラスのインスタンスはメモリから破棄されます

 6. インスタンスが破棄されたのでdeinitが呼び出され、 print("(name)が家から逃げました")が実行されます
   そして、99行目のprint("家に戻ってきました")が実行されました

名称未設定 1 (48).png

 
 これで、参照値にnilを代入する以外の方法でインスタンスをメモリから破棄することができました
 ローカル変数(今回は定数ですが)はそのスコープの外ではないものとして扱われます
 今回は定数whiteCatがintroduce関数のスコープの中にあり、introduce関数を実行している時のみ、定数whiteCatは存在できるのです
 
 なので、introduce関数を実行し終わると、プログラムの実行制御は定数wihiteCatが存在できるスコープの外側に行ってしまい、それ以降のプログラムでは定数whiteCatはないものとして実行されていきます

 
 
 ということは、Catクラスのインスタンスを代入する変数(もしくは定数)をグローバル変数に変えれば、インスタンスの破棄を回避できるということでもあります

スクリーンショット 2024-01-28 0.57.12.png
 
 

 
 一見万能そうに見えるARCですが、問題点もあります

 インスタンスの参照が0になればインスタンスは破棄される訳ですが、意図しないタイミングで参照値が増減した結果、思った通りの箇所でインスタンスが破棄されなかったり、インスタンスが残ってしまうという問題が発生してしまいます
 
 
 その問題の一つが循環参照です

Cat
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //ここを追加
   var family:Cat?
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
       
       print("\(self.name)が家にきました")
   }
   deinit {
       print("\(name)が家から逃げました")
   }
}


func introduce() {
   //インスタンスを生成
   let whiteCatInstance = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")
   print("\(whiteCatInstance.name)は家にいます")
   
}

var whiteCat:Cat? = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")
var brownCat:Cat? = Cat(name: "タマ", age: 2, catBreed: "マンチカン", catColor:  "茶色")

whiteCat?.family = brownCat
brownCat?.family = whiteCat

whiteCat = nil
brownCat = nil

 CatクラスにCat?型のfamilyプロパティを追加しました
 そして、Catクラスのインスタンスを2つ作り、お互いにお互いのインスタンスを代入します
 最後にnilが代入されているので、これまでの流れを汲めば、この二つのインスタンスは破棄されるはずです

 実行してみましょう

スクリーンショット 2024-01-28 1.30.47.png

 deinitが呼び出されず、print("(name)が家から逃げました")が出力されていません
 変数whiteCatにも、brownCatにもnilが代入されているというのに、インスタンスがメモリから破棄されていないということになります。おかしいですね
 
 
 インスタンスが参照されるたびに参照カウンターが増えるので、この部分が悪さをしてそうな感じがします

Cat
whiteCat?.family = brownCat
brownCat?.family = whiteCat

 もちろん、変数whiteCatにも、brownCatにもnilが代入されているので、その二つの変数からの参照は消えています
 ですが、それでもインスタンスは破棄されていないので、まだインスタンスへの参照が残っており、参照カウンターが残っているということです
 
 実際にその通りで、現状のCatクラスのインスタンスへの参照関係はこうなっています

名称未設定 1.png

 お互いのfamilyプロパティがお互いのインスタンスを参照しあっているので、いつまで経っても参照カウンターが0にならないのです
 なので、friendプロパティにもnilを代入しましょう
スクリーンショット 2024-01-29 17.51.28.png

 deinitが実行されました
 これで、二つのインスタンスの参照カウンターが0になり、無事にメモリから破棄されました
 
 今回の場合、変数whiteCatに代入されたCatクラスのインスタンスと、変数brownCatに代入されたCatクラスのインスタンスが大概に参照し合っていたので、意図したタイミングでインスタンスが破棄されないという問題が発生しました

 循環参照はこのようにインスタンスが互いに参照し合っているせいで、意図しないタイミングでの破棄が行われないという問題があります
 インスタンスが破棄されないとやがてメモリがインスタンスで埋まってしまい、メモリーリークという問題を引き起こしてします
 
 
 
 ところで、今回はインスタンスを強制的に破棄する為にfamilyプロパティにnilを代入しましたが、二回もnilを代入するのは少し面倒ですよね
 循環参照を未然に防ぐ方法はないのでしょうか?

 
 もちろん、これを解決する方法をAppleは用意してます

 
 循環参照が発生する原因は、インスタンスのプロパティ同士が参照し合っている所為で参照カウントが0にならないということです
 では、最初からfamilyプロパティに参照値を代入した時点で参照カウントが増えなければ、変数whiteCatと変数brownCatにnilを代入した時点で、きちんとインスタンスの参照カウントが0になり、意図したタイミングでメモリから破棄されるはずです
 
 つまり、私たち人間は、ARCにはfamilyプロパティを参照カウントの対象に含めないで欲しい訳です
 なので、ARCに「familyプロパティは数えなくていいよ〜」と伝えなければいけません
 
 
 その為に使うのが、weakというキーワードです
 

 プロパティの先頭にweakと書くことで、ARCはそのプロパティをカウントしなくなります
 ちなみにweakとは弱いとか脆いとか、そういった意味合いの英単語です

 では、familyプロパティにweakをつけて、コードを実行してみます
 分かりやすいように、107行目はコメントアウトしておきました
スクリーンショット 2024-01-30 0.08.28.png

 これで、変数whiteCatに代入されている参照値のfamilyプロパティにnilを代入しなくても、循環参照を解消することができました
 
 

 
 ところで、インスタンスがメモリでいっぱいにならないようにする為に、swiftには二つの仕組みがあると書いたことを覚えていますか?

 これまでの説明は不必要になったインスタンスを破棄する仕組みについて説明していましたが、swiftにはそもそもインスタンスを必要以上に作らないという仕組みが備わっています
 それがクラスが参照型であるという理由です

 三匹の猫ちゃんのインスタンスを作る時、こういうコードを書きました

Cat
//ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

//タマ
//インスタンス(オブジェクト)を作成している
var brownCat = Cat()

//チビ
//インスタンス(オブジェクト)を作成している
var stripesCat = Cat()

 
 この時、変数whiteCatと変数brownCatと変数stripesCatに代入されているインスタンスは全て別物です
 つまり、三つのインスタンスが作られているということになります
 
名称未設定 1 (44) (13).png

 ですが、この場合はどうなのでしょうか?

Cat
 //ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

var whiteCat2 = whiteCat

var whiteCat3 = whiteCat2

var whiteCat4 = whiteCat3

var whiteCat5 = whiteCat4

 これはインスタンスが5個作られているということになるのでしょうか?
 メモリのどこかにインスタンスとして区画が確保されているのでしょうか?
 
 正解は、作られていないです
 
 既に何回か書いていますが、変数whiteCatに代入されているものはインスタンスそのものではありません
 メモリのどこかにCat()という名前の区画があって、そこにCatクラスのインスタンスがあると教えてくれている値が代入されています
 その値のことを参照値と言います
 
 つまり、このコードで作られているインスタンスは一つだけで、五つの変数は全て同じものを参照しているということになります

Cat
 //ミケ
//インスタンス(オブジェクト)を作成している
var whiteCat = Cat()

var whiteCat2 = whiteCat

var whiteCat3 = whiteCat2

var whiteCat4 = whiteCat3

var whiteCat5 = whiteCat4

名称未設定 1 (44) (14).png

 参照されるたびにインスタンスを作っていたら、すぐにメモリがいっぱいになってしまいます
 なので、クラスは不必要にインスタンスを作らない仕組みが必要なのです
 
 
 
 ですが、当然これもARCと同じように注意しなければいけない点があります
 一つのインスタンスを参照しているということは、大元のインスタンスが何かしらによって変更された場合、そのインスタンスを参照している部分全てに変更の影響が出てしまうということです

 このコードで試していきます

Cat
class Cat {
  var name: String
  var age: Int
  var catBreed: String
  var catColor: String
  
  //イニシャライザ
  init(name: String, age: Int, catBreed: String, catColor: String) {
      self.name = name
      self.age = age
      self.catBreed = catBreed
      self.catColor = catColor
  }
}

//機能を拡張する
extension Cat {
  func introduce() {
      print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
  }
}

var whiteCat = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")

 
 まず、変数whiteCatと同じインスタンスを参照している変数を用意します
 

Cat
class Cat {
   var name: String
   var age: Int
   var catBreed: String
   var catColor: String
   
   //イニシャライザ
   init(name: String, age: Int, catBreed: String, catColor: String) {
       self.name = name
       self.age = age
       self.catBreed = catBreed
       self.catColor = catColor
   }
}

//機能を拡張する
extension Cat {
   func introduce() {
       print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
   }
}

var whiteCat = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")

//ここを追加
var littleCat = whiteCat

 introduceメソッドを実行するとこうなります
 同じインスタンスを参照しているので、同じ文章がコンソールに表示されるのは当然ですね
スクリーンショット 2024-01-30 17.19.55.png
 
 
 では、次はlittleCatのnameプロパティを変更してみましょう
 そして、改めて実行してみます

スクリーンショット 2024-01-30 21.04.41.png

 whiteCat.Introduceで呼び出して実行したメソッドまで、nameプロパティがポチに変更されています
 本当はコンソールにはこうなっていて欲しいはずです

ミケは3才で、毛色はスコティッシュフォールドの白です
 ポチは3才で、毛色はスコティッシュフォールドの白です
 
 どうしてこのような不具合が起こるのかといえば、何度も書いてきたように、変数whiteCatと変数littleCatに代入されているのがCatクラスのインスタンスそのものではないからです

 代入されているのものはCatクラスのインスタンスがメモリのどこにあるかを示す参照値で、この二つの変数は同じものを代入されて、同じものを参照しています
 なので、どこか一ヶ所でCatクラスのインスタンスの内容が変更されると、そのCatクラスの参照している全ての箇所で意図しない変更が発生してしまうのです

 
 では、同じインスタンスを参照しながら、プロパティに別々に値を代入して出力させることはできるのでしょうか?
 
 
 結論から言うと、不可能です
 そして、これが参照型の限界です
 
 もし、ミケとポチのそれぞれを反映した文章をコンソールに表示したければ、改めてインスタンスを作らなければいけません
 ですが、そうして別のインスタンスを作る場合、これでは参照型であるクラスを用いる必要性が薄く、むしろ構造体(struct)を用いて、コードを記述しているのと何ら変わりがありません
 
 
 なぜなら、クラスが参照型であるのに対し、構造体(struct)は参照されるたびにインスタンスを生成する値型であるからです
 そもそも、プロパティとして定義した特徴をまとめ、それを出力するだけのコードであるなら、最初からこのコードはクラスではなく構造体(struct)を用いるべきでした
 

 そして、この参照型の限界はとある問題も引き起こします
 それはふとした拍子に、意図しないプロパティの変更を引き起こしてしまうということです

Cat
class Cat {
  var name: String
  var age: Int
  var catBreed: String
  var catColor: String
  
  //イニシャライザ
  init(name: String, age: Int, catBreed: String, catColor: String) {
      self.name = name
      self.age = age
      self.catBreed = catBreed
      self.catColor = catColor
  }
}

//機能を拡張する
extension Cat {
  func introduce() {
      print("\(name)\(age)才で、毛色は\(catBreed)\(catColor)です")
  }
}

var whiteCat = Cat(name: "ミケ", age: 3, catBreed: "スコティッシュフォールド", catColor: "白")
var littleCat = whiteCat

print("変更前")
whiteCat.introduce()
littleCat.introduce()

print("-----------")
littleCat.name = "ポチ"

print("変更後")
whiteCat.introduce()
littleCat.introduce()

 
 このコードでは、whiteCat.introduce()では「ミケは3才で、毛色はスコティッシュフォールドの白です」と表示して欲しかったはずです
 ですが、実際にlittleCat.nameでプロパティを変更した結果、変わって欲しくなかっwhiteCat.introduceも変わってしまい、想定していない挙動を引き起こしてしまいました

 今回は二つの変数での話だったので、すぐにバグに気がつき、被害も軽微なものでしたが、これが実際にアプリに開発している段階で、何回もCatクラスのインスタンスの参照値を用いている状態だったらどうだったでしょうか?

 例えば、Catクラスのインスタンスの参照値を呼び出す箇所が100ヶ所あったとして、30回目でうっかりインスタンスのプロパティを変更してしまったら、残りの70回はずっと想定と異なった値で処理をしていくということになります
 これでは不具合やバグの原因となってしまいます
 

 ですが、意図しない変更を防ぐ手段はあります

 
 それはプロパティに最初に設定された値以外は許さないと決めることです
 その為にはプロパティの先頭にprivateをつけます
スクリーンショット 2024-01-30 22.59.20.png

 
 プロパティにprivateをつけた結果、インスタンスのプロパティを変更しているlittleCat.nameでエラーが発生しました
 このエラーは、要するに「プロパティのスコープ外で値を変更しても認めねーから」という意味です
 なので、littleCat.nameと記述して一度設定したプロパティを変更しようとしても、それはCatクラスのスコープの外側での記述なので、privateによって変更が認められることはありません

 
 このスコープの外からプロパティの変更を認めないことを、カプセル化と言います
 

 
 ところで、プロパティの変更を認めないように、インスタンスの変更(=新しくインスタンスを作ること)を禁止する方法もあります
 それがシングルトンパターンと呼ばれる、デザインパターンの一つです

 カプセル化をする際、プロパティに対してprivateをつけることによって値の変更を禁止しました
 ですが、シングルトンパターンではカプセル化の時と同様にクラスにキーワードをつけるのかというと、そのようなことをコンピュータに命令することのできるキーワードは存在しません
では、どうやって複数のインスタンスを作成することを回避できるのでしょうか

 
 確かにキーワードは存在しませんが、今回もprivateを使うことで複数にインスタンスを作ることを禁止することができます
 

 initにprivateをつけると、インスタンスを作る際、エラーが発生します
 privateに設定したプロパティやメソッドにアクセスすることが出来るのは、同じスコープ内のみです

 クラスのスコープ外でインスタンスを作成すると、initの性質上、自動的にinitが呼び出されてアクセスすることになります。ですが、作成したインスタンスはクラスのスコープの外にあるので、privateでカプセル化したinitにアクセスすることはできません
 なので、クラスのスコープ外でインスタンスを作成すると、エラーが発生するのです

スクリーンショット 2024-02-03 2.23.12.png

 これで、クラス外でインスタンスを作成することは禁止することはできました

 
 ですが、まだ問題があります
 これではクラスの機能を利用することはできません

 initをカプセル化したことによってインスタンスが作成できないので、別のアプローチを使ってクラスの機能にアクセスする必要があります
 その為には、スタティックプロパティ(静的プロパティ) を用います
 

 スタティックプロパティ(静的プロパティ)とはクラス、構造体、列挙型などの型を独自に定義することのできる機能に備わっているプロパティです
 そして、通常のプロパティ(動的プロパティ)とは異なり、型自身に紐付いていることが特徴です

 
 これまで、インスタンスを作る際にイニシャライザから渡された情報を元値を設定し、異なる値を持つインスタンスを作成してきました。ですが、前述した通り、スタティックプロパティは型自体に紐付いているプロパティです

 つまり、100個インスタンスを作っても、そのプロパティがスタティックプロパティである限り、その100個のインスタンスの中でスタティックプロパティの値が共有されているということになります

 なので、インスタンスごとに個別の数値を設定することはできません
 

 また、インスタンスを作ることでアクセスできるようになるのが動的プロパティですが、インスタンスを作らなくてもアクセスすることができるのが静的プロパティです
 
 
 staticプロパティのみを定義したクラスを用意しました
スクリーンショット 2024-02-02 2.02.29.png

 通常であれば、クラスの中にあるプロパティにアクセスする為にはインスタンスを作成する必要があります
 ですが、今回はインスタンスを作成していません
 そして、クラスに直接アクセスをして、プロパティの内容をコンソールに出力させています
 
 そして、もう一つの特徴はあります
 それは継承によるoverrideでの書き換えが不可能であるという点です
スクリーンショット 2024-02-02 23.09.01.png

 
 このインスタンスを作らなくてもアクセスすることが出来るスタティックプロパティの特徴を用いれば、カプセル化したinitによってインスタンスを作れなくなったクラスにアクセスできると思いませんか?

 privateは同じスコープであればアクセス可能なので、initと同じスコープにクラスのインスタンスを作成します
 そして、そのインスタンスをスタティックプロパティにします

スクリーンショット 2024-02-03 2.58.54.png

 スタティックプロパティはインスタンスを作成しなくても、クラスの機能にアクセスすることが出来るので、インスタンスを保持している定数sharedを通じて、クラスの機能を利用することが出来るようになりました

 定数名は任意ですが、だいたいsharedになっているので、「これはシングルトンパターン!」と知らせるためにsharedにしておいた方が丸いと思います

 これにより、一つのインスタンスしか認めないシングルトンパターンを実現することができました

おわり

クラスがむずいの、全部参照型ってやつのせいじゃね〜〜〜〜????

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?