Swift 4.0 出来了,这几天也是重点关注了下Swift 4.0的新特性,看了WWDC中关于Swift部分的视频,也研究了下github上一个Swift 4.0新特性的开源项目whats-new-in-swift-4,很全面,有解释有代码。自己也整理了一下,主要是为了加深理解。
整体来看,Swift 4.0的语法变动并不大,从Swift 3 升级到Swift 4并不麻烦,更多的还是性能方面的提升,比如对Strings的底层优化,对@objc的优化,以及Xcode9上的性能提升。
One-sided ranges 单侧边界
在某些情况下可以省略startIndex或endIndex
- Used in Collection subscripts 集合下标操作
let letters = ["a", "b", "c", "d"]
let sub1 = letters[2..<letters.endIndex] // ["c", "d"]
let sub2 = letters[2…] // ["c", "d”]
let sub3 = letters[..<1] // ["a"]
- Used in Pattern Matching 模式匹配
let value = 5
switch value {
case 1...:
print("greater than zero")
case 0:
print("zero")
case ..<0:
print("less than zero")
default:
fatalError("unreachable")
}
结果是打印greater than zero
Strings 字符串
- 性能提升
Swift 4 的字符串优化了底层实现,对于英语、法语、德语、西班牙语的处理速度提高了 3.5 倍,对于简体中文、日语的处理速度提高了 2.5 倍 - Multi-line string literals 多行字符串字面量
let multilineString = """
This is a multi-line string. //这里实际上会被加入一个\n
You don't have to escape "quotes" in here.
The position of the closing delimiter
controls whitespace stripping.
"""
- remove Characters 去掉了characters
let greeting = "Hello, World!"
greeting.count // Swift3: greeting.characters.count
- String is a Collection 集合
String本身没有实现Collection协议,只是增加了很多Collection协议的方法,使得它可以被当做Collection来使用
for char in greeting {
print(char)
}
greeting.filter { …. }
greeting.map { …. }
- Substring is the new type for string slices 新增类型Substring
对String进行substring等操作会得到一个类型为Substring的字符串。Substring跟String很类似,他们都实现了StringProtocol。
let comma = greeting.index(of: ",")!
let substring = greeting[..<comma]
type(of: substring) //Substring.Type
print(substring.uppercased()) // String API can be called on Substring
之所以重新定义了一个Substring,主要是为了提高性能。
在 Swift 中,String 的背后有个 Owner Object 来跟踪和管理这个 String,String 对象在内存中的存储由内存起始地址、字符数、指向 Owner Object 指针组成。Owner Object 指针指向 Owner Object 对象,Owner Object 对象持有 String Buffer。当对 String 做取子字符串操作时,子字符串的 Owner Object 指针会和原字符串指向同一个对象,因此子字符串的 Owner Object 会持有原 String 的 Buffer。当原字符串销毁时,由于原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 并不会释放,造成极大的内存浪费。
在 Swift 4 中,做取子串操作的结果是一个 Substring 类型,它无法直接赋值给需要 String 类型的地方。必须用 String(<substring>) 包一层,系统会通过复制创建出一个新的字符串对象,这样原字符串在销毁时,原字符串的 Buffer 就可以完全释放了。
- Unicode
改善了在计算Unicode字符长度时的正确性
在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 \u{E9} 来表示
var family = "👩" // "👩"
family += "\u{200D}👩” // "👩👩"
family += "\u{200D}👧” //"👩👩👧"
family += "\u{200D}👦” //"👩👩👧👦"
family.count //在swift3中,count=4,在swift4中,count=1
Private declarations visible in same-file extensions 修改了Private修饰符的权限范围
private修饰的属性或方法,可以在同文件中的extension中访问到。跟swift3中的fileprivate相似而又不同。相同点是都可以在同一个文件中访问到,不同点是private修饰的只能在当前类型的extension中访问到,而fileprivate修饰的,也可以在其他的类型定义和extension中访问到。
struct SortedArray<Element: Comparable> {
private var storage: [Element] = []
init(unsorted: [Element]) {
storage = unsorted.sorted()
}
}
extension SortedArray {
mutating func insert(_ element: Element) {
// storage is visible here
storage.append(element)
storage.sort()
}
}
let array = SortedArray(unsorted: [3,1,2])
// 这里无法访问到storage属性(跟fileprivate不同)
//array.storage // error: 'storage' is inaccessible due to 'private' protection level
Key Paths
类似Objective-C里的KVC,Swift 4.0里的Key Paths是强类型的
struct Person {
var name: String
}
struct Book {
var title: String
var authors: [Person]
var primaryAuthor: Person {
return authors.first!
}
}
let abelson = Person(name: "Harold Abelson")
let sussman = Person(name: "Gerald Jay Sussman")
var sicp = Book(title: "Structure and Interpretation of Computer Programs", authors: [abelson, sussman])
- 使用key paths获取对象值
sicp[keyPath: \Book.title]
//Structure and Interpretation of Computer Programs
- key paths 可以用于计算属性
sicp[keyPath: \Book.primaryAuthor.name]
//Harold Abelson
- 使用key paths可以修改值
sicp[keyPath: \Book.title] = "Swift 4.0”
//sicp.title 现在是Swift 4.0
- key paths是对象,可以被存储和操纵
let authorKeyPath = \Book.primaryAuthor
print(type(of: authorKeyPath)) //KeyPath<Book, Person>
let nameKeyPath = authorKeyPath.appending(path: \.name) // 这个可以省略Book,因为编译器可以推断出类型
sicp[keyPath: nameKeyPath] //Harold Abelson
KeyPath实际上是一个class,它的定义如下:
/// A key path from a specific root type to a specific resulting value type.
public class KeyPath<Root, Value> : PartialKeyPath<Root> {
}
- key paths暂时还不支持下标操纵
//sicp[keyPath: \Book.authors[0].name] //编译失败
Archival and serialization 归档和序列化
可以通过指定Swift 类型(class
, enum
, struct
)实现Codable
协议来标识该类型支持归档和序列化。
大部分情况下,如果某个类型的所有成员类型都实现了Codeable
协议的话,只需要这一步就可以完成对该类型的数据的归档和序列化功能的支持,因为编译器会自动生成相应的encode\decode方法,当然你也可以重写这些方法。
Codable其实是一个组合协议:
public typealias Codable = Decodable & Encodable
struct Card: Codable {
enum Suit: String, Codable {
case clubs, spades, hearts, diamonds
}
enum Rank: Int, Codable {
case ace = 1, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king
}
var suit: Suit
var rank: Rank
}
let hand = [Card(suit: .clubs, rank: .ace), Card(suit: .hearts, rank: .queen)]
- encode
//json
var encoder = JSONEncoder()
let jsonData = try encoder.encode(hand)
String(data: jsonData, encoding: .utf8)
结果是:
[{\"rank\":1,\"suit\":\"clubs\"},{\"rank\":12,\"suit\":\"hearts\”}]
//plist
var encoder2 = PropertyListEncoder()
encoder2.outputFormat = .xml
let propertyData = try encoder2.encode(hand)
String(data: propertyData, encoding: .utf8)
结果是:
//<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<array>\n\t<dict>\n\t\t<key>rank</key>\n\t\t<integer>1</integer>\n\t\t<key>suit</key>\n\t\t<string>clubs</string>\n\t</dict>\n\t<dict>\n\t\t<key>rank</key>\n\t\t<integer>12</integer>\n\t\t<key>suit</key>\n\t\t<string>hearts</string>\n\t</dict>\n</array>\n</plist>
- decode
//json
var decoder = JSONDecoder()
let hand2 = try decoder.decode([Card].self, from: jsonData) //[{clubs, ace}, {hearts, queen}]
hand2[0].suit //clubs
//plist
var decoder2 = PropertyListDecoder()
let hand3 = try decoder2.decode([Card].self, from: propertyData)
hand3[0].suit //clubs
Associated type constraints 关联类型约束
- protocol中也可以使用
where
语句对关联类型进行约束了
protocol SomeProtocol where Self: UICollectionViewCell {
}
SomeProtocol
要求它的实现者必须继承UICollectionViewCell
,不是随便一个类型都能实现SomeProtocol
- Sequence协议有自己的
Element associatedtype
了,不需要写Iterator.Element
了
extension Sequence where Element: Numeric {
var sum: Element {
var result: Element = 0
for element in self {
result += element
}
return result
}
}
[1,2,3,4].sum
Dictionary and Set enhancements 加强
- 使用key-value sequence初始化Dictionary
let names = ["Cagney", "Lacey", "Bensen”]
let dict = Dictionary(uniqueKeysWithValues: zip(1..., names))
//[2: "Lacey", 3: "Bensen", 1: "Cagney”]
如果key存在重复的情况,使用:
let users = [(1, "Cagney"), (2, "Lacey"), (1, "Bensen”)]
//zip函数的作用就是把两个Sequence合并成一个key-value元组的Sequence
let dict = Dictionary(users, uniquingKeysWith: {
(first, second) in
print(first, second)
return first
})
//[2: "Lacey", 1: "Cagney”]
- merge 合并
let defaults = ["foo": false, "bar": false, "baz": false]
var options = ["foo": true, "bar": false]
// 按照merge函数的注释应该是如下写法,但是这种写法会报错error: generic parameter 'S' could not be inferred
//let mergedOptions = options.merge(defaults) { (old, _) in old }
// 需要替换成
options.merge(defaults.map { $0 }) { (old, _) in old }options
//["bar": false, "baz": false, "foo": true]
- 带默认值的下标操作
使用下标取某个key值时,可以传一个default参数作为默认值,当这个key不存在时,会返回这个默认值
let source = "how now brown cow"
var frequencies: [Character: Int] = [:]
for c in source {
frequencies[c, default: 0] += 1
}
print(frequencies)
//["b": 1, "w": 4, "r": 1, "c": 1, "n": 2, "o": 4, " ": 3, "h": 1]
- map和filter函数返回值类型是Dictionary,而不是Array
let filtered = dict.filter {
$0.key % 2 == 0
}
filtered //[2: "Lacey"]
let mapped = dict.mapValues { value in
value.uppercased()
}
mapped //[2: "LACEY", 1: "CAGNEY”]
- 对Sequence进行分组
let contacts = ["Julia", "Susan", "John", "Alice", "Alex"]
let grouped = Dictionary(grouping: contacts, by: { $0.first! })
print(grouped)
//["J": ["Julia", "John"], "S": ["Susan"], "A": ["Alice", "Alex"]]
MutableCollection.swapAt method
新增的一个方法,用于交换MutableCollection中的两个位置的元素
var numbers = [1,2,3,4,5]
numbers.swapAt(0,1) //[2, 1, 3, 4, 5]
Generic subscripts 泛型下标
下标操作现在支持传递泛型参数和返回值类型了
struct JSON {
fileprivate var storage: [String:Any]
init(dictionary: [String:Any]) {
self.storage = dictionary
}
subscript<T>(key: String) -> T? {
return storage[key] as? T
}
}
let json = JSON(dictionary: [
"name": "Berlin",
"country": "de",
"population": 3_500_500
])
// No need to use as? Int
let population: Int? = json["population"]
NSNumber bridging 桥接
主要修复了几个NSNumber bridging的问题
let n = NSNumber(value: UInt32(543))
let v = n as? Int8 // nil in Swift 4. This would be 31 in Swift 3 (try it!).
Composing classes and protocols
协议与协议可以组合,协议与class也可以组合
protocol Shakeable {
func shake()
}
func shakeControls(_ controls:[Shakeable & UIControl]) {
for control in controls {
if control.isEnabled {
control.shake()
}
}
}
从 Swift 3 升级到 Swift 4
由于公司项目中就有Swift开发的App,因此看完Swift 4.0的新特性后,必须得来尝试一下,看看到底有多少个坑。下面主要是我目前遇到的一些问题:
- 作为
#selector
函数参数的方法必须添加@objc
标识,可根据Xcode提示自动修复 -
dynamic
修饰的属性必须添加@objc
标识,可根据Xcode提示自动修复` - 需要暴露给OC调用的属性或方法必须添加
@objc
标识
这个坑有时候隐藏的比较深,有时候只有在运行时才会出现,而一旦出现,就会Crash,如果项目中有混编的,一定要小心。 - 一些字符串常量修改,可根据Xcode提示自动修复
NSFontAttributeName -> NSAttributedStringKey.font
NSForegroundColorAttributeName -> NSAttributedStringKey.foregroundColor
-
extension
中的函数不能被override
了,两种解决办法:
1.把func移到class的申明里
2.在extension的func前面加上@objc dynamic
标识
其中,对于@objc的使用要比之前多了不少,这个改动主要是为了给编译生成的二进制文件进行瘦身的。
在之前的Swift 版本中,所有继承自NSObject的类的属性和方法都会被默认加上@objc标识,以便将其暴露给Objective-C,编译器会为这些方法和属性生成可供OC调用的代码,但是实际情况可能只有很少量的几个方法需要暴露给OC,这就造成很大的空间浪费。因此在Swift 4中,编译器大部分情况下不会自动添加@objc标识,如果要暴露给OC,需要手动添加。
网友评论