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

前編へ

目次へ

このサイトのトップへ

相互参照とweak修飾子

 

二つのインスタンスが相互に参照しあう場合、OSのARCシステムはどちらのインスタンスも消去すべからずと判断します。 しかし、「Aはいらなくなった」「BはAを便宜上参照しているが、参照が切れても別に構わない」という場合、Aをどのように消去 すればよいでしょうか。
その方法のひとつに「weak修飾子をつけて参照する」方法があります。

ただし、相互参照を行わない方法はいくらでもありますし、アプリの途中でインスタンスを無理に消去する必要はほとんどないので、 これから示すサンプルはかなり「無理に問題をひねりだして」作ってあります。むしろ、フレームワークのテンプレートなどに「weak」と 書いてある理由を考えるのに役立ててください。

iOSアプリがめんどくさい理由

「コンソール」がない

これから示すサンプルプログラムは「ずいぶん面倒だな」と思うかも知れません。
その理由は、iOSプログラムだからです。世の中の多くのサンプルプログラムは「print」のような名前のメソッドによる 「コンソール出力」で、手早く結果を確かめることができますが、iOSにはコンソールはありません。
なにかのインスタンスが持つプロパティとしての文字列にまとめて、ラベルやテキストビューなどに表示しなければならないのです。
その「なにかのインスタンス」を作るのが面倒な作業になります。

でも、考えてみれば、サーバープログラムでなければコンソールに出力する意味はなきに等しいですから、 iOSに限らず多くのアプリでは、このようなプログラムの書き方になると思います。

これからのプログラムの考え方

人は残るが、イベントは消滅

これから作るプログラムは、前編と同じく、 「あるギター奏者が自分のコンサートツアーをするのに、ベース奏者とドラム奏者と契約する」というシナリオで記述します。

この場合、ベース奏者及びドラム奏者との契約が切れることがあります。でも、演奏者はインスタンスとして残ります。

一方、ツアーが中止になった場合、そのツアーのインスタンスは消滅するようにします。


クラスLeaderのインスタンスに全ての作業記録を持たせる

ツアーを企画するギター奏者は、Leaderというクラスのインスタンスにします。
この奏者のインスタンスはひとつしか作りませんが、Swiftの使用上スタティックなクラスは使いにくいので、普通のインスタンスにします。

Leaderにmessageという文字列プロパティを持たせ、ここに作業記録を全て保持させます。


三者相互参照の中でTourを消滅させるには

ギター奏者と契約するベース奏者とドラム奏者はそれぞれクラスMusicianのインスタンスで、クラスTourのインスタンスと相互参照の 関係になります。

また、全ての作業を記録するため、MusicianとLeader、及びTourとLeaderも相互参照になります。

三者が相互に参照する中でTourのインスタンスを消滅させます。考え方のキモは、Tourを参照するインスタンスを切ることです。
丁寧に切って行くならweak修飾子は不要です。乱暴でも手早く切るときにweakを使います。

インスタンスの消滅はdeinitで確認

クラスTourにメソッドdeinitを定義し、その中でLeaderのプロパティmessageに記録を残します。

各クラスの定義の概要

クラスLeader

クラスLeaderは、クラスTourのオプショナル型のインスタンスをプロパティとして定義します。
Musicianの操作はTourを通して間接的に行います。

また、記録保持のためのプロパティmessageを持ちます。

Leaderはツアーの企画, 奏者との契約, 契約破棄, 及びツアーの中止メソッドを持ちますが、 奏者との契約と契約破棄のメソッドは、Tourの同名メソッドを通じて間接的に行います。

以上の内容を、リスト1に概略で示します。

リスト1 クラスLeaderの定義概要
class Leader{

	var tour:Tour?
	var message:String


	func planTour(tourName:String){

		self.tour = Tour(name:tourName)
		tour!.leader = self
		message += 作業記録
	}


	func addBass(bassist:Musician){
		tour!.addBass(bassist)
		message += 作業記録

	}

	同様にメソッドaddDrums, delBass, delDrumsを定義

	//作業記録は残さない
	func cancelTour(){

		if (tour != nil){
			tour = nil
		}
	}

	//作業記録を全部出力
	func reportMessage()->String{
		return message
	}

}

メソッドcancelTourは、Tourへの参照を切るメソッドです。
このメソッドではプロパティmessageに記録を残しません。
なぜなら、この作業でTourのインスタンスが消滅するはずなので、Tourのdeinitメソッドから作業記録を書き込むのです。


クラスTour(前編からの改訂版)

クラスTourは、前編と同様、ベースとドラム奏者をMusicianのオプショナル型でプロパティとして有します。

そして、奏者を直接操作するaddメソッドとdelメソッドを持ちます。これを、Leaderのaddメソッドとdelメソッドが間接的に呼び出します。

一方、前編で用いていたプロパティmessageはなくなります。Leaderのプロパティになったからです。

Tourでは自分が消滅するときのdeinitメソッドで、Leaderのプロパティmessageを用います。 そのため、自身のプロパティとしてLeaderのオプショナル型を有します。

リスト2 クラスTour(前編からの改訂版)の定義の概要
class Tour{

	let name:String

	var leader:Leader?
	var bass:Musician?
	var drums:Musician?


	func addBass(bassist:Musician){

		ベース奏者を追加

	}
	func addDrums(drummer:Musician){

		ドラム奏者を追加

	}

	func delBass(){

		ベース奏者を外す

	}

	func delDrums(){

		ドラム奏者を外す

	}

	deinit{
		if (leader != nil){
		leader!.message += このインスタンスが消滅するという報告
		}
	}

}

クラスMusicianの定義(前編からの改訂版)の概要

TourとMusicianのインスタンスを包括するクラスLeaderを作成したので、実はMusicianはTourを参照する必要がなくなります。
しかし、「相互参照」の問題を論じたいので、前編同様、Tourを参照します。特に重要な意味の参照でなくて構いません。

また、記録を保持するために、Leader(オプショナル型)のインスタンスをプロパティとして持ちます。

なお、ベース奏者・ドラム奏者の消滅は考えないので、Musicianのdeinitメソッドは特に書きません。

リスト3 クラスMusicianの定義(前編からの改訂版)の概要
class Musician{
	let name:String
	var part:String
	var leader:Leader?
	var tour:Tour?


	Tourを参照するようなメソッド(作業記録はleader!messageに書き込む)

}

ツアーのインスタンスを作成

クラスLeaderへの書き込み

ツアーのインスタンスの作成を、クラスLeaderのメソッドplanTourにおいて行うようにします。

作成したインスタンスを、自らのプロパティtourに割り当てます。

重要なことは、インスタンスには変数名をつけずに、直接新規作成したものをプロパティに放り込むことです。
変数をつけると、「その変数が参照している」ことになるので、消滅させるのが面倒になります。

リスト4 クラスLeaderのメソッドplanTour
func planTour(tourName:String){

	self.tour = Tour(name:tourName) //新規作成して直接投入
	tour!.leader = self
	message += "今度、\(tour!.name)に行くよ!\n"
}

奏者を加える

クラスLeaderへの書き込み

クラスLeaderに、奏者を加えるためのメソッドaddBass及びaddDrumsを定義します。

主な作業は、プロパティtourの同名のメソッド(後述)を呼ぶだけですが、リーダーの立場から「彼らを加えた」という作業も記録します。

リスト5 クラスLeaderに定義するメソッドaddBassとaddDrums
func addBass(bassist:Musician){
	tour!.addBass(bassist)
	message += "\(bassist.name)期待してるぜ!\n"

}


func addDrums(drummer:Musician){
	tour!.addDrums(drummer)
	message += "\(drummer.name)期待してるぜ!\n"

}

クラスTourへの書き込み

前編でもクラスTourにaddBass, addDrumsを定義しました。
それは、「『bass及びdrumsのそれぞれプロパティ』tour」に、自らを割り当てることです。

今回のそれらのメソッドには、さらに追加の作業があります。

リスト6 奏者をツアーに参加させるためにクラスTourに定義するメソッド
func addBass(bassist:Musician){

	bass = bassist
	bass!.tour = self
	bass!.leader = self.leader
	bass!.joinTour()


}
func addDrums(drummer:Musician){

	drums = drummer
	drums!.tour = self
	drums!.leader = self.leader
	drums!.joinTour()

}

クラスMusicianへの書き込み

リスト7のようなメソッドJoinTourを書き込みます。
内容は大したことはありません。ただ、自らのプロパティmessageを用いて、「自分が加わった」という記録を保存します。
同時に、「MusicianがTourを参照」しているという事実を作ります。

なお、リスト7でプロパティleaderやtourの中身は、Leader及びTourのインスタンスが操作してくれますから、 自らはなんらかの代入作業を行う必要はありません。

リスト7 メソッドjoinTour
func joinTour(){

	if(leader != nil && tour != nil){
		leader!.message += "「\(name)です。\(tour!.name)に参加できて嬉しいです。」\n"
	}
}

奏者を円満に外す

相互参照を両方外す

さてこれから、ツアーから奏者を外す作業を書きます。参照の問題なく奏者を外すには、ツアーから奏者への参照も、 奏者からツアーへの参照もキチンと外さなければなりません。


クラスLeaderの定義への書き込み

クラスLeaderにメソッドdelBassとdelDrumsを定義します。いずれも、プロパティtourに同名のメソッドを呼ばせるだけです。

リスト8 クラスLeaderのメソッドdelBassとdelDrums
func delBass(){
	tour!.delBass()
	message += "そいつは残念だ!\n"
}


func delDrums(){
	tour!.delDrums()
	message += "そいつは残念だ!\n"
}

クラスTourの定義への書き込み

奏者とツアーの参照を相互に消す作業は、Tourで定義するdelBass, delDrumsメソッドで行います。 まず、自身のプロパティbassとnilに、Musicianで定義する(後述)cancelTourメソッドを呼ばせて奏者からツアーへの参照を外します。
そのあと、bassとdrumsをnilにします。

リスト9 クラスTourに定義するdelBass, delDrumsメソッド
func delBass(){

	if(bass != nil){

		bass!.cancelTour()

		bass = nil
	}

}

func delDrums(){

	if(drums != nil){

		drums!.cancelTour()

		drums = nil
	}

}


クラスMusicianの定義への書き込み

クラスMusicianには、メソッドcancelTourを定義します。 このメソッドでは、奏者がツアーへの参照を切ります。すなわち、自分のプロパティtourをnilにします。

 また、作業を記録します。

リスト9 クラスMusicianに定義するメソッドcancelTour

func cancelTour(){

	if (leader != nil && tour != nil){

		leader!.message += "「\(name)です。\(tour!.name)にいけなくなって残念です。」\n"

	tour = nil

	}
}

ツアーを消滅させる

クラスLeaderの定義への書き込み

クラスLeaderにメソッドcancelTourを作成します。
クラスMusicianのメソッドと同じ名前ですが、直接の依存関係はありません。

参照関係を外す作業はプロパティtourがやってくれたので、そのtourをnilにするだけです。
作業記録も残しません。tourがdeinitメソッド(後述)で記録します。

リスト10 クラスLeaderに定義するメソッドcancelTour
func cancelTour(){

	if (tour != nil){
		tour = nil
	}
}


クラスTourの定義への書き込み

deinitメソッドで、「自分のプロパティleader」のプロパティmessageに作業記録を残します。

リスト11 クラスTourのメソッドdeinit
deinit{
	if (leader != nil){
		leader!.message +=
			"バッドニュースだ。\(name)は中止になっちゃったんだ。この次は必ず行くよ!"
	}
}

以上、重要な記述について説明しました。 他の記述も含めてViewController.swiftに書くと、 別ページのようになります。

円満にツアーがキャンセルされたことを確かめる

実行してみてください。以下のようにメッセージが現れるでしょう。

	こんにちは、ジェフです! //インスタンスjeffの作成

	今度、2014アメリカ横断スーパーツアーに行くよ!
						//メソッドplanTourの実行

	「トニーです。2014アメリカ横断スーパーツアーに参加できて嬉しいです。」
						//TourのメソッドaddBassの処理

	トニー期待してるぜ! 	//LeaderのメソッドaddBaseの処理

	「ビルです。2014アメリカ横断スーパーツアーに参加できて嬉しいです。」
						//TourのメソッドaddDrumsの処理

	ビル期待してるぜ!	//LeaderのメソッドaddDrumsの処理

	「トニーです。2014アメリカ横断スーパーツアーにいけなくなって残念です。」
						//MusicianのメソッドcancelTourの処理

	そいつは残念だ!	//LeaderのメソッドdellBassの処理

	「ビルです。2014アメリカ横断スーパーツアーにいけなくなって残念です。」
						//MusicianのメソッドcancelTourの処理

	そいつは残念だ!	//LeaderのメソッドcancelTourの処理

	バッドニュースだ。2014アメリカ横断スーパーツアーは中止になっちゃったんだ。この次は必ず行くよ!
					//Tourのdeinitの処理

図1 実際の実行結果

最後の出力で、Tourのインスタンスがめでたく消滅したことがわかります。


参照に関係なくnilにする場合

もしもジェフさんの性格がアレな場合

もし、リーダーのジェフさんが奏者にキャンセルするしないを通知せず一方的にツアーをキャンセルするとしたら、 プログラムはどのように書けばいいでしょうか?

それには、クラスLeadersの定義において、一方的に「プロパティtourのプロパティbassをnilにする」という操作を行ってしまうようにします。 MusicianのメソッドcanselTourも、TourのメソッドdelBassなども使いません(参照関係を残すために、そのまま書いて置きます)。

リスト12 クラスLeaderのメソッドdelBassとdelDrums
   func delBass(){

        message += "\(tour!.bass!.name)悪く思わんでくれ!\n"
        tour!.bass = nil
    }


    func delDrums(){
        message += "\(tour!.drums!.name)悪く思わんでくれ!\n"
        tour!.drums = nil

    }

リスト12では、処理の順番に注意してください。プロパティbassやdrumsを用いた処理(「悪く思わんでくれ」と言う) が終わってから、nilにします。


参照されているのでツアーが消滅しない

 

以上のように変更して実行すると、ツアーが消滅するときの出力が表示されません。 すなわち、Tourインスタンスのdeinitメソッドが呼ばれなかったのです。

図2 ツアーがキャンセルされない

weak「参照はしているが、nilならそれでもいい」

そこで、weak修飾子です。これは、「外部操作でnilになってもいいプロパティ」につけます。
今の例では、「ベースやドラムの奏者はツアーがnilになっても存在に問題を生じません。
そこで、クラスMusicianにおけるプロパティtourの定義にweak修飾子をつけます。

リスト13 クラスMusicianのプロパティtourに修飾子weakをつける
class Musician{
    let name:String
    var part:String
    var leader:Leader?
    weak var tour:Tour?

	//他はこれまで通り

実行してみましょう。図3のように、Tourのdeinitメソッドが再び呼ばれるようになりました。

図3 ツアーのキャンセル通知が復活

簡単なプログラムで、「weak」を使ってでもあるインスタンスを強制的にnilにしなければいけないということは、 ほとんどありません。
しかし、フレームワークのテンプレート(iOSのUIKitがまさにそれ)などで「weak」修飾子が要求されるとき、「どうしてか?」 「これはweakなのか違うのか?」と迷うことがあると思います。そのときは、「メモリの節約」と「データの重要性」を考慮してください。


前編へ

目次へ

このサイトのトップへ