このページは「はじめてのSwiftプログラミング」(清水美樹著、工学社刊、2014年8月23日発売、ISBN978-4-7775-1851-7)の著者によるサポートページです

続編へ

目次へ

このサイトのトップへ

Nilかも知れないプロパティ

 

Swiftでは、クラスに定義したプロパティには初期値を与えておくか、initメソッドで初期値を与えるようにしなければなりません。
ただし、プロパティを「オプショナル型」にしておけば、インスタンスの作成時には定義しなくて構いません。加えて、あとからnilにもできます。

なお、以下のサンプルは、SwiftのPlaygroundでは動作しないかも知れません。iOSなど、アプリ開発のプロジェクト上で実行するのが確実です。

「オプショナル型」で対応

あるギター奏者が組織するコンサートツアー

リスト1は、あるギター奏者がコンサートツアーに出かけることを想定したクラスTourの定義です。

リスト1 クラスTour
class Tour{
 let name:String
   init(name:String){
     self.name = name
   }
   var bass:Musician?
   var drums:Musician?
}

二つのvar型プロパティbass及び drumsは、ツアーに同行してくれるベースとドラム奏者を表します。 いずれのプロパティも、値があるとしたら下のリスト2に示すクラスMusicianのインスタンスです。

リスト2 クラスMusician
class Musician{
   let name:String
   var part:String
   init(name:String, part:String){
      self.name = name
      self.part = part
   }
}
しかし、プロパティbassとdrumsはプロパティは「クラスMusicianのオプショナル型」という型で定義します。 なぜなら、「ツアー」が決まっても、まだギター奏者本人以外のメンバーは決まっていないこともあるからです。
リスト1の中で、リスト3に示す部分に注目してください。
リスト3 オプショナル型のプロパティ
var bass:Musician?
var drums:Musician?

オプショナル型プロパティは初期化しない

オプショナル型のプロパティはinitメソッドで初期化しません。クラスTourのinitメソッドに注目してください。 リスト4のように、オプショナル型でなくかつ初期値も与えられていないプロパティ(要するに普通のプロパティ) nameだけを初期化します。

リスト4 クラスTourのinitメソッド
init(name:String){
     self.name = name
}

インスタンスの作成後初期化する

以上のような構造ですから、クラスTourのインスタンスを作成するときは、名前だけ決めます。
次に、プロパティbassとdrumsにそれぞれクラスMusicianのインスタンスを割り当てます。
リスト5では、それぞれその場でインスタンスを新規作成して割り当てています。

リスト5 Tourのインスタンス
var us2014 = Tour(name:"2014アメリカ横断スーパーツアー")
us2014.bass = Musician(name:"トニー", part:"ベース")
us2014.drums = Musician(name:"ビル", part:"ドラム")

オプショナルなプロパティの扱い方

結果を確認

リスト5のインスタンスがちゃんと作成されたかどうか調べるには、出力メソッドが必要です。

注目すべきは、ただ「Musicianのインスタンスができたか」ではなく、「インスタンスus2014のプロパティとしてできたか」です。
そこで、出力メソッドはクラスTourのほうに定義します。リスト6のような「reportMember」です。

リスト6 クラスTourに定義するメソッドreportMember
func reportMember()->String{
    var message = ""

    if(bass == nil){
        message = "ベースは決まっていません。"

    }else{
        message = "ベースは\((bass!).name)さん。"
    }
    if(drums == nil){
        message += "ドラムは決まっていません。"

    }else{
        message += "ドラムは\((drums!).name)さん。"
    }

	return message
}

上のリスト6で注目すべきは、プロパティbass及びdrumsが「オプショナル型」であることです。
そのため、プロパティの値を取り出す際には、まず「nilでないかどうか」を確認の上、「強制アンラップ」で取り出します。
「nilである場合」には別の処理をします。


iOSプロジェクトとして作成・実行

 以上のコードをiOSプロジェクトして「ViewControllwer.swift」にまとめると、たとえば 別ページのようになります。

一度与えたプロパティをnilにする

オプショナル値は直接nilにできる

変数をオプショナル型で指定の場合、その変数に直接nilを代入することができます。

コンサートツアーに一時参加したベースやドラムの人が、都合により途中で脱退することもあるでしょう。
その場合は、リスト7のようにズバッと「nil」にできます。

リスト7 ベースの人がツアーから脱退
us2014.bass = nil

メソッドで表す

しかし、リスト7では、このデータに何が起こったのかよくわかりません。
「何が起こったのか」と言っても、「家庭の事情」とか「意見の相違」とかいう世の中的な理由ではありません。
「明らかな意図を持ってこのデータを削除した」のか、「成り行き上このデータがnilになった」 のかという区別です。

そこで、クラスTourに、「ベースの人を削除する」という名前のメソッドを作成します。リスト8にその考え方を 示します。メソッドdelBassを用いることによって、「明らかな意図で削除した」ということがわかります。

リスト8 メソッドを使ってプロパティを操作
class Tour{
	........

	func delBass(){

		bass = nil
	}

}

us2014.delBass()

「del」というメソッドを作ったのなら、プロパティbassに値を割り当てる作業も、 クラスTourに「add」というメソッドを作成して、それを用いるほうが「明らかな意図」を表すことができます。
リスト9 addメソッドとdelメソッドで操作
class Tour{
	........

	func addBass(bassist:Musician){

        bass = bassist

    }

	func delBass(){

		bass = nil
	}

}

us2014.addBass(Musician(name:"トニー", part:"ベース"))
us2014.delBass()

これで、「一度割り当てたベース奏者を、あとから削除した」という、操作の意図が明確になります。

操作記録を保持するプロパティ

addやdelなどのメソッドを行ったことが記録に残るようにしましょう。

リスト6で、「message」は「ローカル変数」として用いられ、 戻り値を出して役目を終えます。これを、インスタンスのプロパティにします。 戻すのもプロパティとしてのmessageが戻っていることになります。

リスト9 プロパティとしての変数message

class Tour{

	......
	var message = ""

	......
	func reportMember()->String{

		if(bass == nil){
			message += "ベースは決まっていません。"

		}else{
			message += "ベースは\((bass!).name)さん。"
		}
		.....

		return message + "\n"
	}
}

同じように、メソッドaddDrumsとdelDrumsも作ります。

以上の変更を終えたクラスTourの定義を 別ページに示します。


一度加入、その後脱退

メソッドviewDidLoadに書く内容を、リスト10のように書き換えてみましょう。

リスト10 メソッドviewDidLoadに書く実際の作業
var myStr = ""

let us2014 = Tour(name:"2014アメリカ横断スーパーツアー")

//ベースとドラムの奏者を追加
us2014.addBass(Musician(name:"トニー", part:"ベース"))
us2014.addDrums(Musician(name:"ビル", part:"ドラム"))

//ベースの奏者を削除
us2014.delBass()

//操作記録を表示
myStr += us2014.reportMember()

resultText.text = myStr
図1 iOSシミュレータでの実行結果

ベースの人はどうなったのか?

ツアーから外れるだけか、存在自体が消えるのか

ここでひとつ疑問が出ます。コンサートツアーであるインスタンスus2014において、そのプロパティであるbassを nilにしました。この場合、「ベースの人」というインスタンスは、「us2014のプロパティでなくなる」だけで、 そのインスタンスは存在するのでしょうか? それとも、インスタンスそのものが消滅するのでしょうか?


deinitメソッドで確認

あるインスタンスが消滅したかどうか確認する方法として、「deinit」メソッドの定義を使うことができます。

「deinit」メソッドでは、インスタンスが消滅する直前に行う作業を定義します。作業中のデータの保存など、必要なことはほとんど iOSが自動でやってくれますが、特にやりたい作業がある場合、 及びこれから行うようにインスタンスの消滅をログに書き込んだり通知したりという目的で使用します。

リスト11のように用います。deinitメソッドは引数も戻り値もとりません。

リスト11 メソッドdeinitの使い方
class Musician{

	....

    init(.....){
    	......
    }

    deinit{
        ここに処理を書く
    }
}

MusicianからTourを参照(注意が必要、後述)

クラスMusicianのdeinitメソッド中に、そのインスタンスが消滅したという通知をさせる処理を記述すればよいことに なりますが、その通知をどのように出力すればいいでしょうか?

面白い方法をしてみましょう。ただし、この方法を行うにはメモり管理上注意が必要です。 後述しますので、続けてお読みください。

クラスMusicianのほうにも、クラスTourのインスタンスをデータ型とするプロパティtourを 定義します。しかし、みんなが常にこのギタリストさんのツアーに参加するわけではありませんから、オプショナル型になります。

リスト12 クラスTour型(オプショナル)のプロパティ
class Musician{
	.....

	var tour:Tour?

}

リスト12では、まだ特定のツアーを参照したことにはなりません。

ツアーを参照させる操作は、クラスTourの定義「addBass」などで行います。 「『Tourのプロパティbass』のプロパティtour」に、自分自身を代入するのです。

リスト13 クラスTourのメソッドaddBaseに追記
class Tour{
	.....

	func addBass(bassist:Musician){

        bass = bassist
        bass!.tour = self
        message += "ベースに\(bass!.name)さんが参加しました。\n"

    }

}
こうしておいて、クラスMusicianのdeinitメソッドを編集します。
リスト14 メソッドdeinitの完成
class Musician{

	var tour:Tour?

	....

	deinit{
		if(tour != nil){
			tour!.message += "\(name)さんは都合により休業します。\n"
		}
	}
}

もし、リスト10において、インスタンスus2014のプロパティbassをnilにしたとき、 そのインスタンスが消滅するならばdeinitメソッドが呼ばれて通知がなされるはずです。

ここまでの「ViewController.html」を別ページにまとめました。

実行してみましょう。図2のように、ベースのトニーさんの休業通知が表示されました。

図2 通知が加わった

変数名でインスタンスを参照すると

しかし、これはデータの扱い方として変ですね。 演奏者はツアーを離れても演奏活動が終わるわけではありません。

そこで、以下のように、リスト10の操作を変えてみましょう。
まず、ベース奏者のインスタンスを作成し、これにtonyという変数名をつけます。
それから、インスタンスus2014のプロパティbassにtonyを渡します。

リスト15 メソッドdeinitの使い方
//ベースとドラムの奏者を追加
let tony = Musician(name:"トニー", part:"ベース"))
us2014.addBass(tony)

このようにしてus2014のプロパティbassを設定した場合、あとからbassを削除しても、トニーさんの休業通知は出ません。

トニーさんが健在なのを確かめるには、メソッドdelBassを呼んだあとに、変数tonyの内容を呼び出してみてください。
リスト16 変数tonyの内容を確認
//プロパティbassの内容はnilに
us2014.delBass()
myStr += us2014.reportMember()

//変数tonyの内容を確かめる
myStr += "\(tony.name)さんは帰国してスタジオに入ります。\n"

図2 変数tonyの内容が表示された

「ARC」のためだった

tonyという変数が参照しているため

us2014のプロパティbassを削除しても、ベース奏者のインスタンスが消滅しなかったのは、 tonyという変数が、このインスタンスを参照していたためです。

MacのOSやiOSには「Automatic Reference Counting(自動参照計数)」というシステムがあり、 「不要になった」すなわち「どんな変数やプロパティからも参照されなくなった」インスタンスは、 自動で削除するようになっています。
ただし、常に、すぐ削除するというわけではありません。一時的に参照されなくなったあと、 再びそのインスタンスが参照されることもあるかも知れないからです。
メモリに余裕があるかどうかによりシステムが判断しますが、 iOSの場合はただちに、参照されなくなったデータは消されると考えてよいと思います。


「nilを代入」の意味

「ARC」というしくみを知った事により、以下のことが明らかになります。

すなわち、「 a = nil 」という操作は、aが参照していたデータを消去することではありません。
aが何も参照しないようにすることです。
aが参照しなくなったことで、データを参照するものがなくなったとき、ARCがそのデータを消すのです。


参照する変数を増やすことの問題

「参照されないデータは削除する」ARCですが、反面「参照されているデータは削除されません」。
これで起きる問題は、あるインスタンスを必要もないのに別の変数で参照すると、必要もないデータがアプリの 稼働中ずっと残ることです。
アプリを終了すればメモリにとどまった全てのデータは消えますので、あまり神経質になる必要はありませんが、 頻繁にインスタンスの作成・終了を繰り返すようなアプリですと、メモリの食い過ぎで、iOSに強制終了させられるでしょう。


相互参照による問題

「参照されているデータは削除されない」しくみが起こすもうひとつの問題は、「相互参照」です。

リスト12に「注意が必要」と示したように、そこからの作業により、インスタンス「us2014」も「tony」を 参照し、かつ「tony」も「us2014」を参照する「相互参照」となっています。

すると、どちらも、消去されません。相手が参照しているからです。

リスト17に至るまでの目的は、ツアーがキャンセルされても奏者は消滅しないようにすることでした。
その目的は達成しましたが、実は隠れた問題があります。

インスタンスus2014からはインスタンスtonyの情報は失われましたが、tonyのほうにはus2014の情報が残っています。
確認のために、リスト16のすぐあとに、tonyのプロパティtourを書き出す以下の文を追加して実行してみましょう。

リスト17 tonyからtourへのの参照は、自動では消えない
//ベースとドラムの奏者を追加
 myStr += "\(tony.name)さんは\(tony.tour!.name)の成功を願っています。\n"

「トニーさんは2014アメリカ横断スーパーツアーの成功を願っています」という文字列が表示されるでしょう。

アプリが終了すれば、アプリに関連するデータは全て消されますから、2つや3つの小さなデータなら問題はありません。

しかし、このような相互参照データがいくつもあって、生成・消滅(実はしていない)を繰り返すようになると、「メモリリーク」が深刻になります。

また、むしろ相互参照が必要な場合もあるでしょう。
そのような場合、参照関係をどのように対処すればよいか、続編で解説します。


続編へ

目次へ

このサイトのトップへ