var name = ""。Swiftで最もよく使う1行ですが、ここに置ける選択肢は思ったより多くあります。stored property、computed property、lazy、willSetとdidSet、そしてtype propertyです。構文は一通り学んでも、「この値はlazyにすべきか、computedにすべきか」と考えた瞬間に基準が曖昧になりがちです。
Swift入門シリーズ第3回です。5種類のプロパティを、構文の羅列ではなく選択基準を中心に整理します。核心となる問いは1つ。この値は保存されるのか、計算されるのか、そしていつ生成されるのか。
共有状態が必要だからといってtype propertyを選ぶ前に、Swiftシングルトンのテストコストも確認しておくと、static letとstatic varの境界がより明確になります。
stored vs computed — メモリにある値と、その都度作る値
プロパティの最初の分岐は、保存か計算かです。
stored propertyは、インスタンス内で実際に領域を占める値です。var name = "Kim"と書くと、インスタンスメモリにnameの領域が確保されます。structのサイズはstored propertyの合計で決まります。
computed propertyには領域がありません。アクセスするたびにコードを実行して値を作る、実質的には関数です。
struct Rectangle {
var width: Double // 保存
var height: Double // 保存
var area: Double { // 計算 — 保存領域なし
width * height
}
}
areaをstored propertyにすることもできました。しかしそうするとwidthが変わるたびにareaも更新する必要があり、2つがずれた瞬間にバグになります。ここで最初の基準が得られます。他の値から導かれる値は保存せず、計算する。single source of truthを1つに保てば、同期バグを根本からなくせます。
computed propertyにsetを付ければ書き込みも可能です。celsiusを保存し、fahrenheitを計算にして、fahrenheitに代入されたらcelsiusを逆算して更新する形です。ここでも保存される真実は1つで、それ以外はその真実を映す窓です。
関数ではなくcomputed propertyを使う基準も確認しておきましょう。AppleのAPI Design Guidelinesの慣例は次のとおりです。計算が安価で、概ねO(1)に近く、副作用がなく、概念的に「そのオブジェクトの属性」であるならproperty。計算コストが高い、または副作用があるならmethodです。array.countがpropertyで、array.sorted()がmethodなのはこのためです。
lazy — 初めて使うときに作られるstored property
lazyは3つ目の選択肢です。stored propertyですが、インスタンス生成時ではなく、初回アクセス時に初期化されます。
class ImageProcessor {
lazy var filters: [Filter] = loadExpensiveFilters()
}
使いどころは2つあります。1つ目は初期化コストが高く、使わない可能性がある値です。重いリソースをインスタンス生成のたびに準備するのは無駄だからです。2つ目はselfを参照する必要があるプロパティです。通常のstored propertyのデフォルト値はinit完了前に計算されるためselfを使えませんが、lazyは初回アクセスまで初期化を遅延できるのでselfを参照できます。クロージャ編で見た即時実行クロージャ(= { ... }())とlazyがよく組み合わされる理由です。
注意点も2つあります。lazyは必ずvarでなければなりません。値が「後から変わる」ためletにはできません。またスレッドセーフではありません。複数スレッドが同時に初回アクセスすると、初期化が2回実行される可能性があります。マルチスレッド環境で必ず1回だけ生成したい値には、別の仕組みが必要です。
computed propertyとの違いも明確です。lazyは一度計算して保存し、その後はその値を返します。computedは毎回再計算します。「高コストだが変わらない値」ならlazy、「安価だが常に最新である必要がある値」ならcomputedです。
willSetとdidSet — 値の変更に反応する
stored propertyにはオブザーバーを付けられます。値が変わる直前(willSet)と直後(didSet)に実行されるコードです。
class ProgressBar {
var progress: Double = 0 {
didSet {
// oldValueが自動的に提供されます
guard progress != oldValue else { return }
updateUI()
}
}
}
典型的な用途は、値が変わったら連動して行う処理を自動化することです。UI更新、ロギング、値の検証(範囲外なら戻す)などが該当します。自分でsetterを作らず、代入構文を保ったまま副作用を組み込めます。
実務で混乱しやすい動作規則を2つだけ挙げます。まず、init内で値を代入してもオブザーバーは呼ばれません。初期化は「変更」ではなく「設定」として設計されているため、didSetのUI更新は初期値には適用されません。次に、値型のプロパティを変更すると、それを含むプロパティのdidSetも呼ばれます。person.name = "Lee"のようにstructの内部だけを変えても、person自体のdidSetが呼ばれます。値型編で見た「変更とは新しい値への置き換え」という意味論がここでも貫かれています。
didSetの乱用には注意が必要です。didSetにロジックが積み重なると、代入1行が何を引き起こすのか追跡しにくくなります。値を1つ変えただけでネットワークリクエストが発生するなら、最小驚きの原則に反します。didSetには軽い同期だけを置き、重いロジックは明示的なメソッドに切り出すのが安全です。
type property — インスタンスではなく型に付く値
staticを付けると、プロパティはインスタンスではなく型そのものに付きます。
struct APIConfig {
static let baseURL = URL(string: "https://api.example.com")!
static var requestCount = 0
}
インスタンスをいくつ作っても、type propertyは1つだけです。設定値や共有formatterなどの定数の集合に向いており、標準ライブラリもInt.maxやDouble.piのような場所でよく使っています。
知っておくとよい事実が1つあります。static letはスレッドセーフな遅延初期化を保証します。初回アクセス時に一度だけ初期化され、同時アクセスにも安全です。lazy varにはない保証がstaticにはあります。シングルトンのstatic let sharedが別途ロックなしで成り立つ根拠でもあります。ただしstatic varの可変状態は実質的にグローバル変数なので、テストの分離を損ない、データ競合の候補になります。シングルトンがアンチパターンと呼ばれる理由は別記事で扱ったので、ここでは「static varは最後の手段」という基準だけ覚えておきましょう。
選択フローチャート — 4つの質問で整理
5種類を1行の質問に圧縮すると、次のようになります。
- 他の値から導かれるか? → computed property。真実は1つだけ保存します。
- 初期化コストが高い、またはselfが必要か? → lazy var。マルチスレッドでの初回アクセスには注意。
- 値の変更に伴う処理があるか? → stored property + didSet。ただし軽い処理だけにします。
- インスタンスに関係なく1つでよいか? → static。letなら安全な遅延初期化も得られます。
- どれにも当てはまらないなら → 普通のstored property。ほとんどの場合、これが正解です。
SwiftUIの@Stateや@Publishedのようなproperty wrapperも、結局はこのproperty systemの上に作られた構文です。stored・computed・observerの原理を理解していれば、wrapperが「stored propertyを包み、didSetのような動作を自動化したもの」だと分かります。property wrapperの自作は中級シリーズで扱う予定です。
実行して確認した結果
Apple Swift 6.3.3で、lazyの初期化回数、didSetの旧値と新値、computed propertyの結果を1つのオブジェクトに記録しました。lazyを2回読み、stored propertyを0から7に変更した出力です。
properties=lazy-builds:1,didSet:0->7,computed:14
この小さな実行結果から、キャッシュを本当に1回だけ作る必要があるなら、lazyという構文だけに頼ることはしません。同時アクセスの可能性があるなら初期化回数をテストし、別途同期が必要か確認します。一方、didSetには上の出力のように状態変化を観察する軽い処理だけを置き、失敗し得るI/Oは明示的なメソッドへ移します。
まとめ
- プロパティの最初の基準は、保存か計算かです。他の値から導かれる値はcomputedにして、single source of truthを1つに保ちます。
- lazyは初回アクセス時に初期化されるstored propertyで、高コストな初期化とself参照の問題を解決します。varが必須で、スレッドセーフではないことが代償です。
- willSet/didSetは値の変更に反応する仕組みですが、initでは呼ばれず、重いロジックを入れると追跡が難しくなります。
- static letはスレッドセーフな遅延初期化が保証されたtype propertyで、static varはグローバル状態なので最後の手段です。
次回は入門第4回、guardです。オプショナル編で少し触れた早期脱出を掘り下げ、なぜSwiftコミュニティがインデントと戦っているのかを整理します。
あわせて読みたい
- Swiftオプショナルの正体:実はenumです(実務で使えるアンラップ5選まで)
- [Swift入門 #4] Swift guardの正しい使い方:破滅のピラミッドを倒す早期脱出
- [Swift入門 #5] Swiftエラー処理の全体像:throws・try?・try!・Resultをいつ使うか
出典と確認基準
- The Swift Programming Language: PropertiesSwift.org · 公式ドキュメント · 確認日 2026年8月26日根拠: stored・computed・lazy・プロパティオブザーバー・型プロパティの言語仕様

![[Swift入門 #3] Swiftプロパティ総まとめ:stored・computed・lazy・didSetの選び方4つのカバー画像](/assets/images/posts/7621582a-1bb6-4f5a-8fca-12fae48417af/1.jpg)