Edited at

Swiftのインスタンス生成と解放

More than 1 year has passed since last update.

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

【 ポイント 】


  1. 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

【 ポイント 】



  1. fooCarbarCarは一つの同じインスタンスを参照している。だからfooCarbarCarは常に同じ残り燃料となる。



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"

【 ポイント 】


  1. init() が2回呼ばれている。 => 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"

 

【 ポイント 】


  1. barCarが参照しているインスタンスは内側のdoブロックを出るタイミングで解放されている


  2. 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回)しか生成されていない。

  2. その1つのインスタンスをfooCarbarCarの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"

【 ポイント 】


  1. 関数 paint() の外側のfooCarbarCarも、そして関数 paint() の内側のcarも、たった一つのインスタンスを参照している。

  2. だから、どれか一つの参照型変数のプロパティを書き換えると、他の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"
}

【 ポイント 】


  1. 構造体(struct)は値型だからfooCarbarCarは異なる。

  2. 関数 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
}