Использование Generi c swift enum в протоколе - PullRequest
1 голос
/ 07 мая 2020

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

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

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

Ответы [ 3 ]

2 голосов
/ 28 мая 2020

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

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

Не могли бы вы привести пример вызывающего кода, который вы хотите улучшить с помощью этого расширения?

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

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


Общее c решение этого выглядит следующим образом:

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

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 голосов
/ 28 мая 2020

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

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

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

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

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

0 голосов
/ 28 мая 2020

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...