このページは「はじめてのSwiftプログラミング」(清水美樹著、工学社刊、2014年8月23日発売、ISBN978-4-7775-1851-7)の著者によるサポートページです
Swiftでは、クラスに定義したプロパティには初期値を与えておくか、initメソッドで初期値を与えるようにしなければなりません。
ただし、プロパティを「オプショナル型」にしておけば、インスタンスの作成時には定義しなくて構いません。加えて、あとからnilにもできます。
なお、以下のサンプルは、SwiftのPlaygroundでは動作しないかも知れません。iOSなど、アプリ開発のプロジェクト上で実行するのが確実です。
リスト1は、あるギター奏者がコンサートツアーに出かけることを想定したクラスTourの定義です。
class Tour{ let name:String init(name:String){ self.name = name } var bass:Musician? var drums:Musician? }
二つのvar型プロパティbass及び drumsは、ツアーに同行してくれるベースとドラム奏者を表します。 いずれのプロパティも、値があるとしたら下のリスト2に示すクラスMusicianのインスタンスです。
class Musician{ let name:String var part:String init(name:String, part:String){ self.name = name self.part = part } }しかし、プロパティbassとdrumsはプロパティは「クラスMusicianのオプショナル型」という型で定義します。 なぜなら、「ツアー」が決まっても、まだギター奏者本人以外のメンバーは決まっていないこともあるからです。
var bass:Musician? var drums:Musician?
オプショナル型のプロパティはinitメソッドで初期化しません。クラスTourのinitメソッドに注目してください。 リスト4のように、オプショナル型でなくかつ初期値も与えられていないプロパティ(要するに普通のプロパティ) nameだけを初期化します。
init(name:String){ self.name = name }
以上のような構造ですから、クラスTourのインスタンスを作成するときは、名前だけ決めます。
次に、プロパティbassとdrumsにそれぞれクラスMusicianのインスタンスを割り当てます。
リスト5では、それぞれその場でインスタンスを新規作成して割り当てています。
var us2014 = Tour(name:"2014アメリカ横断スーパーツアー") us2014.bass = Musician(name:"トニー", part:"ベース") us2014.drums = Musician(name:"ビル", part:"ドラム")
リスト5のインスタンスがちゃんと作成されたかどうか調べるには、出力メソッドが必要です。
注目すべきは、ただ「Musicianのインスタンスができたか」ではなく、「インスタンスus2014のプロパティとしてできたか」です。
そこで、出力メソッドはクラスTourのほうに定義します。リスト6のような「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を代入することができます。
コンサートツアーに一時参加したベースやドラムの人が、都合により途中で脱退することもあるでしょう。
その場合は、リスト7のようにズバッと「nil」にできます。
us2014.bass = nil
しかし、リスト7では、このデータに何が起こったのかよくわかりません。
「何が起こったのか」と言っても、「家庭の事情」とか「意見の相違」とかいう世の中的な理由ではありません。
「明らかな意図を持ってこのデータを削除した」のか、「成り行き上このデータがnilになった」
のかという区別です。
そこで、クラスTourに、「ベースの人を削除する」という名前のメソッドを作成します。リスト8にその考え方を 示します。メソッドdelBassを用いることによって、「明らかな意図で削除した」ということがわかります。
class Tour{ ........ func delBass(){ bass = nil } } us2014.delBass()「del」というメソッドを作ったのなら、プロパティbassに値を割り当てる作業も、 クラスTourに「add」というメソッドを作成して、それを用いるほうが「明らかな意図」を表すことができます。
class Tour{ ........ func addBass(bassist:Musician){ bass = bassist } func delBass(){ bass = nil } } us2014.addBass(Musician(name:"トニー", part:"ベース")) us2014.delBass()
これで、「一度割り当てたベース奏者を、あとから削除した」という、操作の意図が明確になります。
addやdelなどのメソッドを行ったことが記録に残るようにしましょう。
リスト6で、「message」は「ローカル変数」として用いられ、 戻り値を出して役目を終えます。これを、インスタンスのプロパティにします。 戻すのもプロパティとしてのmessageが戻っていることになります。
class Tour{ ...... var message = "" ...... func reportMember()->String{ if(bass == nil){ message += "ベースは決まっていません。" }else{ message += "ベースは\((bass!).name)さん。" } ..... return message + "\n" } }
同じように、メソッドaddDrumsとdelDrumsも作ります。
以上の変更を終えたクラスTourの定義を 別ページに示します。
メソッドviewDidLoadに書く内容を、リスト10のように書き換えてみましょう。
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
ここでひとつ疑問が出ます。コンサートツアーであるインスタンスus2014において、そのプロパティであるbassを nilにしました。この場合、「ベースの人」というインスタンスは、「us2014のプロパティでなくなる」だけで、 そのインスタンスは存在するのでしょうか? それとも、インスタンスそのものが消滅するのでしょうか?
あるインスタンスが消滅したかどうか確認する方法として、「deinit」メソッドの定義を使うことができます。
「deinit」メソッドでは、インスタンスが消滅する直前に行う作業を定義します。作業中のデータの保存など、必要なことはほとんど iOSが自動でやってくれますが、特にやりたい作業がある場合、 及びこれから行うようにインスタンスの消滅をログに書き込んだり通知したりという目的で使用します。
リスト11のように用います。deinitメソッドは引数も戻り値もとりません。
class Musician{ .... init(.....){ ...... } deinit{ ここに処理を書く } }
クラスMusicianのdeinitメソッド中に、そのインスタンスが消滅したという通知をさせる処理を記述すればよいことに なりますが、その通知をどのように出力すればいいでしょうか?
面白い方法をしてみましょう。ただし、この方法を行うにはメモり管理上注意が必要です。 後述しますので、続けてお読みください。
クラスMusicianのほうにも、クラスTourのインスタンスをデータ型とするプロパティtourを 定義します。しかし、みんなが常にこのギタリストさんのツアーに参加するわけではありませんから、オプショナル型になります。
class Musician{ ..... var tour:Tour? }
リスト12では、まだ特定のツアーを参照したことにはなりません。
ツアーを参照させる操作は、クラスTourの定義「addBass」などで行います。 「『Tourのプロパティbass』のプロパティtour」に、自分自身を代入するのです。
class Tour{ ..... func addBass(bassist:Musician){ bass = bassist bass!.tour = self message += "ベースに\(bass!.name)さんが参加しました。\n" } }こうしておいて、クラスMusicianのdeinitメソッドを編集します。
class Musician{ var tour:Tour? .... deinit{ if(tour != nil){ tour!.message += "\(name)さんは都合により休業します。\n" } } }
もし、リスト10において、インスタンスus2014のプロパティbassをnilにしたとき、 そのインスタンスが消滅するならばdeinitメソッドが呼ばれて通知がなされるはずです。
ここまでの「ViewController.html」を別ページにまとめました。
実行してみましょう。図2のように、ベースのトニーさんの休業通知が表示されました。
しかし、これはデータの扱い方として変ですね。 演奏者はツアーを離れても演奏活動が終わるわけではありません。
そこで、以下のように、リスト10の操作を変えてみましょう。
まず、ベース奏者のインスタンスを作成し、これにtonyという変数名をつけます。
それから、インスタンスus2014のプロパティbassにtonyを渡します。
//ベースとドラムの奏者を追加 let tony = Musician(name:"トニー", part:"ベース")) us2014.addBass(tony)
このようにしてus2014のプロパティbassを設定した場合、あとからbassを削除しても、トニーさんの休業通知は出ません。
トニーさんが健在なのを確かめるには、メソッドdelBassを呼んだあとに、変数tonyの内容を呼び出してみてください。//プロパティbassの内容はnilに us2014.delBass() myStr += us2014.reportMember() //変数tonyの内容を確かめる myStr += "\(tony.name)さんは帰国してスタジオに入ります。\n"
us2014のプロパティbassを削除しても、ベース奏者のインスタンスが消滅しなかったのは、 tonyという変数が、このインスタンスを参照していたためです。
MacのOSやiOSには「Automatic Reference Counting(自動参照計数)」というシステムがあり、
「不要になった」すなわち「どんな変数やプロパティからも参照されなくなった」インスタンスは、
自動で削除するようになっています。
ただし、常に、すぐ削除するというわけではありません。一時的に参照されなくなったあと、
再びそのインスタンスが参照されることもあるかも知れないからです。
メモリに余裕があるかどうかによりシステムが判断しますが、
iOSの場合はただちに、参照されなくなったデータは消されると考えてよいと思います。
「ARC」というしくみを知った事により、以下のことが明らかになります。
すなわち、「 a = nil 」という操作は、aが参照していたデータを消去することではありません。
aが何も参照しないようにすることです。
aが参照しなくなったことで、データを参照するものがなくなったとき、ARCがそのデータを消すのです。
「参照されないデータは削除する」ARCですが、反面「参照されているデータは削除されません」。
これで起きる問題は、あるインスタンスを必要もないのに別の変数で参照すると、必要もないデータがアプリの
稼働中ずっと残ることです。
アプリを終了すればメモリにとどまった全てのデータは消えますので、あまり神経質になる必要はありませんが、
頻繁にインスタンスの作成・終了を繰り返すようなアプリですと、メモリの食い過ぎで、iOSに強制終了させられるでしょう。
「参照されているデータは削除されない」しくみが起こすもうひとつの問題は、「相互参照」です。
リスト12に「注意が必要」と示したように、そこからの作業により、インスタンス「us2014」も「tony」を 参照し、かつ「tony」も「us2014」を参照する「相互参照」となっています。
すると、どちらも、消去されません。相手が参照しているからです。
リスト17に至るまでの目的は、ツアーがキャンセルされても奏者は消滅しないようにすることでした。
その目的は達成しましたが、実は隠れた問題があります。
インスタンスus2014からはインスタンスtonyの情報は失われましたが、tonyのほうにはus2014の情報が残っています。
確認のために、リスト16のすぐあとに、tonyのプロパティtourを書き出す以下の文を追加して実行してみましょう。
//ベースとドラムの奏者を追加 myStr += "\(tony.name)さんは\(tony.tour!.name)の成功を願っています。\n"
「トニーさんは2014アメリカ横断スーパーツアーの成功を願っています」という文字列が表示されるでしょう。
アプリが終了すれば、アプリに関連するデータは全て消されますから、2つや3つの小さなデータなら問題はありません。
しかし、このような相互参照データがいくつもあって、生成・消滅(実はしていない)を繰り返すようになると、「メモリリーク」が深刻になります。
また、むしろ相互参照が必要な場合もあるでしょう。
そのような場合、参照関係をどのように対処すればよいか、続編で解説します。