安全地解包可选值并将其添加到 Alamofire 参数

Safely unwrapping optional values and add it to 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
        }
    }

名为 doctorsFilter 的 Typealias 中的某些值是可选的。 目前我有一个警告要求我为可选值提供默认值,我不想提供默认值,我想检查该值是否存在以添加它,否则我不会添加键和值

我怎样才能安全地解开可选值并将其添加到参数字典中而不说是否让所有可选值? 示例:

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

我不想以这种方式解包,因为我将检查所有可选值,这将需要更多代码行

编辑:- 记录了 DoctorsFilter 类型,当我初始化 DoctorsFilter 类型的实例时,自动完成会告诉我它们中的哪一个是什么,我之前考虑过制作 DoctorsFilter class 但我正在寻找另一种方式,如果有的话,也许一个内置的保留字可以处理整个情况! ,我想让它尽可能简单。 创建一个处理字典的函数,returns 它在 DoctorsFilter class 中是一个选项。我正在考虑将此功能添加到 APIRouter 中,是否可以将其添加到那里?处理参数是APIRouter的规则吗?或者 APIRouter 只是对获取参数感兴趣而不会处理它?

可通过三种主要方法安全地解包可选。如果你想打开一个可选的,你也可以提供默认值。

保护语句展开

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
    }
}

没有“一行”解决方案,但您可以使用 KeyPaths 将一系列 if let ... 语句缩减为一个循环。

首先为过滤器创建一个结构,而不是使用元组。

为了促进这一点,我们为 Parameterable 定义了一个协议 - 该协议需要一个将参数名称 (String) 映射到 属性 (KeyPath) 的字典它包含该参数名称以及 return 参数字典的函数。

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 分配给字典键,则字典中不会为该键存储 key/value 对。在这种情况下,我保留了您的元组方法,但您可以使用结构(我强烈建议您这样做)

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 return 的下标运算符是一个双精度可选。这需要处理。

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]
}