markdown
はじめに
この記事は、SwiftでiOSアプリを作り始めたばかりの方や、「画面遷移で値を渡すコードを毎回コピペしている」方を対象にしています。
A画面で入力したテキストをB画面で即座に反映させたい、タブバー同士でデータを共有したい、といった場面で「どの手段を選べばいいのか混乱している」方も多いはずです。
本記事を読むと、Swift4で動作する「Delegateパターン」「NotificationCenter」「Closureコールバック」の3手法を、それぞれのメリット・デメリットと共に使い分けられるようになります。さらに、実装時に起きがちな「循環参照によるメモリリーク」「予期せぬnilクラッシュ」を事前に防ぐコツもお伝えします。
前提知識
- Swiftの基本的な文法(クラス、構造体、オプショナル)
- Xcodeで新規プロジェクトを作成し、StoryboardまたはSwiftUIで画面遷移ができること
- ARC(自動参照カウント)の仕組みをざっくり理解していること
なぜ「値渡し」で悩むのか
iOS開発で「画面遷移=値渡し」とセットに考えるのは自然なことです。しかし、Swiftには複数のパターンが存在し、公式ドキュメントも「状況によって使い分けてください」としか書かれていないため、初学者の方が「どれを選べばいいか分からない」という声を多く耳にします。
加えて、Swift4以降はAPIの一部が非推奨になり、検索して出てきた「Swift3時代の古い記事」がコンパイルエラーを引き起こすことも。正確で最新の情報が必要です。
3つの値渡しパターンを実装して比較する
ここでは、シンプルな「設定画面→メイン画面へ文字列を渡す」ケースを題材に、3パターンを順に実装します。いずれもSwift4/5で動作し、UIKitとSwiftUIの両方で利用可能なコードを提示します。
Delegateパターン(弱参照で安全に)
Delegateは「誰かに任せる」という意味通り、設定画面が「メイン画面に値を返してね」と依頼する方式です。
ステップ1:Protocolを定義
Swift// SettingsViewController.swift protocol SettingsDelegate: AnyObject { func settingsDidFinish(_ text: String) }
ステップ2:呼び出し側でDelegateメソッドを実装
Swiftclass MainViewController: UIViewController, SettingsDelegate { @IBOutlet private weak var label: UILabel! override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let nav = segue.destination as? UINavigationController, let settings = nav.topViewController as? SettingsViewController { settings.delegate = self } } // MARK: - SettingsDelegate func settingsDidFinish(_ text: String) { label.text = text } }
ステップ3:呼ばれる側でdelegateをweak参照
Swiftclass SettingsViewController: UIViewController { weak var delegate: SettingsDelegate? @IBAction private func doneButtonTapped(_ sender: UIBarButtonItem) { delegate?.settingsDidFinish(textField.text ?? "") navigationController?.popViewController(animated: true) } }
ポイントはweak varにして循環参照を防ぐことです。これを忘れると、両方のViewControllerが破棄されずメモリリークします。
NotificationCenter(一対多のブロードキャスト)
Delegateは1対1が基本ですが、NotificationCenterを使えば「設定が変わったよ」と他の画面に一斉に通知できます。
ステップ1:通知名を定義
Swiftextension Notification.Name { static let settingsDidChange = Notification.Name("settingsDidChange") }
ステップ2:通知をポスト
Swift@IBAction private func doneButtonTapped(_ sender: UIBarButtonItem) { NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: ["text": textField.text ?? ""]) navigationController?.popViewController(animated: true) }
ステップ3:通知を監視
Swiftclass MainViewController: UIViewController { private var observer: NSObjectProtocol? override func viewDidLoad() { super.viewDidLoad() observer = NotificationCenter.default .addObserver(forName: .settingsDidChange, object: nil, queue: .main) { [weak self] note in guard let text = note.userInfo?["text"] as? String else { return } self?.label.text = text } } deinit { if let observer = observer { NotificationCenter.default.removeObserver(observer) } } }
通知のメリットは「密結合を避けられる」ことですが、デメリットもあります。通知名の文字列ミスやuserInfoの型キャストミスは実行時エラーのため、コンパイル時に気づけません。
Closureコールバック(シンプルで直感的)
Swiftらしい記法で書きたい場合はClosureを使いましょう。UIKitでもSwiftUIでも同じ要領で動作します。
ステップ1:クロージャプロパティを保持
Swiftclass SettingsViewController: UIViewController { var onCompleted: ((String) -> Void)? @IBAction private func doneButtonTapped(_ sender: UIBarButtonItem) { onCompleted?(textField.text ?? "") navigationController?.popViewController(animated: true) } }
ステップ2:クロージャを渡して値を受け取る
Swiftoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let nav = segue.destination as? UINavigationController, let settings = nav.topViewController as? SettingsViewController { settings.onCompleted = { [weak self] text in self?.label.text = text } } }
クロージャの良い点は「通知名の文字列が不要」「引数の型が明確」なこと。ただし、クロージャ内でselfを強参照すると循環参照になるため、[weak self]を忘れないようにしましょう。
ハマった点・エラー解決
-
delegateがnilで呼ばれない
→ segue遷移でNavigationControllerを噛ましている場合、topViewControllerで正しくインスタンスを取れているか確認。 -
Notificationを2重登録して複数回呼ばれる
→viewWillAppearでaddObserver、viewWillDisappearでremoveObserverするパターンもありますが、都度購読・解除が安全。 -
Closureで循環参照しメモリリーク
→[weak self]を付け忘れるとself→クロージャ→selfの強参照ループが発生。XcodeのDebug Memory Graphで確認できます。
まとめ
本記事では、Swift4で動作する3つの値渡し手法(Delegate/NotificationCenter/Closure)を比較し、それぞれの実装例と注意点を解説しました。
- Delegate:1対1、インターフェースが明確で保守性が高い
- NotificationCenter:1対多、密結合を避けられるが実行時エラーが起きやすい
- Closure:コードが短く直感的、循環参照に注意
この記事を通して、画面遷移時の値渡しで「毎回ググってコピペ」から脱却し、状況に応じた最適なパターンを選べるようになりました。次回は「SwiftUIで双方向バインディング」「EnvironmentObjectでの大域状態管理」について掘り下げていきます。
参考資料
- The Swift Programming Language - Protocols
- Apple Developer Documentation - NotificationCenter
- プログラミングSwift ――iOSアプリ開発の教科書、第4版(技術評論社)
