【问题标题】:Is it possible to abort a map function on a Swift collection?是否可以中止 Swift 集合上的地图功能?
【发布时间】:2018-05-23 06:59:31
【问题描述】:

我们有一个案例,我们收到了一个Array<Any> 类型的对象,我们需要将其转换为Array<Codable>。如果原始数组中的任何一项不符合Codable,那么我们希望整个事情中止并返回nil。

或者当前的方法是手动循环遍历所有内容,一路测试,就像这样......

func makeCodable(sourceArray:Array<Any>) -> Array<Codable>?{

    var codableArray = Array<Codable>()

    for item in sourceArray{

        guard let codableItem = item as? Codable else {
            return nil
        }

        codableArray.append(codableItem)
    }

    return codableArray
}

但是,我想知道是否有更简单的方法可以使用 map 命令执行此操作,但如果无法映射任何元素,则需要将其短路。这就是我不确定是否可能。

比如这个伪代码……

func makeCodable(sourceArray:Array<Any>) -> Array<Codable>?{

    return sourceArray.map({ $0 as? Codable});
}

这可能吗,还是我们原来的方式是正确/唯一的方式?

【问题讨论】:

  • 当然for 方法是最有效的。它最多只迭代一次,它会尽快失败。好的旧循环并不是一件坏事,因为 Swift 容器提供了其他方便的方法。
  • 您还可以检查您的数组是否包含不可编码的对象,如果为真则返回 nil 或映射元素。试试return sourceArray.contains(where: {!($0 is Codable)}) ? nil : sourceArray.map { $0 as? Codable}
  • @LeoDabus 这就是我的效率评论的用武之地。任何其他方法都需要最多两次迭代数组。
  • 但它不会映射元素,如果存在,它会在第一个非 Codable 元素处破坏它
  • 但不是吗?它必须检查它是否可映射,然后在实际执行映射时再次检查。同一循环上的两次迭代执行两次相同的检查。我开始认为我应该创建自己的 mapCompletely 扩展来做这种事情。

标签: swift map-function codable


【解决方案1】:

这是使用mapthrows 的一种解决方案。

func makeCodable(sourceArray: [Any]) -> [Codable]? {
    enum CodableError: Error {
        case notCodable
    }

    let res: [Codable]? = try? sourceArray.map {
        guard let codable = $0 as? Codable else {
            throw CodableError.notCodable
        }

        return codable
    }

    return res
}

let res = makeCodable2(sourceArray: [5, 6.5, "Hi", UIView()])
print(res) // nil

这是一个使makeCodable 抛出并返回非可选数组的变体:

enum CodableError: Error {
    case notCodable
}

func makeCodable(sourceArray: [Any]) throws -> [Codable] {
    let res: [Codable] = try sourceArray.map {
        guard let cod = $0 as? Codable else {
            throw CodableError.notCodable
        }

        return cod
    }

    return res
}

do {
    let res = try makeCodable(sourceArray: [5, 6.5, "Hi"])
    print(res) // prints array
    let bad = try makeCodable(sourceArray: [5, 6.5, "Hi", UIView()])
    print(bad)
} catch {
    print(error) // goes here on 2nd call
}

【讨论】:

  • 漂亮!我正在考虑走错误路线。这只是证实了这一点。谢谢!
  • @MarqueIV 我更新了一个变体。任何一种方法都有效。选择满足自己的需求。
  • @Hamish,为了完整起见,介意把你的答案也扔进戒指吗?
  • 为什么不简单地return try sourceArray.map { guard let cod = $0 as? Codable else { throw CodableError.notCodable } return cod }
  • @LeoDabus 这也有效。有时我只是喜欢有一个可以调试的中间结果。
【解决方案2】:

作为@rmaddy shows,您可以利用map(_:) 可以接受抛出闭包这一事实,并在抛出错误时停止映射,将抛出的错误传播回调用者(然后您可以使用@987654324 吸收@)。

对此的一个细微变化是定义您自己的抛出 cast(_:to:) 函数来调用转换闭包:

struct TypeMismatchError : Error {
  var expected: Any.Type
  var actual: Any.Type
}

func cast<T, U>(_ x: T, to _: U.Type) throws -> U {
  guard let casted = x as? U else {
    throw TypeMismatchError(expected: U.self, actual: type(of: x))
  }
  return casted
}

func makeCodable(sourceArray: [Any]) -> [Codable]? {
  return try? sourceArray.map { try cast($0, to: Codable.self) }
}

虽然我们完全忽略了在这种情况下抛出的错误,但我发现在其他情况下,偶尔有一个抛出转换函数很有用(你当然也可以通过将 makeCodable 设置为抛出函数和使用try)。

但是,话虽如此,请注意,您生成的 [Codable]? 在当前形式下确实不太有用。您无法从中解码内容,因为您手头没有任何具体类型,也无法直接将其编码为protocols don't conform to themselves(即Codable 不符合Encodable,因此您可以)不要只是将Codable[Codable] 交给JSONEncoder)。

如果您真的想对 [Codable] 进行一些编码,则需要将每个元素包装在符合 Encodable 的包装器中,例如:

struct AnyEncodable : Encodable {

  var base: Encodable

  init(_ base: Encodable) {
    self.base = base
  }

  func encode(to encoder: Encoder) throws {
    try base.encode(to: encoder)
  }
}

func makeEncodable(sourceArray: [Any]) -> [AnyEncodable]? {
  return try? sourceArray.map {
    AnyEncodable(try cast($0, to: Encodable.self))
  }
}

现在[AnyEncodable] 是您可以传递给的东西,例如JSONEncoder

【讨论】:

  • 将您的代码粘贴到操场上,我得到“使用未解析的标识符 'cast'”。你自己试过吗? (我正在使用 Xcode 9.1 的游乐场功能)
  • @MarqueIV 在 Xcode 9.2 中编译得很好。第二个示例使用了第一个示例中的 cast(_:to:) 函数,听起来您试图编译第二个示例而不复制它。
  • 糟糕!你说得对。 “演员”是您的功能,而不是图书馆的功能。那是我的事。
  • 问题...为什么在该函数中指定'T'?你不会在任何地方使用它。难道你不能刚刚完成 cast 并制作了 Any 类型的 X 吗?
  • @MarqueIV 没有真正的区别; Any 确实也完全可以接受。通用函数可以作为优化从专门的实现中受益,而Any 参数则不能。但这可能不应该是这里的因素,因为该函数可能会被内联。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
  • 2020-03-22
  • 1970-01-01
相关资源
最近更新 更多