iOS-SwiftiOS Swift && Objective-Cswift

What's new in Swift 4 - Swif

2017-06-15  本文已影响307人  是的蛮大人

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

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"]
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 字符串

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.
"""
let greeting = "Hello, World!"
greeting.count  // Swift3: greeting.characters.count
for char in greeting {
    print(char)
}
greeting.filter { …. }
greeting.map { …. }
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 就可以完全释放了。

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])
sicp[keyPath: \Book.title] 
//Structure and Interpretation of Computer Programs
 sicp[keyPath: \Book.primaryAuthor.name] 
//Harold Abelson
sicp[keyPath: \Book.title] = "Swift 4.0” 
//sicp.title 现在是Swift 4.0
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> {
}
//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)]
//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>
//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 SomeProtocol where Self: UICollectionViewCell {
}

SomeProtocol要求它的实现者必须继承UICollectionViewCell,不是随便一个类型都能实现SomeProtocol

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 加强

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”]
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]
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]
let filtered = dict.filter {
    $0.key % 2 == 0
}
filtered //[2: "Lacey"]
let mapped = dict.mapValues { value in
    value.uppercased()
}
mapped //[2: "LACEY", 1: "CAGNEY”]
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的新特性后,必须得来尝试一下,看看到底有多少个坑。下面主要是我目前遇到的一些问题:

NSFontAttributeName -> NSAttributedStringKey.font
NSForegroundColorAttributeName -> NSAttributedStringKey.foregroundColor

其中,对于@objc的使用要比之前多了不少,这个改动主要是为了给编译生成的二进制文件进行瘦身的。
在之前的Swift 版本中,所有继承自NSObject的类的属性和方法都会被默认加上@objc标识,以便将其暴露给Objective-C,编译器会为这些方法和属性生成可供OC调用的代码,但是实际情况可能只有很少量的几个方法需要暴露给OC,这就造成很大的空间浪费。因此在Swift 4中,编译器大部分情况下不会自动添加@objc标识,如果要暴露给OC,需要手动添加。

上一篇 下一篇

猜你喜欢

热点阅读