このページは「はじめてのSwiftプログラミング」(清水美樹著、工学社刊、2014年7月11日発売、ISBN978-4-7775-1841-8)の著者によるサポートページです
「参照(Reference、リファレンス)」とは、値の置かれているメモリ上の位置を変数名で表すことです。
「メモリ上の位置」といっても、OSにより間接的な表現に変換されているので、その表現を直接読み取ることには、意味はありません。
第7章で説明したように、「参照」を論ずる時、Swiftでは「その変数はメモリ上の場所を示すのか、値を示すのか」が問題になります。
前者を「参照タイプ」後者を「値タイプ」と呼ぶ事にしましょう。
「参照タイプ」か「値タイプ」かという問題でまず注目されるのは、
「変数と変数で値の受け渡しをしたとき、一方の値を勝手に変更してもよいか?」という問題です。
var a = なにかの値 var b = a bのなにかの性質 = 別の物 aのなにかの性質は?
本書において随時議論してきたように、リスト1のような操作をした場合、aが「値タイプ」だった場合は、bの性質を変えてもaの性質は変わりません。
なぜなら、aが持つ「なにかの値」と同じ値を持つ「別のインスタンス」が新しく作成されて、bに渡されるからです。
そのあとbの性質を変えても、それはaとは別のインスタンスですから、aの性質は変わりません。
一方、aが「参照タイプ」だった場合、bには「aの値が置かれている場所」が伝えられるだけです。
「同じインスタンスの呼び方」が二通りになるのです。
そこで、「bを変更する」というのは「a を変更する」のと同じ意味になります。
プログラミングをしていくと、このような「参照」に関わるトラブルで困惑することがあります。
どのようなトラブルが起こり、それをどう克服して行くか、いくつかの例を紹介します。
参照で陥りやすい単純な間違いは、今扱っている変数が参照タイプか値タイプかを間違えることです。
「値タイプを参照タイプと間違えて、同じインスタンスを指すはずだった二つの変数が、別のインスタンスになってしまった」
「逆の間違いで、元の値を退避させたつもりが上書きされてしまった」などのトラブルです。
これらは、以下のような工夫で解決できます。
リスト2は本書で何度か解説してきた文字列の「値タイプ」としての性質です。 変数stringBは、stringAと同じ内容で別のインスタンスになりますので、stringBの内容を変えてもstringAの内容は変わりません。
var stringA = "Apple" var stringB = stringA stringB = "リンゴ" myStr = "stringAの文字列は\(stringA)" //”stringAの文字列はApple”
もし、同じ文字列を別名で使いたい場合はリスト3のように、クラスを作成し、 そのプロパティとしての文字列を扱います。
class ShareableString{ var str:String func change(#newStr:String){ str = newStr } init(str:String){ self.str = str } }
変数sStringAとsStringBは同じインスタンスを共有するので、そのプロパティstrも共有することになります。 よって、sStringBのプロパティstrを変更すればsStringAのstrも変わります。
var sStringA = ShareableString(str:"Apple") var myStr = "sStringAの文字列は\(sStringA.str)" //”sStringAの文字列はApple” var sStringB = sStringA sStringB.change(newStr:"リンゴ") var myStr = "sStringAの文字列は\(sStringA.str)" //”sStringAの文字列はリンゴ”
class noClone{ var name:String var age:Int init(name:String, age:Int){ self.name = name self.age = age } //自身をコピーした別のインスタンスを戻す func makeClone()->noClone{ return noClone( name:self.name, age:self.age) } //自身のプロパティを記述する func introduce()->String{ return "私は\(self.name), 年は\(self.age)歳" } }
まず、インスタンスを普通にコピーした場合を考えます。 次のリスト6のインスタンスcloneBは、その名の通り、「cloneAのクローンではありません」。同じインスタンスです。 そのため、cloneBのプロパティ「age」を変更すると、cloneAのageも変わります。
var cloneA = noClone(name:"黒音 瑛", age:18) var cloneB = cloneA ++cloneB.age cloneA.introduce() //"私は黒音 瑛, 年は19歳"
そこで、メソッドmakeCloneによって、本当の「クローン」を作ります。 こうして作成されたcloneCのプロパティを変更しても、cloneAのプロパティは変更されません。
var cloneC = cloneA.makeClone() cloneC.introduce() //"私は黒音 瑛, 年は19歳" cloneC.name = "久楼雲 椎" cloneC.introduce() //”私は久楼雲 椎, 年は19歳” cloneA.introduce() //"私は黒音 瑛, 年は19歳"
このように、「値タイプ」と「参照タイプ」の違いの問題は、特別な技術を必要としません。 「工夫」で行けます。