IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Swift 1.2

    nate@nshipster.com (Nate Cook)发表于 2015-02-09 00:00:00
    love 0

    Swift 正如其名,速度飞快。本周将关注 Swift 1.2 版本的一个 重大 更新。Swift 开发团队在这次加速俯冲中一次性响应了开发社区的诸多需求,带来了许多激动人心的新特性。本次发布的每一个小更新都带来了巨大的好处:增量构建、丰富了 Xcode 中的错误信息、提升 Xcode 稳定性、静态类属性、支持 C 的 union 和 bitfield、将 Swift 中的 enum 打通到 Objective-C 中、更安全的类型转换、单行闭包的提升等等。

    在这么多的更新中我们主要关注两个明显提升使用体验的新功能:一个是 if let 语句 optional 绑定 终于有了 巨大变化,另一个是 Objective-C 中的空值标注。

    提升 Optional 绑定

    Swift 1.2 允许通过多值并行 optional 绑定来避免 if let 语句的多重深嵌套。多个 optional 绑定可以通过逗号分隔开,并且可以带一个和传统的 if 语句同样效果的 where 分句。这样的话,拜占庭的 厄运金字塔 就可以被修缮成中世纪风格的现代牧场了:

    从前:

    let a = "10".toInt()
    let b = "5".toInt()
    let c = "3".toInt()
    
    if let a = a {
        if let b = b {
            if let c = c {
                if c != 0 {
                    println("(a + b) / c = \((a + b) / c)")
                }
            }
        }
    }
    

    现在:

    if let a = a, b = b, c = c where c != 0 {
        println("(a + b) / c = \((a + b) / c)")     // (a + b) / c = 5
    }
    

    这两个例子中判断语句的执行顺序是一样的。使用新的语法可以让每个绑定都按顺序执行,如果某一个绑定是 nil 就会停下来。只有在所有的 optional binding 都成功的情况下才会进行 where 语句检查。

    一个 if 语句可以包含多个通过逗号分隔的 let 绑定。因为每个 let 可以绑定多个 optional 和 一个 where 分句,所以通过这种方式可以支持真正更高级的逻辑。(感谢 Stephen Celis 帮忙解释清楚这点。)

    更棒的是,后续的绑定语句可以引用之前的绑定。这意味着你只用一个 if let 语句就可以解析 Dictionary 对象或者对 AnyObject? 值进行强制类型转换,然后将其用于另一个语句中。

    让我们回顾一个经典的例子,将 这样一个庞大的 JSON 块 在 Swift 1.2 中解析。样例中用一个 if let 将 JSON 解析成指定的包含 NSBundle、NSURL 和 NSData 类型以及一些 AnyObject? 值的对象:

    var users: [User] = []
    
    // load and parse the JSON into an array
    if let
        path     = NSBundle.mainBundle().pathForResource("users", ofType: "json"),
        url      = NSURL(fileURLWithPath: path),
        data     = NSData(contentsOfURL: url),
        userList = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [[String: AnyObject]] 
    {
        // extract individual users
        for userDict in userList {
            if let
                id      = userDict["id"] as? Int,
                name    = userDict["name"] as? String,
                email   = userDict["email"] as? String,
                address = userDict["address"] as? [String: AnyObject]
            {
                users.append(User(id: id, name: name, ...))
            }
        }
    }
    
    [
      {
        "id": 1,
        "name": "Leanne Graham",
        "username": "Bret",
        "email": "Sincere@april.biz",
        "address": {
          "street": "Kulas Light",
          "suite": "Apt. 556",
          "city": "Gwenborough",
          "zipcode": "92998-3874",
          "geo": {
            "lat": "-37.3159",
            "lng": "81.1496"
          }
        },
        "phone": "1-770-736-8031 x56442",
        "website": "hildegard.org",
        "company": {
          "name": "Romaguera-Crona",
          "catchPhrase": "Multi-layered client-server neural-net",
          "bs": "harness real-time e-markets"
        }
      },
      {
        "id": 2,
        "name": "Ervin Howell",
        "username": "Antonette",
        "email": "Shanna@melissa.tv",
        "address": {
          "street": "Victor Plains",
          "suite": "Suite 879",
          "city": "Wisokyburgh",
          "zipcode": "90566-7771",
          "geo": {
            "lat": "-43.9509",
            "lng": "-34.4618"
          }
        },
        "phone": "010-692-6593 x09125",
        "website": "anastasia.net",
        "company": {
          "name": "Deckow-Crist",
          "catchPhrase": "Proactive didactic contingency",
          "bs": "synergize scalable supply-chains"
        }
      },
      ...
    ]
    

    我感觉我们之后写的代码中会有不少的逗号出现。

    空值标注

    在 Swift 第一版发布的时候,每一个对 Cocoa API 的调用方法都会返回一个模糊解析的 optional 类型(比如说 AnyObject!)。因为它们会在读取过程中让程序崩溃,所以如果没有清晰的文档表明该方法是否会返回一个 null 值那么这种模糊解析内在是不安全的。这些都是不好的现象。Swift 确实能够调用 Cocoa API 了,但调用方法长得真奇怪。

    随着 beta 发布的完善,内部评估中不断移除不友好的标点符号,用真 optional 、非 optional 或永不为空的值来替代模糊解析的 optional。这极大提高了使用 Cocoa 时的体验,但是,使用三方代码的时候这个问题却仍然存在。

    这种情况不复存在了!Swift 1.2 带来了新版本的 Clang。新的属性参数和指针标注允许指针、Objective-C 属性、方法参数或返回值是否可以为 nil。

    • nonnull:标示该指针 不应该 为 nil。在 Swift 中使用标注 nonnull 的指针时会保留他们原始的值(例如 NSData)。
    • nullable:标示该指针在通常情况下 可以 为 nil。它们在 Swift 中会以 optional 值表示(比如 NSURL?)。
    • null_unspecified:像以前一样的模糊解析 optional 类型,理想状态是只标注而不使用。
    • null_resettable:标示这个属性永远会有一个值,也可以被赋为 nil。拥有非 nil 默认值的属性可以用这个字段标注,例如:tintColor。根据文档,这种属性在 Swift 中使用时会变成一种(相对安全)的模糊解析 optional 类型。

    前三种标注也可以通过双下划线前置(__nonnull、__nullable 和 __null_unspecified)用于 C 指针和 block 指针。最后一种 null_resettable 标注仅可用于 Objective-C 属性。

    立即动手尝试

    下面的样例中,我们使用一个 data controller 来控制一个位置信息列表,每个位置信息可能附图。这个样例展示了这几种新标注的优点:

    @interface LocationDataController : NSObject
    
    @property (nonatomic, readonly) NSArray *locations;
    @property (nonatomic, readonly) Location *latestLocation;
    
    - (void)addPhoto:(Photo *)photo forLocation:(Location *)location;
    - (Photo *)photoForLocation:(Location *)location;
    @end
    

    如果不用空值标注,LocationDataController 类中的每个指针在 Swift 中使用时都会变成模糊解析的 optional 类型:

    class LocationDataController : NSObject {
        var locations: [AnyObject]! { get }
        var latestLocation: Location! { get }
    
        func addPhoto(photo: Photo!, forLocation location: Location!)
        func photoForLocation(location: Location!) -> Photo!
    }
    

    这!么!多!感!叹!号!真!是!够!了!如果在 Objective-C 中就标注好:

    @interface LocationDataController : NSObject
    
    @property (nonnull, nonatomic, readonly) NSArray *locations;
    @property (nullable, nonatomic, readonly) Location *latestLocation;
    
    - (void)addPhoto:(nonnull Photo *)photo forLocation:(nonnull Location *)location;
    - (nullable Photo *)photoForLocation:(nonnull Location *)location;
    @end
    

    首先 locations 列表是 nonnull 的,因为最糟糕的情况下它也就是一个空数组了,但如果列表中没有任何位置信息,latestLocation 却 可以 是 nil。同理,两个方法中的参数永远不应该为空。因为不是每个位置信息都有一张照片,所以第二个方法的返回值是 nullable 型的 Photo 实例。这种声明方式在 Swift 中就变得更好、更清晰、更安全,而且也没那么多烦人的感叹号了:

    class LocationDataController : NSObject {
        var locations: [AnyObject] { get }
        var latestLocation: Location? { get }
    
        func addPhoto(photo: Photo, forLocation location: Location)
        func photoForLocation(location: Location) -> Photo?
    }
    

    NS_ASSUME_NONNULL_BEGIN/END

    在 Objective-C 头文件中标注 任何 指针都会导致编译器对整个文件进行标注分析,带来不少的警告。因为大多数的标注都是 nonnull 的,一个用于标注已存在类的提高效率的宏就出现了。在头文件中,例外情况单独标注,其他地方开始和结尾的地方用 NS_ASSUME_NONNULL_BEGIN 和 ..._END 包围起来就好了。

    和上述 data controller 能在 Swift 中产生同样效果的另一个更易读的版本就出现了:

    @interface LocationDataController : NSObject
    NS_ASSUME_NONNULL_BEGIN
    
    @property (nonatomic, readonly) NSArray *locations;
    @property (nullable, nonatomic, readonly) Location *latestLocation;
    
    - (void)addPhoto:(Photo *)photo forLocation:(Location *)location;
    - (nullable Photo *)photoForLocation:(Location *)location;
    
    NS_ASSUME_NONNULL_END
    @end
    

    不只是 Swift

    新的 Objective-C 空值标注为 Swift 带来了巨大好处,与此同时如果不写任何 Swift 还是可以从中受益的。如果将 nil 值传给了带有 nonnull 标注的指针,那么自动补全时就会有提示:

    // Can I remove a photo by sending nil?
    [dataController addPhoto:nil forLocation:currentLocation];
    // Nope -- Warning: Null passed to a callee that requires a non-null argument
    

    激动人心的是,关于这次更新的故事我们只讲了一半。除了 Swift 语法的变化和编译器变异方式的变化之外,标准库也升级到了一个 大版本,带来了新的 Set 类(再见了朋友)。呵呵呵呵,所以,我们以前的代码也都不能运行了,Stack Overflow 上又多了 21,000 个过期的 Swift 问题?其实想想还蛮有意思的。



沪ICP备19023445号-2号
友情链接