原文:What’s New in Swift 3.1?
作者:Cosmin Pupaza
译者:kmyhy

重要消息:Xcode 8.3 以及 Swift 3.1 现在推出了 Beta 版!这个版本包含了期待已久的 Swift 包管理器以及语言自身的一些改进。

如果你最近没有关注过 Swift 路线图,请接着阅读——这篇文章正是你需要的!

在本文,我将集中介绍 Swift 3.1 的重要改变,这些都会对你的代码产生重大影响。让我们开始吧:]

开始

Swift 3.1 和 Swift 3.0 保持源代码兼容,因此新特性不会破坏你的代码,如果你的项目已经用 Xcode 的 Edit\Convert\To Current Swift Syntax… 迁移到 Swift 3.0 的话。但是,苹果已经在 Xcode 8.3 中终止了对 Swift 2.3 的支持。因此如果你还没有从 Swift 2.3 迁移过来,现在你就需要去迁移了。

在后面的内容中,你会看到一些类似 [SE-0001] 一样的标签。这些是 Swift 路线图的提案编号。我引用了每个提案编号,这样你可以了解每个改变的完整细节。我建议你在 playground 中测试这些特性,这样你就会对每个改变有更深刻的理解。

打开 Xcode,选择 File\New\Playground…。选择 iOS 平台,命名随意,保存位置随意。在阅读本文的过程中,这个 Playground 会用于测试每个特性。

注意:如果你需要复习一下 Swift 3.0 的内容,请阅读我们的What’s New in Swift 3。

语法改进

首先,来看一眼这个版本的语法改进,包括:数值类型的失败初始化、新的sequence 函数等等。

允许失败的数值转换的初始化方法

Swift 3.1 实现了对所有数值类型(Int,Int8,Int16,Int32,Int64,UInt,UInt8,UInt16,UInt32,UInt64,Float,Float80,Double)的允许失败的初始化方法,要么成功不丢失任何信息,要么简单返回 nil。[SE-0080]

这个特性是有用的,例如,可以以一种能够恢复的方式,将来自于外部源的松散类型数据转换成安全类型。例如,你用这样的方式处理一个关于学生的 JSON 数组:

class Student {
  let name: String
  let grade: Int

  init?(json: [String: Any]) {
    guard let name = json["name"] as? String,let gradeString = json["grade"] as? String,let gradeDouble = Double(gradeString),let grade = Int(exactly: gradeDouble)  // <-- 3.1 feature here
    else {
        return nil
    }
    self.name = name
    self.grade = grade
  }
}

func makeStudents(with data: Data) -> [Student] {
  guard let json = try? JSONSerialization.jsonObject(with: data,options: .allowFragments),let jsonArray = json as? [[String: Any]] else {
    return []
  }
  return jsonArray.flatMap(Student.init)
}

let rawStudents = "[{\"name\":\"Ray\",\"grade\":\"5.0\"},{\"name\":\"Matt\",\"grade\":\"6\"},{\"name\":\"Chris\",\"grade\":\"6.33\"},{\"name\":\"Cosmin\",\"grade\":\"7\"},{\"name\":\"Steven\",\"grade\":\"7.5\"}]"
let data = rawStudents.data(using: .utf8)!
let students = makeStudents(with: data)
dump(students) // [(name: "Ray",grade: 5),(name: "Matt",grade: 6),(name: "Cosmin",grade: 7)]

在 Student 类的允许失败的指定初始化方法中,你使用了一个允许失败的初始化方法,将 grade 属性从 Double 转换成 Int:

let grade = Int(exactly: gradeDouble)

如果 gradeDouble 是一个小数值比如 6.33,它会转换失败。如果它是一个整数字,比如 6.0,它会转换成功。

注意:另一种方法是在初始化方法中抛出异常来替代允许失败的初始化方法。社区觉得允许失败的初始化方法更好,更符合工效学设计。

新的 Sequence 函数

Swift 3.1 新增了两个函数用于数据过滤,它们被添加到标准库的 Sequence 协议中:prefix(while:) 和 drop(while:) [SE-0045]。

以斐波那契无限序列为例:

let fibonacci = sequence(state: (0,1)) {
  (state: inout (Int,Int)) -> Int? in
  defer {state = (state.1,state.0 + state.1)}
  return state.0
}

在 Swift 3.0 中,你可以简单地指定迭代次数,来产生斐波那契序列:

// Swift 3.0
for number in fibonacci.prefix(10) {
  print(number)  // 0 1 1 2 3 5 8 13 21 34
}

Swift 3.1 允许你使用 prefix(while:) 和 drop(while:) ,它们带有一个条件参数,允许获得位于两个值之间的完整序列:

// Swift 3.1
let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100})
for element in interval {
  print(element) // 144 233 377 610 987
}

prefix(while:) 返回满足指定条件的最大序列。这个序列从序列的起始值开始,当闭包返回 false 时的序列截止。

drop(while:) 则相反: 它返回一个子序列,当该序列中的元素不满足闭包中指定的条件时,子序列开始,直到序列的最后一个值。

注意:你可以将闭包语法简化为:

let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}

具化带约束的扩展

Swift 3.1 允许你用一个具体的类型约束来扩展泛型。在此之前,你无法扩展这种类型,因为约束肯定是一种协议。让我们来看一个例子。

例如,RoR 提供了一个非常有用的 isBlank 方法,用于检查用户输入。用 Swift 3.0 你可以这样实现它,在 String 类型的扩展中用一个计算属性:

// Swift 3.0
extension String {
  var isBlank: Bool {
    return trimmingCharacters(in: .whitespaces).isEmpty
  }
}

let abc = " "
let def = "x"

abc.isBlank // true
def.isBlank // false

如果你想在一个 Optional 的 String 上实现 isBlank 计算属性,在 Swift 3.0 中你可以这样做:

// Swift 3.0
protocol StringProvider {
  var string: String {get}
}

extension String: StringProvider {
  var string: String {
    return self
  }
}

extension Optional where Wrapped: StringProvider {
  var isBlank: Bool {
    return self?.string.isBlank ?? true
  }
}

let foo: String? = nil
let bar: String? = " "
let baz: String? = "x"

foo.isBlank // true
bar.isBlank // true
baz.isBlank // false

这里声明了一个 StringProvider 协议用于给 String 进行实现。然后你扩展了 Optional 类型,同时要求 wrapped 类型为 StringProvider,并在其中添加了 isBlank 方法。

Swift 3.1 允许你扩展具体的类型,而不是协议:

// Swift 3.1
extension Optional where Wrapped == String {
  var isBlank: Bool {
    return self?.isBlank ?? true
  }
}

这提供了同样的功能,需要编写的代码变少了!

嵌套的泛型

Swift 3.1 允许你将嵌套类型和泛型混用。设想一下(很疯狂)这个例子。比如 raywenderlich.com 某个团队的 lead 想发表一篇博客,他让一队专门的程序员去实现,以便满足网站的优质标准:

class Team<T> {
  enum TeamType {
    case swift
    case iOS
    case macOS
  }

  class BlogPost<T> {
    enum BlogPostType {
      case tutorial
      case article
    }

    let title: T
    let type: BlogPostType
    let category: TeamType
    let publishDate: Date

    init(title: T,type: BlogPostType,category: TeamType,publishDate: Date) {
      self.title = title
      self.type = type
      self.category = category
      self.publishDate = publishDate
    }
  }

  let type: TeamType
  let author: T
  let teamLead: T
  let blogPost: BlogPost<T>

  init(type: TeamType,author: T,teamLead: T,blogPost: BlogPost<T>) {
    self.type = type
    self.author = author
    self.teamLead = teamLead
    self.blogPost = blogPost
  }
}

在外部类 Team 中嵌套了 BlogPost 内部类,同时两个类都是泛型化的。Team 要查找我在网站上发布的文章和教程时要怎么做:

Team(type: .swift,author: "Cosmin Pupăză",teamLead: "Ray Fix",
     blogPost: Team.BlogPost(title: "Pattern Matching",type: .tutorial,
     category: .swift,publishDate: Date()))

Team(type: .swift, blogPost: Team.BlogPost(title: "What's New in Swift 3.1?",type: .article, category: .swift,publishDate: Date())) 实际上,这段代码可以简化。如果嵌套的内部类型使用了外部泛型类的类型,它会继承父类的类型。因此并不需要声明这个类型,这样: ```swift class Team<T> { // original code  class BlogPost { // original code } // original code  let blogPost: BlogPost init(type: TeamType,author: T,teamLead: T,blogPost: BlogPost) { // original code  } } <div class="se-preview-section-delimiter"></div> 

注意:如果你了解更多 Swift 泛型,请阅读我们最近更新的教程getting started with Swift generics。

检测 Swift 版本

你可以用 #if swift(>= N) 静态结构来检测 Swift 版本是否是某个特定的版本。

// Swift 3.0




<div class="se-preview-section-delimiter"></div>

#if swift(>=3.1)
  func intVersion(number: Double) -> Int? {
    return Int(exactly: number)
  }




<div class="se-preview-section-delimiter"></div>

#elseif swift(>=3.0)
  func intVersion(number: Double) -> Int {
    return Int(number)
  }




<div class="se-preview-section-delimiter"></div>

#endif




<div class="se-preview-section-delimiter"></div>

但是,这种方式用在某些地方比如 Swift 标准库时有一个很大的缺点。它需要编译编译每个标准库和每个所支持的老的语言版本。因为你运行的 Swift 编译是是以向后兼容模式运行的,例如你想在 Swift 3.0 下使用,就需要用这个版本的标准库编译这个兼容版本。如果你在用 3.1 的标准库中用,这段代码将不正确。

因此,Swift 3.1 扩展了 @available 属性,以支持某个 Swift 版本号以及存在的平台版本。

// Swift 3.1

@available(swift 3.1)
func intVersion(number: Double) -> Int? {
  return Int(exactly: number)
}

@available(swift,introduced: 3.0,obsoleted: 3.1)
func intVersion(number: Double) -> Int {
  return Int(number)
}




<div class="se-preview-section-delimiter"></div>

这个新特性提供了同样的功能,也就是说某个 intVersion 方法只会在对应的 Swift 版本下有效。此外它还允许向标准库这样的库只需要编译一次。编译会为指定兼容的版本选择对应的功能。

注意:如果你想了解更多 availability 属性的内容,请阅读我们的教程availability attributes in Swift。

将非逃逸闭包转换为逃逸闭包

Swift 3.0函数的闭包参数默认是非逃逸闭包 [SE-0103]。但是,这个提议那时并没有实现。在 Swift 3.1,你可以临时性地将非逃逸闭包转换成逃逸闭包,使用一个新的 withoutActuallyEscaping() 函数。

为什么要这样做?这种做法不太常用,但可以参考一下提议中的例子。

func perform(_ f: () -> Void,simultaneouslyWith g: () -> Void,on queue: dispatchQueue) {
  withoutActuallyEscaping(f) { escapableF in     // 1
    withoutActuallyEscaping(g) { escapableG in
      queue.async(execute: escapableF)           // 2
      queue.async(execute: escapableG)     

      queue.sync(flags: .barrier) {}             // 3
    }                                            // 4
  }
}




<div class="se-preview-section-delimiter"></div>

这个函数同时执行了两个闭包,然后在两者都完成后返回。

  1. f 和 g 是非逃逸闭包,它们被转换成了 escapableF 和 escapableG。

  2. async(execute:) 函数用到的是逃逸闭包。幸好已经通过前面的步骤获得了逃逸闭包。

  3. 通过 sync(flags:.barrie),你可以确保 async(execute:)方法已经完成,闭包不会在后面调用。

  4. 作用域决定了 escapableF 和 escapableG 的使用。

如果你想将临时的逃逸闭包保存(比如真的 escaped 它们),它会是一个 bug。在未来的标准库版本中,标准库可能会在你试图调用它们时进行检查并捕获。

Swift 包管理器升级

哈,期待已久的包管理器来了!

允许编辑的 Package

在 Swift 3.1 中,包管理器中添加了一个新概念,允许编辑的包。[SE-0082]

swift package edit 命令允许用一个已有的包为参数,然后将它转换成可编辑的包。可编辑的包会替换所有传统包在依赖图谱中的出现的地方。用 –end-edit 命令将可编辑包转换回传统 resolved 的包。

Version Pinning

Swift 3.1 在包管理器中添加了一个新概念,版本植入,将包依赖植入到指定版本[SE-0145]。pin 命令用于植入一个或所有依赖:

$ swift package pin --all      // pins all the dependencies
$ swift package pin Foo        // pins Foo at current resolved version
$ swift package pin Foo --version 1.2.3  // pins Foo at 1.2.3




<div class="se-preview-section-delimiter"></div>

unpin 命令转回原有的包版本:

$ swift package unpin —all
$ swift package unpin Foo




<div class="se-preview-section-delimiter"></div>

包管理器将 activie 版本的每个包的 pin 信息保存在 Packege.pins 中。如果这个文件不存在,包管理器自动根据 package manifest 中的必要信息创建文件,同时自动 pinning。

其它

swift package reset 命令将包重置为干净状态,去掉依赖检查或编译文件。

swift test –parallel 命令执行并行测试。

杂项

Swift 3.1 中还有几个小改变,并不太常见:

多次返回函数

C 语言中能够返回两次的函数比如 vfork 和 setjmp 是无效的。它们以一种有趣的方式改变持续的控制流。因此 Swift 社区觉得应当禁止它们的使用,并引发一个编译时错误。

禁用 Auto-Linking

Swift 包管理器禁用了 C 语言 target 中的模块映射的 auto-linking 功能:

// Swift 3.0
module MyCLib {
    header “foo.h" link “MyCLib"
    export *
}

// Swift 3.1
module MyCLib {
    header “foo.h”
    export *
}

结束

Swift 3.1 完善了 Swift3.0 的功能,并准备将更激烈的改变方到今年稍晚的 Swift 4.0 中去。包括对泛型的改进、正则式、更符合工效学的字符串设计等等。

如果你觉得意犹未尽,请看一下Swift standard library diffs 或者官方的 Swift CHANGELOG,那里会有更多的关于这次修改的信息。或者,你可以继续等待 Swift 4.0 的到来!

如果你真的想知道 Swift 4 以后还会有什么变化,请访问 Swift 演进计划,在那里你可以获得当前正在提出的计划。如果急切地想知道对为什么某个正在评审中的提议迟迟得不到回复, 你甚至可以提出自己的提议 ;]

你喜欢或者不喜欢 Swift 3.1 的什么特性?请在下面留言。

What’s New in Swift 3.1?的更多相关文章

  1. HTML5实现直播间评论滚动效果的代码

    这篇文章主要介绍了HTML5实现直播间评论滚动效果的代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. 前端监听websocket消息并实时弹出(实例代码)

    这篇文章主要介绍了前端监听websocket消息并实时弹出,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. HTML5之消息通知的使用(Web Notification)

    通知可以说是web中比较常见且重要的功能,私信、在线提问、或者一些在线即时通讯工具我们总是希望第一时间知道对方有了新的反馈。本篇文章主要介绍了HTML5之消息通知的使用(Web Notification),感兴趣的小伙伴们可以参考一下

  4. HTML5中的Web Notification桌面通知功能的实现方法

    这篇文章主要介绍了HTML5中的Web Notification桌面通知功能的实现方法,需要的朋友可以参考下

  5. HTML5仿微信聊天界面、微信朋友圈实例代码

    小编最近开发一个基于html5开发的一个微信聊天前端界面,功能很全面,下面小编给大家分享实例代码,需要的朋友参考下

  6. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. HTML5的postMessage的使用手册

    HTML5提出了一个新的用来跨域传值的方法,即postMessage,这篇文章主要介绍了HTML5的postMessage的使用手册的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  8. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

  9. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  10. xcode找不到匹配的配置文件

    我有一个AdhociOS应用程序,它给了我“在xcode6中找不到匹配的配置文件”,我创建了一个Adhoc配置文件,下载它,双击它并在General–Identity下选择了一个团队.但我接着得到了那条消息,并尝试使用“修复问题”按钮没有帮助.在构建设置–供应配置文件–发布我有“自动”.任何人都可以帮助我,我完全迷失了……

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部