SwiftはAppleのプログラミング言語で、Objective-Cのパワーと最新言語の安全性を兼ね備えています。他の多くの言語とは異なり、Swiftは独自の構造を提供します:自動コピー付きの値型、コンパイラレベルの分離を備えたActor、プロパティロジックを再利用するためのProperty Wrapper、そして古典的な継承に代わるプロトコル指向プログラミング。Apple Swiftドキュメントによると、これらの機能によりコードの安全性とパフォーマンスが向上します。
重要なポイント
Swiftでは、structは値型、classは参照型です。この基本的な違いが、代入、関数への受け渡し、メモリ管理の際の動作を決定します。structは代入のたびに完全にコピーされ、classは参照で渡されます—ヒープオブジェクトへのポインタのみがコピーされます。
Structは自動的に値セマンティクスを実装します—各インスタンスは独立しています。Structプロパティの変更はvarを介してのみ可能で、プロパティを変更するメソッドはmutatingとマークする必要があります。Structは継承をサポートしませんが、プロトコルに準拠できます。Swiftの標準ライブラリはString、Array、Dictionary、Int、Boolにstructを使用します—すべての基本型は値型です。
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// 完全なコピー — 独立したインスタンス
Classはヒープに格納され、参照の等価性をサポートします。複数の変数が同じオブジェクトを参照できます。ある参照を介した変更は他のすべての参照からも見えます。Classは継承をサポートし、メソッドのオーバーライド(override)を許可し、リソース解放のためのdeinitを持ちます。Swiftでは、structよりもclassの使用頻度は低くなっています—Appleはstructから始め、継承や参照セマンティクスが必要な場合にのみclassに切り替えることを推奨しています。
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// 同じ参照 — 変更はどこでも見える
structとclassの選択はデータのセマンティクスに依存します。設定、データモデル、状態にはstructを使用してください—コピーの方が安全で予測可能です。アイデンティティを持つオブジェクト(ユーザーセッション、データベース接続)や継承が必要な場合はclassを使用してください。
Actorは、ミュータブルな状態を同時アクセスから保護する参照型です。Swiftコンパイラは、actor変数に同じactorコンテキストからのみアクセスできることを保証します—外部からのアクセスはコンパイルエラーになります。これにより、手動ロックなしで言語レベルでのデータ競合を防止します。
すべてのactorプロパティとメソッドはデフォルトで分離されています。外部からactorメソッドを呼び出すにはawaitが必要です—Swiftは実行をactorに切り替えて戻します。Actorはアクセスを順次同期します:異なるタスクからのリクエストはキューに入れられます。これにより、actor内部で一貫した状態が保証されます。非分離メソッド(nonisolated)はawaitなしで呼び出せますが、ミュータブルなプロパティにはアクセスできません。
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "銀行口座"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — アトミック、競合状態なし
Actorは、複数のタスクが同時にデータを読み書きする状態マネージャー、キャッシュ、ネットワークサービスに便利です。NSLockやDispatchQueueを使った手動同期とは異なり、actorはコンパイル時にチェックされるため、間違いを犯すことは不可能です。分離エラーは実行時ではなくコンパイル時に発生します。
Property Wrapperは、プロパティの宣言を変更せずに動作を追加するアノテーションです。SwiftUIとCombineは、ビューの状態管理のための組み込みラッパーを提供します。コンパイラはプロパティアクセスをラップするコードを生成し、監視、同期、変換のロジックを追加します。
@Publishedは、ObservableObjectに準拠するクラスで変更を自動公開するために使用されます。Combineのサブスクライバーは更新のたびに通知を受け取ります。@StateはSwiftUIビューのローカル状態です。@Stateが変更されると、SwiftUIは自動的にビューを再描画します。@Bindingは、データを所有せずに親ビューと子ビューの間に双方向の接続を作成します。
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "こんにちは"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("増加", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("カウント: \(count)")
}
}
カスタムProperty Wrapperは、@propertyWrapperとwrappedValueフィールドを持つ構造体を使用して作成します。これにより、検証、正規化、UserDefaults保存などのロジックを再利用できます。Property Wrapperはボイラープレートコードを大幅に削減し、アノテーションを通じて開発者の意図を明確にします。
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — 値が自動的に制限される
Genericsは、型安全性を失わずに柔軟なコードを記述するための強力なSwiftのメカニズムです。ジェネリック関数と型は、制約を満たす任意の型で動作します。プロトコル指向プログラミング(POP)は、protocol + extensionが古典的なクラス継承を置き換え、振る舞いの合成を提供するSwiftのパラダイムです。
Swiftは、プロトコル(where句)でジェネリックパラメータを制約できます。関連型を持つプロトコルは、プロトコルに対するジェネリックの同等物です:具体的な型は準拠時に決定されます。これは型安全なコレクションとアルゴリズムの基礎です。Swiftの標準ライブラリは積極的にGenericsを使用しています:Array、Dictionary、Optional — すべてジェネリック型です。
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — すべてのEquatable型に対する1つのジェネリックメソッド
プロトコルextensionはPOPの重要なメカニズムです。extensionではプロトコルメソッドのデフォルト実装を提供できます。プロトコルに準拠する型はこの実装を無料で取得し、必要に応じてオーバーライドできます。これにより、クラス継承なしで振る舞いの階層を構築できます—Swiftにはない多重継承の代替手段です。
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("時速\(speed)kmで走行中")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("時速\(speed)kmで飛行中")
}
}
let bike = Bicycle()
bike.drive() // "時速15.0kmで走行中" — デフォルト
let plane = Plane()
plane.drive() // "時速900.0kmで飛行中" — オーバーライド
extensionを使用したPOPは、複数のプロトコルを通じた合成を可能にします—型は複数のプロトコルに準拠し、その実装を取得できます。これは階層全体が固定された単一クラス継承よりも柔軟です。Swiftの標準ライブラリはPOP上に構築されています:CustomStringConvertible、Equatable、Hashable、Codable — すべてextensionを持つプロトコルです。
EnumはSwiftでは他の言語よりもはるかに強力な構造です。各ケースは関連値を持つことができます—任意の型のデータセット。extensionとパターンマッチングを組み合わせることで、enumは状態、操作結果、有限状態マシンのモデリングの基盤となります。
関連値により、各ケースが固有のデータを格納できます。たとえば、loadingケースはデータを含まず、successは結果を含み、failureはエラーを含みます。switchによるパターンマッチングは関連値を抽出し、各ケースを処理します。コンパイラは網羅性をチェックします(exhaustive switch)—これにより、忘れられた状態が排除されます。
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("読み込み中...")
case .success(let data):
print("受信: \(data)")
case .failure(let error):
print("エラー: \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("動画 \(url) 再生時間 \(duration)秒")
}
Swiftでは、extensionを使用してenumにメソッド、計算プロパティ、プロトコル準拠を追加できます。元の宣言を変更する必要はありません—コードはクリーンでモジュール化されたままです。extension内のメソッドは関連値を操作し、派生データを計算し、ビジネスロジックを実装できます。
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "画像 \(w)x\(h)"
case .video(_, let d):
return "動画 \(d)秒"
case .audio(_, let b):
return "音声 \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "動画 120.0s"
関連値 + extensionを持つEnumは、Kotlinのsealedクラスや他の言語のdiscriminated unionの代替です。Swiftはこの組み合わせをResult(標準ライブラリの標準型)、Optional(Optionalは.noneと.someを持つenumです)、およびSwiftUIの状態処理に使用します。パターンマッチングによりすべての状態が処理されることが保証され、実行時エラーは発生しません。
よくある質問
Struct — 値型。代入時にコピーされ、継承をサポートしません。Class — 参照型。参照で渡され、継承、deinit、同一性チェック(===)をサポートします。Appleはstructから始め、参照セマンティクスが必要な場合にのみclassに切り替えることを推奨しています。
Actor — スレッドセーフな参照型。コンパイラは異なるコンテキストからのミュータブルな状態への同時アクセスを禁止します。Actorは、複数のタスクからアクセスされる状態マネージャー、キャッシュ、サービスに使用します。Actorメソッドの呼び出しにはawaitが必要です。
主要なもの:@State — ローカルビュー状態;@Binding — 所有権なしのデータ接続;@Published — ObservableObjectでの変更公開;@StateObject — ObservableObjectの所有;@EnvironmentObject — 環境からの依存関係。カスタムラッパーは@propertyWrapperで作成します。
POP — クラス継承の代わりにprotocol + extensionが振る舞いを定義するパラダイム。型は複数のプロトコルに準拠し、その実装を無料で取得します。継承よりも合成—より柔軟で安全です。Swiftの標準ライブラリ(Equatable、Codable)はPOP上に構築されています。
Swiftのenumは関連値をサポートします—各ケースは異なる型の任意のデータを格納できます。パターンマッチング(switch)と組み合わせることで、状態、エラー、ドメイン駆動設計のための強力なモデルを提供します。OptionalとResultは関連値を持つ標準的なSwiftのenumです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。