Swift编译报错:含关联类型/Self的协议仅能用作泛型约束求助
Hey there, let's break down this error and get your code back on track!
Why This Error Happens
The root issue here is your FormVMProtocol includes an associatedtype RequestModel. In Swift, protocols with associated types (or Self requirements) can't be used as regular types—you can't declare a variable like var viewModel: FormVMProtocol directly, because Swift can't infer the concrete type of RequestModel from just the protocol. Instead, these protocols only work as generic constraints.
Solutions to Try
1. Use Generic Constraints (Most Recommended)
The cleanest fix is to use a generic type wherever you're trying to use FormVMProtocol. For example, if you're using this protocol in a view controller:
class FormViewController<VM: FormVMProtocol>: UIViewController { var viewModel: VM! // Rest of your view controller code... }
When you initialize this view controller, you'll pass in a concrete implementation of FormVMProtocol (like MyConcreteFormVM), and Swift will automatically resolve the associated RequestModel type for you.
2. Implement Type Erasure
If you need to use the protocol in a context where generics aren't feasible (like storing multiple view models in an array), you can create a "type-erased" wrapper that hides the associated type details:
class AnyFormVM<RequestModel: GatewayObjectRequestModel>: FormVMProtocol { // Store closures to forward calls to the underlying viewModel private let _getDelegate: () -> EventFromVMDelegate? private let _setDelegate: (EventFromVMDelegate?) -> Void private let _handleDidCancelForm: (() -> ())? private let _handleDidAcceptForm: ((RequestModel) -> ())? private let _title: () -> String private let _cellsViewModels: () -> [EventFormCellVMProtocol] private let _getIndexPath: (EventFormCellVMProtocol) -> IndexPath? private let _refreshRow: (EventFormCellVMProtocol) -> Void private let _refreshAllRows: () -> Void private let _createRequestModel: () -> Promise<RequestModel> // Initialize with a concrete viewModel that conforms to FormVMProtocol init<VM: FormVMProtocol>(_ viewModel: VM) where VM.RequestModel == RequestModel { _getDelegate = { viewModel.delegate } _setDelegate = { viewModel.delegate = $0 } _handleDidCancelForm = viewModel.handleDidCancelForm _handleDidAcceptForm = viewModel.handleDidAcceptForm _title = { viewModel.title } _cellsViewModels = { viewModel.cellsViewModels } _getIndexPath = viewModel.getIndexPath _refreshRow = viewModel.refreshRow _refreshAllRows = viewModel.refreshAllRows _createRequestModel = viewModel.createRequestModel } // Conform to FormVMProtocol by forwarding to the stored closures weak var delegate: EventFromVMDelegate? { get { _getDelegate() } set { _setDelegate(newValue) } } var handleDidCancelForm: (() -> ())? { get { _handleDidCancelForm } set { /* Add setter logic if your concrete VM requires it */ } } var handleDidAcceptForm: ((RequestModel) -> ())? { get { _handleDidAcceptForm } set { /* Add setter logic if needed */ } } var title: String { _title() } var cellsViewModels: [EventFormCellVMProtocol] { _cellsViewModels() } func getIndexPath(for rowViewModel: EventFormCellVMProtocol) -> IndexPath? { _getIndexPath(rowViewModel) } func refreshRow(with viewModel: EventFormCellVMProtocol) { _refreshRow(viewModel) } func refreshAllRows() { _refreshAllRows() } func createRequestModel() -> Promise<RequestModel> { _createRequestModel() } // Default implementations from your extension are already handled }
Now you can use this wrapper like a regular type:
var viewModel: AnyFormVM<MyRequestModel> = AnyFormVM(MyConcreteFormVM())
3. Adjust Your Protocol Design (Conditional)
If your business logic allows, you could remove the associated type and use a base protocol instead. For example:
protocol FormVMProtocol: FormCellVMsProvider { weak var delegate: EventFromVMDelegate? { get set } var handleDidCancelForm: (() -> ())? { get set } var handleDidAcceptForm: ((GatewayObjectRequestModel) -> ())? { get set } var title: String { get } var cellsViewModels: [EventFormCellVMProtocol] { get } // ... rest of your methods func createRequestModel() -> Promise<GatewayObjectRequestModel> }
This works if you can accept working with the base GatewayObjectRequestModel type (you might need to downcast later), but it's less type-safe than the first two solutions.
Final Notes
Stick with the generic constraint approach (Solution 1) whenever possible—it's the most idiomatic Swift solution and keeps your code type-safe. Type erasure (Solution 2) is great for cases where you need flexibility with multiple conforming types.
内容的提问来源于stack exchange,提问作者Kamil Harasimowicz

