Swift数组扩展封装compactMap遇问题:无法移除nil值
问题根源:扩展的类型约束与返回值不匹配
这个问题我之前也踩过坑,核心是你写的扩展没有针对元素为可选类型的数组做专门的约束,导致compactMap的实际作用和你直接调用时完全不一样,最终达不到你想要的“移除nil并解包”的效果。
先搞懂:直接调用compactMap为什么能正常工作?
当你对[UIBarButtonItem?]直接调用compactMap { $0 }时:
- 闭包接收的参数是
UIBarButtonItem?,返回的也是这个可选类型 compactMap的逻辑是:自动过滤掉闭包返回的nil,同时把非nil的可选值自动解包,最终返回的是[UIBarButtonItem]——也就是你想要的非可选元素数组。
你的扩展为什么失效?
你写的removeNilElements()是给所有Array<Element>做的扩展,返回值是[Element]。当你在[UIBarButtonItem?]上调用它时:
- 这里的
Element是UIBarButtonItem?(可选类型) - 方法里的
compactMap { $0 }本来可以生成[UIBarButtonItem],但因为你的方法返回值被限定为[Element](也就是[UIBarButtonItem?]),编译器会自动把解包后的非可选值重新包装成可选类型。 - 最终得到的数组虽然过滤掉了原本的
nil元素,但剩下的元素依然是可选类型,看起来就像“还有nil”(其实是可选包装的非nil值),和你直接调用compactMap的结果完全不同。
正确的两种实现方式
方式1:给方法添加类型约束
直接在方法上限定数组元素为可选类型,让返回值自动对应解包后的非可选类型:
extension Array { /// 移除可选类型数组中的所有nil值,返回非可选元素的数组 public func removeNilElements<Wrapped>() -> [Wrapped] where Element == Optional<Wrapped> { return compactMap { $0 } } }
调用的时候非常自然,编译器会自动推断返回类型:
let buttons: [UIBarButtonItem?] = [actionMenuButton, createButton] let nonNilButtons2 = buttons.removeNilElements() // 类型为[UIBarButtonItem],正确过滤nil
方式2:用协议扩展更优雅
先定义一个描述可选类型的协议,再给元素遵循该协议的数组做扩展,这种方式不需要额外的泛型参数,代码更简洁:
// 定义一个协议,用来标识可选类型 protocol OptionalType { associatedtype Wrapped var wrappedValue: Wrapped? { get } } // 让系统的Optional类型遵循这个协议 extension Optional: OptionalType { var wrappedValue: Wrapped? { return self } } // 给元素为可选类型的数组添加扩展方法 extension Array where Element: OptionalType { /// 移除可选类型数组中的所有nil值,返回非可选元素的数组 public func removeNilElements() -> [Element.Wrapped] { return compactMap { $0.wrappedValue } } }
调用时和普通方法一样,完全不用关心泛型参数:
let nonNilButtons2 = buttons.removeNilElements() // 自动推断为[UIBarButtonItem]
额外提醒:如果是双重可选类型怎么办?
如果你的数组是双重可选(比如[UIBarButtonItem??]),那上面的方法只会过滤最外层的nil。这种情况下你可以嵌套调用removeNilElements(),或者在compactMap里多解包一次,但这种场景其实很少见,一般规范的代码里不会出现双重可选。
内容的提问来源于stack exchange,提问作者Kevin_TA
相关产品推荐
相关产品推荐

