Swiftで関数を作成していると、「パラメータの値を変更したいのに、変更が反映されない」という壁にぶつかることがあります。その解決策の一つがinout
キーワードです。この記事では、Swiftのinout
について、基礎から実践的な使い方まで初心者にもわかりやすく解説します。
inoutとは何か
inout
は、Swiftで関数のパラメータを参照渡しにするためのキーワードです。
通常、Swiftの関数パラメータは値渡し(コピー)で渡されるため、関数内で値を変更しても呼び出し元には影響しません。しかし、inout
を使うことで、関数内での変更を呼び出し元に反映させることができます。
値渡しと参照渡しの違い
プログラミングには、データを関数に渡す方法として主に2つの方式があります。
値渡し(Pass by Value) データのコピーを関数に渡す方式です。関数内で値を変更しても、元のデータには影響しません。
参照渡し(Pass by Reference) データの参照(アドレス)を関数に渡す方式です。関数内で値を変更すると、元のデータも変更されます。
Swiftはデフォルトで値渡しですが、inout
を使うことで参照渡しを実現できます。
値渡しとinoutの違いを比較
まず、通常の値渡しの動作を見てみましょう。
値渡しの場合
func increment(number: Int) {
var number = number // パラメータをコピー
number += 1
print("関数内の値: \(number)")
}
var value = 5
increment(number: value)
print("関数外の値: \(value)")
// 出力:
// 関数内の値: 6
// 関数外の値: 5 ← 変更されていない
関数内で値を変更しても、呼び出し元のvalue
は変更されません。
inoutを使った場合
func incrementWithInout(number: inout Int) {
number += 1
print("関数内の値: \(number)")
}
var value2 = 5
incrementWithInout(number: &value2) // &をつける
print("関数外の値: \(value2)")
// 出力:
// 関数内の値: 6
// 関数外の値: 6 ← 変更されている!
inout
を使うと、関数内での変更が呼び出し元にも反映されます。
inoutの基本的な書き方
関数の定義
func 関数名(パラメータ名: inout 型) {
// 処理
}
パラメータの型の前にinout
キーワードを記述します。
関数の呼び出し
関数名(パラメータ名: &変数名)
呼び出し時には、変数名の前に&
(アンパサンド)をつけます。この&
は「この変数の参照を渡しますよ」という意味を持ちます。
inoutの実践的な使用例
例1:2つの値を入れ替える
func swap(a: inout Int, b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
print("入れ替え前: x=\(x), y=\(y)")
swap(a: &x, b: &y)
print("入れ替え後: x=\(x), y=\(y)")
// 出力:
// 入れ替え前: x=10, y=20
// 入れ替え後: x=20, y=10
実は、Swiftの標準ライブラリにはswap()
関数が用意されているため、実際にはこのように書けます。
var x = 10
var y = 20
swap(&x, &y) // 標準ライブラリの関数
例2:配列の要素を直接変更
func doubleAllValues(array: inout [Int]) {
for i in 0..<array.count {
array[i] *= 2
}
}
var numbers = [1, 2, 3, 4, 5]
print("変更前: \(numbers)")
doubleAllValues(array: &numbers)
print("変更後: \(numbers)")
// 出力:
// 変更前: [1, 2, 3, 4, 5]
// 変更後: [2, 4, 6, 8, 10]
例3:文字列を大文字に変換
func uppercaseString(text: inout String) {
text = text.uppercased()
}
var message = "hello world"
print("変更前: \(message)")
uppercaseString(text: &message)
print("変更後: \(message)")
// 出力:
// 変更前: hello world
// 変更後: HELLO WORLD
例4:複数の値を同時に変更
func updateUserInfo(name: inout String, age: inout Int) {
name = name.uppercased()
age += 1
}
var userName = "taro"
var userAge = 25
updateUserInfo(name: &userName, age: &userAge)
print("名前: \(userName), 年齢: \(userAge)")
// 出力:
// 名前: TARO, 年齢: 26
inoutを使う際の注意点
1. 変数のみ渡せる
inout
パラメータには変数しか渡せません。定数や直接の値(リテラル)は渡せません。
func increment(number: inout Int) {
number += 1
}
// これはOK
var mutableValue = 5
increment(number: &mutableValue)
// これはエラー
let constantValue = 5
// increment(number: &constantValue) // エラー!
// これもエラー
// increment(number: &10) // エラー!
2. &(アンパサンド)を忘れずに
inout
パラメータを渡すときは、必ず&
をつける必要があります。
var value = 10
// 正しい
increment(number: &value)
// エラー
// increment(number: value) // &がないのでエラー
3. 同じ変数を複数のinoutパラメータに渡せない
同じ変数を同時に複数のinout
パラメータとして渡すことはできません。
func addValues(a: inout Int, b: inout Int) {
a = a + b
}
var value = 5
// addValues(a: &value, b: &value) // エラー!
4. グローバル変数やプロパティとの競合に注意
inout
パラメータとして渡した変数は、関数実行中に他の方法でアクセスすることができません。
var globalValue = 10
func modifyGlobal(value: inout Int) {
value += 1
// print(globalValue) // 実行中は直接アクセスできない
}
// modifyGlobal(value: &globalValue)
inoutとreturnの使い分け
多くの場合、inout
を使わずに戻り値を返す方法でも同じことができます。どちらを使うべきでしょうか?
returnを使う方法
func doubled(number: Int) -> Int {
return number * 2
}
var value = 5
value = doubled(number: value)
print(value) // 10
inoutを使う方法
func double(number: inout Int) {
number *= 2
}
var value = 5
double(number: &value)
print(value) // 10
どちらを選ぶべきか
returnを使うべき場合
- 1つの値を返すだけの場合
- 関数型プログラミングのスタイルを重視する場合
- 元の値を保持したい場合
inoutを使うべき場合
- 複数の値を変更する必要がある場合
- 大きなデータ構造のコピーを避けたい場合(パフォーマンス向上)
- 標準ライブラリの関数(
swap
など)との一貫性を保つ場合
一般的に、Swiftでは戻り値を使う方が推奨されています。inout
は必要なときだけ使うようにしましょう。
inoutのパフォーマンス面でのメリット
大きなデータ構造を扱う場合、inout
はパフォーマンス面で有利になることがあります。
struct LargeData {
var items: [Int] = Array(repeating: 0, count: 10000)
}
// 値渡し:データ全体をコピー
func processWithCopy(data: LargeData) -> LargeData {
var data = data
data.items[0] = 999
return data
}
// inout:コピーなし
func processWithInout(data: inout LargeData) {
data.items[0] = 999
}
inout
を使うと、データのコピーが発生しないため、メモリ効率が良くなります。
よくある間違いと解決法
間違い1:定数に使おうとする
let constant = 5
// increment(number: &constant) // エラー
解決法: 変数として宣言する
var variable = 5
increment(number: &variable) // OK
間違い2:&を忘れる
var value = 5
// increment(number: value) // エラー
解決法: &をつける
increment(number: &value) // OK
間違い3:関数内で不要なvarを使う
// 不要な書き方
func increment(number: inout Int) {
var number = number // 不要!
number += 1
}
解決法: inoutパラメータはそのまま使える
func increment(number: inout Int) {
number += 1 // これでOK
}
実務での活用例
データベースレコードの更新
struct User {
var name: String
var email: String
var age: Int
}
func updateUserProfile(user: inout User, newEmail: String, incrementAge: Bool) {
user.email = newEmail
if incrementAge {
user.age += 1
}
}
var currentUser = User(name: "太郎", email: "old@example.com", age: 25)
updateUserProfile(user: &currentUser, newEmail: "new@example.com", incrementAge: true)
ゲーム開発でのプレイヤー状態管理
struct Player {
var health: Int
var score: Int
var level: Int
}
func takeDamage(player: inout Player, damage: Int) {
player.health -= damage
if player.health <= 0 {
player.health = 0
}
}
func addScore(player: inout Player, points: Int) {
player.score += points
if player.score >= 100 {
player.level += 1
player.score = 0
}
}
まとめ
Swiftのinout
は、関数のパラメータを参照渡しにするための強力な機能です。重要なポイントをおさらいしましょう。
inoutの特徴
- 関数内での変更が呼び出し元に反映される
- パラメータの型の前に
inout
キーワードを記述 - 呼び出し時には変数の前に
&
をつける - 変数のみ渡せる(定数やリテラルは不可)
使い分けの基準
- 基本的には戻り値を使う方が推奨
- 複数の値を変更する場合や大きなデータ構造を扱う場合に
inout
を検討 - パフォーマンスが重要な場面では
inout
が有利
注意点
&
を忘れない- 定数には使えない
- 同じ変数を複数の
inout
パラメータに渡せない
inout
は便利な機能ですが、使いすぎると可読性が下がることもあります。適切な場面で使用することで、より効率的でわかりやすいSwiftコードを書くことができます。
まずは簡単な例から試してみて、徐々に実践的な場面で活用していきましょう。