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的底層優(yōu)化,對@objc的優(yōu)化嗤疯,以及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 的字符串優(yōu)化了底層實現(xiàn),對于英語身弊、法語、德語列敲、西班牙語的處理速度提高了 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本身沒有實現(xiàn)Collection協(xié)議戴而,只是增加了很多Collection協(xié)議的方法凑术,使得它可以被當做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很類似所意,他們都實現(xiàn)了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 對象在內存中的存儲由內存起始地址、字符數(shù)备籽、指向 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>) 包一層,系統(tǒng)會通過復制創(chuàng)建出一個新的字符串對象八酒,這樣原字符串在銷毀時空民,原字符串的 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 現(xiàn)在是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
)實現(xiàn)Codable
協(xié)議來標識該類型支持歸檔和序列化祖搓。
大部分情況下,如果某個類型的所有成員類型都實現(xiàn)了Codeable
協(xié)議的話湖苞,只需要這一步就可以完成對該類型的數(shù)據的歸檔和序列化功能的支持拯欧,因為編譯器會自動生成相應的encode\decode方法,當然你也可以重寫這些方法袒啼。
Codable其實是一個組合協(xié)議:
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 關聯(lián)類型約束
- protocol中也可以使用
where
語句對關聯(lián)類型進行約束了
protocol SomeProtocol where Self: UICollectionViewCell {
}
SomeProtocol
要求它的實現(xiàn)者必須繼承UICollectionViewCell
哈扮,不是隨便一個類型都能實現(xiàn)SomeProtocol
- Sequence協(xié)議有自己的
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函數(shù)的作用就是把兩個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函數(shù)的注釋應該是如下寫法滑肉,但是這種寫法會報錯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參數(shù)作為默認值摘仅,當這個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函數(shù)返回值類型是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 泛型下標
下標操作現(xiàn)在支持傳遞泛型參數(shù)和返回值類型了
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
協(xié)議與協(xié)議可以組合六荒,協(xié)議與class也可以組合
protocol Shakeable {
func shake()
}
func shakeControls(_ controls:[Shakeable & UIControl]) {
for control in controls {
if control.isEnabled {
control.shake()
}
}
}
從 Swift 3 升級到 Swift 4
由于公司項目中就有Swift開發(fā)的App,因此看完Swift 4.0的新特性后矾端,必須得來嘗試一下掏击,看看到底有多少個坑。下面主要是我目前遇到的一些問題:
- 作為
#selector
函數(shù)參數(shù)的方法必須添加@objc
標識秩铆,可根據Xcode提示自動修復 -
dynamic
修飾的屬性必須添加@objc
標識砚亭,可根據Xcode提示自動修復` - 需要暴露給OC調用的屬性或方法必須添加
@objc
標識
這個坑有時候隱藏的比較深,有時候只有在運行時才會出現(xiàn)殴玛,而一旦出現(xiàn)捅膘,就會Crash,如果項目中有混編的滚粟,一定要小心寻仗。 - 一些字符串常量修改,可根據Xcode提示自動修復
NSFontAttributeName -> NSAttributedStringKey.font
NSForegroundColorAttributeName -> NSAttributedStringKey.foregroundColor
-
extension
中的函數(shù)不能被override
了凡壤,兩種解決辦法:
1.把func移到class的申明里
2.在extension的func前面加上@objc dynamic
標識
其中署尤,對于@objc的使用要比之前多了不少,這個改動主要是為了給編譯生成的二進制文件進行瘦身的亚侠。
在之前的Swift 版本中曹体,所有繼承自NSObject的類的屬性和方法都會被默認加上@objc標識,以便將其暴露給Objective-C盖奈,編譯器會為這些方法和屬性生成可供OC調用的代碼混坞,但是實際情況可能只有很少量的幾個方法需要暴露給OC狐援,這就造成很大的空間浪費钢坦。因此在Swift 4中究孕,編譯器大部分情況下不會自動添加@objc標識,如果要暴露給OC爹凹,需要手動添加厨诸。