iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0
生成式 AI

iOS? AI-Yes!:用 Vide Coding 加速我的 Swift 學習曲線系列 第 8

Day 8 - 承先啟後:為何在深入 AI 前,我們必須先征服 App 內部溝通的核心?

  • 分享至 

  • xImage
  •  

在過去的七天,我們學會了 Swift 語法、學會了畫面排版,甚至為我們的 App 規劃了 MVC 架構。我們好像已經準備好要大展身手了。

但在我開始規劃「鬧鐘 App」的具體功能時,我發現了一個根本問題:

我的 App 至少會有兩個畫面:一個是「鬧鐘」,一個是「新增鬧鐘」。當我在「新增鬧鐘」畫面上點擊儲存後,「鬧鐘列表」畫面要如何知道這件事,並即時更新列表呢?


畫面的孤島問題

  • 困惑點:「新增鬧鐘」畫面能不能直接呼叫「鬧鐘」畫面

  • AI 協作日誌

    • 我的 Prompt:我是一個 iOS 新手。我的 App 有兩個 ViewController,A 和 B。A 呈現了 B。當 B 完成一個任務後(例如:點擊儲存按鈕),我該如何將 B 的資料傳回給 A,並通知 A 更新畫面?請用一個非技術性的簡單比喻。

    AI 的回答

    你好!這是一個在 iOS 開發中非常經典且重要的問題。作為新手,掌握這個概念對你未來的開發非常有幫助。

    簡單來說,當 A 呈現 B 時,A 是 B 的「來源」或「父層」。B 不應該直接知道 A 的具體存在(這會讓程式碼耦合度太高),而是應該透過一個「約定」或「回呼機制」來通知 A。

    最常用且專業的方法有兩種:Delegate (代理)Closure (閉包)。以下我會用簡單的方式為你說明這兩種方法。


    方法一:Delegate (代理模式)

    你可以把 Delegate 想像成一份「合約」。ViewController B 定義了一份合約 (Protocol),裡面說明了「當我的任務完成時,我會執行這個合約上的方法」。而 ViewController A 則簽署這份合約,並實作合約裡的方法。

    比喻:

    • ViewController A (老闆):指派任務給 B。
    • ViewController B (員工):執行任務。
    • Protocol (工作回報合約):員工 B 身上帶著一份合約,規定任務完成後要如何向老闆回報。
    • Delegate (代理人):老闆 A 自己就是那個接收回報的代理人。

    操作步驟:

    1. 在 ViewController B 中,定義合約 (Protocol) 並宣告一個代理人 (delegate)

    // 記得加上註解,說明這個 Protocol 的用途
    // ViewControllerBDelegate 是一個協定,用來將 ViewControllerB 的資料傳回
    protocol ViewControllerBDelegate: AnyObject {
        // 當任務完成時,代理人需要實作這個方法
        // withData 參數是用來傳遞資料的
        func didFinishTask(withData data: String)
    }
    
    class ViewControllerB: UIViewController {
    
        // 宣告一個代理人屬性,型別是我們剛定義的協定
        // 這裡使用 weak 關鍵字是為了防止「記憶體洩漏」(Retain Cycle)
        weak var delegate: ViewControllerBDelegate?
    
        // ... 你的其他程式碼 ...
    
        // 當儲存按鈕被點擊時
        @IBAction func saveButtonTapped(_ sender: UIButton) {
            let dataToPassBack = "這是從 B 傳回的資料"
    
            // 透過代理人,呼叫合約上的方法,並把資料傳出去
            delegate?.didFinishTask(withData: dataToPassBack)
    
            // 關閉自己,回到 A
            dismiss(animated: true, completion: nil)
        }
    }
    

    2. 在 ViewController A 中,簽署並實作合約

    class ViewControllerA: UIViewController {
    
        @IBOutlet weak var dataLabel: UILabel!
    
        // 當你要呈現 ViewController B 時
        func presentB() {
            // 從 Storyboard 或用程式碼實例化 B
            let viewControllerB = storyboard?.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB
    
            // 告訴 B:「我就是你的代理人!」
            viewControllerB.delegate = self
    
            // 呈現 B
            present(viewControllerB, animated: true, completion: nil)
        }
    }
    
    // 透過 extension 讓程式碼更清晰
    // 讓 ViewControllerA 遵守 (Conform to) ViewControllerBDelegate 這個協定
    extension ViewControllerA: ViewControllerBDelegate {
    
        // 實作協定中定義的方法
        func didFinishTask(withData data: String) {
            // 在這裡,你就能收到從 B 傳回來的資料
            print("收到了來自 B 的資料: \(data)")
    
            // 更新畫面上的 Label
            // 重要:所有 UI 的更新都必須在主執行緒 (Main Thread) 上完成
            DispatchQueue.main.async {
                self.dataLabel.text = data
            }
        }
    }
    

    方法二:Closure (閉包 / 回呼函式)

    Closure 是一種更現代、更輕量級的方式。你可以把它想像成 A 交給 B 一張「待辦紙條」。這張紙條上寫著:「當你完成任務後,請執行這張紙條上的指令」。

    比喻:

    • ViewController A (媽媽):請兒子 B 去買東西。
    • ViewController B (兒子):去買東西。
    • Closure (待辦紙條):媽媽在出門前,寫了一張紙條給兒子:「買完東西後,把東西放到冰箱裡(這就是紙條上的指令)」。兒子買完東西回來,看到紙條,就把東西放進冰箱。

    操作步驟:

    1. 在 ViewController B 中,定義一個 Closure 屬性

    class ViewControllerB: UIViewController {
    
        // 宣告一個 closure 屬性
        // 它的型別是一個函式,這個函式接收一個 String,並且沒有回傳值 (Void)
        // `?` 表示這個 closure 是可選的 (optional)
        var onCompletion: ((String) -> Void)?
    
        // ... 你的其他程式碼 ...
    
        @IBAction func saveButtonTapped(_ sender: UIButton) {
            let dataToPassBack = "這是從 B 透過 Closure 傳回的"
    
            // 如果這個 closure 存在,就執行它,並把資料當作參數傳入
            onCompletion?(dataToPassBack)
    
            // 關閉自己
            dismiss(animated: true, completion: nil)
        }
    }
    

    2. 在 ViewController A 中,實作 Closure 的內容

    class ViewControllerA: UIViewController {
    
        @IBOutlet weak var dataLabel: UILabel!
    
        func presentB() {
            let viewControllerB = storyboard?.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB
    
            // 在呈現 B 之前,把「待辦紙條」交給它
            // 這段 { [weak self] data in ... } 就是 closure 的實作內容
            viewControllerB.onCompletion = { [weak self] data in
                // [weak self] 是為了防止記憶體洩漏
                guard let self = self else { return }
    
                print("透過 Closure 收到了資料: \(data)")
    
                // 更新 UI
                DispatchQueue.main.async {
                    self.dataLabel.text = data
                }
            }
    
            present(viewControllerB, animated: true, completion: nil)
        }
    }
    

    給新手的建議:

    兩種方法都非常重要,但我建議你先從 Delegate 模式學起

    因為 Delegate 是 Apple 官方框架(如 UITableViewDelegate)中大量使用的模式,理解它有助於你理解整個 iOS SDK 的設計哲學。當你完全掌握 Delegate 後,再學習使用 Closure,你會發現它在處理簡單回呼時是多麼方便。

    希望這個說明對你有幫助!

    • 看來我們必須先從Delegate 模式先打好基礎,完全掌握後再學習閉包的使用方法,是最好的學習路徑。

AI總結與建議

特性 Delegate (代理模式) Closure (閉包)
結構 較為正式、嚴謹,需要定義 Protocol 較為輕量、簡潔,直接定義函式型別的屬性。
耦合度 低耦合,B 完全不知道 A 是誰,只認得合約。 低耦合,B 不知道是誰寫的紙條,只負責執行。
程式碼分散 實作邏輯會寫在 extension 裡,與觸發點分開。 回呼邏輯直接寫在呼叫 present 的地方,上下文更清晰。
適用情境 當 B 需要回報多種不同的事件時(例如:儲存成功、取消、刪除),用 Delegate 可以定義多個函式,結構較清晰。 當 B 只需要回報單一完成事件時,用 Closure 非常方便快速。

今天我們沒有寫任何專案程式碼,但卻學到了比程式碼更重要的 App 內部溝通

但在我們開始動手打造鬧鐘之前,還需要準備最後一樣東西:一個可靠的倉庫,用來存放我們建立的所有鬧鐘。

明天將介紹一個強大又好用的 iOS 資料庫:RealmSwift,為我們的鬧鐘實戰做好最後的準備!


上一篇
Day 7 - 架構思維:淺談 MVC 架構與專案的資料夾結構
下一篇
Day 9 - 數據儲存:認識 RealmSwift,為之後的專案做準備
系列文
iOS? AI-Yes!:用 Vide Coding 加速我的 Swift 學習曲線11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言