Options

在Objective-C中,我们有很多需要提供某些选项的API,它们一般用来控制API的具体的行为配置等。举个例子,常用的 UIView 动画的API在使用时就可以进行选项指定:

[UIView animateWithDuration:0.3
                      delay:0.0
                    options:UIViewAnimationOptionCurveEaseIn |
                            UIViewAnimationOptionAllowUserInteraction
                      animations:^{

                    } completion:nil];

我们可以使用 | 或者 & 这样的按位逻辑符对这些选项进行操作,这是因为一般来说在Objective-C中的Options的定义都是类似这样的按位错开的:

typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
    UIViewAnimationOptionLayoutSubviews            = 1 <<  0,
    UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, 
    UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, 
    //...
    UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20,
    //...
}

通过一个 typeof 的定义,我们可以使用 NS_OPTIONS 来把 UIViewAnimationOptions 映射为每一位都不同的一组 NSUInteger。不仅是这个动画的选项如此,其他的Option值也都遵循着相同的规范映射到整数上。如果我们不需要特殊的什么选项的话,可以使用 KNilOptions作为输入,它被定义为数字0。

enum {
  kNilOptions = 0
};

在Swift中,对于原来的枚举类型 NS_ENUM 我们有新的类型 enum 来对应。但是原来的 NS_OPTIONS 在Swift里显然没有枚举类型那样重要,并没有直接的原生类型来进行定义。原来的Option值现在被映射为了满足 OptionSet 协议的类型,以及一组静态的 get 属性:

public struct UIViewAnimationOptions : OptionSet {

    public init(rawValue: UInt)

    public static var layoutSubviews: UIViewAnimationOptions { get }

    public static var allowUserInteraction: UIViewAnimationOptions { get } // turn on user interaction while animating

    // ...

    public static var preferredFramesPerSecond30: UIViewAnimationOptions { get }
}

这样一来,我们就可以用和原来类似的方式为方法指定选项了。用Swift重写上面 UIView 的动画的代码的话,我们可以使用这个新的struct的值。在使用时,可以用生成集合的方法来制定符 合“或”逻辑多个选项:

UIView.animate(withDuration: 0.3,
               delay: 0.0,
               options: [.curveEaseIn, .allowUserInteraction],
               animations: {},
               completion: nil)

OptionSet 是实现了 SetAlgebra 的,因此我们可以对两个集合进行各种集合运算,包括并集(union)、交集(intersect)等等。另外,对于不需要选项输入的情况,也就是对应原来的 kNilOptions ,现在我们直接使用一个空的集合 [] 来表示:

UIView.animate(withDuration: 0.3,
               delay: 0.0,
               options: [],
               animations: {},
               completion: nil)

要实现一个Options的 struct 的话,可以参照已有的写法建立类并实现 OptionSet 。因为基本上所有的Options都是很相似的,所以最好是准备一个snippet以快速重用:

struct MyOption: OptionSet {
    let rawValue: UInt

    static let none = MyOption(rawValue: 0)
    static let option1 = MyOption(rawValue: 1)
    static let option2 = MyOption(rawValue: 1 << 1)
    static let option3 = MyOption(rawValue: 1 << 2)
    //...

}

数组 enumerate

使用NSArray 时一个很常遇见的的需求是在枚举数组内元素的同时也想使用对应的下标索引,在Objective-C中最方便的方式是使用NSArrayenumerateObjectsUsingBlock: 方法。因为通过这个方法可以显式地同时得到元素和下标索引,这会有最好的可读性,并且block也意味着可以方便地在不同的类之间传递和复用这些代码。

比如我们想对某个数组的前三个数字进行累加:

NSArray *arr = @[@1, @2, @3, @4, @5];
__block NSInteger result = 0;
[arr enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL * _Nonnull stop) {
    result += [num integerValue];
    if (idx == 2) {
        *stop = YES;
    }
}];

NSLog(@"%ld", result); //6

这里我们需要用到 *stop 这个停止标记的指针,并且直接设置他的值为 YES 来打断并跳出循环。而在Swift中,这个API的 *stop 被转换为了对应的 UnsafeMutablePointer<ObjCBool> 。如果不明白Swift的指针的表示形式的话,一开始可能会被吓一跳,但是一旦当我们明白 Unsafe 开头的这些指针类型的用法之后,就会知道我们需要对应做的事情就是将这个指向 ObjCBool 的指针指向的内存的内容设置为 true 而已:

let arr: NSArray  = [1, 2, 3, 4, 5]
var result = 0
arr.enumerateObjects({ (num, idx, stop) in
    result += num as! Int
    if idx == 2 {
        stop.pointee = true
    }
})
print(result) //6

虽然说使用 enumerateObjectsUsingBlock: 非常方便,但是其实从性能上来说这个方法并不理想(这里有一篇四年前的星期五问答阐述了这个问题,而且一直以来情况都没什么变化)。另外这个方法要求作用在 NSArray 上,这显然已经不符合Swift的编码方式了。在Swift中,我们在遇到这样的需求的时候,有一个效率,安全性和可读性都很好的替代,那就是快速枚举某个数组的 enumerrateGenerator,它的元素是同时包含了元素下标索引以及元素本身的多元组:

let arr: NSArray  = [1, 2, 3, 4, 5]
var result = 0
for (idx, num) in arr.enumerated() {
    result += num as! Int
    if idx == 2 {
        break
    }
}
print(result) //6

基本上来说,是时候和 enumerateObjectsUsingBlock: 说再见了。

results matching ""

    No results matching ""