控制器中泛型类属性注入及iOS类Redux状态管理框架化问询
Hey there! Let's break down your two questions one by one with practical, Swift-focused solutions tailored for iOS development:
Injecting generic classes into view controllers requires balancing type safety and flexibility. Here are three reliable approaches:
方案一:构造器注入(最推荐,符合依赖倒置原则)
This is the cleanest approach—it makes dependencies explicit and simplifies testing.
Example generic service class:
class GenericService<T> { func fetchData() -> T? { // Generic data fetch logic return nil } }
Inject via custom initializer in your view controller:
class MyViewController: UIViewController { private let service: GenericService<MyDataModel> init(service: GenericService<MyDataModel>) { self.service = service super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("Use the custom initializer instead") } override func viewDidLoad() { super.viewDidLoad() if let data = service.fetchData() { // Handle fetched data } } }
If you need to support Storyboards/XIBs, you can combine this with a setup method called after initialization, but constructor injection remains the priority for clarity.
方案二:协议驱动的属性注入
For scenarios where constructor injection isn't feasible (e.g., Storyboard instantiation), use a protocol to abstract generic functionality:
Define a protocol for your generic service:
protocol DataFetching { associatedtype DataType func fetchData() -> DataType? } extension GenericService: DataFetching {}
Inject via property in the view controller:
class MyViewController: UIViewController { var dataFetcher: DataFetching where DataType == MyDataModel? override func viewDidLoad() { super.viewDidLoad() if let data = dataFetcher.fetchData() { // Handle data } } }
Assign the service when navigating to the controller:
let service = GenericService<MyDataModel>() let vc = MyViewController() vc.dataFetcher = service navigationController?.pushViewController(vc, animated: true)
方案三:依赖注入容器(适合 complex projects)
For large apps with many generic dependencies, use a DI container to centralize management:
class DIContainer { static let shared = DIContainer() private var registrations: [String: Any] = [] private init() {} func register<T>(_ instance: T) { let key = String(describing: T.self) registrations[key] = instance } func resolve<T>() -> T? { let key = String(describing: T.self) return registrations[key] as? T } }
Register your generic service at app launch:
DIContainer.shared.register(GenericService<MyDataModel>())
Resolve it in your view controller:
class MyViewController: UIViewController { private let service: GenericService<MyDataModel> override init(nibName: String?, bundle: Bundle?) { guard let service = DIContainer.shared.resolve() else { fatalError("Failed to resolve GenericService") } self.service = service super.init(nibName: nibName, bundle: bundle) } // ... rest of the code }
Great job building a working Redux-like solution! Abstracting it into a reusable framework will absolutely boost reusability and testability. Let's structure this step by step:
Step 1: Abstract the Core Store Class
First, create a generic Store class that accepts custom State and Reducer from the user:
// Base protocol for all user-defined actions public protocol Action {} // Reducer type: takes current State and Action, returns new State public typealias Reducer<State> = (State, Action) -> State public class Store<State> { private var currentState: State private let reducer: Reducer<State> private let queue = DispatchQueue(label: "com.yourframework.store.queue") private var subscribers: [(State) -> Void] = [] public init(initialState: State, reducer: @escaping Reducer<State>) { self.currentState = initialState self.reducer = reducer } // Thread-safe state access public func getState() -> State { queue.sync { currentState } } // Thread-safe action dispatch public func dispatch(_ action: Action) { queue.async { [weak self] in guard let self = self else { return } let newState = self.reducer(self.currentState, action) self.currentState = newState // Notify all subscribers of state change self.subscribers.forEach { $0(newState) } } } // Subscribe to state updates public func subscribe(_ subscriber: @escaping (State) -> Void) { queue.async { [weak self] in guard let self = self else { return } self.subscribers.append(subscriber) // Send current state to new subscriber immediately subscriber(self.currentState) } } }
This generic Store lets users plug in their own State structs and Reducers without modifying the framework core.
Step 2: Implement Store Injection into Any Class
Here are three flexible ways to inject the Store into classes like UIViewControllers or UIViews:
方式一:协议注入(最适合测试)
Define a protocol for classes that need access to the Store, then inject via constructor or property:
public protocol StoreHolder { associatedtype StateType var store: Store<StateType> { get } } // Example view controller using the protocol class MyViewController: UIViewController, StoreHolder { typealias StateType = AppState let store: Store<AppState> init(store: Store<AppState>) { self.store = store super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("Use custom initializer") } override func viewDidLoad() { super.viewDidLoad() // Access current state let currentState = store.getState() // Subscribe to state changes store.subscribe { [weak self] newState in DispatchQueue.main.async { self?.updateUI(with: newState) } } } private func updateUI(with state: AppState) { // Update UI based on new state } }
This approach makes testing easy—you can pass a mock Store in unit tests.
方式二:属性包装器(语法糖简化代码)
Use a Swift property wrapper to reduce boilerplate when injecting the Store:
@propertyWrapper public struct InjectStore<State> { private let store: Store<State> public init() { guard let store = DIContainer.shared.resolve() else { fatalError("Store not registered in DI container") } self.store = store } public var wrappedValue: Store<State> { store } }
Use it in your view controller:
class MyViewController: UIViewController { @InjectStore var store: Store<AppState> override func viewDidLoad() { super.viewDidLoad() let state = store.getState() // ... use store here } }
Remember to register your Store in the DI container at app launch:
let initialAppState = AppState() let appStore = Store(initialState: initialAppState, reducer: appReducer) DIContainer.shared.register(appStore)
方式三:全局 Store(简单但谨慎使用)
For small projects, a global Store is quick to implement, but note it can make testing harder:
// Global variable in your framework public var SharedStore: Store<AppState>? // Initialize at app launch SharedStore = Store(initialState: AppState(), reducer: appReducer) // Use in view controller class MyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() guard let store = SharedStore else { return } // ... use store here } }
Step 3: Boost Reusability & Testability
- Modularize the framework: Split Action, Reducer, and Store into separate modules so users only import what they need.
- Add testing tools: Create a
MockStoreclass to help users test state changes and action dispatches:
public class MockStore<State>: Store<State> { public var dispatchedActions: [Action] = [] override public func dispatch(_ action: Action) { dispatchedActions.append(action) // Optional: manually control state updates for testing } }
- Include examples: Add sample code showing how to define a custom State, write a Reducer, and inject the Store into a view controller.
内容的提问来源于stack exchange,提问作者Fabien Gavory

