Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 第 10 章:协议化编程在 iOS 开发中的应用

第 10 章:协议化编程在 iOS 开发中的应用

10.3 实战案例:用协议重构一个复杂的视图控制器

在 iOS 开发中,视图控制器(View Controller)往往因为承担过多职责而变得臃肿不堪。这种“Massive View Controller”问题会导致代码难以维护和测试。本节通过一个实战案例,展示如何利用协议化编程(Protocol-Oriented Programming, POP)重构一个复杂的视图控制器,将其职责分解为清晰、可复用的模块。

案例背景:一个复杂的用户详情视图控制器

假设我们有一个 UserDetailViewController,负责显示用户详情,包括头像、用户名、简介,以及一个“关注”按钮的功能。初始实现如下:

class UserDetailViewController: UIViewController {
    private let userId: String
    private let avatarImageView = UIImageView()
    private let nameLabel = UILabel()
    private let bioLabel = UILabel()
    private let followButton = UIButton()
    
    init(userId: String) {
        self.userId = userId
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        fetchUserData()
    }
    
    private func setupUI() {
        view.backgroundColor = .white
        // 配置 UI 布局(省略具体代码)
        view.addSubview(avatarImageView)
        view.addSubview(nameLabel)
        view.addSubview(bioLabel)
        view.addSubview(followButton)
        followButton.addTarget(self, action: #selector(followButtonTapped), for: .touchUpInside)
    }
    
    private func fetchUserData() {
        // 模拟网络请求
        DispatchQueue.global().async {
            let userData = self.mockFetchUser(userId: self.userId)
            DispatchQueue.main.async {
                self.avatarImageView.image = userData.avatar
                self.nameLabel.text = userData.name
                self.bioLabel.text = userData.bio
                self.followButton.setTitle(userData.isFollowing ? "取消关注" : "关注", for: .normal)
            }
        }
    }
    
    private func mockFetchUser(userId: String) -> (avatar: UIImage, name: String, bio: String, isFollowing: Bool) {
        // 模拟数据
        return (UIImage(named: "avatar")!, "Alice", "iOS Developer", false)
    }
    
    @objc private func followButtonTapped() {
        // 模拟关注/取消关注逻辑
        print("Follow button tapped")
    }
}

这个实现存在以下问题:

  1. 职责混杂:视图控制器同时负责 UI 配置、数据获取和用户交互逻辑。
  2. 耦合严重:数据获取和 UI 更新逻辑直接嵌入视图控制器,难以复用或测试。
  3. 扩展性差:如果需要支持不同的数据源或 UI 样式,必须修改视图控制器内部代码。

重构目标

我们将使用协议化编程,将职责分解为以下模块:

  • 数据提供者(Data Provider):负责获取用户数据。
  • 视图模型(View Model):处理数据并提供 UI 友好的格式。
  • 视图更新协议(View Updating):定义 UI 更新行为。

重构步骤

1. 定义协议

首先,定义必要的协议来抽象职责:

// 数据提供协议
protocol UserDataProviding {
    func fetchUserData(userId: String, completion: @escaping (UserData) -> Void)
}

// 视图模型输出协议
protocol UserDetailViewModelOutput {
    var avatar: UIImage { get }
    var name: String { get }
    var bio: String { get }
    var followButtonTitle: String { get }
    func toggleFollow()
}

// 视图更新协议
protocol UserDetailViewUpdating {
    func updateUserDetails(avatar: UIImage, name: String, bio: String, followButtonTitle: String)
}

2. 实现数据提供者

将数据获取逻辑抽离到独立模块:

struct UserDataProvider: UserDataProviding {
    func fetchUserData(userId: String, completion: @escaping (UserData) -> Void) {
        DispatchQueue.global().async {
            // 模拟网络请求
            let userData = UserData(
                avatar: UIImage(named: "avatar")!,
                name: "Alice",
                bio: "iOS Developer",
                isFollowing: false
            )
            DispatchQueue.main.async {
                completion(userData)
            }
        }
    }
}

// 用户数据模型
struct UserData {
    let avatar: UIImage
    let name: String
    let bio: String
    let isFollowing: Bool
}

3. 实现视图模型

视图模型负责协调数据并提供 UI 所需的格式:

class UserDetailViewModel: UserDetailViewModelOutput {
    private let dataProvider: UserDataProviding
    private var userData: UserData?
    
    var avatar: UIImage { userData?.avatar ?? UIImage() }
    var name: String { userData?.name ?? "" }
    var bio: String { userData?.bio ?? "" }
    var followButtonTitle: String { userData?.isFollowing == true ? "取消关注" : "关注" }
    
    init(userId: String, dataProvider: UserDataProviding) {
        self.dataProvider = dataProvider
        loadUserData(userId: userId)
    }
    
    private func loadUserData(userId: String) {
        dataProvider.fetchUserData(userId: userId) { [weak self] userData in
            self?.userData = userData
        }
    }
    
    func toggleFollow() {
        guard let data = userData else { return }
        userData = UserData(
            avatar: data.avatar,
            name: data.name,
            bio: data.bio,
            isFollowing: !data.isFollowing
        )
    }
}

4. 重构视图控制器

视图控制器只负责 UI 配置和协调:

class UserDetailViewController: UIViewController, UserDetailViewUpdating {
    private let viewModel: UserDetailViewModelOutput
    private let avatarImageView = UIImageView()
    private let nameLabel = UILabel()
    private let bioLabel = UILabel()
    private let followButton = UIButton()
    
    init(viewModel: UserDetailViewModelOutput) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        updateUserDetails(
            avatar: viewModel.avatar,
            name: viewModel.name,
            bio: viewModel.bio,
            followButtonTitle: viewModel.followButtonTitle
        )
    }
    
    private func setupUI() {
        view.backgroundColor = .white
        // 配置 UI 布局(省略具体代码)
        view.addSubview(avatarImageView)
        view.addSubview(nameLabel)
        view.addSubview(bioLabel)
        view.addSubview(followButton)
        followButton.addTarget(self, action: #selector(followButtonTapped), for: .touchUpInside)
    }
    
    func updateUserDetails(avatar: UIImage, name: String, bio: String, followButtonTitle: String) {
        avatarImageView.image = avatar
        nameLabel.text = name
        bioLabel.text = bio
        followButton.setTitle(followButtonTitle, for: .normal)
    }
    
    @objc private func followButtonTapped() {
        viewModel.toggleFollow()
        updateUserDetails(
            avatar: viewModel.avatar,
            name: viewModel.name,
            bio: viewModel.bio,
            followButtonTitle: viewModel.followButtonTitle
        )
    }
}

// 使用示例
let dataProvider = UserDataProvider()
let viewModel = UserDetailViewModel(userId: "123", dataProvider: dataProvider)
let viewController = UserDetailViewController(viewModel: viewModel)

重构后的优势

  1. 职责分离:
    • 数据获取由 UserDataProvider 负责。
    • 数据处理和状态管理由 UserDetailViewModel 负责。
    • UI 更新和用户交互由 UserDetailViewController 负责。
  2. 松耦合:视图控制器通过协议依赖 UserDetailViewModelOutput,无需关心具体实现。
  3. 可测试性:可以轻松 mock UserDataProviding 和 UserDetailViewModelOutput 进行单元测试。
  4. 可扩展性:需要替换数据源或添加新功能时,只需提供新的协议实现。

小结

通过协议化编程,我们成功将一个复杂的视图控制器重构为多个模块化的组件。每个模块职责清晰,代码更易于维护和扩展。这种方法在实际项目中尤其适用于大型应用,能够有效提升开发效率和代码质量。

Last Updated:: 3/18/25, 4:45 PM