为什么需要组件化
单体架构的痛点
┌─────────────────────────────────────────────────────────┐
│ 单体应用架构 │
├─────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 首页 │←→│ 购物车 │←→│ 订单 │←→│ 我的 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └────────────┴─────┬──────┴────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 公共代码 & 工具类 │ │
│ │ (网络层、数据库、工具类、基础UI 全部耦合在一起) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
问题:
❌ 编译时间长(修改一行代码需全量编译)
❌ 代码冲突频繁(多人协作噩梦)
❌ 业务边界模糊(功能交叉,职责不清)
❌ 无法独立测试(牵一发动全身)
❌ 技术债务累积(代码腐化严重)
组件化目标
┌─────────────────────────────────────────────────────────┐
│ 组件化架构 │
├─────────────────────────────────────────────────────────┤
│ 独立开发 │ 独立测试 │ 独立发布 │ 快速编译 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 首页组件 │ │ 购物车组件│ │ 订单组件 │ 业务层 │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ─────┴─────────────┴─────────────┴───── │
│ ↓ │
│ ┌─────────────────────────────────────┐ │
│ │ 组件通信中间件 │ 中间层 │
│ └─────────────────────────────────────┘ │
│ ↓ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 网络库 │ │ 数据库 │ │ 工具库 │ 基础层 │
│ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
组件化基础概念
组件分类
public class NetworkKit {
public static let shared = NetworkKit()
public func request<T: Decodable>(
_ endpoint: Endpoint,
responseType: T.Type
) async throws -> T {
}
}
public class ShareKit {
public static func share(
content: ShareContent,
from viewController: UIViewController,
completion: @escaping (Result<Void, Error>) -> Void
) {
}
}
public class HomeModule: ModuleProtocol {
public static func initialize() {
}
public static func createViewController() -> UIViewController {
return HomeViewController()
}
}
组件依赖原则
依赖规则图示:
┌─────────────┐
│ 业务层 │ 最上层
└──────┬──────┘
│ 依赖
▼
┌─────────────┐
│ 功能层 │ 中间层
└──────┬──────┘
│ 依赖
▼
┌─────────────┐
│ 基础层 │ 最底层
└─────────────┘
规则:
✅ 上层可以依赖下层
✅ 同层之间通过协议/中间件通信
❌ 下层不可依赖上层
❌ 禁止循环依赖
模块拆分策略
拆分维度分析
enum BusinessModule: String, CaseIterable {
case home = "Home"
case product = "Product"
case cart = "Cart"
case order = "Order"
case user = "User"
case message = "Message"
var priority: Int {
switch self {
case .user: return 100
case .message: return 90
case .home: return 80
case .product: return 70
case .cart: return 60
case .order: return 50
}
}
}
enum FeatureModule: String {
case share = "Share"
case payment = "Payment"
case push = "Push"
case analytics = "Analytics"
case imageLoader = "Image"
}
enum CoreModule: String {
case network = "Network"
case database = "Database"
case cache = "Cache"
case log = "Log"
case router = "Router"
}
Podfile 组织结构
platform :ios, '13.0'
use_frameworks!
inhibit_all_warnings!
source 'https://github.com/CocoaPods/Specs.git'
source 'https://your-company.com/ios-specs.git'
abstract_target 'CommonPods' do
pod 'CoreNetwork', '~> 1.0'
pod 'CoreDatabase', '~> 1.0'
pod 'CoreCache', '~> 1.0'
pod 'CoreLogger', '~> 1.0'
pod 'CoreRouter', '~> 1.0'
pod 'CoreUIKit', '~> 1.0'
pod 'FeatureShare', '~> 1.0'
pod 'FeaturePayment', '~> 1.0'
pod 'FeaturePush', '~> 1.0'
target 'MainApp' do
pod 'BizHome', '~> 1.0'
pod 'BizProduct', '~> 1.0'
pod 'BizCart', '~> 1.0'
pod 'BizOrder', '~> 1.0'
pod 'BizUser', '~> 1.0'
pod 'BizMessage', '~> 1.0'
target 'MainAppTests' do
inherit! :search_paths
pod 'Quick'
pod 'Nimble'
end
end
target 'BizHomeDemo' do
pod 'BizHome', :path => '../BizHome'
end
target 'BizProductDemo' do
pod 'BizProduct', :path => '../BizProduct'
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
组件 Podspec 规范
Pod::Spec.new do |s|
s.name = 'BizHome'
s.version = '1.0.0'
s.summary = '首页业务组件'
s.description = <<-DESC
首页业务组件,包含:
- 首页推荐流
- Banner轮播
- 分类入口
- 活动专区
DESC
s.homepage = 'https://your-company.com/BizHome'
s.license = {
:type => 'MIT', :file => 'LICENSE' }
s.author = {
'iOS Team' => 'ios@company.com' }
s.source = {
:git => 'https://github.com/company/BizHome.git', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
s.swift_version = '5.0'
s.source_files = 'BizHome/Classes/**/*'
s.resources = 'BizHome/Assets/**/*'
s.resource_bundles = {
'BizHomeBundle' => ['BizHome/Assets/*.xcassets', 'BizHome/Assets/*.xib']
}
s.public_header_files = 'BizHome/Classes/Public/**/*.h'
s.dependency 'ServiceProtocols', '~> 1.0'
s.dependency 'CoreUIKit', '~> 1.0'
s.dependency 'CoreNetwork', '~> 1.0'
s.dependency 'CoreRouter', '~> 1.0'
s.subspec 'Core' do |core|
core.source_files = 'BizHome/Classes/Core/**/*'
end
s.subspec 'UI' do |ui|
ui.source_files = 'BizHome/Classes/UI/**/*'
ui.dependency 'BizHome/Core'
end
s.default_subspecs = 'Core', 'UI'
end
依赖反转原则
依赖反转核心思想
传统依赖 vs 依赖反转
【传统依赖】
┌─────────────┐ ┌─────────────┐
│ 首页模块 │ ──────→ │ 商品模块 │
│ (高层模块) │ 依赖 │ (低层模块) │
└─────────────┘ └─────────────┘
问题:首页直接依赖商品模块,耦合严重
【依赖反转】
┌─────────────┐ ┌─────────────────┐
│ 首页模块 │ ──────→ │ 商品服务协议 │
│ (高层模块) │ 依赖 │ (抽象) │
└─────────────┘ └────────┬────────┘
│
│ 实现
│
┌────────▼────────┐
│ 商品模块 │
│ (具体实现) │
└─────────────────┘
优势:
✅ 高层模块不依赖低层模块
✅ 两者都依赖抽象
✅ 抽象不依赖细节
✅ 细节依赖抽象
协议层设计
public struct ProductInfo: Codable, Hashable {
public let id: String
public let name: String
public let price: Decimal
public let imageURL: URL?
public let description: String
public let stock: Int
public init(id: String, name: String, price: Decimal,
imageURL: URL?, description: String, stock: Int) {
self.id = id
self.name = name
self.price = price
self.imageURL = imageURL
self.description = description
self.stock = stock
}
}
public protocol ProductServiceProtocol {
func fetchProduct(by id: String) async throws -> ProductInfo
func fetchProducts(category: String, page: Int, pageSize: Int) async throws -> [ProductInfo]
func searchProducts(keyword: String) async throws -> [ProductInfo]
func productDetailViewController(productId: String) -> UIViewController
}
public struct CartItem: Codable, Identifiable {
public let id: String
public let productId: String
public let productName: String
public let price: Decimal
public var quantity: Int
public let imageURL: URL?
public init(id: String, productId: String, productName: String,
price: Decimal, quantity: Int, imageURL: URL?) {
self.id = id
self.productId = productId
self.productName = productName
self.price = price
self.quantity = quantity
self.imageURL = imageURL
}
}
public protocol CartServiceProtocol {
func addToCart(productId: String, quantity: Int) async throws
func removeFromCart(itemId: String) async throws
func updateQuantity(itemId: String, quantity: Int) async throws
func fetchCartItems() async throws -> [CartItem]
func cartItemCount() -> Int
var cartCountPublisher: AnyPublisher<Int, Never> {
get }
}
public struct UserInfo: Codable {
public let userId: String
public let nickname: String
public let avatar: URL?
public let phone: String?
public let isVip: Bool
public init(userId: String, nickname: String, avatar: URL?,
phone: String?, isVip: Bool) {
self.userId = userId
self.nickname = nickname
self.avatar = avatar
self.phone = phone
self.isVip = isVip
}
}
public enum LoginState {
case notLogin
case logging
case logged(UserInfo)
case expired
}
public protocol UserServiceProtocol {
var currentState: LoginState {
get }
var statePublisher: AnyPublisher<LoginState, Never> {
get }
func currentUser() -> UserInfo?
func isLoggedIn() -> Bool
func login(phone: String, code: String) async throws -> UserInfo
func logout() async throws
func presentLoginIfNeeded(from viewController: UIViewController,
completion: @escaping (Bool) -> Void)
}
public enum OrderStatus: String, Codable {
case pending = "pending"
case paid = "paid"
case shipping = "shipping"
case delivered = "delivered"
case completed = "completed"
case cancelled = "cancelled"
}
public struct OrderInfo: Codable, Identifiable {
public let id: String
public let orderNo: String
public let status: OrderStatus
public let totalAmount: Decimal
public let items: [OrderItem]
public let createTime: Date
public struct OrderItem: Codable {
public let productId: String
public let productName: String
public let price: Decimal
public let quantity: Int
}
}
public protocol OrderServiceProtocol {
func createOrder(from cartItems: [CartItem], addressId: String) async throws -> OrderInfo
func fetchOrders(status: OrderStatus?, page: Int) async throws -> [OrderInfo]
func fetchOrderDetail(orderId: String) async throws -> OrderInfo
func cancelOrder(orderId: String) async throws
func orderDetailViewController(orderId: String) -> UIViewController
}
public enum RoutePath {
case home
case productDetail(productId: String)
case productList(category: String)
case cart
case order(orderId: String)
case orderList(status: OrderStatus?)
case userCenter
case login
case webView(url: URL, title: String?)
case custom(path: String, params: [String: Any])
public var pathString: String {
switch self {
case .home: return "/home"
case .productDetail(let id): return "/product/\(id)"
case .productList(let category): return "/products?category=\(category)"
case .cart: return "/cart"
case .order(let id): return "/order/\(id)"
case .orderList: return "/orders"
case .userCenter: return "/user"
case .login: return "/login"
case .webView(let url, _): return "/web?url=\(url.absoluteString)"
case .custom(let path, _): return path
}
}
}
public protocol RouterServiceProtocol {
func register(path: String, handler: @escaping (URL, [String: Any]) -> UIViewController?)
func navigate(to path: RoutePath, from viewController: UIViewController?, animated: Bool)
func open(url: URL, from viewController: UIViewController?)
func goBack(from viewController: UIViewController, animated: Bool)
func popToRoot(from viewController: UIViewController, animated: Bool)
}
服务注册与发现
public final class ServiceContainer {
public static let shared = ServiceContainer()
private var services: [String: Any] = [:]
private var factories: [String: () -> Any] = [:]
private let lock = NSRecursiveLock()
private init() {
}
public func register<T>(_ type: T.Type, instance: T) {
lock.lock()
defer {
lock.unlock() }
let key = String(describing: type)
services[key] = instance
print(" [ServiceContainer] Registered: \(key)")
}
public func registerFactory<T>(_ type: T.Type, factory: @escaping () -> T) {
lock.lock()
defer {
lock.unlock() }
let key = String(describing: type)
factories[key] = factory
print(" [ServiceContainer] Registered Factory: \(key)")
}
public func registerLazy<T>(_ type: T.Type, factory: @escaping () -> T) {
lock.lock()
defer {
lock.unlock() }
let key = String(describing: type)
factories[key] = {
[weak self] in
if let existing = self?.services[key] as? T {
return existing
}
let instance = factory()
self?.services[key] = instance
return instance
}
print(" [ServiceContainer] Registered Lazy: \(key)")
}
public func resolve<T>(_ type: T.Type) -> T? {
lock.lock(