在协议中使用通用 swift 枚举
Using Generic swift enum in a protocol
我经常摸不着头脑,假设我在可以处理权限的 pod 中有一个通用的 Manager
class,并且在应用程序中,我希望能够扩展它以创建更有意义的方法名称,也就是使用枚举作为参数,使其使用更清晰,更不容易出错。
但是在别处创建扩展时好像不能调用私有方法
我确定 Generic/AssociatedValue
会有更简洁的方法,或者我的模式可能是错误的...
这是它的简化版本:
Class 在外部 pod 中:
public class FeatureDataManager {
public static let shared = FeatureDataManager()
private var permissionManager: PermissionManager!
private init() {
self.permissionManager = PermissionManager()
}
private getPermission(forFeature feature: String) -> Bool {
return self.permissionManager.isEnable(feature)
}
}
和应用程序中的扩展程序:
extension FeatureDataManager {
enum FeatureType: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
public func isPermissionEnable(forFeature feature: FeatureType) {
// Does not compile, visibility issue
self.getPermission(forFeature: feature.rawValue)
}
}
澄清:
FeatureDataManager
是 Pod 中的一个 class,仅用于以 String 值的形式检查许多正在使用的应用程序的权限导入它。
我希望每个使用它的应用程序都可以定义一个扩展程序,该扩展程序将拥有自己的受支持权限的有限枚举。假设 App A
支持广告,但 App B
不支持。所以我想有一个通用的方法,当你调用 featureManager.isPermissionEnable(.Ads) 时,无论何时应用程序,自动完成都会提供该应用程序支持的权限列表。此外,将我的字符串权限值包装到一个枚举中的目的是为了更容易出错,并且如果名称更改更容易重构,只需在一个地方更改它。
private
访问控制限制实体的使用:
Private access restricts the use of an entity to the enclosing declaration, and to extensions of that declaration that are in the same file.
Access Control - The Swift Programming Language
如果您希望通过 class 扩展名访问实体(在另一个文件和 相同的 module/package 中):使用 internal
访问控制。
如果您希望通过 class 扩展名访问实体(在另一个文件中和 在另一个 module/package 中):使用 public
访问控制.
您必须声明 fileprivate 访问级别才能在同一文件中定义的扩展中访问它。即使扩展依赖于同一文件,您也无法访问其定义范围之外的私有实体!
您正在寻找的是 "protected" 级别,而 Swift 中不存在该级别,并且如果不创建新的保护级别就无法存在,因为它会破坏编译器优化。在您的示例中,由于 getPermission(forFeature:)
承诺永远不会在此范围之外调用,因此编译器可以自由地将其内联。所以这个函数在你的扩展程序想要调用它的时候甚至可能不存在。
Swift 可以添加 "protected" 级别,即 "semi-public,",但 Swift 没有任何此类功能。您需要重新设计 FeatureDataManager 才能实现这一点。从你的例子来看,如何做到这一点并不明显,因为你根本没有提供 public 权限接口,所以不清楚你所说的 "I want to be able to extend it to create more meaningful method name." 是什么意思 目前有 no public 方法名。如果有的话,那么像你描述的那样制作一个更方便的语法会很容易。
您能否举例说明您希望此扩展改进的调用代码?
有关语言为何如此的更多信息,请参阅 Access Control and protected。这不是偶然的。
您注意到您可以在同一个文件中执行此操作,这是真的。 Swift 出于风格原因允许这样做(许多人出于代码组织原因在单个文件中使用扩展名)。 Swift 将同一文件中的所有扩展名都视为在主定义中。但这不会扩展到其他文件,当然也不会扩展到其他模块。
这个问题的通用解决方案如下:
public class FeatureDataManager<Feature>
where Feature: RawRepresentable, Feature.RawValue == String {
private func getPermission(forFeature feature: String) -> Bool { ... }
public func isPermissionEnable(forFeature feature: Feature) {
self.getPermission(forFeature: feature.rawValue)
}
}
然后 App 将创建一个功能集并为该功能集创建一个管理器:
enum AppFeature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
let featureDataManager = FeatureDataManager<AppFeature>()
featureDataManager.isPermissionEnable(forFeature: .ads)
这确实阻止了 .shared
实例的轻松创建。这是好是坏是有争议的,但假设你想要它,你需要把它包起来:
class AppFeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
static var shared = AppFeatureDataManager()
let manager = FeatureDataManager<Feature>()
public func isPermissionEnable(forFeature feature: Feature) {
manager.isPermissionEnable(forFeature: feature)
}
}
现在,对于应用程序端来说,样板文件太多了(特别是如果方法比 isPermissionEnable
多的话),因此您可以通过这种方式删除样板文件(完整代码):
public class FeatureDataManager<Feature>
where Feature: RawRepresentable, Feature.RawValue == String {
private var permissionManager: PermissionManager
init() {
self.permissionManager = PermissionManager()
}
private func getPermission(forFeature feature: String) -> Bool {
self.permissionManager.isEnable(feature)
}
public func isPermissionEnable(forFeature feature: Feature) {
self.getPermission(forFeature: feature.rawValue)
}
}
protocol AppFeatureDataManager {
associatedtype Feature: RawRepresentable where Feature.RawValue == String
var manager: FeatureDataManager<Feature> { get }
}
// Here you can write any necessary pass-through functions so the app doesn't have to
extension AppFeatureDataManager {
public func isPermissionEnable(forFeature feature: Feature) {
manager.isPermissionEnable(forFeature: feature)
}
}
//
// Application Developer writes this:
//
class MyGreatAppFeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
// This is the only thing that's really required
let manager = FeatureDataManager<Feature>()
// They're free make this a shared instance or not as they like.
// That's not something the framework cares about.
static var shared = MyGreatAppFeatureDataManager()
private init() {}
}
综上所述,我认为如果 FeatureDataManager 真的只是 PermissionManager 的前端,就像您在此处描述的那样,那么层数太多了。 (也许您的示例已高度简化,因此以下内容不适用。)
如果 PermissionManager 是 public,而真正的目标只是让它有一个更好的前端,我会这样写:
protocol FeatureDataManager {
associatedtype Feature: RawRepresentable where Feature.RawValue == String
var permissionManager: PermissionManager { get }
}
extension FeatureDataManager {
func isPermissionEnable(forFeature feature: Feature) {
permissionManager.isEnable(feature.rawValue)
}
}
//
// App developer writes this
//
class MyGreatAppFeatureDataManager: FeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
// This is the only required boilerplate; the protocol can't do this for you.
let permissionManager = PermissionManager()
// And the developer can decide to make it a shared instance if they like,
// but it's not the business of the framework
static let shared = MyGreatAppFeatureDataManager()
private init() {}
}
我经常摸不着头脑,假设我在可以处理权限的 pod 中有一个通用的 Manager
class,并且在应用程序中,我希望能够扩展它以创建更有意义的方法名称,也就是使用枚举作为参数,使其使用更清晰,更不容易出错。
但是在别处创建扩展时好像不能调用私有方法
我确定 Generic/AssociatedValue
会有更简洁的方法,或者我的模式可能是错误的...
这是它的简化版本:
Class 在外部 pod 中:
public class FeatureDataManager {
public static let shared = FeatureDataManager()
private var permissionManager: PermissionManager!
private init() {
self.permissionManager = PermissionManager()
}
private getPermission(forFeature feature: String) -> Bool {
return self.permissionManager.isEnable(feature)
}
}
和应用程序中的扩展程序:
extension FeatureDataManager {
enum FeatureType: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
public func isPermissionEnable(forFeature feature: FeatureType) {
// Does not compile, visibility issue
self.getPermission(forFeature: feature.rawValue)
}
}
澄清:
FeatureDataManager
是 Pod 中的一个 class,仅用于以 String 值的形式检查许多正在使用的应用程序的权限导入它。
我希望每个使用它的应用程序都可以定义一个扩展程序,该扩展程序将拥有自己的受支持权限的有限枚举。假设 App A
支持广告,但 App B
不支持。所以我想有一个通用的方法,当你调用 featureManager.isPermissionEnable(.Ads) 时,无论何时应用程序,自动完成都会提供该应用程序支持的权限列表。此外,将我的字符串权限值包装到一个枚举中的目的是为了更容易出错,并且如果名称更改更容易重构,只需在一个地方更改它。
private
访问控制限制实体的使用:
Private access restricts the use of an entity to the enclosing declaration, and to extensions of that declaration that are in the same file.
Access Control - The Swift Programming Language
如果您希望通过 class 扩展名访问实体(在另一个文件和 相同的 module/package 中):使用 internal
访问控制。
如果您希望通过 class 扩展名访问实体(在另一个文件中和 在另一个 module/package 中):使用 public
访问控制.
您必须声明 fileprivate 访问级别才能在同一文件中定义的扩展中访问它。即使扩展依赖于同一文件,您也无法访问其定义范围之外的私有实体!
您正在寻找的是 "protected" 级别,而 Swift 中不存在该级别,并且如果不创建新的保护级别就无法存在,因为它会破坏编译器优化。在您的示例中,由于 getPermission(forFeature:)
承诺永远不会在此范围之外调用,因此编译器可以自由地将其内联。所以这个函数在你的扩展程序想要调用它的时候甚至可能不存在。
Swift 可以添加 "protected" 级别,即 "semi-public,",但 Swift 没有任何此类功能。您需要重新设计 FeatureDataManager 才能实现这一点。从你的例子来看,如何做到这一点并不明显,因为你根本没有提供 public 权限接口,所以不清楚你所说的 "I want to be able to extend it to create more meaningful method name." 是什么意思 目前有 no public 方法名。如果有的话,那么像你描述的那样制作一个更方便的语法会很容易。
您能否举例说明您希望此扩展改进的调用代码?
有关语言为何如此的更多信息,请参阅 Access Control and protected。这不是偶然的。
您注意到您可以在同一个文件中执行此操作,这是真的。 Swift 出于风格原因允许这样做(许多人出于代码组织原因在单个文件中使用扩展名)。 Swift 将同一文件中的所有扩展名都视为在主定义中。但这不会扩展到其他文件,当然也不会扩展到其他模块。
这个问题的通用解决方案如下:
public class FeatureDataManager<Feature>
where Feature: RawRepresentable, Feature.RawValue == String {
private func getPermission(forFeature feature: String) -> Bool { ... }
public func isPermissionEnable(forFeature feature: Feature) {
self.getPermission(forFeature: feature.rawValue)
}
}
然后 App 将创建一个功能集并为该功能集创建一个管理器:
enum AppFeature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
let featureDataManager = FeatureDataManager<AppFeature>()
featureDataManager.isPermissionEnable(forFeature: .ads)
这确实阻止了 .shared
实例的轻松创建。这是好是坏是有争议的,但假设你想要它,你需要把它包起来:
class AppFeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
static var shared = AppFeatureDataManager()
let manager = FeatureDataManager<Feature>()
public func isPermissionEnable(forFeature feature: Feature) {
manager.isPermissionEnable(forFeature: feature)
}
}
现在,对于应用程序端来说,样板文件太多了(特别是如果方法比 isPermissionEnable
多的话),因此您可以通过这种方式删除样板文件(完整代码):
public class FeatureDataManager<Feature>
where Feature: RawRepresentable, Feature.RawValue == String {
private var permissionManager: PermissionManager
init() {
self.permissionManager = PermissionManager()
}
private func getPermission(forFeature feature: String) -> Bool {
self.permissionManager.isEnable(feature)
}
public func isPermissionEnable(forFeature feature: Feature) {
self.getPermission(forFeature: feature.rawValue)
}
}
protocol AppFeatureDataManager {
associatedtype Feature: RawRepresentable where Feature.RawValue == String
var manager: FeatureDataManager<Feature> { get }
}
// Here you can write any necessary pass-through functions so the app doesn't have to
extension AppFeatureDataManager {
public func isPermissionEnable(forFeature feature: Feature) {
manager.isPermissionEnable(forFeature: feature)
}
}
//
// Application Developer writes this:
//
class MyGreatAppFeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
// This is the only thing that's really required
let manager = FeatureDataManager<Feature>()
// They're free make this a shared instance or not as they like.
// That's not something the framework cares about.
static var shared = MyGreatAppFeatureDataManager()
private init() {}
}
综上所述,我认为如果 FeatureDataManager 真的只是 PermissionManager 的前端,就像您在此处描述的那样,那么层数太多了。 (也许您的示例已高度简化,因此以下内容不适用。)
如果 PermissionManager 是 public,而真正的目标只是让它有一个更好的前端,我会这样写:
protocol FeatureDataManager {
associatedtype Feature: RawRepresentable where Feature.RawValue == String
var permissionManager: PermissionManager { get }
}
extension FeatureDataManager {
func isPermissionEnable(forFeature feature: Feature) {
permissionManager.isEnable(feature.rawValue)
}
}
//
// App developer writes this
//
class MyGreatAppFeatureDataManager: FeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}
// This is the only required boilerplate; the protocol can't do this for you.
let permissionManager = PermissionManager()
// And the developer can decide to make it a shared instance if they like,
// but it's not the business of the framework
static let shared = MyGreatAppFeatureDataManager()
private init() {}
}