原文: https://www.raywenderlich.com/187826/whats-new-in-swift-4-1
语言改进
此版本中有许多语言改进,包括条件一致性,协议中相关类型的递归约束等。
条件一致性
条件一致性使得泛型类型的协议一致性,其中类型参数满足特定条件[ SE-0143 ]。这是一个强大的功能,使您的代码更灵活。您可以通过一些示例了解它的工作原理。
标准库中的条件一致性
在Swift 4中,您可以比较数组,字典和选项,只要它们的元素是Equatable
。这对于基本场景非常好,例如:
// Arrays of Int
let firstArray = [1, 2, 3]
let secondArray = [1, 2, 3]
let sameArray = firstArray == secondArray
// Dictionaries with Int values
let firstDictionary = ["Cosmin": 10, "George": 9]
let secondDictionary = ["Cosmin": 10, "George": 9]
let sameDictionary = firstDictionary == secondDictionary
// Comparing Int?
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional
在这些示例中测试相等性的运算符是有效的,因为Int在Swift 4中是Equatable。但是,比较选项集合是您可能遇到的Swift 4的常见情况,因为选项不符合Equatable。 Swift 4.1使用条件一致性修复了这个问题,让可比较底层Equatable类型的可选类型:
// Array of Int?
let firstArray = [1, nil, 2, nil, 3, nil]
let secondArray = [1, nil, 2, nil, 3, nil]
let sameArray = firstArray == secondArray
// Dictionary with Int? values
let firstDictionary = ["Cosmin": 10, "George": nil]
let secondDictionary = ["Cosmin": 10, "George": nil]
let sameDictionary = firstDictionary == secondDictionary
// Comparing Int?? (Optional of Optional)
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional
Int?
是Equatable
在Swift4.1中,所以==
运算符可以服务于[Int?]
,[String: Int?]
和Int??
。
在比较阵列阵列(例如[[Int]]
)时,已经解决了类似的问题。在Swift 4中,你只能比较集合的数组(例如[Set<Int>]
),因为集合符合Equatable
。Swift 4.1解决了这个问题,因为数组(和字典)只要它们的基础值也是Equatable。
let firstArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])]
let secondArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])]
// Will work in Swift 4 and Swift 4.1
// since Set<Int> is Equatable
firstArrayOfSets == secondArrayOfSets
let firstArrayOfArrays = [[1, 2, 3], [3, 4, 5]]
let secondArrayOfArrays = [[1, 2, 3], [3, 4, 5]]
// Caused an error in Swift 4, but works in Swift 4.1
// since Arrays are Equatable in Swift 4.1
firstArrayOfArrays == secondArrayOfArrays
通常,Swift 4.1的Optional,Array和Dictionary现在符合Equatable和Hashable,只要它们的底层值或元素符合这些协议。
这是条件一致性在标准库中的工作方式。接下来,您将在自己的代码中实现它。
代码中的条件一致性
您将使用条件一致性来创建自己的乐器乐队。在操场底部添加以下代码块以开始:
// 1
class LeadInstrument: Equatable {
let brand: String
init(brand: String) {
self.brand = brand
}
func tune() -> String {
return "Standard tuning."
}
static func ==(lhs: LeadInstrument, rhs: LeadInstrument) -> Bool {
return lhs.brand == rhs.brand
}
}
// 2
class Keyboard: LeadInstrument {
override func tune() -> String {
return "Keyboard standard tuning."
}
}
// 3
class Guitar: LeadInstrument {
override func tune() -> String {
return "Guitar standard tuning."
}
}
这是一步一步的做法:
-
LeadInstrument
符合Equatable
。它有一个特定的品牌和一个名为tune()的方法,你最终会用它来调整乐器。 - 您可以覆盖
tune()
在Keyboard
返回的键盘标准调整。 - 你做同样的事情
Guitar
。
接下来,宣布乐器乐队:
// 1
class Band<LeadInstrument> {
let name: String
let lead: LeadInstrument
init(name: String, lead: LeadInstrument) {
self.name = name
self.lead = lead
}
}
// 2
extension Band: Equatable where LeadInstrument: Equatable {
static func ==(lhs: Band<LeadInstrument>, rhs: Band<LeadInstrument>) -> Bool {
return lhs.name == rhs.name && lhs.lead == rhs.lead
}
}
以下是您正在逐步完成的工作:
- 您创建一个
Band
使用泛型类型调用的类 -LeadInstrument
。每个乐队都有一个独特的名称和主要乐器。 - 您可以使用
where
约束Band
,以符合Equatable
只要LeadInstrument
做。你符合的能力Band
的通用LeadInstrument
到Equatable
是完全有条件的地方一致性的用武之地。
接下来,定义您喜欢的乐队并进行比较:
// 1
let rolandKeyboard = Keyboard(brand: "Roland")
let rolandBand = Band(name: "Keys", lead: rolandKeyboard)
let yamahaKeyboard = Keyboard(brand: "Yamaha")
let yamahaBand = Band(name: "Keys", lead: yamahaKeyboard)
let sameBand = rolandBand == yamahaBand
// 2
let fenderGuitar = Guitar(brand: "Fender")
let fenderBand = Band(name: "Strings", lead: fenderGuitar)
let ibanezGuitar = Guitar(brand: "Ibanez")
let ibanezBand = Band(name: "Strings", lead: ibanezGuitar)
let sameBands = fenderBand == ibanezBand
在这段代码中,您将创建两个Keyboard
s和Guitar
s以及它们的相应Band
s。然后,您可以直接比较波段,这要归功于您之前定义的条件一致性。
JSON解析中的条件一致性
数组,字典,集合和选项符合Codable
如果它们的元素符合Codable
在Swift 4.1。将以下代码添加到您的playground以尝试此操作:
struct Student: Codable, Hashable {
let firstName: String
let averageGrade: Int
}
let cosmin = Student(firstName: "Cosmin", averageGrade: 10)
let george = Student(firstName: "George", averageGrade: 9)
let encoder = JSONEncoder()
// Encode an Array of students
let students = [cosmin, george]
do {
try encoder.encode(students)
} catch {
print("Failed encoding students array: \(error)")
}
// Encode a Dictionary with student values
let studentsDictionary = ["Cosmin": cosmin, "George": george]
do {
try encoder.encode(studentsDictionary)
} catch {
print("Failed encoding students dictionary: \(error)")
}
// Encode a Set of students
let studentsSet: Set = [cosmin, george]
do {
try encoder.encode(studentsSet)
} catch {
print("Failed encoding students set: \(error)")
}
// Encode an Optional Student
let optionalStudent: Student? = cosmin
do {
try encoder.encode(optionalStudent)
} catch {
print("Failed encoding optional student: \(error)")
}
您可以使用此代码进行编码[Student]
,[String: Student]
,Set<Student>
和Student?
。这在Swift 4.1中运行顺利,因为Student是Codable,这使得这些集合类型也符合它。
在JSON编码期间在Camel Case和Snake Case之间进行转换
Swift 4.1允许您在JSON编码期间将CamelCase属性转换为snake_case键:
var jsonData = Data()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
do {
jsonData = try encoder.encode(students)
} catch {
print(error)
}
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
创建编码器时,设置keyEncodingStrategy
为.convertToSnakeCase
。查看控制台,您应该看到:
[
{
"first_name" : "Cosmin",
"average_grade" : 10
},
{
"first_name" : "George",
"average_grade" : 9
}
]
您还可以在JSON解码期间从下划线返回到驼峰案例属性:
var studentsInfo: [Student] = []
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
studentsInfo = try decoder.decode([Student].self, from: jsonData)
} catch {
print(error)
}
for studentInfo in studentsInfo {
print("\(studentInfo.firstName) \(studentInfo.averageGrade)")
}
这一次,你设置keyDecodingStrategy
为.convertFromSnakeCase
。
Equatable和Hashable的协议一致性
Swift 4要求你编写样板代码以使structs符合Equatable
和Hashable
:
struct Country: Hashable {
let name: String
let capital: String
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
var hashValue: Int {
return name.hashValue ^ capital.hashValue &* 16777619
}
}
使用此代码,您实现==(lhs:rhs:)
并hashValue
支持Equatable
和Hashable
。您可以比较国家/地区,将它们添加到集合中,甚至将它们用作字典键:
let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let sameCountry = france == germany
let countries: Set = [france, germany]
let greetings = [france: "Bonjour", germany: "Guten Tag"]
Swift 4.1在Equatable和Hashable的结构中添加了默认实现,只要它们的所有属性都是Equatable和Hashable SE-0185。
这极大地简化了您的代码,可以简单地重写为:
struct Country: Hashable {
let name: String
let capital: String
}
枚举与关联值也需要额外的代码一起工作Equatable
,并Hashable
在swift4:
enum BlogPost: Hashable {
case tutorial(String, String)
case article(String, String)
static func ==(lhs: BlogPost, rhs: BlogPost) -> Bool {
switch (lhs, rhs) {
case let (.tutorial(lhsTutorialTitle, lhsTutorialAuthor), .tutorial(rhsTutorialTitle,
rhsTutorialAuthor)):
return lhsTutorialTitle == rhsTutorialTitle && lhsTutorialAuthor == rhsTutorialAuthor
case let (.article(lhsArticleTitle, lhsArticleAuthor), .article(rhsArticleTitle, rhsArticleAuthor)):
return lhsArticleTitle == rhsArticleTitle && lhsArticleAuthor == rhsArticleAuthor
default:
return false
}
}
var hashValue: Int {
switch self {
case let .tutorial(tutorialTitle, tutorialAuthor):
return tutorialTitle.hashValue ^ tutorialAuthor.hashValue &* 16777619
case let .article(articleTitle, articleAuthor):
return articleTitle.hashValue ^ articleAuthor.hashValue &* 16777619
}
}
}
您使用枚举的情况为==(lhs:rhs:)
和编写实现hashValue
。这使您可以比较博客文章并在集和词典中使用它们:
let swift3Article = BlogPost.article("What's New in Swift 3.1?", "Cosmin Pupăză")
let swift4Article = BlogPost.article("What's New in Swift 4.1?", "Cosmin Pupăză")
let sameArticle = swift3Article == swift4Article
let swiftArticlesSet: Set = [swift3Article, swift4Article]
let swiftArticlesDictionary = [swift3Article: "Swift 3.1 article", swift4Article: "Swift 4.1 article"]
根据具体情况Hashable
,由于默认Equatable
和Hashable
实现,此代码的大小在Swift 4.1中大大减少:
enum BlogPost: Hashable {
case tutorial(String, String)
case article(String, String)
}
您只是保存了20行样板代码!
使用Swift 4.1节省时间!Hashable Index类型
如果下标参数的类型Hashable
在Swift 4中,则键路径可能使用了下标。这使得它们可以使用数组double
; 例如:
let swiftVersions = [3, 3.1, 4, 4.1]
let path = \[Double].[swiftVersions.count - 1]
let latestVersion = swiftVersions[keyPath: path]
您keyPath
用来获取当前的Swift版本号swiftVersions
。
Swift 4.1增加了对标准库[ SE-0188 ]中所有索引类型的Hashable
一致性:
let me = "Cosmin"
let newPath = \String.[me.startIndex]
let myInitial = me[keyPath: newPath]
下标返回字符串的第一个字母。它起作用,因为String
索引类型Hashable
在Swift 4.1中。
协议中关联类型的递归约束
Swift 4不支持在协议中定义关联类型的递归约束:
protocol Phone {
associatedtype Version
associatedtype SmartPhone
}
class IPhone: Phone {
typealias Version = String
typealias SmartPhone = IPhone
}
在此示例中,您定义了一个SmartPhone
关联类型,但事实证明它可以用来约束它Phone
,因为所有智能手机都是手机。现在可以在Swift 4.1 [ SE-0157 ]中使用它:
protocol Phone {
associatedtype Version
associatedtype SmartPhone: Phone where SmartPhone.Version == Version, SmartPhone.SmartPhone == SmartPhone
}
您where
用来限制两者Version
并SmartPhone
与手机相同。
协议中的weak和Unowned参考
支持Swift 4 weak
和unowned
协议属性:
class Key {}
class Pitch {}
protocol Tune {
unowned var key: Key { get set }
weak var pitch: Pitch? { get set }
}
class Instrument: Tune {
var key: Key
var pitch: Pitch?
init(key: Key, pitch: Pitch?) {
self.key = key
self.pitch = pitch
}
}
你在一定的调整的工具key
和pitch
。音调可能是nil
,所以你可以像weak
在Tune
协议中那样对它进行建模。
但两者weak
和unowned
实际上是毫无意义的,如果定义范围内的协议本身,所以swift4.1删除它们,你将在协议[使用这些关键字得到一个警告,SE-0186 ]:
protocol Tune {
var key: Key { get set }
var pitch: Pitch? { get set }
}
集合中的索引距离
Swift 4用于IndexDistance
声明集合中的元素数量:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, C.IndexDistance) {
let collectionType: String
switch collection.count {
case 0...100:
collectionType = "small"
case 101...1000:
collectionType = "medium"
case 1001...:
collectionType = "big"
default:
collectionType = "unknown"
}
return (collectionType, collection.count)
}
typeOfCollection(_:)
返回一个元组,其中包含集合的类型和计数。您可以将它用于任何类型的集合,如数组,字典或集合; 例如:
typeOfCollection(1...800) // ("medium", 800)
typeOfCollection(greetings) // ("small", 2)
您可以通过使用where子句将IndexDistance约束为Int来改进函数的返回类型:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) where C.IndexDistance == Int {
// same code as the above example
}
swift4.1内容替换IndexDistance
与Int
标准库,所以你不需要where
在这种情况下【条款SE-0191 ]:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) {
// same code as the above example
}
模块中的结构初始化器
向public
结构体添加属性可能会导致Swift 4中的源代码更改。在本教程中,通过转到View \ Navigators \ Show Project Navigator,确保Project Navigator在Xcode中可见。接下来,右键单击Sources并从菜单中选择New File。重命名文件DiceKit.swift。用以下代码块替换其内容:
public struct Dice {
public let firstDie: Int
public let secondDie: Int
public init(_ value: Int) {
let finalValue: Int
switch value {
case ..<1:
finalValue = 1
case 6...:
finalValue = 6
default:
finalValue = value
}
firstDie = finalValue
secondDie = 7 - finalValue
}
}
struct的初始化程序确保两个骰子都有1到6之间的有效值。切换回操场并在其末尾添加此代码:
// 1
let dice = Dice(0)
dice.firstDie
dice.secondDie
// 2
extension Dice {
init(_ firstValue: Int, _ secondValue: Int) {
firstDie = firstValue
secondDie = secondValue
}
}
// 3
let newDice = Dice(0, 7)
newDice.firstDie
newDice.secondDie
以下是您对此代码所做的操作:
- 你创造了一对有效的骰子。
- 您使用
Dice
另一个可直接访问其属性的初始化程序进行扩展。 - 您使用struct的新初始化程序定义了一对无效的骰子。
在Swift 4.1中,跨目标初始值设定项应调用默认值。将您的扩展名更改Dice
为:
extension Dice {
init(_ firstValue: Int, _ secondValue: Int) {
self.init(abs(firstValue - secondValue))
}
}
这种改变使得结构表现得像类:跨模块初始化器必须是Swift 4.1 [ SE-0189 ]中的便利初始化器。
在Swift 4.1中你不能再在骰子游戏中作弊了!平台设置和构建配置更新
Swift 4.1为代码测试添加了一些急需的平台和构建功能:
建立Imports
在Swift 4中,您通过检查操作系统本身来测试某个模块是否在某个平台上可用; 例如:
#if os(iOS) || os(tvOS)
import UIKit
print("UIKit is available on this platform.")
#else
print("UIKit is not available on this platform.")
#endif
UIKit
可在iOS和tvOS上使用,因此如果测试成功,您可以导入它。Swift 4.1通过让您检查模块本身来进一步简化了这一过程:
#if canImport(UIKit)
print("UIKit is available if this is printed!")
#endif
在Swift 4.1中,您#if canImport(UIKit)
用来确认某个框架可用于导入[ SE-0075 ]。
目标环境
在编写Swift 4代码时,检查您是在模拟器还是在物理设备上运行的最着名的方法是检查架构和操作系统:
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(tvOS) || os(watchOS))
print("Testing in the simulator.")
#else
print("Testing on the device.")
#endif
如果您的架构是基于Intel的,并且您的操作系统是iOS,tvOS或watchOS,则您在模拟器中进行测试。否则,您正在测试设备。
这个测试非常麻烦,对手头的问题也非常缺乏描述性。Swift 4.1使这个测试更直接; 只需使用targetEnvironment(simulator)
[ SE-0190 ]:
#if targetEnvironment(simulator)
print("Testing in the simulator.")
#endif
杂项比特和碎片
Swift 4.1中还有一些其他更新值得了解:
压缩序列
在Swift 4中,使用flatMap(_:)
过滤掉nil
序列中的值是相当常见的:
let pets = ["Sclip", nil, "Nori", nil]
let petNames = pets.flatMap { $0 } // ["Sclip", "Nori"]
不幸的是,flatMap(_:)
它以各种方式过载,并且在特定情况下,flatMap(_:)
命名并不是对所采取的行动的描述。
由于这些原因,swift4.1引入的重命名flatMap(_:)
,以compactMap(_:)
使其更清晰的意义和独特的[ SE-0187 ]:
let petNames = pets.compactMap { $0 }
不安全的指针
Swift 4使用临时的不安全可变指针来创建和改变不安全的可变缓冲区指针:
let buffer = UnsafeMutableBufferPointer<Int>(start: UnsafeMutablePointer<Int>.allocate(capacity: 10),
count: 10)
let mutableBuffer = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(mutating: buffer.baseAddress),
count: buffer.count)
Swift 4.1允许您使用与不安全可变指针相同的方法直接使用不安全的可变缓冲区指针[ SE-0184 ]:
let buffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 10)
let mutableBuffer = UnsafeMutableBufferPointer(mutating: UnsafeBufferPointer(buffer))
新的playground功能
Swift 4允许您在Xcode playground中自定义类型描述:
class Tutorial {}
extension Tutorial: CustomPlaygroundQuickLookable {
var customPlaygroundQuickLook: PlaygroundQuickLook {
return .text("raywenderlich.com tutorial")
}
}
let tutorial = Tutorial()
您为Tutorial实现了CustomPlaygroundQuickLookable,以返回自定义快速查看playground描述。 customPlaygroundQuickLook中的描述类型仅限于PlaygroundQuickLook案例。在Swift 4.1中不再是这种情况(双关语):
extension Tutorial: CustomPlaygroundDisplayConvertible {
var playgroundDescription: Any {
return "raywenderlich.com tutorial"
}
}
您这次实现CustomPlaygroundDisplayConvertible。描述的类型现在是Any,因此您可以从playgroundDescription返回任何内容。这简化了您的代码并使其更加灵活 SE-0198 ]。
网友评论