Swift中含关联类型的协议如何实现依赖注入?
你碰到的这个报错是Swift里很常见的限制:带Self或关联类型要求的协议没法直接当作具体类型使用,因为编译器无法确定关联类型的具体形态。下面给你几个靠谱的解决方案,既能实现依赖注入,又能保留后续替换数据库的扩展性:
方案1:用泛型ViewModel包装
把LoginViewModel改成泛型结构,让它依赖一个遵循DataBaseManager协议的具体类型,这样编译器就能明确关联类型的具体值:
struct LoginViewModel<DB: DataBaseManager> { var dbManager: DB init(dbManager: DB) { self.dbManager = dbManager } // 举个使用例子 func saveUser(_ user: DB.type) { dbManager.insert(object: [user]) } }
调用的时候直接传具体实现类就行:
let loginViewModel = LoginViewModel(dbManager: RealmManager())
这个方案简单直接,完全保留类型安全,后续换成SqliteManager只需要传对应的实例,ViewModel核心逻辑不用改。
方案2:类型擦除(Type Erasure)
创建一个类型擦除的包装类,把带关联类型的协议转换成普通类,这样就能直接当作变量类型用:
先定义AnyDataBaseManager:
class AnyDataBaseManager<T>: DataBaseManager { typealias type = T private let _insert: ([T]) -> Void private let _getObject: (T.Type) -> [T] init<DB: DataBaseManager>(_ dbManager: DB) where DB.type == T { _insert = dbManager.insert _getObject = dbManager.getObject } func insert(object obj: [T]) { _insert(obj) } func getObject(ofType type: T.Type) -> Array<T> { _getObject(type) } }
然后修改LoginViewModel:
struct LoginViewModel { var dbManager: AnyDataBaseManager<Object> init(dbManager: AnyDataBaseManager<Object>) { self.dbManager = dbManager } }
调用时把具体管理器包装一下:
let realmManager = RealmManager() let loginViewModel = LoginViewModel(dbManager: AnyDataBaseManager(realmManager))
这个方案适合你希望ViewModel保持非泛型的场景,把关联类型的细节藏在擦除类里,对外只暴露统一接口。
方案3:Swift 5.7+用any存在类型
如果你的项目用的是Swift 5.7及以上版本,可以用any关键字声明存在类型,不过要先调整协议让关联类型有明确约束:
先修改协议,让关联类型遵循Object:
protocol DataBaseManager{ associatedtype type: Object init() func insert(object obj: [type]) func getObject(ofType type: type.Type)-> Array<type> }
然后调整ViewModel:
struct LoginViewModel { var dbManager: any DataBaseManager init(dbManager: any DataBaseManager) { self.dbManager = dbManager } }
调用时直接传实例就行:
let loginViewModel = LoginViewModel(dbManager: RealmManager())
这是Swift 5.7新增的特性,简化了存在类型的使用,不过要确认你的项目部署目标支持对应Swift版本。
方案4:调整协议设计(去掉关联类型)
如果业务场景允许,把关联类型改成方法的泛型参数,这样协议就不再有类型要求:
protocol DataBaseManager{ init() func insert<T>(object obj: [T]) where T: Object func getObject<T>(ofType type: T.Type)-> Array<T> }
然后修改RealmManager的实现:
class RealmManager: DataBaseManager{ var realm:Realm! required init() { do{ realm = try Realm() }catch { print(error) } } func insert<T>(object obj: [T]) where T : Object { do { try realm.write { realm.add(obj) } } catch let error { print("the error in creation of object is \(error)") } } func getObject<T>(ofType type: T.Type)-> Array<T> where T : Object { let objects = realm.objects(type) return Array(objects) } }
这样LoginViewModel就能直接用协议作为类型:
struct LoginViewModel { var dbManager: DataBaseManager init(dbManager: DataBaseManager) { self.dbManager = dbManager } }
这个方案最直接,前提是所有数据库操作的对象都遵循Object(比如Realm的Object),完全符合业务需求的话非常推荐。
内容的提问来源于stack exchange,提问作者Costello

