在 iOS 开发中,经常会使用枚举表示一些选项或者状态,而有的时候一个状态是需要枚举中的多个选项进行组合起来表示的,比如最常见的 UIViewAutoresizing 就可能同时需要使用 UIViewAutoresizingFlexibleWidth 和 UIViewAutoresizingFlexibleHeight 进行表示,所以会通过位操作中的 | 运算进行组合。而位运算实际上是直接对二进制数据进行操作的,在处理速度上会比较快,所以这里就总结一下
Objective-C 和 Swift 中位运算符常见的一些使用。
首先呢,位运算符以及具体用法如下:
| 运算符 | 规则 |
|---|---|
| & 与 | 都为 1 时结果为 1 |
| ∣ 或 | 都为 0 时结果为 0 |
| ^ 异或 | 相同为 0 否则为 1 |
| ~ 取反 | 0 为 1,1 为 0 |
| << 左移 | 左移,高位丢弃,0 补低位 |
| >> 右移 | 右移,高位根据编译器补 0 或者补符号位 |
其实,单就论用位操作做一些数据的处理其实很巧妙,比如用来判断奇数还是偶数可以通过二进制数最后一位来判断,0 为偶数,1 为奇数,那么只需要通过对一个数字 a 进行 a & 1 操作通过结果就能判断出来这个数字是奇数还是偶数。再比如两个数 a b 进行数据交换,一般引入第三个数 c 当作中间转换用的,但其实通过 ^ 操作符,执行下列代码即可进行交换。
123 |
a ^= b;b ^= a;a ^= b; |
回到主题,比如 UIKit 中关于 UIViewAutoresizing 的声明如下:
123456789 |
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 << 0, UIViewAutoresizingFlexibleWidth = 1 << 1, UIViewAutoresizingFlexibleRightMargin = 1 << 2, UIViewAutoresizingFlexibleTopMargin = 1 << 3, UIViewAutoresizingFlexibleHeight = 1 << 4, UIViewAutoresizingFlexibleBottomMargin = 1 << 5}; |
这里会发现使用了 NS_OPTIONS 而不是 NS_ENUM, 关于这两者的却别可以看一下这篇文章NS_ENUM vs NS_OPTIONS
关于 UIViewAutoresizingNone 就没什么好说的了,但是如何通过进行按位或操作就能同时组合实现多个选项呢,原理其实也不难,比如现在我们实现 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight 这样的代码,那么 autoresizing 属性就能既实现了 width 上的动态调整又实现了 height 上的动态调整,我们先一步一步进行计算。
UIViewAutoresizingFlexibleWidth = 1 << 1 , 1 转换成二进制为 00000001,然后执行 1 << 1 之后结果为 00000010;
UIViewAutoresizingFlexibleHeight = 1 << 4 , 1 转换成二进制然后执行左移 4 为操作结果为 00010000
现在 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight 也就相当于上述两个结果执行按位或操作:
00000010 | 00010000 = 00010010
也就是说我有一个 UIView, 现在对这个 view 的 autoresizingMask 属性赋值
1 |
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
结果按照 | 操作的结果来定的话,那么这个 autoresizingMask 的值就是 00010010. 所以接下来 UIView 内部根据 autoresizingMask 进行一些 resize 操作的话,就要看看这个属性包含哪些选项,这时候只需要将 autoresizingMask 和某个选项进行 & 操作即可判断出属性是否包含这一选项。比如刚刚的 autoresizingMask 值为 00010010,而 UIViewAutoresizingFlexibleWidth 的值为 00000010,现在
00010010 & 00000010 = 00000010
转换为十进制为 2,如果对这个结果执行 true / false 判断的话,结果为 true,也就是说 autoresizingMask 属性包含了 UIViewAutoresizingFlexibleWidth 选项,就能针对此做一些操作了。按照这个逻辑继续对其他的一些选项进行判断,也能得到相应正确的结果。
上面所说的都是 Objective-C 下的情况,那么如果我们使用的是 Swift,情况就大不同了。在 Swift 中,enum 是不能够多个选项进行相互组合的,也就是说类似于上述 Objective-C 中使用 | 运算符进行 enum 多项组合在 Swift 中是完全不能做到的,那 Swift 是如何处理这种情况的呢?看一下 Swift 版本的 UIViewAutoresizing 就知道了:
1234567891011 |
public struct UIViewAutoresizing : OptionSet { public init(rawValue: UInt) public static var flexibleLeftMargin: UIViewAutoresizing { get } public static var flexibleWidth: UIViewAutoresizing { get } public static var flexibleRightMargin: UIViewAutoresizing { get } public static var flexibleTopMargin: UIViewAutoresizing { get } public static var flexibleHeight: UIViewAutoresizing { get } public static var flexibleBottomMargin: UIViewAutoresizing { get }} |
这里可以看到,在 Swift 中的 UIViewAutoresizing 并不是一个 enum,而是一个 struct,且引入了 OptionSet 这个协议。之所以是 struct 而不是 enum 就是因为 Swift 中的 enum 是不支持多个值进行组合的,而 OptionSet 的引入就是为了解决 Swift 关于这种需求的方案。
OptionSet 遵循 SetAlgebra 协议,所以在声明的时候可以直接使用类似于数组声明的方式进行创建,比如
1 |
let resizingMasks = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth] |
但是呢,它看起来像数组,并不是数组,它没有遵循 Sequence 或者 Collection 这类协议,没有 count 方法,就算上述代码看似像是赋了两个值,但其实结果只有一个值。OptionSet 其实也算是替代本文一开始说的需求来的,所以实际上内部也是进行了位运算,比如 OptionSet 有 contain 方法判断某一选项是否包含在内,还有
union 方法对两个数进行位运算操作。所以如果使用 Swift 遇到多个状态互相组合的情况,可以创建一个遵循 OptionSet 协议的 struct 即可解决问题。
参考: