【问题标题】:Are there cases where firstIndex doesn't return an optional in Swift?是否存在 firstIndex 在 Swift 中不返回可选项的情况?
【发布时间】:2021-01-28 01:11:57
【问题描述】:

我有一个简单的函数,可以像这样使用firstIndex 在数组中按索引查找特定元素,其中Entry.idString

private func findUserEntry(inEntries entries: [Entry],
                           currentUserID: String?) -> Entry? {
    
    guard let currentUserID = currentUserID else { return nil }
     
    guard let index = entries.firstIndex(where: { $0.id == currentUserID }) else { return nil }

    return entries[index]
}

我正在重构我的代码以尝试以不同的方式完成相同的事情,以此作为一种学习和实践的方式。我认为这个函数会有效地做同样的事情:

private func findUserEntry(inEntries entries: [Entry],
                           currentUserID: String?) -> Entry? {
    
    guard let currentUserID = currentUserID else { return nil }
    
    return entries
        .firstIndex { $0.id == currentUserID }
        .map { possibleIndex -> Entry? in
            
            guard let index = possibleIndex else { return nil }
            
            return entries[index]
        }
}

但是,我遇到了一个错误:Initializer for conditional binding must have Optional type, not 'Array<Entry>.Index' (aka 'Int')。所以,不知何故,possibleIndex 的值从.firstIndex 变为.mapInt,而不是我预期的Int?,就像上面的原始版本一样。为什么这里有区别?

【问题讨论】:

  • 是的,如果您需要 CaseIterable 枚举的索引,它永远不会失败

标签: swift


【解决方案1】:

如果您在 Apple 的文档中查找 Array 函数 firstIndex(where:),您会发现:https://developer.apple.com/documentation/swift/array/2994722-firstindex

函数被声明为返回一个可选整数:

func firstIndex(where predicate: (Element) throws -> Bool) rethrows -> Int?

所以不,它总是会返回一个 Optional。

但是,您使用的是 map,在本例中是 Optional 上的一个函数:

https://developer.apple.com/documentation/swift/optional/1539476-map

函数 Optional.map() 声明如下:

func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

如果Optional 包含一个值,它会解包并传递给map 语句的闭包。如果Optional 包含 nil,则 map 函数返回 nil。

因此在您的代码中

private func findUserEntry(inEntries entries: [Entry],
                           currentUserID: String?) -> Entry? {
    
    guard let currentUserID = currentUserID else { return nil }
    
    return entries
        .firstIndex { $0.id == currentUserID }
        .map { possibleIndex in 
            //At this point the value in possibleIndex can't be nil, 
            //so there is no need for the guard statement.
            //guard let index = possibleIndex else { return nil }
            return entries[possibleIndex]
        }
}

不需要你的保护声明。如果 firstIndex 包含 nil,则 map 语句返回 nil。如果 firstIndex 包含一个值,则 map 语句将 firstIndex 中未包装的值传递给闭包并返回该闭包的结果。

【讨论】:

    【解决方案2】:

    其他人回答了你的问题,但你真正想要的是flatMap

    currentUserID.flatMap { currentUserID in
      entries.first { $0.id == currentUserID }
    }
    

    或者,如果Entry.id 不是可选的,则删除平面映射。

    【讨论】:

    • 非常简洁的解决方案!
    【解决方案3】:

    Array<Int>.firstIndex 返回一个可选的Array<Int>.Index,即Int?

    所以,您使用的.map(_:)Optional<Int> 的方法,具有以下签名:

    func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
    

    当这个 Optional 实例不是 nil 时评估给定的闭包,将展开的值作为参数传递。

    【讨论】:

    • 谢谢。有没有一种方法可以在 map 或其他函数的闭包中进行评估,即使它为 nil?
    • 您为什么需要这样做呢?你可以直接使用return entries.firstIndex {...}.map { entries[$0] },或者直接使用.first(where:)
    • 谢谢。我在文档中看到了这一点,但这对我来说是一个令人困惑的方法签名,因为我不理解“抛出”和“重新抛出”的语法,也找不到很好的解释。
    • 我主要关注闭包获得Wrapped这一事实 - 即非可选值。 throwsrethrows 部分与您的问题并不真正相关,但如果您愿意,您可以阅读更多关于 here 的差异。
    猜你喜欢
    • 2015-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-16
    • 1970-01-01
    • 2016-09-17
    • 1970-01-01
    相关资源
    最近更新 更多