Swift 4.0 更新合集、新功能汇总

本文由陈云峰翻译,转载请注明。

注意:本教程将使用Swift 4版本捆绑在Xcode 9 beta 1中。

Swift 4

Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性。

本文重点介绍对Swift的更改将对您的代码产生最大的影响。 而且,让我们开始吧!

入门

Swift 4包含在Xcode 9中。您可以从Apple的开发者门户下载最新版本的Xcode 9(您必须拥有一个活跃的开发者帐户)。 每个Xcode测试版将在发布时捆绑最新的Swift 4快照。

在阅读时,您会注意到[SE-xxxx]格式的链接。 这些链接将带您到相关的Swift Evolution提案。 如果您想了解有关任何主题的更多信息,请务必查看。

我建议您在操场上尝试每个Swift 4功能或更新。 这将有助于巩固您的头脑中的知识,并使您有能力深入了解每个主题。 试图扩大/打破他们的例子来玩弄这些例子。 玩得开心!

注意:本文将针对每个Xcode测试版进行更新。 如果您使用不同的Swift快照,这里的代码不能保证工作。

迁移到Swift 4

从Swift 3迁移到4将比从2.2到3更麻烦。一般来说, 大多数变化是相加的,不应该需要大量的个人感觉。 因此,Swift迁移工具将为您处理大部分更改。

Xcode 9同时支持Swift 4以及Swift 3.2中的Swift 3中间版本。 您的项目中的每个目标可以是Swift 3.2或Swift 4,如果需要,您可以逐个迁移。 然而,转换为Swift 3.2并不是完全免费的 – 您可能需要更新代码部分才能与新SDK兼容,并且由于Swift尚未ABI稳定,因此您将需要使用Xcode 9重新编译依赖项。

当您准备迁移到Swift 4时,Xcode再次提供了一个迁移工具来帮助您。 在Xcode中,您可以导航到编辑/转换/到当前Swift语法…以启动转换工具。

选择要转换的目标后,Xcode将提示您对Objective-C推理的偏好。 选择推荐的选项通过限制引用来减少二进制大小(有关此主题的更多信息,请查看下面的限制@objc推断 )

为了更好地了解您的代码中期望的更改,我们将首先介绍Swift 4中的API更改。

API更改

在跳转到Swift 4中介绍的补充之前,我们先来看看现有API所做的更改/改进。

字符串

String在Swift 4中获得了很多很好的爱。这个提案包含很多变化,所以让我们分解最大的。 [SE-0163] :

如果你感觉怀旧,字符串再次收藏,就像他们是Swift 2.0之前一样。 此更改消除了对String上的String数组的需求。 您现在可以直接在String对象上进行迭代:

let galaxy = "Milky Way "
for char in galaxy {
  print(char)
}

是!

您不仅可以通过String逻辑迭代,还可以从SequenceCollection获取所有的响铃和口哨:

galaxy.count       // 11
galaxy.isEmpty     // false
galaxy.dropFirst() // "ilky Way "
String(galaxy.reversed()) // " yaW ykliM"

// Filter out any none ASCII characters
galaxy.filter { char in
  let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII })
  return isASCII
} // "Milky Way "

上面的ASCII示例显示了对Character 。 您现在可以直接从Character访问Character 。 以前,您需要实例化一个新的String [SE-0178] 。

另外还有一个是StringProtocol 。 它声明了以前在String上声明的大部分功能。 这种变化的原因是改善切片如何工作。 Swift 4添加了Substring类型,用于引用String上的子序列。

StringSubstring实现了StringProtocol使它们具有几乎相同的功能:

// Grab a subsequence of String
let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3)
var milkSubstring = galaxy[galaxy.startIndex...endIndex]   // "Milk"
type(of: milkSubstring)   // Substring.Type

// Concatenate a String onto a Substring
milkSubstring += ""     // "Milk"

// Create a String from a Substring
let milkString = String(milkSubstring) // "Milk"

另一个很大的改进是String如何解释图形集合。 此解决方案来自于Unicode 9的改编。以前,由多个代码点组成的Unicode字符会导致count大于1.常见的情况是具有所选肤色的表情符号。 以下是几个示例,显示前后行为:

"‍".count // Now: 1, Before: 2
"".count // Now: 1, Before: 2
"‍❤️‍‍".count // Now: 1, Before, 4

这只是“ 字符串宣言”中提到的更改的一个子集。 您可以阅读有关将来希望看到的原始动机和提出的解决方案。

词典和集合

至于Collection类型, SetDictionary并不总是最直观的。 幸运的是,斯威夫特队给了他们一些非常需要的爱[SE-0165] 。

基于序列的初始化
列表首先是从一系列键值对(元组)创建一个字典的能力:

let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"]
let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78]

// Dictionary from sequence of keys-values
let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances)) 
// ["Wolf 359": 7.78, "Alpha Centauri B": 4.37, "Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Barnard's Star": 5.96]

重复键处理
您现在可以使用重复的键来处理初始化字典的任何方式。 这有助于避免覆盖键值对,而不会有任何问题:

// Random vote of people's favorite stars
let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"]

// Merging keys with closure for conflicts
let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1]

上面的代码使用zip和速记+来通过添加两个冲突的值来解析重复的键。

注意:如果您不熟悉zip ,您可以在Apple的Swift文档中快速了解它

过滤
DictionarySet现在都可以将结果过滤到原始类型的新对象中:

// Filtering results into dictionary rather than array of tuples
let closeStars = starDistanceDict.filter { $0.value < 5.0 }
closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37]

字典映射
Dictionary获得了一个非常有用的方法来直接映射其值:

// Mapping values directly resulting in a dictionary
let mappedCloseStars = closeStars.mapValues { "\($0)" }
mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"]

字典默认值
在Dictionary上访问某个值时,常见的做法是使用nil coalescing运算符给出默认值,以防数值为nil 。 在Swift 4中,这变得更加清洁,并允许您在线突变中做一些真棒:

// Subscript with a default value
let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown"

// Subscript with a default value used for mutating
var starWordsCount: [String: Int] = [:]
for starName in nearestStarNames {
  let numWords = starName.split(separator: " ").count
  starWordsCount[starName, default: 0] += numWords // Amazing 
}
starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]

以前,这种类型的突变将需要在一个blo肿的if-let语句中包装。 在Swift 4中,可能是一条线!

字典分组
另一个令人惊讶的有用的补充是从Sequence Dictionary并将它们分组到桶中的能力:

// Grouping sequences by computed key
let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! }

// ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]]

当通过特定模式对数据进行分组时,这很方便。

预留容量
SequenceDictionary现在都具有明确保留容量的能力。

// Improved Set/Dictionary capacity reservation
starWordsCount.capacity  // 6
starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity
starWordsCount.capacity // 24

这些类型的重新分配可能是一项昂贵的任务。 使用reserveCapacity(_:)是一个简单的方法来提高性能,当您了解需要存储多少数据时。

这是一大堆信息,所以绝对检查这两种类型,并寻找使用这些添加剂来调整代码的方法。

私有访问修饰符

Swift 3的一个元素,一些不太喜欢的是添加fileprivate 。 从理论上讲,这是非常好的,但实际上它的使用往往会令人困惑。 目标是在成员本身中使用private的,并且在您想要在同一文件中的成员共享访问的情况下很少使用fileprivate 。

问题是Swift鼓励使用扩展将代码分解成逻辑组。 扩展被认为在原始成员声明范围之外,这导致对fileprivate的广泛需求。

Swift 4通过在类型和所述类型的任何扩展之间共享相同的访问控制范围来实现原始意图。 这只适用于相同的源文件[SE-0169] :

struct SpaceCraft {
  private let warpCode: String

  init(warpCode: String) {
    self.warpCode = warpCode
  }
}

extension SpaceCraft {
  func goToWarpSpeed(warpCode: String) {
    if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
      print("Do it Scotty!")
    }
  }
}

let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode  // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"

这允许您使用fileprivate作为其预期目的,而不是作为带状编码组织。

新增API

现在让我们来看看Swift 4的新功能。这些更改不应该打破你现有的代码,因为它们是简单的加法。

归档和序列化

谷物人

到目前为止,在Swift中,为了序列化和归档您的自定义类型,您必须跳过一些环。对于class类型,您需要对NSObject进行子类化并实现NSCoding协议。

structenum这样的值类型需要许多hacks,例如创建一个可以扩展NSObjectNSCoding的子对象。

Swift 4通过将序列化到所有三种Swift类型[SE-0166]来解决这个问题:

struct CuriosityLog: Codable {
  enum Discovery: String, Codable {
    case rock, water, martian
  }

  var sol: Int
  var discoveries: [Discovery]
}

// Create a log entry for Mars sol 42
let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])

在这个例子中,您可以看到,使Swift类型可Encodable和可Decodable所需的唯一Decodable是实现可编Codable协议。 如果所有属性都是Codable ,则协议实现由编译器自动生成。

本文由陈云峰翻译,转载请注明。

要实际编码对象,您需要将其传递给编码器。 Swift编码器正在Swift 4中积极实施。每个编码器根据不同的方案对您的对象进行编码[SE-0167] ( 注意:此提案的一部分仍在开发中):

let jsonEncoder = JSONEncoder() // One currently available encoder

// Encode the data
let jsonData = try jsonEncoder.encode(logSol42)
// Create a String from the data
let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"

这采取了一个对象,并自动将其编码为JSON对象。 确保查看JSONEncoder暴露的属性来自定义其输出。

该过程的最后一部分是将数据解码为具体对象:

let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder

// Attempt to decode the data to a CuriosityLog object
let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
decodedLog.sol         // 42
decodedLog.discoveries // [rock, rock, rock, rock]

使用Swift 4编码/解码,您可以在Swift中获得预期的类型安全性,而不依赖于@objc协议的开销和限制。

键值编码

到目前为止,您可以参考函数而不调用它们,因为函数是Swift中的闭包。 你不能做的是保持对属性的引用,而不实际访问属性保存的底层数据。

对Swift 4来说,令人兴奋的补充是能够引用类型的关键路径来获取/设置实例的基础值[SE-0161] :

struct Lightsaber {
  enum Color {
    case blue, green, red
  }
  let color: Color
}

class ForceUser {
  var name: String
  var lightsaber: Lightsaber
  var master: ForceUser?

  init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
    self.name = name
    self.lightsaber = lightsaber
    self.master = master
  }
}

let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)

在这里,您将通过设置他们的名字,光剑和主人来创建强制用户的几个实例。 要创建一个关键路径,您只需使用一个反斜杠后跟您感兴趣的属性:

// Create reference to the ForceUser.name key path
let nameKeyPath = \ForceUser.name

// Access the value from key path on instance
let obiwanName = obiwan[keyPath: nameKeyPath]  // "Obi-Wan Kenobi"

在这种情况下,您正在为ForceUsername属性创建一个关键路径。 然后,通过将其传递给新的下标keyPath来使用此键路径。 默认情况下,此下标现在可用于每种类型。

以下是使用关键路径深入到子对象,设置属性和构建关键路径引用的更多示例:

// Use keypath directly inline and to drill down to sub objects
let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color]  // blue

// Access a property on the object returned by key path
let masterKeyPath = \ForceUser.master
let anakinMasterName = anakin[keyPath: masterKeyPath]?.name  // "Obi-Wan Kenobi"

// Change Anakin to the dark side using key path as a setter
anakin[keyPath: masterKeyPath] = sidious
anakin.master?.name // Darth Sidious

// Note: not currently working, but works in some situations
// Append a key path to an existing path
//let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
//anakin[keyPath: masterKeyPath] // "Darth Sidious"

Swift的关键路径的美丽在于它们是强类型的! 没有更多的Objective-C字符串风格混乱!

多行字符串文字

许多编程语言的一个非常常见的特征是能够创建多行字符串文字。 Swift 4通过在三个引号[SE-0168]中包装文本来添加这个简单而有用的语法:

let star = "⭐️"
let introString = """
  A long time ago in a galaxy far,
  far away....

  You could write multi-lined strings
  without "escaping" single quotes.

  The indentation of the closing quotes
       below deside where the text line
  begins.

  You can even dynamically add values
  from properties: \(star)
  """
print(introString) // prints the string exactly as written above with the value of star

这在构建XML / JSON消息或构建长格式的文本以在UI中显示时非常有用。

单面范围

为了减少冗长度并提高可读性,标准库现在可以使用单面范围[SE-0172]来推断起始和终点索引。

派上用场的一种方法是创建一个从索引到集合的开始或结束索引的范围:

/ Collection Subscript
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]
let firstThree = planets[..<4]          // Before: planets[planets.startIndex..<4]

如您所见,单面范围减少了明确指定开始索引或结束索引的需要。

无限序列
当起始索引为可数类型时,它们还允许您定义无限Sequence :

// Infinite range: 1...infinity
var numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]

planets.append("Pluto")
numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]

模式匹配
单面范围的另一个很好的用途是模式匹配:

// Pattern matching

func temperature(planetNumber: Int) {
  switch planetNumber {
  case ...2: // anything less than or equal to 2
    print("Too hot")
  case 4...: // anything greater than or equal to 4
    print("Too cold")
  default:
    print("Justtttt right")
  }
}

temperature(planetNumber: 3) // Earth

通用下标

下标是使数据类型以简洁方式可访问的重要组成部分。 为了提高其有用性,下标现在可以是通用的[SE-0148] :

struct GenericDictionary<Key: Hashable, Value> {
  private var data: [Key: Value]

  init(data: [Key: Value]) {
    self.data = data
  }

  subscript<T>(key: Key) -> T? {
    return data[key] as? T
  }
}

在这个例子中,返回类型是通用的。 你可以使用这个通用的下标,如下所示:

// Dictionary of type: [String: Any]
var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])

// Automatically infers return type without "as? String"
let name: String? = earthData["name"]

// Automatically infers return type without "as? Int"
let population: Int? = earthData["population"]

返回类型不仅可以是通用的,而且实际的下标类型也可以是通用的:

extension GenericDictionary {
  subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
    var values: [Value] = []
    for key in keys {
      if let value = data[key] {
        values.append(value)
      }
    }
    return values
  }
}

// Array subscript value
let nameAndMoons = earthData[["moons", "name"]]        // [1, "Earth"]
// Set subscript value
let nameAndMoons2 = earthData[Set(["moons", "name"])]  // [1, "Earth"]

在这个例子中,你可以看到,传递两个不同的Sequence类型( ArraySet )会导致一个数组的各自的值。

更多更新

它处理了Swift 4中最大的变化。现在让我们通过一些较小的位和块来更快速地进行一些。

MutableCollection.swapAt(_:_ )

MutableCollection现在具有mutate方法swapAt(_:_:) ,就像它的声音一样; 交换给定索引值[SE-0173] :

// Very basic bubble sort with an in-place swap
func bubbleSort<T: Comparable>(_ array: [T]) -> [T] {
  var sortedArray = array
  for i in 0..<sortedArray.count - 1 {
    for j in 1..<sortedArray.count {
      if sortedArray[j-1] > sortedArray[j] {
        sortedArray.swapAt(j-1, j) // New MutableCollection method
      }
    }
  }
  return sortedArray
}

bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]

相关类型限制

您现在可以使用where子句来限制关联类型[SE-0142] :

protocol MyProtocol {
  associatedtype Element
  associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}

使用协议约束,许多associatedtype声明可以直接约束其值,而不必跳过环。

类和协议存在

最终将其从Objective-C转换为Swift的功能是定义符合类和一组协议的类型的能力[SE-0156] :

protocol MyProtocol { }
class View { }
class ViewSubclass: View, MyProtocol { }

class MyClass {
  var delegate: (View & MyProtocol)?
}

let myClass = MyClass()
//myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
myClass.delegate = ViewSubclass()

限制@objc推论

要将Objective-C或Swift API公开,请使用@objc编译器属性。 在许多情况下,Swift编译器为您推断出这一点。 质量推理的三个主要问题是:

    1. 潜在的二进制大小显着增加
    2. 知道何时@objc

被推断不明显

  1. 不经意间创建Objective-C选择器碰撞的机会增加。

Swift 4通过限制@objc [SE-0160]的推论来解决这个问题。 这意味着在需要Objective-C的完整动态调度功能的情况下,您需要使用@objc 。

您需要进行这些更改的几个示例包括private方法, dynamic声明和NSObject子类的任何方法。

NSNumber桥接

NSNumber和Swift数字之间已经有很多时髦的行为,这些行为一直困扰着语言太久。 幸运的是,Swift 4压缩了这些错误[SE-0170] 。

以下是一个示例演示示例:

let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

Swift 3中的奇怪行为表明,如果数字溢出,则从0开始。在此示例中,999%2 ^ 8 = 231。

Swift 4通过强制可选的转换来解决问题,只有当数字可以在包含类型中被安全地表达时才返回值。

Swift包管理器

在过去几个月里,Swift Package Manager已经有了一些更新。 一些最大的变化包括:

  • 从分支或提交哈希采购依赖关系
  • 更多控制可接受的包版本
  • 用更为常见的解决方案替代非直观的钉扎命令
  • 能够定义用于编译的Swift版本
  • 指定每个目标的源文件的位置

这些都是获得SPM所需要的重大步骤。 SPM还有很长的路要走,但是我们可以通过保持积极的建议来帮助形成一个。

有关最近解决的提案的全面了解,请查看“ Swift 4软件包管理器更新” 。

从哪里走?

还在举行视频
想要更快学习吗?节省时间与我们的视频课程

Swift语言多年来一直在增长和成熟。 提案过程和社区参与使得跟踪管道中出现的变化非常容易。 它也使东方任何一个人直接影响演变。

随着Swift 4中的这些变化,我们终于到了一个ABI稳定性就在拐角处的地方。 升级Swift版本的痛苦越来越小。 构建性能和工具大大提高。 在苹果生态系统之外使用Swift变得越来越可行。 并且想一想,我们可能只是从一个直观的实现中完全重写String的一些;]。

斯威夫特还有更多的东西。 要保持最新的所有更改,请确保查看以下资源:

你对Swift 4有什么想法? 你最喜欢的变化是什么? 你还想从语言中看到什么? 你有没有找到新的和令人兴奋的东西,这里没有涵盖? 让我们在下面的评论中知道!

本文由陈云峰翻译,转载请注明。原文地址

展开阅读全文

没有更多推荐了,返回首页