Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性。
学习Swift4.0的两个官方文档
The Swift Programming Language (Swift 4)
Swift的180个介绍
Swift4.0到底有哪些新改变呢?
- 字符串String类型更加人性化,多行字符串文字,支持Range,也算集合类型
- 改进的private的访问权限,私有访问修饰符
- 更智能更安全的Key Value Coding 键值编码
- 词典和集合
- 归档和序列化
- 单面范围
- 通用下标
- MutableCollection.swapAt( __ : _ _ )
- 相关类型限制
- 类和协议存在
- 限制@objc推论
字符串String类型更加人性化,多行字符串文字,支持Range,也算集合类型
String在Swift 4中获得了很多很好的爱。这个提案包含很多变化,所以让我们分解最大的。 [SE-0163] :
let galaxy = "Milky Way "
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 "
更便捷的Range
var str = "Hello, playground"
// Swift3.0
var index = str.index(of: " ")!
let greeting = str[str.startIndex ..< index]
index = str.index(index, offsetBy: 1) // index 下标 +1
let name = str[index ..< str.endIndex]
// Swift4.0
var index1 = str.index(of: " ")!
let greeting1 = str.prefix(upTo: index1)
index1 = str.index(index1, offsetBy: 1)
let name1 = str.suffix(from: index1)
print(Array(str.enumerated()))
print(Array(zip(1..., str)))
*多行文字
通过一对三个双引号直接来赋值 """ """。
/// plist格式
let plistInfo = """
title
设置WiFi
imageName
serversSet
"""
/// JSON格式
let jsonInfo = """
{
"data": {
"title": "String is a collection"
"author": "23"
"creat_at": "2017-06-13"
}
}
"""
print(plistInfo)
print(jsonInfo)
集合用法
字符串可以像集合哪有进行遍历,直接通过.count知道字符串个数
var str = "Hello, Swift 4.0"
print(str.characters.count) // Swift3.0写法
print(str.count) // Swift4.0写法
/// 遍历
str.forEach {
$0
}
改进的private的访问权限,私有访问修饰符
Swift 3的一个元素,一些不太喜欢的是添加fileprivate
。 从理论上讲,这是非常好的,但实际上它的使用往往会令人困惑。 目标是在成员本身中使用private的,并且在您想要在同一文件中的成员共享访问的情况下很少使用fileprivate。
问题是Swift鼓励使用扩展将代码分解成逻辑组。 扩展被认为在原始成员声明范围之外,这导致对fileprivate的广泛需求。
Swift 4通过在类型和所述类型的任何扩展之间共享相同的访问控制范围来实现原始意图。 这只适用于相同的源文件[SE-0169] :
struct SpaceCraft {
private let warpCode: String
fileprivate var name: String = "Lewis"
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!")
} else {
self. name += warpCode
}
}
}
let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"
在extension文件里面也可以访问private的变量,fileprivate的变量了,
更智能更安全的Key Value Coding 键值编码
到目前为止,您可以参考函数而不调用它们,因为函数是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)
在这种情况下,您正在为ForceUser的name属性创建一个关键路径。 然后,通过将其传递给新的下标keyPath来使用此键路径。 默认情况下,此下标现在可用于每种类型。
以下是使用关键路径深入到子对象,设置属性和构建关键路径引用的更多示例:
// 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"
// 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"
通过** keyPath关键字来使用,\**关键符号来获取key值,从而实现了键值编码
词典和集合
至于Collection类型, Set和Dictionary并不总是最直观的。 幸运的是,斯威夫特队给了他们一些非常需要的爱[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文档中快速了解它过滤Dictionary和Set现在都可以将结果过滤到原始类型的新对象中:
// 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"]]
当通过特定模式对数据进行分组时,这很方便。
预留容量
Sequence和Dictionary现在都具有明确保留容量的能力。
// Improved Set/Dictionary capacity reservation
starWordsCount.capacity // 6
starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity
starWordsCount.capacity // 24
这些类型的重新分配可能是一项昂贵的任务。 使用reserveCapacity(_:)是一个简单的方法来提高性能,当您了解需要存储多少数据时。
这是一大堆信息,所以绝对检查这两种类型,并寻找使用这些添加剂来调整代码的方法。
归档和序列化
到目前为止,在Swift中,为了序列化和归档您的自定义类型,您必须跳过一些环。对于class类型,您需要对NSObject进行子类化并实现NSCoding协议。
像struct和enum这样的值类型需要许多hacks,例如创建一个可以扩展NSObject和NSCoding的子对象。
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协议的开销和限制。
序列化很有用处,可以直接json数据转化模型了,不依赖于第三方库YYMode,或者runtime来操作了。
单面范围
为了减少冗长度并提高可读性,标准库现在可以使用单面范围[SE-0172]来推断起始和终点索引。
派上用场的一种方法是创建一个从索引到集合的开始或结束索引的范围:
/ Collection Subscript
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let outsideAsteroidBelt = planets[4...] // Before: planets[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 {
private var data: [Key: Value]
init(data: [Key: Value]) {
self.data = data
}
subscript(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: 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类型( Array和Set )会导致一个数组的各自的值。
MutableCollection.swapAt( __ :_ _ )
MutableCollection现在具有mutate方法swapAt(::)
,就像它的声音一样; 交换给定索引值[SE-0173] :
// Very basic bubble sort with an in-place swap
func bubbleSort(_ array: [T]) -> [T] {
var sortedArray = array
for i in 0.. 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()
如你所见,delegate的类型是View与Myprotocol 同时存在的类型,我个人觉得可以把类和协议存在理解成一种自己新定义类型,把几个基本类型结合成一个新的对象。
限制@objc推论
要将Objective-C或Swift API公开,请使用@objc
编译器属性。 在许多情况下,Swift编译器为您推断出这一点。 质量推理的三个主要问题是:
- 潜在的二进制大小显着增加
- 知道何时@objc会被推断不明显
- 不经意间创建Objective-C选择器碰撞的机会增加。
Swift 4通过限制@objc
[SE-0160]的推论来解决这个问题。 这意味着在需要Objective-C的完整动态调度功能的情况下,您需要使用@objc
。
您需要进行这些更改的几个示例包括private方法, dynamic声明和NSObject子类的任何方法。
参考资料:
视频地址:http://www.swiftv.cn/course/j3kaqyes
如有写的不正确的地方,请指出。