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メソッドを実装

Swift
class 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参照

Swift
class 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:通知名を定義

Swift
extension 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:通知を監視

Swift
class 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:クロージャプロパティを保持

Swift
class SettingsViewController: UIViewController { var onCompleted: ((String) -> Void)? @IBAction private func doneButtonTapped(_ sender: UIBarButtonItem) { onCompleted?(textField.text ?? "") navigationController?.popViewController(animated: true) } }

ステップ2:クロージャを渡して値を受け取る

Swift
override 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]を忘れないようにしましょう。

ハマった点・エラー解決

  1. delegateがnilで呼ばれない
    → segue遷移でNavigationControllerを噛ましている場合、topViewControllerで正しくインスタンスを取れているか確認。

  2. Notificationを2重登録して複数回呼ばれる
    viewWillAppearでaddObserver、viewWillDisappearでremoveObserverするパターンもありますが、都度購読・解除が安全。

  3. Closureで循環参照しメモリリーク
    [weak self]を付け忘れるとself→クロージャ→selfの強参照ループが発生。XcodeのDebug Memory Graphで確認できます。

まとめ

本記事では、Swift4で動作する3つの値渡し手法(Delegate/NotificationCenter/Closure)を比較し、それぞれの実装例と注意点を解説しました。

  • Delegate:1対1、インターフェースが明確で保守性が高い
  • NotificationCenter:1対多、密結合を避けられるが実行時エラーが起きやすい
  • Closure:コードが短く直感的、循環参照に注意

この記事を通して、画面遷移時の値渡しで「毎回ググってコピペ」から脱却し、状況に応じた最適なパターンを選べるようになりました。次回は「SwiftUIで双方向バインディング」「EnvironmentObjectでの大域状態管理」について掘り下げていきます。

参考資料