Swift 4.2 在单个 if-let 中向下转换多个变量时合并

Swift 4.2 coalescing while downcasting multiple variables in a single if-let

我最近开始学习 Swift,现在我正在尝试安全地解包来自 JSON 响应的多个变量,这些变量可能包含或不包含该密钥。

JSON 响应示例:

{
    "products: [{
        "foo": "foo"
        "bar": "bar"
    }, {
        "foo": "foo"
    }]
}

我正在尝试以下操作:

let dataTask = URLSession.shared.dataTask(with: myURL) { (data, response, error) in
    guard let safeData = data else { return }

    do {
        let json = try JSONSerialization.jsonObject(with: safeData, options: .mutableLeaves)
        if let jsonDict = json as? [String : Any] {
            let productArray = jsonDict["products"] as? [[String : Any]]
            for product in productArray! {
                if let foo = product["foo"] as? String, let bar = product["bar"] as? String {
                    let prod = Product(foo: foo, bar: bar)
                    products.append(prod)
                }
            }
        }
    } catch {
        print ("Error: \(error)")
    }
}

我想做的是给bar一个默认值(合并),如果值为nil,比如"Not Available",以便在标签中显示。

可能吗?我该怎么做?

你可以试试

 let prod = Product(foo:product["foo"] as? String ?? "Not Available" , bar: product["bar"] as? String ?? "Not Available" )

struct Root: Decodable {
    let products: [Product]
}

struct Product: Decodable {
    let foo: String
    let bar: String?

    enum CodingKeys: String, CodingKey {
        case foo , bar  
    }

    init(from decoder: Decoder) throws {

         let container = try decoder.container(keyedBy: CodingKeys.self)


        do { 
            let foo = try container.decode(String.self, forKey: .foo)  
            let bar = try container.decodeIfPresent(String.self, forKey: .bar) ?? "Not Available"
            self.init(foo:foo, bar:bar)

        } catch let error {
            print(error)
            throw error
        }
     }
}

由于 json 中的键 Decodable 友好,您可以使用这个最少的代码,

struct ProductResponse: Decodable {
    let products: [Product]
}

struct Product: Decodable {
    let foo: String
    let bar: String?
}

let dataTask = URLSession.shared.dataTask(with: myURL) { (data, response, error) in
    guard let data = data else { return }

    do {
        let productResponse = JSONDecoder().decode(ProductResponse.self, from: data)
        print(productResponse.products.forEach({ print([=10=].foo)}))
    } catch {
        print ("Error: \(error)")
    }
}

在解析级别将默认值分配给 bar 似乎并不自然。 Product 是一种只有两个属性的简单类型,但对于具有数十个属性的类型,你会讨厌实现 init(from decoder: Decoder)CodingKeys enumerations 只是因为一个或两个属性需要一个默认值。

我建议一个更好的方法,如下所示,将 extension 引入 Optional

extension Optional where Wrapped == String {

    /// Unwrapped string
    /// `u7d` comes from starting `u` in `Unwrapped`, 
    ///  7 letters in between and last letter `d`.
    public func u7d(_ defaultString: String = "N/A") -> String {
        guard let value = self, value.isEmpty == false else { return defaultString }
        return value
    }
}

所以现在当你想使用默认值 value 如果这个 属性 是 nil,你可以通过传递默认值 value 来解包,如下所示,

productResponse.products.forEach({ product in
    print(product.bar.u7d("Not Available"))
})

这有一些主要好处,如下所示,

  • 当您将此可选 属性 与 nil 进行比较时,您的 if statement 结果将保持预期。
  • 你可以在不同的地方传递不同的默认值而不用任何if statement
  • 因为 UITextFieldUITextViewUILabel 接受 Optional 文本,在许多情况下,您需要在 [=32] 时显示 placeholder =] 属性是来自 api 响应的 nil。因此,在这些情况下,您不必重新设计 string 属性来了解它是否具有默认值或 api 返回值。