What's new in Swift 4 - Swift 4 资料整理

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,需要手动添加。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容