У меня есть случай, когда я часто ломаю голову, скажем, у меня есть универсальный класс Manager в модуле, который может обрабатывать разрешения, и в приложении я хочу иметь возможность расширить его, чтобы создать более осмысленное имя метода , иначе использовать с enum в качестве параметра, чтобы сделать его использование более понятным и менее подверженным ошибкам.

Но кажется, что вы не можете вызвать приватный метод, когда создаете расширение в другом месте.

Я уверен, что с Generic/AssociatedValue был бы более чистый путь, или, возможно, мой шаблон просто неправильный ...

Вот упрощенная версия этого:

Класс во внешнем модуле .

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 - это класс в модуле, который используется исключительно для проверки разрешений в виде значения String во многих приложениях, использующих его импорт.

Я хотел, чтобы каждое приложение использовало его, чтобы определить расширение, которое будет иметь свой конечный перечень поддерживаемых разрешений. Допустим, App A поддерживают рекламу, но не App B. Поэтому я хотел иметь универсальный метод, который при вызове featureManager.isPermissionEnable (.Ads), когда бы приложение ни было, автозаполнение просто предлагало бы список поддерживаемых разрешений для этого приложения. Кроме того, цель включения моего значения разрешения строки в перечисление состоит в том, чтобы быть более пуленепробиваемым для ошибок и упростить рефакторинг при изменении имени, просто нужно изменить его в одном месте.

1
Jaythaking 7 Май 2020 в 01:59

3 ответа

Лучший ответ

То, что вы ищете, это "защищенный" уровень, который не существует в Swift и не может существовать без создания нового уровня защиты, поскольку он нарушит оптимизацию компилятора. В вашем примере, поскольку getPermission(forFeature:) обещано никогда не вызываться вне этой области видимости, компилятор может встроить его. Так что эта функция может даже не существовать в тот момент, когда ваше расширение хочет вызвать ее.

Swift мог бы добавить «защищенный» уровень, который является «полуобщественным», но Swift не имеет такой функции. Вам нужно будет перепроектировать FeatureDataManager, чтобы сделать это возможным. Из вашего примера не очевидно, как это сделать, потому что вы вообще не предоставляете общедоступный интерфейс для разрешений, поэтому неясно, что вы подразумеваете под «Я хочу расширить его, чтобы создать более осмысленное имя метода». В настоящее время нет публичного имени метода. Если бы он был, то сделать более удобный синтаксис, как вы описали, было бы легко.

Можете ли вы привести пример кода вызова, который вы хотите улучшить это расширение?

Подробнее о том, почему язык так устроен, см. Контроль доступа и защита. , Это не случайность.

Вы заметили, что вы можете сделать это в том же файле, и это правда. 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)
    }   
}

Приложение затем создаст набор функций и создаст менеджер для этого набора функций:

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 общедоступен, и настоящая цель состоит в том, чтобы сделать его более привлекательным, я бы написал так:

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() {}
}
2
Rob Napier 29 Май 2020 в 13:05

private контроль доступа ограничивает использование объекта:

Частный доступ ограничивает использование объекта прилагаемой декларацией и расширениями этой декларации, которые находятся в том же файле.

Контроль доступа - язык программирования Swift

Если вы хотите, чтобы объект был доступен через расширение класса (в другом файле и в том же модуле / пакете ): используйте internal контроль доступа.

Если вы хотите, чтобы объект был доступен через расширение класса (в другом файле и в другом модуле / пакете ): используйте public контроль доступа.

2
Magiguigui 28 Май 2020 в 14:58

Вы должны объявить fileprivate уровень доступа для доступа к нему в расширении, определенном в том же файле. Вы не можете получить доступ к частным объектам вне его определенной области, даже если расширение полагается на тот же файл!

0
jegadeesh 28 Май 2020 в 15:03