OptionSet
是 Swift 中比较常用的一个内置协议,它的存在算是填补了既要强类型,又要支持按位取与的空白,是对 OC 中 NS_OPTIONS
的桥接。从声明上可以看到,OptionSet
协议本身又服从 SetAlgebra
协议,后者只定义了常见的集合操作方法,如 union
remove
insert
等,而前者在 extension
中给了默认实现:
public protocol OptionSet : RawRepresentable, SetAlgebra {
/// The element type of the option set.
///
/// To inherit all the default implementations from the `OptionSet` protocol,
/// the `Element` type must be `Self`, the default.
associatedtype Element = Self
/// Creates a new option set from the given raw value.
///
/// This initializer always succeeds, even if the value passed as `rawValue`
/// exceeds the static properties declared as part of the option set. This
/// example creates an instance of `ShippingOptions` with a raw value beyond
/// the highest element, with a bit mask that effectively contains all the
/// declared static members.
///
/// let extraOptions = ShippingOptions(rawValue: 255)
/// print(extraOptions.isStrictSuperset(of: .all))
/// // Prints "true"
///
/// - Parameter rawValue: The raw value of the option set to create. Each bit
/// of `rawValue` potentially represents an element of the option set,
/// though raw values may include bits that are not defined as distinct
/// values of the `OptionSet` type.
init(rawValue: Self.RawValue)
}
Swift 对 OC 中使用 NS_OPTIONS
声明的类型自动做了转换,如常用的正则表达式的选项:
typedef NS_OPTIONS(NSUInteger, NSMatchingOptions) {
NSMatchingReportProgress = 1 << 0, /* Call the block periodically during long-running match operations. */
NSMatchingReportCompletion = 1 << 1, /* Call the block once after the completion of any matching. */
NSMatchingAnchored = 1 << 2, /* Limit matches to those at the start of the search range. */
NSMatchingWithTransparentBounds = 1 << 3, /* Allow matching to look beyond the bounds of the search range. */
NSMatchingWithoutAnchoringBounds = 1 << 4 /* Prevent ^ and $ from automatically matching the beginning and end of the search range. */
};
对应的 Swift 声明:
public struct MatchingOptions : OptionSet {
public init(rawValue: UInt)
public static var reportProgress: NSRegularExpression.MatchingOptions { get } /* Call the block periodically during long-running match operations. */
public static var reportCompletion: NSRegularExpression.MatchingOptions { get } /* Call the block once after the completion of any matching. */
public static var anchored: NSRegularExpression.MatchingOptions { get } /* Limit matches to those at the start of the search range. */
public static var withTransparentBounds: NSRegularExpression.MatchingOptions { get } /* Allow matching to look beyond the bounds of the search range. */
public static var withoutAnchoringBounds: NSRegularExpression.MatchingOptions { get } /* Prevent ^ and $ from automatically matching the beginning and end of the search range. */
}
另外,为了开发者的方便,SetAlgebra
服从了 ExpressibleByArrayLiteral
使得它可以从一个数组字面量初始化构造一个对象:
public protocol SetAlgebra : Equatable, ExpressibleByArrayLiteral {
associatedtype Element
}
因此我们通常可以这样来使用 OptionSet
:
let options: UIView.AutoresizingMask = [.flexibleHeight, .flexibleWidth]
let options: UIView.AutoresizingMask = []
等同与之前 OC 的写法:
UIViewAutoresizing option = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
UIViewAutoresizing option = UIViewAutoresizingNone;
然而,使用数组初始化有时会出现一些迷惑行为,比如下图中的 crash 问题,使用的是开源的 URLNavigator 库。
对应的堆栈如下:
#0 0x0000000125822388 in swift_getAssociatedTypeWitnessSlowImpl(swift::MetadataRequest, swift::TargetWitnessTable*, swift::TargetMetadata const*, swift::TargetProtocolRequirement const*, swift::TargetProtocolRequirement const*) ()
#1 0x00000001258210d8 in swift_getAssociatedTypeWitness ()
#2 0x000000012562cf70 in OptionSet<>.init() ()
#3 0x00000001050ed292 in protocol witness for SetAlgebra.init() in conformance UIRectCorner ()
#4 0x00000001053129c9 in protocol witness for SetAlgebra.init() in conformance NSRegularExpressionOptions ()
#5 0x000000012564af54 in SetAlgebra.init<τ_0_0>(_:) ()
#6 0x0000000106c57356 in protocol witness for SetAlgebra.init<τ_0_0>(_:) in conformance NSRegularExpressionOptions ()
#7 0x000000012564b218 in SetAlgebra<>.init(arrayLiteral:) ()
#8 0x0000000106c55423 in URLMatcher.replaceRegex(_:_:_:) at /Users/hztanxin/Code/MusicDating/Pods/URLNavigator/Sources/URLMatcher/URLMatcher.swift:110
#9 0x0000000106c550e4 in URLMatcher.normalizeURL(_:) at /Users/hztanxin/Code/MusicDating/Pods/URLNavigator/Sources/URLMatcher/URLMatcher.swift:103
#10 0x0000000106c53ae6 in URLMatcher.match(_:from:) at /Users/hztanxin/Code/MusicDating/Pods/URLNavigator/Sources/URLMatcher/URLMatcher.swift:50
#11 0x0000000106c4e8b7 in Navigator.handler(for:context:) at /Users/hztanxin/Code/MusicDating/Pods/URLNavigator/Sources/URLNavigator/Navigator.swift:62
其中 #7 是数组初始化的方法调用,一直到 #4 都正常,但是 #3 出现了一个不该出现的符号 UIRectCorner
。两个风牛马不相及的类型不知为何会产生联系,从编译出来的二进制反汇编看,#4 的方法实现体就是跳转到 #3 的实现体。
但是有时候又是好的,比如所依赖的库某几个从静态库改为源代码(或者全用静态库);有时侯 Debug 与 Release 打包出来的结果也不一样。当它是好的时侯,编译出来的方法的实现体是完整的,而不是一个跳转。
目前没有思路与检测手段,只有出问题时可以显式地用 .init()
方法初始化绕过此问题:
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return string }
/// 改为
guard let regex = try? NSRegularExpression(pattern: pattern, options: .init()) else { return string }