Swiftで参照渡ししたい時のinoutの便利な使い方

inoutは引数に入れた変数を関数の処理により変更する際に使用するコードです。

このページではinoutを使わない場合と比較しつつinoutの使い方について解説します。

inoutのなしとありの比較

まずは簡単なコードでinoutなしの場合とありの場合を比較します。inoutなしのコードは以下のようになります。

var aa = 0
func plusA(_ a: Int) {
    aa += 10
}
plusA(aa)
print(aa) // 0

シンプルな加算処理ですが、出力結果では加算処理が反映されていません。理由は関数のスコープで、スコープ外ということで関数の処理は外部の変数に影響を与えていないからです。では次にinoutありで同じ処理を実行します。

var bb = 0
func plusB( _ b: inout Int) {
    b += 10
}
plusB(&bb)
print(bb) // 10

このコードでは関数内の処理が外部の変数に影響を与えています。そのため、最終的な出力結果は10になっています。このように、関数外の変数に影響を与えるために指定するのがinoutです。関数側でinoutを指定し、呼び出し側では&を付けます。

多少面倒な構造にはなっていますが、これは誤って関係ない変数に影響を与えないため安全性を考慮したSwiftの仕様です。変数が二つ以上あるコードでの例は以下です。まず値渡しの方です。

func swapInt(var a:Int, var b:Int)
{
    var tmp:Int
    tmp = a
    a = b
    b = tmp
}

var x:Int = 2
var y:Int = 3

println("x:\(x)") //x:2
println("y:\(y)") //y:3

swapInt(x, y)

//値渡しなのでSwapされない
println("x:\(x)") //x:2
println("y:\(y)") //y:3

次に、参照渡しのコードです。

func swapInt(inout a:Int, inout b:Int)
{
    var tmp:Int
    tmp = a
    a = b
    b = tmp
}

var x:Int = 2
var y:Int = 3

println("x:\(x)") //x:2
println("y:\(y)") //y:3

swapInt(&x, &y)

//参照渡しなのでSwapされている
println("x:\(x)") //x:3
println("y:\(y)") //y:2

スワップするコードでしたが、inoutと&を使用した参照渡しでのみスワップされています。

コードの失敗例

inoutの挙動がうまくいかないケースもあるようです。具体的なコードは以下です。

func test(inout param: Int) -> () -> Void {
    let closure = {
        param = param + 1
    }
    closure() // 呼び出し(A)
    return closure
}

var x = 42
print(x) // "42"
let closure = test(&x)
print(x) // "43" : (A)で呼んだから + 1 されてる
closure() // 呼び出し(B)
print(x) // "43" : (B)でまた呼んだのに + 1 されてない。

次に、同じ処理内容でうまくいくパターンのコードです。

class Wrapper {
    var value: Int
    init(value: Int) { self.value = value }
}

func test(param: Wrapper) -> () -> Void {
    let closure = {
        param.value = param.value + 1
    }
    closure() // 呼び出し(A)
    return closure
}

var x = Wrapper(value: 42)
print(x.value) // "42"
let closure = test(x)
print(x.value) // "43" : Aで呼んだから + 1 されてる
closure() // 呼び出し(B)
print(x.value) // "44" : Bでまた呼んだから + 1 されてる

inoutで挙動がうまくいかない場合クラス経由に書き換えることで処理が実行されるケースがあるようです。Swiftはバージョンによる仕様の変化が激しいためバージョンによって挙動が変わりますが、今まで動いていなかったものが動くようになったり、逆に動いていたものが動くようになったりすることもあるでしょう。

バージョン変更でバグが解消されたり逆にバグが発生することもありますが、そもそも仕様が変わって使えないコードが出てくる場合もあります。仕様変更を意識しておくことと、動かなかった場合に備えて複数のコーディングパターンを考えておくことが重要です。

配列の値渡しと参照渡し

変数でのinoutについて解説してきましたが、次に配列で紹介します。まず値渡しのサンプルコードは以下です。

func plusOne(array : [Int]) -> [Int] {
    var array = array
    for i in 0 ..< array.count {
        array[i] = array[i] + 1
    }
    return array
}
var oldArray = [0, 1, 2]
print(oldArray) // => [0, 1, 2]
var newArray = plusOne(array: oldArray) // => [1, 2, 3]
print(oldArray) // => [0, 1, 2]

値渡しなので配列の中身は変化していません。次に参照渡しのサンプルコードです。

func plusOne(array : inout [Int]) -> [Int] {
    for i in 0 ..< array.count {
        array[i] = array[i] + 1
    }
    return array
}
var oldArray = [0, 1, 2]
print(oldArray) // => [0, 1, 2]
var newArray = plusOne(array: &oldArray) // => [1, 2, 3]
print(oldArray) // => [1, 2, 3]

参照渡しでinoutを使用すると、配列の中身が変わりました。変数でも配列でも値渡し、参照渡しの違いは同じです。

クラスインスタンスの値渡しと参照渡し

変数や配列同様に、クラスインスタンスも値渡しと参照渡しがあります。そして扱いとしては同じです。まず値渡しのサンプルコードは以下です。

class Car {
    var color : String
    init(color : String) {
        self.color = color
    }
}

func swap(car1: Car, car2: Car) {
    let tmp = car1.color
    car1.color = car2.color
    car2.color = tmp
}

do {
    let fooCar = Car(color: "Red")
    let barCar = Car(color: "Blue")

    print(fooCar.color) // => "Red"
    print(barCar.color) // => "Blue"

    swap(car1: fooCar, car2: barCar)

    print(fooCar.color) // => "Blue"
    print(barCar.color) // => "Red"
}

次に参照渡しのサンプルコードは以下です。

class Car {
    var color : String

    init(color : String) {
        self.color = color
    }
}

func swap(car1: inout Car, car2: inout Car) {
    let tmpCar : Car

    tmpCar = car1
    car1 = car2
    car2 = tmpCar
}

do {
    var fooCar = Car(color: "Red")
    var barCar = Car(color: "Blue")

    print(fooCar.color)
    print(barCar.color)

    swap(car1: &fooCar, car2: &barCar)

    print(fooCar.color)
    print(barCar.color)
}

参照渡しでは変数の中身が直接書き換えられます。

▼Swift関連の記事一覧
1, 【Swiftのフリーランス案件情報付き】仕事やエンジニアの将来性・学習方法!
2, 繰り返し処理に使えるSwiftのfor文とfor-in文の便利な使い方まとめ
3, 条件分岐に便利なSwiftのSwitch文の使い方まとめ
4, アプリ開発でよく使うSwiftの4つの文法を解説します
5, Swiftのoptional型を使いこなすための7つのポイント
6, Swiftで文字列を扱うためのstring型の使い方
7, Swiftでの配列の宣言・初期化・代入・参照の方法まとめ
8, 実際の開発現場でのSwiftの変数・定数の使われ方を解説します
9, Swiftでif let文を使って出来ることとguard letの使い分けに関して
10, 開発現場でよく使うSwiftのif文の条件分岐をまとめました
11, SwiftにおけるTupleの使い方とわかりやすく解説します
12, 初心者にオススメのSwiftのサンプルコードをまとめてみました
13, SwiftのString、Int、Doubleの型の変換方法をまとめました
14, 開発現場で役に立つSwiftのクロージャの使い方
15, Swiftでwhileを使って上手に繰り返し処理を行おう
16, Swiftで文字列の長さをチェックするのにlengthを使ってみよう
17, Swiftのdictionaryの使い方と初期化方法をまとめました
18, Swiftの値渡しと参照渡しに関するまとめ
19,(この記事)Swiftで参照渡ししたい時のinoutの便利な使い方
20, Swiftでfor-in文でindexを使いたい時の方法まとめ
21, Swiftでのクラスとstruct(構造体)の使い分けを解説します

人気記事

編集部おすすめ記事

この記事を読んだ人はこんな記事を読んでいます