Swiftのインスタンス生成と解放を理解するためにサンプルコードを書いてみました。
Xcode 8.2.1のPlaygroundで動作を確認しています。
1. 2つのインスタンスの生成
class Car {
var fuel : Int = 0 // 搭載燃料(残り燃料)量
// 給油関数 (引数:給油量)
func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
// fooCar と barCar の2つの自動車インスタンスを生成
var fooCar = Car()
var barCar = Car()
// 初期化直後の残り燃料を表示
print(fooCar.fuel) // => 0
print(barCar.fuel) // => 0
// fooCar と barCar に給油する
fooCar.refuel(fuel : 30) // 30リットル給油
barCar.refuel(fuel : 50) // 50リットル給油
// 給油後の残り燃料を表示
print(fooCar.fuel) // => 30
print(barCar.fuel) // => 50
【 ポイント 】
- 2つのインスタンス(車)が存在しているから残り燃料(fuel)は車ごとに異なる。
2. 2つの変数が1つのインスタンスを参照
class Car {
var fuel : Int = 0
func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
var fooCar = Car()
var barCar = fooCar // fooCarを代入する
print(fooCar.fuel) // => 0
print(barCar.fuel) // => 0
fooCar.refuel(fuel : 30)
barCar.refuel(fuel : 50)
print(fooCar.fuel) // => 80
print(barCar.fuel) // => 80
【 ポイント 】
- fooCarとbarCarは一つの同じインスタンスを参照している。だからfooCarとbarCarは常に同じ残り燃料となる。
3. インスタンスの生成と解放のタイミング
class Car {
var fuel : Int = 0
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
deinit {
print("destruct \(self.color) car")
}
func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
do {
var fooCar = Car(color: "Red") // => "create Red car"
var barCar = Car(color: "Blue") // => "create Blue car"
print(fooCar.fuel)
print(barCar.fuel)
fooCar.refuel(fuel : 30)
barCar.refuel(fuel : 50)
print(fooCar.fuel)
print(barCar.fuel)
}
// => "destruct Blue car"
// => "destruct Red car"
【 ポイント 】
- init() が2回呼ばれている。 => 2つのインスタンスが生成されている。
- deinit が2回呼ばれている。 => doブロックから出たタイミングで2つのインスタンスが解放されている。
4. 生存期間(Life-time)が異なる2つのインスタンス
class Car {
var fuel : Int = 0
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
deinit {
print("destruct \(self.color) car")
}
func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
do {
var fooCar = Car(color: "Red") // => "create Red car"
do {
var barCar = Car(color: "Blue") // => "create Blue car"
print(fooCar.fuel) // => 0
print(barCar.fuel) // => 0
fooCar.refuel(fuel : 30)
barCar.refuel(fuel : 50)
print(barCar.fuel) // => 50
} // => "destruct Blue car"
print(fooCar.fuel)
} // => "destruct Red car"
【 ポイント 】
-
barCarが参照しているインスタンスは内側のdoブロックを出るタイミングで解放されている
-
fooCarが参照しているインスタンスは外側のdoブロックを出るタイミングで解放されている
5. 2つの変数が1つのインスタンスを参照 その2
class Car {
var fuel : Int = 0
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
deinit {
print("destruct \(self.color) car")
}
func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
do {
var fooCar = Car(color: "Red") // => "create Red car"
var barCar = fooCar
print(fooCar.fuel)
print(barCar.fuel)
fooCar.refuel(fuel : 30)
barCar.refuel(fuel : 50)
print(fooCar.fuel)
print(barCar.fuel)
} // => "destruct Red car"
【 ポイント 】
- インスタンスは1つ(1回)しか生成されていない。
- その1つのインスタンスをfooCarとbarCarの2つの変数から参照している
6. 関数引数が参照型のときの関数の外側と内側の変数のふるまい
class Car {
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
deinit {
print("destruct \(self.color) car")
}
}
func paint(car: Car, color: String) {
car.color = color
}
do {
var fooCar = Car(color: "Red") // => "create Red car"
var barCar = fooCar
print(fooCar.color) // => "Red"
print(barCar.color) // => "Red"
print(fooCar === barCar ? "同一である" : "同一でない")
// => "同一である"
paint(car: fooCar, color: "Yellow")
print(fooCar.color) // => "Yellow"
print(barCar.color) // => "Yellow"
} // => "destruct Yellow car"
【 ポイント 】
- 関数 paint() の外側のfooCarもbarCarも、そして関数 paint() の内側のcarも、たった一つのインスタンスを参照している。
- だから、どれか一つの参照型変数のプロパティを書き換えると、他の2つの変数のプロパティも書き換わっている。
7. 構造体への書き換え
struct Car { // 構造体(struct)で定義
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
// 構造体はdeinitを記述できない(記述する必要はない)
}
func paint(car: inout Car, color: String) { // 参照渡し(inout)
car.color = color
}
do {
var fooCar = Car(color: "Red") // => "create Red car"
var barCar = fooCar
print(fooCar.color) // => "Red"
print(barCar.color) // => "Red"
paint(car: &fooCar, color: "Yellow")
print(fooCar.color) // => "Yellow"
print(barCar.color) // => "Red"
}
【 ポイント 】
- 構造体(struct)は値型だからfooCarとbarCarは異なる。
- 関数 paint() の内側でのcarの変更を関数の外側に引き継ぎたい、という理由で参照渡し(inout)を使っている。
8. NSCopyingプロトコルを継承したインスタンスの複製
import Foundation
class Car : NSObject, NSCopying {
var fuel : Int = 0
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
deinit {
print("destruct \(self.color) car")
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Car(color:color)
copy.fuel = fuel
return copy
}
func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
do {
var fooCar = Car(color: "Red")
fooCar.refuel(fuel : 30) // => "create Red car"
print(fooCar.fuel) // => 30
do {
var barCar = fooCar.copy() as! Car // => "create Red car"
print(barCar.fuel) // => 30
print(fooCar === barCar ? "同一である" : "同一でない")
// => "同一でない"
barCar.refuel(fuel : 50)
print(barCar.fuel) // => 80
} // => "destruct Red car"
print(fooCar.fuel) // => 30
} // => "destruct Red car"
9. Carをクラス(参照型)から構造体(値型)に書き換え
struct Car {
var fuel : Int = 0
var color : String
init(color : String) {
self.color = color
print("create \(self.color) car")
}
mutating func refuel(fuel : Int) -> Int {
self.fuel = self.fuel + fuel
return fuel
}
}
do {
var fooCar = Car(color: "Red")
fooCar.refuel(fuel : 30) // => 30
print(fooCar.fuel)
do {
var barCar = fooCar
print(barCar.fuel) // => 30
barCar.refuel(fuel : 50)
print(barCar.fuel) // => 80
}
print(fooCar.fuel) // => 30
}