Swift 语言新特性

1、Swift 2.0 带来哪些新变化

  • 常规变化:

    • 1、OS X 10.11、iOS 9 和 watchOS 2 SDK 采纳了一些 Objective-C 的特性用来提高 Swift 的编程体验, 如可空性、类型化集合和一些别的特性。

    • 2、编译器对冗余的协议一致性,未被使用的绑定值以及可以设为常量的变量这些情况目前会给予警告或报错。

    • 3、修复了跨文件协议遵循时符号不可见或者重复的错误。

    • 4、Swift 语言的调用约定更加智能,能够理解 API 所发生的变化和 Swift 所给出的警告。

    • 5、便利的可失败构造器(failable initializer)可以先返回 nil,而不必首先调用 self.init。这是有利的一 面,但指定了构造器在返回 nil 前仍要给所有字段初始化。

    • 6、find 函数改名为 indexOfsort 则变成了 sortInPlacesorted 变成了 sort

    • 7、String.toInt() 重名为 Int(String) 的可失败构造器,因为构造器语法更适合类型转换。

      • String 类型不再遵循 SequenceType,可以使用 .characters.utf8.utf16 对应字符集的运算。允许对泛型添加公共扩展。

      • 字符串长度长度计算由 count(String) 变为 String.characters.count

      • 字符串裁剪由 code.substringToIndex(advance(code.startIndex, 6)) 变为 let endIndex = code.startIndex.advancedBy(6) code.substringToIndex(endIndex)

    • 8、标准库中重构了很多泛型的全局函数(如 mapfiltersort),采用协议扩展方式增加这些方法。这个好处是对于其他的关联类型能很好的适配。

      • 非泛型类类型可以继承泛型类(强制类型参数固定)。

      • 修复了 Swift 中泛型需求打印时 “T==T” 的错误。

      • 在泛型函数中声明了类型参数但是在函数中没有使用时将产生一个编译时错误,例如:

      • func foo<T> () {} // error:generic parameter ’T’ is not used in function signature

    • 9、基本上可以使用 enum SomeEnum<T,U,V> 来声明 multi-payload 风格的枚举,这样就能正常运行。这用来提示未完成的指令寄存器(IR)引发的错误。

      • 在 Objective-C 的枚举类型导入到 Swift 时,已经废弃的枚举元素将不会影响可用元素的使用,这个可能需要 Swift 中一些枚举名称的改变。

      • 从 C 中导入的枚举类型都表示为 RawRepresentable,这包括哪些没有被声明为 NS_ENUMNS_OPTIONS 枚举值,所有这些枚举类型中的 value 属性都需要重名为 rawValue.

    • 10、方法和函数现在使用同样的参数命名规则了,我们可以用 “_” 符号来省略一个外部的参数名,为了简化使用,用来指定参数名的简化符号 “#” 被移除,因为 Swift 为默认参数提供了特殊的规则:

      • 声明:

        1
        2
        3
        func printFunction(str:String, newline:Bool)
        func printMethod(str:String, newline:Bool)
        func printFunctionOmitParameterName(str:String, _newline:Bool)
      • 调用:

        1
        2
        3
        printFunction("hello", newline:true)
        printMethod("hello", newline:true)
        printFunctionOmitParameterName("hello", true)
    • 11、条件循环语句 do/while 循环被重名为 repeat/while。关键字 do 目前用来引入一个新的作用域(这对新引进的错误处理和 defer 关键字很重要)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      Swift 1.2

      do {
      ...
      } while <condition>

      Swift 2.0

      repeat {
      ...
      } while <condition>
    • 12、打印语句的改变,在 Swift1 中,有 println()print() 两个在控制台打印语句的方法,前者是换行打印,后者是连行打印。在 Swift 2 中,println() 已成为过去,取而代之的是他俩的结合体。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      Swift 1.2

      func print(<stuff to print>)
      func println(<stuff to print>)

      Swift 2.0

      func print(<stuff to print>, appendNewline:Bool = true)

      如果你想做连行打印,现在需要这样写:

      print("我要换行!", appendNewline: true)
    • 13、Swift 的文本注释(doc comments)换成了 Markdown 语法格式,与 Playgrounds 统一(Playgrounds 注释格式源于功能有限的 reStructured Text)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      参数纵览语法:

      Parameters:
      ‐ x:...
      ‐ y:...

      单独参数语法:

      ‐ parameterx:...
      ‐ parametery:..

      返回值:

      ‐ returns:...

      其他需要在 QuickHelp 中高亮的语法字段,可以参考 Markdown 语法。
    • 14、在 Swift 中增加了 @objc(propertyName) 属性,当该属性导入到 Objective-C 时可以采用这个 propertyName 作为 getter/setter 访问器的默认名,例如:

      1
      2
      3
      4
      5
      6
      7
      8
      class MyClass:NSObject {

      // Objective‐C 属性被命名为 “theProperty”
      @objc(theProperty) property:String

      // Objective‐Cgetter 访问器被命名为 “theProperty”
      // Objective‐Csetter 访问器被命名为 “setTheProperty:”
      }
    • 15、注册通知由

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
          var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
      var acceptAction = UIMutableUserNotificationAction()

      acceptAction.identifier = "ACCEPT_IDENTIFIER"
      acceptAction.title = "Accept"
      acceptAction.activationMode = UIUserNotificationActivationMode.Foreground
      acceptAction.destructive = false
      acceptAction.authenticationRequired = false

      var inviteCategory = UIMutableUserNotificationCategory()

      inviteCategory.identifier = "INVITE_CATEGORY"
      inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Default)
      inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Minimal)

      var categories = NSSet(object: inviteCategory)
      var mySettings = UIUserNotificationSettings(forTypes: types, categories: categories as Set<NSObject>)

      UIApplication.sharedApplication().registerUserNotificationSettings(mySettings)
      UIApplication.sharedApplication().registerForRemoteNotifications()

      修改为:

      let acceptAction = UIMutableUserNotificationAction()

      acceptAction.identifier = "ACCEPT_IDENTIFIER"
      acceptAction.title = "Accept"
      acceptAction.activationMode = UIUserNotificationActivationMode.Foreground
      acceptAction.destructive = false
      acceptAction.authenticationRequired = false

      let inviteCategory = UIMutableUserNotificationCategory()

      inviteCategory.identifier = "INVITE_CATEGORY"
      inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Default)
      inviteCategory.setActions([acceptAction], forContext: UIUserNotificationActionContext.Minimal)

      let categories = NSSet(object: inviteCategory) as! Set<UIUserNotificationCategory>
      let mySettings = UIUserNotificationSettings( forTypes: [.Alert, .Badge, .Sound], categories: categories)

      UIApplication.sharedApplication().registerUserNotificationSettings(mySettings)
      UIApplication.sharedApplication().registerForRemoteNotifications()
  • 内部的可见性:

    • 这解决了单元测试中的一个较大的难点。以前的做法:

      • Swift 文件包含在 test target 中。现在不同的模块中有重复的类的定义,出现无法将 “X” 转换为 “X” 这样非常可怕的错误,有时会无法执行特定的测试。

      • 在测试中引入引入主程序(main program)作为一个模块。现在一切都声明为 public,所以对于测试来说都是可见的,有时候也包括应该声明为 private 的内部细节。

    • 现在可以启用 testability,它就像 C# 中的 InternalsVisibleTo。主应用程序目标模块的内部细节对测试模块可见。

      • 在对应用或框架的测试设置中,启用 testability。
      • 在单元测试中,使用 @testable import { ModuleName }
    • 这将导致测试忽略某些优化行为并保留稍后导入到测试模块中的那些内部符号。官方文档警告说,由于阻止了某些优化,因此这只适用于调试和测试版本。

  • 模式匹配:

    • Switch 语句的模式匹配(pattern matching)语法和 “if let ..., .... where” 语法一直在推广。可以在任何控制流中使用逗号操作符和 where 条件语句。还可以使用新的 case 条件语句,例 - 如:if case .Silly(let a) { }。还有一种用于 Optional<T> 的特殊形式:if case let a? = anOptional { }

      • 模式匹配在循环语句中也可以使用:for case let thing? in array { }。 这又是值得单独成文的另一个特性。

      • 类型标注不能用于模式匹配,而需要作为标注声明的一部分:

        • 这意味着,以前的这样的写法:

          • var (a:Int, b:Float) = foo()
        • 需要被重构为:

          • var (a, b):(Int, Float) = foo()
        • 其实这个改动原因是为了和元组用法相区分。

  • 错误处理:

    • NSError 变成 throw。这不是我们一贯所认识的异常,这是一个使函数提前返回 Result<T, Error> 的操作,单隐藏了所有提前返回的对象,也隐藏了错误解析(error unwrapping)过程等内容。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      let systemAttributes: [NSObject: AnyObject]?

      do {

      systemAttributes = try NSFileManager.defaultManager()
      .attributesOfFileSystemForPath(documentDirectoryPath.last!)

      } catch _ {

      systemAttributes = nil
      }

      它完美地与 Objective-C 进行互操作,Swift 语言中,将标记为 throws 的方法作为选择器。这是使用 NSError 的方法,
      -(BOOL or nullable type)someMethodTakingParam:(type)param error:(NSError **),
      这种样式会自动引入标记为 throws 的方法。
    • 应该明白的是这并不像 Java 中已经被检查过的异常(checked exception)那样。Swift 语言并不关心异常的类型,或者处理或者不处理。这又是值得单独成文的另一功能特性。

  • guard 语句块:

    • 显式地声明你要恒成立的条件语句,恒成立时跳过整个 guard 语句。这样做的好处是绑定在 guard 语句的变量在函数的其他部分也可用。这就避免了将所有的东西都围绕一条 if 语句嵌套使用来解析(unwrap)可选类型的变量。执行到函数中 guard 语句中的 else 部分,函数一定会退出并抛出异常。也可能会调用带有 @noreturn 标记的函数。
  • Defer 关键字:

    • 关键字 defer 也很重要,因为它可以取代传统 C 风格的 “if (err) goto cleanup”。获得资源后接着就是 defer { release_resource() }。然后不管函数返回结果如何,获得的资源都将被清理。这也意味着资源的释放紧随获取资源之后。这看起来不起眼儿,实则很重要。
  • NS_OPTIONS 和 OptionSetType:

    • NS_OPTIONS 类型现在遵循 OptionSetType 协议,这样可以避免 set 样式的接口调用。位操作枚举(bitwise enumeration)与数组风格的语法相结合,而不使用管道符 “ | ” 按位操作,并且具有所有范围的集合操作功能。检查一下是否具有 contains 功能的标志,或能够执行像 isSubsetOfisDisjointWith 等这样集合操作的其他功能。这是显著的改进,表达了不直接对位进行操作的意愿。

    • 避免采用如下位运算的调用方式:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // Swift1.2:

      object.invokeMethodWithOptions(.OptionA | .OptionB)
      object.invokeMethodWithOptions(nil)

      if options & .OptionC == .OptionC {

      //.OptionC 被设置
      }
    • 选项设置支持字面量语法和 set 样式的调用,如 contains:

      1
      2
      3
      4
      5
      6
      7
      object.invokeMethodWithOptions([.OptionA, .OptionB])
      object.invokeMethodWithOptions([])

      if options.contains(.OptionC) {

      //.OptionC is set
      }
    • 这种变化意味着位操作枚举实际上不再是枚举了。将这些位操作枚举声明为结构体,实现 OptionSetType 协议,提供 rawValue 属性。并且创建值作为结构体的静态成员。Swift 便会搞定其余的一切,自动提供所有集合的操作。

    • 在 Swift 中一个新的 Option 设置类型可以采用结构体遵循 OptionSetType 协议的方式编写。如果该类型中指定了一个 rawValue 属性和 static let 的常量定义,那么标准库将会为其他选项提供默认实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      structMyOptions:OptionSetType {

      let rawValue:Int
      static let TuringMachine = MyOptions(rawValue:1)
      static let LambdaCalculus = MyOptions(rawValue:2)
      static let VonNeumann = MyOptions(rawValue:4)
      }

      let churchTuring:MyOptions = [.TuringMachine, .LambdaCalculus]
  • 协议扩展:

    • 在 Swift 1.0 时代,协议(Protocol)基本上类似一个接口,定义若干属性和方法,供类、结构体、枚举遵循和实现。在 Swift 2.0 中,可以对协议进行属性或者方法的扩展,和扩展类与结构体类似。,包括与类型约束有关的通用协议。还可以自己提供协议的默认实现。这让我们开启了面向协议编程的篇章。先前,你不能,你说:“我要使用方法 X 来扩展 CollectionType,但只有集合中的类型满足某些条件才可以”。现在,你可以这么做,并且很多像 map,filter 和 sort 这样的全局函数已经进行了扩展。这样就解决了很多痛点,这也是值得单独成文的内容。同时,要看看 WWDC 的面向协议编程(Protocol Oriented Programming)了解一些细节。

    • Swift 中,大多数基础对象都遵循了 CustomStringConvertible 协议,比如 Array、Dictionary(Swift 1.0 中的 Printable 协议),该协议定义了 description 方法,用于 print 方法打印对象。现在我们对该协议扩展一个方法,让其打印出大写的内容:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var arr = ["hello", "world"]

      print(arr.description) // "[hello, world]"

      extension CustomStringConvertible {
      var upperDescription: String {
      return "\(self.description.uppercaseString)"
      }
      }

      print(arr.upperDescription) // "[HELLO, WORLD]"
    • 如果在 Swfit 1.0 时代,要想达到上述示例的效果,那么我们需要分别对 Array、Dictionary 进行扩展,所以协议的扩展极大的提高了我们的编程效率,也同样使代码更简洁和易读。

  • available 检查:

    • 作为 iOS 开发者,谁都希望使用最新版本 iOS 的 Api 进行开发,省事省力。但常常事与愿违,因为我们经常需要适配老版本的 iOS,这就会面临一个问题,一些新特性特性或一些类无法在老版本的 iOS 中使用,所以在编码过程中经常会对 iOS 的版本做以判断,就像这样:

      1
      2
      3
      4
      5
      if NSClassFromString("NSURLQueryItem") != nil {
      // iOS 8 或更高版本
      } else{
      // iOS8 之前的版本
      }
    • 以上这只是一种方式,在 Swift 2.0 之前也没有一个标准的模式或机制帮助开发者判断 iOS 版本,而且容易出现疏漏。在 Swift 2.0 到来后,我们有了标准的方式来做这个工作:

      1
      2
      3
      4
      5
      6
      7
      8
      if #available(iOS 8, *) {

      // iOS 8 或更高版本
      let queryItem = NSURLQueryItem()

      } else {
      // iOS8 之前的版本
      }
    • @available 属性自 Swift 1.2 就存在了并且后续支持得很好。添加了一个新的陌生语法 if #available(),为处理版本检查提供了支持。而不是插入你喜欢的方法。

    • 遗憾的是你不能只声明一个属性 UISearchController 并将 target 设置为 iOS 7,然后只允许访问类中的属性。Swift 希望整个类的定义都可以或者不可以。也可以不再采用协议,除非支持 target设置中所有的操作系统版本,除非将整个类标记为只在更新的操作系统版本可用。

    • 这意味着使用 if #available() 存在单独的子类和对创建适当对象的保护。尽管如此,我个人还是发现了一个 Bug,应用在 iOS 4.0-4.1 发生崩溃,由于编译器没有发出警告,方法只在 iOS 4.2 才引入,因此我犹如与定时炸弹相伴。

  • C 函数指针:

    • Swift 现在可以使用 C 函数指针,CFunctionPointer<T -> U> 类型被移除,C 函数现在使用新的 @convention(c) 属性声明,和其他函数类型一样,@convention(c) T -> U 是一个非空的除非是它是可选的。任何全局函数,嵌套函数和不捕获状态的闭包都可以作为一个 C 函数指针直接传递。你也可以调用来自 C 程序的函数。

    • 你可以显示地使用新属性 @convention(c),表示函数应该使用 C 调用约定,简单痛快!尽管我想不出在此对块(block)的支持有何用,作为所发生变化的一部分,@objc_block 也被删掉了,使用 @convention(block) 取而代之。@convention(swift) 默认支持所有函数和闭包。

  • API 审计:

    • 大量的 API 已经进一步进行了审计而更合理。举几个例子:

      • UITableView 的 dequeueReusableCellWithIdentifier 方法现在返回 UITableViewCell? 类型的对象。

      • UIKit 的属性现在也被声明为了实际的属性。

      • translatesAutoresizingMaskToConstraints = false 代替了 setTranslatesAutoresizingMaskToConstrains(false)

  • 库:

    • 这并不是编程语言所特有的。iOS 9 含有不同版本的 Swift 标准库,并且在未来系统中将添加修正后的 Swift 标准库。结合新的 App Thining 技术,下载过程中苹果商店会将 Swift 标准库剥离出去的。我仍然在追根溯源地探求这究竟是如何工作的。
  • 遗漏:

    • 明显的一个遗漏是处理异步代码。苹果公司为我们提供了 GCD,这是一个强大的基础类库,可以构建很多异步操作和并发原语。然而,这些天我们做的每件事,构建用户接口和 API 都需要考虑异步性和并发性。我们把一个文件读操作锁定一段时间,对用户来说整个世界就都静止了。这是个持续的痛点,不是多大的事儿,但如果经常性地每天重复,恐怕也是不行的。C# 和 JavaScript 都采用了 async/await 来为异步代码提供一流的语言支持。我想很多人都想知道,Swift 会提供什么样的语法糖来帮助我们在实现异步操作方面确保正确性。

2、Swift 2.2 新特性

  • 允许更多的关键字用做参数名:

    • 好的参数名对于提高代码可读性很重要。在 Swift 中很多关键字比如 in,repeat,defer 都不能用做参数名。2.2 中,除了少数修饰参数的关键字外都将允许用作参数名。

      swiftnew1

  • 为 Tuples 增加对比操作符:

    • 当前,Tuples 的数据不能使用 == 操作符,2.2 中将支持 Tuples。

      swiftnew2

  • 关联已存在类型时,不再使用 typealias:

    • typealias 现在有两个用处:

      • 为一个已经存在的类型取个别名
      • 在协议中作为一个类型的占位名称
    • 代码如下:

      swiftnew3

    • 这是两种完全不同的用法,不应该用一样的关键字。2.2 中将第一种情况时,启用新的关键字 associatedtype

  • 函数签名将包括参数名:

    • 一个函数有相同的函数名,参数名不同有多个重载很常见。当有多个重载时,在调用时能够通过参数名来区别。但是在获取类型时,却不包括参数名。

    • 举例 UIView 中有这么几个方法:

      swiftnew4

    • 使用时可以通过参数名区分:

      swiftnew5

    • 但是这样使用时却会报错,2.2 中将会解决这个问题。

      • let fn = someView.insertSubview // ambiguous: could be any of the three methods
  • 一个新的方法生成 selector:

    • 现在为了生成 OC 下使用的 selector 只能使用字符串生成,没有类型检查,很容易造成失误。将提供一个 #selector() 方法生成 selector,如下:

    • let sel = #selector(UIView.insertSubview(_:at:)) // produces the Selector "insertSubview:atIndex:" 增加 #if swift 语法判断当前 swift 版本

    • 使用如下:

      swiftnew6

文章目录
  1. 1. 1、Swift 2.0 带来哪些新变化
  2. 2. 2、Swift 2.2 新特性
隐藏目录