一、先理解:iOS 開發中函數式編程的核心原則
二、Swift 中函數式編程的核心工具(iOS 開發高頻用)
1. 高階函數(替代命令式循環)
示例 1:數據模型轉換(替代 for 循環)
swift
// 命令式(傳統寫法:修改外部變量,有副作用)
var userNameList: [String] = []
for user in userModels {
if let name = user.name { // 判空
userNameList.append(name)
}
}
// 函數式(純函數,無副作用,一行完成)
let userNameList = userModels.compactMap { $0.name } // compactMap自動過濾nil
示例 2:過濾 + 轉換 + 聚合(iOS 列表數據處理)
swift
// 需求:篩選年齡>18的用户,提取暱稱,拼接成逗號分隔的字符串
let userModels: [UserModel] = [/* 測試數據 */]
// 命令式(多層循環+變量修改)
var filteredNames: [String] = []
for user in userModels {
if user.age > 18, let name = user.nickname {
filteredNames.append(name)
}
}
let nameString = filteredNames.joined(separator: ", ")
// 函數式(聲明式,無外部變量)
let nameString = userModels
.filter { $0.age > 18 } // 過濾:年齡>18
.compactMap { $0.nickname } // 轉換:提取非空暱稱
.joined(separator: ", ") // 聚合:拼接字符串
示例 3:reduce(數據聚合,如計算總價)
swift
// 需求:計算購物車中已選中商品的總價
let cartItems: [CartItem] = [/* 測試數據 */]
// 函數式實現
let totalPrice = cartItems
.filter { $0.isSelected } // 過濾選中商品
.reduce(0.0) { $0 + $1.price * Double($1.quantity) } // 累加:初始值0,$0是累計值,$1是當前項
2. 純函數(無副作用,可複用)
swift
// 純函數:手機號格式化(輸入決定輸出,無副作用)
func formatPhoneNumber(_ phone: String) -> String {
// 僅處理輸入,不依賴外部變量、不修改UI
let digits = phone.filter { $0.isNumber } // 提取數字
guard digits.count == 11 else { return phone }
return "(\(digits.prefix(3))) \(digits.dropFirst(3).prefix(4)) \(digits.suffix(4))"
}
// 使用(可在VC/VM/工具類中複用,無耦合)
let formattedPhone = formatPhoneNumber("13800138000")
// 輸出:(138) 0013 8000
3. 函數組合(將小函數組合成大函數)
swift
// 定義組合運算符(Swift支持自定義運算符)
infix operator >>>: AdditionPrecedence
func >>> <T, U, V>(_ f: @escaping (T) -> U, _ g: @escaping (U) -> V) -> (T) -> V {
return { g(f($0)) }
}
// 小函數1:去除字符串空格
func trim(_ str: String) -> String {
return str.trimmingCharacters(in: .whitespaces)
}
// 小函數2:驗證字符串非空
func validateNonEmpty(_ str: String) -> String? {
return str.isEmpty ? nil : str
}
// 小函數3:轉換為郵箱格式(簡單驗證)
func validateEmail(_ str: String) -> String? {
let pattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
return NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: str) ? str : nil
}
// 組合成:表單郵箱驗證函數
let validateFormEmail = trim >>> validateNonEmpty >>> validateEmail
// 使用(iOS表單提交場景)
let email = " test@example.com "
if let validEmail = validateFormEmail(email) {
print("郵箱有效:\(validEmail)")
} else {
print("郵箱格式錯誤")
}
4. 不可變數據(避免意外修改)
swift
// 命令式(可變數組,易被意外修改)
var mutableUsers = [UserModel]()
mutableUsers.append(UserModel(id: "1", name: "張三"))
mutableUsers[0].name = "李四" // 意外修改
// 函數式(不可變數組,修改需生成新數組)
let immutableUsers = [UserModel(id: "1", name: "張三")]
// 如需修改,生成新數組(原數組不變)
let updatedUsers = immutableUsers.map { user in
var newUser = user
newUser.name = "李四"
return newUser
}
三、iOS 開發中函數式編程的具體場景落地
1. UIKit 控件配置(函數式封裝,替代 Then 庫)
swift
// 函數式封裝UIButton配置
extension UIButton {
// 純函數:配置屬性,返回自身(支持鏈式)
func withTitle(_ title: String, for state: UIControl.State = .normal) -> Self {
setTitle(title, for: state)
return self
}
func withTitleColor(_ color: UIColor, for state: UIControl.State = .normal) -> Self {
setTitleColor(color, for: state)
return self
}
func withBackgroundColor(_ color: UIColor) -> Self {
backgroundColor = color
return self
}
func withCornerRadius(_ radius: CGFloat) -> Self {
layer.cornerRadius = radius
clipsToBounds = true
return self
}
}
// 使用(聲明式配置,無外部變量)
let submitButton = UIButton()
.withTitle("提交")
.withTitleColor(.white)
.withBackgroundColor(.systemBlue)
.withCornerRadius(8)
.withFrame(CGRect(x: 20, y: 100, width: 300, height: 44))
2. 網絡請求 + 數據解析(函數式鏈式處理)
swift
// 定義類型別名,簡化函數簽名
typealias NetworkRequest<T: Decodable> = () -> Result<T, NetworkError>
typealias DataParser<T: Decodable> = (Data) -> Result<T, NetworkError>
// 純函數:網絡請求
func fetchData<T: Decodable>(url: URL) -> NetworkRequest<T> {
return {
do {
let (data, _) = try URLSession.shared.data(from: url)
return .success(data as! T) // 簡化示例,實際需解析
} catch {
return .failure(.networkError)
}
}
}
// 純函數:JSON解析
func parseJSON<T: Decodable>() -> DataParser<T> {
return { data in
do {
let model = try JSONDecoder().decode(T.self, from: data)
return .success(model)
} catch {
return .failure(.parseError)
}
}
}
// 組合:請求+解析
func fetchAndParse<T: Decodable>(url: URL) -> Result<T, NetworkError> {
let requestResult = fetchData(url: url)()
switch requestResult {
case .success(let data):
return parseJSON<T>()(data as! Data)
case .failure(let error):
return .failure(error)
}
}
// 使用(iOS 15+可結合async/await)
Task {
let url = URL(string: "https://api.example.com/users")!
let result: Result<[UserModel], NetworkError> = fetchAndParse(url: url)
switch result {
case .success(let users):
print("獲取用户:\(users.count)")
case .failure(let error):
print("錯誤:\(error)")
}
}
3. 響應式 UI 更新(結合 Combine,函數式響應式)
swift
import Combine
class UserVM {
// 不可變數據源(@Published自動發佈變化)
@Published private(set) var userList: [UserModel] = []
private var cancellables = Set<AnyCancellable>()
// 函數式:請求數據+解析+更新UI(無副作用)
func loadUsers() {
let url = URL(string: "https://api.example.com/users")!
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data } // 提取數據
.decode(type: [UserModel].self, decoder: JSONDecoder()) // 解析
.replaceError(with: []) // 錯誤處理:返回空數組
.receive(on: DispatchQueue.main) // 切換到主線程
.assign(to: \.userList, on: self) // 賦值給數據源
.store(in: &cancellables)
}
}
// VC中綁定UI(函數式響應式)
class UserVC: UIViewController {
let vm = UserVM()
let tableView = UITableView()
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// 監聽數據源變化,自動刷新表格(聲明式,無需手動調用reloadData)
vm.$userList
.sink { [weak self] _ in
self?.tableView.reloadData()
}
.store(in: &cancellables)
vm.loadUsers()
}
}
四、iOS 開發中函數式編程的最佳實踐與避坑
1. 最佳實踐
- 小而純:函數只做一件事(如 “格式化手機號” 而非 “格式化 + 驗證 + 提交”);
- 優先值類型:數據模型用
struct,避免引用類型的副作用; - 避免過度抽象:簡單邏輯(如單次循環)無需強行用高階函數,可讀性優先;
- 結合 iOS 特性:函數式代碼需適配 iOS 主線程、AutoLayout、權限等,不可脱離平台;
- 錯誤處理:用
Result類型替代 try-catch 嵌套,統一錯誤處理邏輯。
2. 避坑點
- 性能問題:多層高階函數(如
map套filter套reduce)會遍歷多次數組,大數據量時可合併為單次遍歷; - 調試難度:函數式代碼的錯誤棧較抽象,可通過拆分小函數、添加日誌解決;
- UI 副作用:純函數不可直接修改 UI,需通過 Combine / 閉包將結果傳遞到 UI 層;
- 兼容低版本:Combine 僅支持 iOS 13+,低版本可使用第三方庫(如 RxSwift)或簡化的函數式封裝。