Безопасная распаковка необязательных значений и добавление их в параметры Alamofire

У меня есть вычисляемое свойство типа Parameters в моем APIRouter

// MARK: - Parameters
    private var parameters: Parameters? {
        switch self {
        case .searchForDoctors(let doctorsFilter):
            var params: Parameters = ["main_category_id": doctorsFilter.0, "page": doctorsFilter.1, "specialty_id": doctorsFilter.2, "city_id": doctorsFilter.3, "region_id": doctorsFilter.4, "name": doctorsFilter.5, "company_id": doctorsFilter.6, "order_by": doctorsFilter.7]
            return params
        default:
            return nil
        }
    }

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

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

if let specialtyID = doctorsFilter.2 {
    params["specialty_id"] = specialtyID
}

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

Обновлено:- тип DoctorsFilter задокументирован, когда я инициализирую экземпляр типа DoctorsFilter, автозаполнение сообщает мне, какой из них является чем, я думал о создании класса DoctorsFilter раньше, но я ищу другой способ, если таковой имеется, возможно, встроенный зарезервированное слово может справиться со всей ситуацией! , я хочу сделать это максимально простым. создание функции, которая обрабатывает словарь и возвращает его в классе DoctorsFilter, является опцией. Я думаю добавить эту функцию в APIRouter, нормально ли добавить ее туда? это правило APIRouter для обработки параметров? или APIRouter просто заинтересован в получении параметров и не будет их обрабатывать?

Я думаю, вам нужно начать с превращения doctorsFilter в нормальный тип: иметь septuple (например, doctorsFilter.0, doctorsFilter.3) плохо, иметь septuple с опционами еще хуже. Ты хоть знаешь, кто из них что? И, может быть, вы это сделаете, но через год кто сможет поддерживать такой код? Как только ваш doctorsFilter станет обычным типом, этот нормальный тип также может иметь функцию (или свойство компьютера), которая возвращает Parameters только с доступными значениями.

klangfarb 21.12.2020 00:51

@КирилС. ну, тип DoctorsFilter задокументирован, когда я инициализирую экземпляр типа DoctorsFilter, автозаполнение говорит мне, какой из них что, я думал о вашем «нормальном типе» раньше, но я ищу другой способ, если таковой имеется, может быть встроенное зарезервированное слово может справиться со всей ситуацией! , я хочу сделать это максимально простым. поэтому я ценю ваш ответ, но ваше предложение будет моим последним вариантом

Ziad Kamel 21.12.2020 01:11

@КирилС. вопрос отредактирован

Ziad Kamel 21.12.2020 02:17
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
3
3
303
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Есть три основных способа безопасно развернуть необязательный файл. Вы также можете указать значения по умолчанию, если хотите развернуть необязательный.

Развертка оператора Guard

var firstString: String?

// In some cases you might performa a break, continue or a return.
guard let someString = firstString else { return }
print(someString)

Если позволить разворачивание

var secondString: String?
var thirdString: String?
thirdString = "Hello, World!"

// Notice that we are able to use the same "IF LET" to unwrap
// multiple values. However, if one fails, they all fail. 
// You can do the same thing with "Guard" statements.
if let someString = secondString,
   let someOtherString = thirdString {
       print(someString)
       print(someOtherString)
} else {
       // With this code snippet, we will ALWAYS hit this block.
       // because secondString has no value.
       print("We weren't able to unwrap.")
}

Развертка значения по умолчанию

var fourthString: String?
// The ?? is telling the compiler that if it cannot be unwrapped,
// use this value instead.
print(fourthString ?? "Hello, World")

В Swift рекомендуется каждый раз, когда вы видите !, использовать какую-либо форму развертывания. Swift очень «безопасен для типов».

Вот ресурс, который вы можете использовать для опций. https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html

Ваше решение

Ваше решение может выглядеть примерно так.

private var parameters: Parameters? {
   switch self {
   case .searchForDoctors(let doctorsFilter):

       if let mainCatID = doctorsFilter.0,
          let page = doctorsFilter.1,
          let specialtyID = doctorsFilter.2,
          let cityID = doctorsFilter.3,
          let regionID = doctorsFilter.4,
          let name = doctorsFilter.5,
          let companyID = doctorsFilter.6,
          let orderBy = doctorsFilter.7 {

          params: Parameters = ["main_category_id": mainCatID, 
                                "page": page, 
                                "specialty_id": specialtyID, 
                                "city_id": cityID, 
                                "region_id": regionID, 
                                "name": name, 
                                "company_id": companyID, 
                                "order_by": orderBy]
          return params
    } else {
          //Unable to safely unwrap, return nothing.
          return nil
    }
    default:
        return nil
    }
}

Я ценю ваш ответ, но вы не получили мой вопрос, я не спрашиваю, как безопасно развернуть необязательные значения, я спрашиваю, как обрабатывать необязательные значения, связанные с конкретной ситуацией

Ziad Kamel 21.12.2020 01:32

Мой ответ, ответ на ваш вопрос. Просто игнорируйте оператор Else или используйте guard let вместо if let. Если вы не можете развернуть, он не войдет в этот блок кода, что позволит вам не добавлять свои kvp.

xTwisteDx 21.12.2020 01:36

Пожалуйста, внимательно прочитайте мой вопрос, я не хочу говорить, допустимо ли для каждого необязательного значения, также я не хочу указывать значение по умолчанию. Я ищу простой способ добавить ключи и значения в словарь без написания 10 строк кода для развертывания необязательных значений!

Ziad Kamel 21.12.2020 01:48

@ZiadKamel, тогда вы просите что-то под названием «Принудительное развертывание», которое приведет к сбою вашего приложения. Вы ДОЛЖНЫ развернуть их, что является соглашением. В противном случае в вашем классе doctorFilter вам нужно убедиться, что эти значения не являются необязательными.

xTwisteDx 21.12.2020 01:50

Создание функции, которая обрабатывает словарь и возвращает его в классе DoctorsFilter, является опцией. Я думаю добавить эту функцию в APIRouter, нормально ли добавить ее туда? это правило APIRouter для обработки параметров? или APIRouter просто заинтересован в получении параметров и не будет их обрабатывать?

Ziad Kamel 21.12.2020 01:58

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

xTwisteDx 21.12.2020 02:02
Ответ принят как подходящий

Не существует «однострочного» решения, но вы можете использовать KeyPaths, чтобы сократить серию операторов if let ... до цикла.

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

Чтобы облегчить это, мы определяем протокол для Parameterable. Для этого протокола требуется словарь, который сопоставляет имена параметров (String) со свойством (KeyPath), которое содержит это имя параметра, а также функцию для возврата словаря параметров.

protocol Parameterable {
    var paramNames: [String:KeyPath<Self,String?>] {get}
    func parameters() -> [String:Any]
}

Используйте расширение, чтобы создать реализацию функции parameters() по умолчанию, так как код будет одинаковым для всех Parameterable. Он перебирает записи словаря и использует связанный KeyPath для доступа к соответствующему свойству и помещения его в выходной словарь. Если данное свойство равно nil, то оно просто не добавляется в выходной словарь, потому что так работают словари. Нет необходимости явно проверять.

(Если вы импортируете Alamofire, вы можете использовать typedef Parameters там, где я использовал [String:Any])

extension Parameterable {
    func parameters() -> [String:Any] {
        var parameters = [String:Any]()
    
        for (paramName,keypath) in self.paramNames {
            parameters[paramName]=self[keyPath:keypath]
        }
        return parameters
    }
}

Используйте этот протокол для создания реализации DoctorsFilter:

struct DoctorsFilter: Parameterable {
    var mainCategoryId: String?
    var page: String?
    var specialtyId: String?
    var cityID: String?
    var regionId: String?
    var name: String?
    var companyId: String?
    var orderBy: String?

    let paramNames:[String:KeyPath<Self,String?>] = [ 
    "main_category_id":\.mainCategoryId,
    "page":\.page,
    "specialty_id":\.specialtyId,
    "city_id":\.cityID,
    "region_id":\.regionId,
    "name":\.name,
    "company_id":\.companyId,
    "order_by":\.orderBy]
}
private var parameters: Parameters? {
    switch self {
        case .searchForDoctors(let doctorsFilter):
            return doctorsFilter.parameters()
        case .someOtherThing(let someOtherThing):
            return someOtherThing.parameters()
        default:
            return nil
        }
    }
}

Другой подход состоит в том, чтобы просто разделить создание словаря parameters на несколько строк; Если вы присвоите nil ключу словаря, то в словаре для этого ключа не будет храниться пара ключ/значение. В этом случае я оставил ваш подход с кортежами на месте, но вы можете использовать структуру (и я настоятельно рекомендую вам это сделать)

private var parameters: Parameters? {
    switch self {
        case .searchForDoctors(let doctorsFilter):
            var params: Parameters()
            params["main_category_id"] = doctorsFilter.0
            params["page"] = doctorsFilter.1
            params["specialty_id"] = doctorsFilter.2
            params["city_id"] = doctorsFilter.3
            params["region_id"] = doctorsFilter.4
            params["name"] = doctorsFilter.5
            params["company_id"] = doctorsFilter.6
            params["order_by"] = doctorsFilter.7
            return params
        default:
            return nil
        }
    }

Если мы хотим обрабатывать смешанные свойства, а не только необязательные строки, нам нужно немного изменить код. Нам нужно использовать PartialKeyPath. Это делает код немного более сложным, так как оператор нижнего индекса для PartialKeyPath возвращает двойной необязательный параметр. С этим нужно справиться.

protocol Parameterable {
    var paramNames: [String:PartialKeyPath<Self>] {get}
    func parameters() -> [String:Any]
}

extension Parameterable {
    func parameters() -> [String:Any] {
        var parameters = [String:Any]()
        for (paramName,keypath) in self.paramNames {
            let value = self[keyPath:keypath] as? Any?
                if let value = value { 
                    parameters[paramName] = value
                }
        }
        return parameters
    }
}

struct DoctorsFilter:Parameterable  {
    var mainCategoryId: String?
    var page: String?
    var specialtyId: String?
    var cityID: Int
    var regionId: String?
    var name: String?
    var companyId: String?
    var orderBy: String?
    let paramNames:[String:PartialKeyPath<Self>] =     
        ["main_category_id":\Self.mainCategoryId,
         "page":\Self.page,
         "specialty_id":\Self.specialtyId,
         "city_id":\Self.cityID,
         "region_id":\Self.regionId,
         "name":\Self.name,
         "company_id":\Self.companyId,
         "order_by":\Self.orderBy]
}

Идеальный! этот ответ работает со значениями String, но что, если мои параметры включают значения Int и значения String

Ziad Kamel 21.12.2020 16:36

Я действительно разработал это решение, но не опубликовал его, потому что оно не такое элегантное; вам нужно обрабатывать некоторые двойные опции, возникающие при использовании AnyKeyPath. Я обновлю свой ответ позже.

Paulw11 21.12.2020 20:19

Я ценю ваши усилия, и мне нужно решение, надеюсь, вы скоро обновите свой ответ. Спасибо

Ziad Kamel 24.12.2020 04:54

Я сделал; это внизу, используя PartialKeyPath

Paulw11 24.12.2020 05:24

Другие вопросы по теме