2024-01-19|閱讀時間 ‧ 約 31 分鐘

iOS常用的Design Pattern

記錄個人常用的Design Pattern,之前面試被問到有用過哪些,瞬間一片空白🥹

以下有寫iOS應該都用過,只是可能講不出這算哪個Pattern

MVC vs. MVVM

詳情在前一篇學習筆記

單例模式 Singleton

確保一個class只能創建一個實例,並提供全域訪問。一般我會用在單一狀態管理上,例如:資料庫連結、登入狀態管理

通常包含以下特點:

  1. 私有構造函數(Private Constructor): Singleton類別的init通常是私有的,以防止外部代碼通過實例化多個對象。
  2. 靜態實例(Static Instance): Singleton類別包含一個靜態成員,用於保存該類別的唯一實例。
  3. 全域訪問點(Global Access Point): 提供一個全域的訪問點,使得其他程式碼可以獲取Singleton實例的引用。

範例:管理登入狀態的Manager。

class LoginManager {
static let shared = LoginManager()
var isLogin = false

private init() {}

func login() {
isLogin = true
}

func logout() {
isLogin = false
}
}


委派模式 Delegation pattern

一個物件把一些職責讓另一個物件來執行。這個Apple官方自己也很常用。

範例:UITableViewDelegate,把TableView的一些行為(如:點擊)給ViewController來代理

class VC1: UIViewController{
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
tableView.delegate = self
}
}

extension VC1: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 點擊後讓VC做一些事
}
}


觀察者模式 Observer Pattern

通知:Notification

這個Apple官方也很常用,例如:鍵盤彈起(UIKeyboardWillShowNotification)收合(UIKeyboardWillHideNotification),或是AppDelegate各種生命週期通知。

這種通知是全域的,發布者不需要知道訂閱者的任何資訊。

鍵值觀察:Key-Value Observing (KVO)

一種機制,允許一個物件可以註冊監聽其他物件指定屬性的變化。

這邊要介紹一下Combine的KVOpublisher(for: \.KeyProperty),我覺得在UIKit上超好用ㄉ

範例:監聽UITabBarController的TabBar.isHidden

import Combine
class MyTabBarController: UITabBarController {
private var subscriptions = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
setBinding()
}

private func setBinding() {
self.tabBar.publisher(for: \.isHidden).sink { [weak self] hidden in
if !hidden {
// 如果Tabbar可見,在Bar上show一條InfoView
self?.showInfoView()
} else {
// 反之隱藏
self?.hideInfoView()
}
}
}.store(in: &subscriptions)
}

Delegate 和 Notification 使用時機

兩個都可以拿來在App裡通訊傳值,但使用時機略有不同

Delegate 的使用時機

  • 一對一:通常是一個委託人對到一個委託對象。
  • 有明確的行為:通常會訂個protocol,讓委託對象實現其中的方法,以達到客製化行為的目的。

Notification 的使用時機

  • 一對多:一個發送通知的對象可以通知多個觀察者。
  • 非特定行為: Notification 通常用於通知一個事件的發生,而不關心具體的行為。多個不同的觀察者可以對同一事件進行不同的反應。

Delegate可不可以一對多?

也可以。通常是一對一,如果要一對多,自己寫Array來保存多個觀察者。通常我會用在有明確行為,又只有兩三個地方需要接受這個訊息的情況下。

範例:Socket連線,當Socket連線狀態產生變化時,要通知底下所有有註冊他的委託對象(聊天室列表&聊天室頁面)​

// 觀察者
class WeakSocketObserver {
weak var value: SocketDelegate?
init (value: SocketDelegate) {
self.value = value
}
}

// 定義Socket連線狀態的協議
protocol SocketDelegate: AnyObject {
// 連線成功
func didConnected()
// 其他狀態省略
// ...
}

Socket本人(委託人)

import SocketIO
@objc class MySocket: NSObject {
@objc static let shared = MySocket()
private var manager: SocketManager()
private var socket: SocketIOClient!
// 所有觀察者
private var observers: [WeakSocketObserver] = []

private override init() {
super.init()
// ...
socket = manager.defaultSocket
socket.on(clientEvent: .connect) { (data, ack) in
// 連線成功時,通知所有觀察者
self.observers.forEach { (observer) in
observer.value?.didConnected()
}
}
}


func tryConnect() {
// 開始連線
}
}

加入委託對象(觀察者)

// 第一個觀察者​-聊天列表
class ChatroomListVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Socket加入觀察者
MySocket.shared.add(WeakSocketObserver(value: self))
// 嘗試連線
MySocket.shared.tryConnect()
}
}

extensionChatroomListVC: SocketDelegate {
func didConnected() {
// 我連線了!
}
}

// 第二個觀察者​​-聊天室
class ChatroomVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Socket加入觀察者
MySocket.shared.add(WeakSocketObserver(value: self))
// 嘗試連線
MySocket.shared.tryConnect()
}
}

extensionChatroomVC: SocketDelegate {
func didConnected() {
// 我連線了!
}
}

Socket連線後,底下的聊天室跟聊天列表都會收到通知。




參考資料:

ChatGPT

https://wilden-chen.gitbook.io/swift-design-patterns



分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.