SwiftのStructの参照渡し、値渡し、CoWの挙動確認


結論

  • クロージャへは参照渡し、キャプチャリストを使えば値渡し
  • 関数へは値渡し、inoutを使えば参照渡し

Copy-on-Write (CoW)

  • SwiftのArrayやStringはパフォーマンスを考慮して、内部的にCoWが実装されている

確認

環境

  • Xcode 9.4
  • Swift 4.1

Playground

import Foundation

func address(of object: UnsafeRawPointer) -> String {
    let addr = Int(bitPattern: object)
    return String(format: "%p", addr)
}

func printLine(_ line: Int = #line) {
    print("#L\(line)")
}

print("--- Clousure: Int ---\n")
do {
    var value = 10

    let closure1 = {
        print("- value = \(value) (\(address(of: &value)))") // valueは参照渡し
    }

    let closure2 = { [value] in
        print("- value = \(value) ← 値渡しなので影響を受けない") // valueは値渡し
    }

    let closure3 = {
        value += 10
        print("- value = \(value) (\(address(of: &value)))") // valueは参照渡し
    }

    printLine()
    print("- value = \(value) (\(address(of: &value)))") // value = 10

    printLine()
    closure1() // value = 10

    printLine()
    value = 20
    print("- value = \(value) (\(address(of: &value)))")
    closure1() // value = 20 (参照渡しなので反映されている)

    printLine()
    closure2() // value = 10 (値渡しなので反映されていない)

    printLine()
    closure3() // value = 30

    printLine()
    print("- value = \(value) (\(address(of: &value)))") // value = 30 (closure3での変更が反映されている)
}

print("\n--- Clousure: [Int] ---\n")
do {
    var values = [10]

    let closure1 = {
        print("- values = \(values) (\(address(of: &values)))") // valueは参照渡し
    }

    let closure2 = { [values] in
        print("- values = \(values) ← 値渡しなので影響を受けない") // valueは値渡し
    }

    let closure3 = {
        values.append(values.last! + 10) // CoW
        print("- values = \(values) (\(address(of: &values))) ← CoWによりバッファへのポインタが変更されていることが確認できる") // valueは参照渡し
    }

    printLine()
    print("- values = \(values) (\(address(of: &values)))")

    printLine()
    closure1() // values = [10]

    printLine()
    values.append(20) // CoW
    print("- values = \(values) (\(address(of: &values))) ← CoWによりバッファへのポインタが変更されていることが確認できる") // values = [10, 20]
    closure1() // values = [10, 20] (参照渡しなので反映されている)

    printLine()
    closure2() // values = [10] (値渡しなので反映されていない)

    printLine()
    closure3() // value = [10, 20, 30]

    printLine()
    print("- values = \(values) (\(address(of: &values)))") // value = [10, 20, 30] (closure3での変更が反映)
}

print("\n--- Function: Int ---\n")
do {
    var value = 10

    func function1(_ value: inout Int) {
        print("- value = \(value) (\(address(of: &value)))") // valueは参照渡し
    }

    func function2( _ value: Int) {
        print("- value = \(value) ← 値渡しなので影響を受けない")  // valueは値渡し
    }

    func function3(_ value: inout Int) {
        value += 10
        print("- value = \(value) (\(address(of: &value)))") // valueは参照渡し
    }

    printLine()
    print("- value = \(value) (\(address(of: &value)))") // value = 10

    printLine()
    function1(&value) // value = 10

    printLine()
    value = 20
    print("- value = \(value) (\(address(of: &value)))") // value = 20

    printLine()
    function3(&value) // value = 30

    printLine()
    print("- value = \(value) (\(address(of: &value)))") // value = 30 (function3での変更が反映されている)
}

print("\n--- Function: [Int] ---\n")
do {
    var values = [10]

    func function1(_ values: inout [Int]) {
        print("- values = \(values) (\(address(of: &values)))") // valuesは参照渡し
    }

    func function2( _ values: [Int]) {
        print("- values = \(values) ← 値渡しなので影響を受けない")  // valuesは値渡し
    }

    func function3(_ values: inout [Int]) {
        values.append(values.last! + 10) // CoW
        print("- values = \(values) (\(address(of: &values))) ← CoWによりバッファへのポインタが変更されていることが確認できる") // valuesは参照渡し
    }

    printLine()
    print("- values = \(values) (\(address(of: &values)))") // values = [10]

    printLine()
    function1(&values) // values = [10]

    printLine()
    values.append(20) // CoW
    print("- values = \(values) (\(address(of: &values))) ← CoWによりバッファへのポインタが変更されていることが確認できる") // values = [10, 20]

    printLine()
    function3(&values) // value = [10, 20, 30]

    printLine()
    print("- values = \(values) (\(address(of: &values)))") // values = [10, 20, 30] (function3での変更が反映)
}

結果

--- Clousure: Int ---

#L31
- value = 10 (0x608000021d90)
#L34
- value = 10 (0x608000021d90)
#L37
- value = 20 (0x608000021d90)
- value = 20 (0x608000021d90)
#L42
- value = 10 ← 値渡しなので影響を受けない
#L45
- value = 30 (0x608000021d90)
#L48
- value = 30 (0x608000021d90)

--- Clousure: [Int] ---

#L69
- values = [10] (0x600000054ed0)
#L72
- values = [10] (0x600000054ed0)
#L75
- values = [10, 20] (0x60c0000553b0) ← CoWによりバッファへのポインタが変更されていることが確認できる
- values = [10, 20] (0x60c0000553b0)
#L80
- values = [10] ← 値渡しなので影響を受けない
#L83
- values = [10, 20, 30] (0x60c00006aba0) ← CoWによりバッファへのポインタが変更されていることが確認できる
#L86
- values = [10, 20, 30] (0x60c00006aba0)

--- Function: Int ---

#L107
- value = 10 (0x7ffeeaabede8)
#L110
- value = 10 (0x7ffeeaabede8)
#L113
- value = 20 (0x7ffeeaabede8)
#L117
- value = 30 (0x7ffeeaabede8)
#L120
- value = 30 (0x7ffeeaabede8)

--- Function: [Int] ---

#L141
- values = [10] (0x60c000056160)
#L144
- values = [10] (0x60c000056160)
#L147
- values = [10, 20] (0x604000055410) ← CoWによりバッファへのポインタが変更されていることが確認できる
#L151
- values = [10, 20, 30] (0x604000066b60) ← CoWによりバッファへのポインタが変更されていることが確認できる
#L154
- values = [10, 20, 30] (0x604000066b60)

参考