相比较Swift 3时的更新,此次Swift 4的变化要小得多,甚至还以新颖和改进的形式重塑了一些旧的功能。更让我们高兴的是,我们不需要把项目立即就更新到Swift 4.0!
集成到Xcode 9中的Swift 4编译器有一个“Swift 3.2”模式。这不是一个独立的编译器,而是一种模式,这种模式允许你继续构建你的Swift 3代码,直到你想更新到Swift 4为止。当然了,你可能会担心CocoaPod是否需要马上update,在这里我可以放心地告诉你,不需要。因为你可以在你的项目中选择你的Swift语言版本,这样你就可以在同一个项目中混合Swift 3和Swift 4的target。话不多说,现在看看到底有哪些更新吧!
One-sided ranges 单边区间
Swift 4引入了RangeExpression
协议,这是一种简化如何描述区间的新协议。它被许多其他的协议所采用,以支持创建新的前缀和后缀操作符(prefix and postfix )。因此,您可以忽略一个区间的上界或下界,从而创建一个One-sided range(单边区间)。
当您想要在集合中获取自索引之后的一个值,或到某个索引结束的值时,这个特性将非常有用。
let esports = ["Hearthstone", "CS:GO", "League of Legends",
"Super Smash Bros", "Overwatch", "Gigantic"]
esports[3...]
// returns ["Super Smash Bros", "Overwatch", "Gigantic"]
// In Swift 3, you had to write
esports[3..<esports.endIndex]
esports[...2]
// returns ["Hearthstone", "CS:GO", "League of Legends"]
esports[..<2]
// returns ["Hearthstone", "CS:GO"]
infinite sequence 无限序列
一个单边区间可以用来创建一个无限序列,一个只指定开始值的序列。例如,您可以构建一个字符与ASCII码表十进制数字相匹配的元组数组。
let uppercase = ["A", "B", "C", "D"]
let asciiCodes = zip(65..., uppercase)
print(Array(asciiCodes))
// prints [(65, "A"), (66, "B"), (67, "C"), (68, "D")]
Pattern matching 模式匹配
单边区间可以极大地简化switch语句的模式。
func gameRank(_ index: Int) -> String {
switch index {
case ...1:
return "Oldie but goodie"
case 3...:
return "Meh"
default:
return "Awesome-sauce!"
}
}
gameRank(2)
// prints "Awesome-sauce!"
func sentiment(_ rating: Double) -> String {
switch rating {
case ..<0.33:
return "😭"
case ..<0.66:
return "😁"
default:
return "😠"
}
}
sentiment(0.5)
// return "😁"
Strings 字符串
字符串在Swift 4中获得了很多的恩宠,苹果在努力使它们变得更强大,更容易使用。最重要的变化是字符串现在是集合,就像它们在Swift 1中一样。这意味着,一个集合可以做的事情,一个字符串也可以做到!
let text = "Hakuna Matata"
let unicodeText = "✋🐘👌🌹🐩🤝"
text.count
text.isEmpty
"".isEmpty
// 13
// false
// true
// `reversed()` 返回 一个 `ReversedCollection<String>`
// 所以需要显式转换
String(text.reversed()) // "atataM anukaH"
在字符串中遍历每个字符是很容易的,当然它也适用于Unicode字符。
for c in unicodeText {
print(c) // 打印出每个Unicode字符
}
字符串下标的类型不是像Int这样的类型,而是一个String.Index
或 Range<String.Index>
这样的类型.。
var index = text.index(text.startIndex, offsetBy: 7)
text[index]
// "M"
//你可以使用prefix(upTo:) 和 suffix(from:)方法进行字符串截选
let upString = text.prefix(upTo: index)
let fromString = text.suffix(from: index)
//当然你也可以使用单边区间
let lastWord = text[index...]
// lastWord is "Matata"
index = text.index(text.endIndex, offsetBy: -7)
let firstWord = text[..<index]
// firstWord is "Hakuna"
Introducing Substring 字符子串
对字符串进行下标操作时会返回部分视图,它源于原始字符串的buffer,这些操作增加了buffer的引用计数。取决于应用程序及其数据的不同,这有可能在内存中保留大量未使用的字符串。虽然从技术上讲,这并不是一种内存泄漏,但它却不怎么让人喜欢。
Swift标准库通过引入一种新的类型:Substring
来解决上面描述的问题,它是由字符串下标操作返回的类型。因为大多数API仍将继续使用String
类型参数,所以您必须显式地利用Substring
类型来创建一个新的String
类型字符串并使用这个新的字符串进行其他操作。而这些操作允许之前原始而又长的字符串自然地离开作用域,而被释放。
其实没有你看起来的那么难理解,因为标准库同时也引入了一个新的协议:StringProtocol
。大多数String
API都被转移到StringProtocol
中,String
和Substring
都遵从这个新协议。
type(of: lastWord)
// Substring.Type
lastWord.uppercased()
// "MATATA"
let lastWordAsString = String(lastWord)
type(of: lastWordAsString)
// String.Type
Unicode magic
Swift 3时,如果您想要访问Character
中单个的Unicode数值,您首先必须将Character
转换为String
类型。Swift 4.0中,Character
类型引入了一个unicodeScalars
属性。
let c : Character = "💕"
Array(c.unicodeScalars)
//[128149]
Swift 3中以下表达式将给出错误的答案。而Swift 4可以正确地处理它们,根据所看到的内容给出其长度,而不需要去encode它。
"✋🐘👌".count
//3
"😢".count
//1
"🐵🐶".count
//2
Converting between Range<String.Index> and NSRange (Range<String.Index> 和 NSRange 类型之间的转换)
Swift中的字符串区间是由Range<String.Index> 所描述。许多Foundation API(如:NSRegularExpression,NSAttributedString,NSLinguisticTagger) 需要NSRange去描述区间。
现在Foundation框架在NSRange和Range<String.Index> 中都引进了新的构造方法。这使得两者之间的转换更加容易。
let population = "1️⃣🐂2️⃣🐔3️⃣🐶"
population.count
// 6
var nsRange = NSRange(population.startIndex...,
in: population)
// population.startIndex... is a Range<String.Index>
// (0, 29)
population.utf16.count
// 29
let display = NSMutableAttributedString(string: population,attributes: [.font: UIFont.systemFont(ofSize: 20)])
如预期所想,NSRange的长度与UTF-16视图的长度相匹配。显示是这样的:
let oneIndex = population.index(of: "1️⃣")!
let twoIndex = population.index(of: "2️⃣")!
let threeIndex = population.index(of: "3️⃣")!
var range = oneIndex..<twoIndex
nsRange = NSRange(range, in: population)
display.addAttribute(.font, value: UIFont.systemFont(ofSize: 40), range: nsRange)
上面代码中创建出每个数字emoji的索引,其中为第一部分的字符串创建出一个Range<String.Index>
类型的区间,并将其转换为NSRange
类型。然后,改变其字体的大小,显示如下:
range = twoIndex..<threeIndex
nsRange = NSRange(range, in: population)
display.addAttribute(.font, value: UIFont.systemFont(ofSize: 30), range: nsRange)
接下来,字符串的中间部分转换为Range<String.Index>
区间,转换它到NSRange
并改变字体大小。显示如下:
在另一种方法中,用NSRange
来创建一个Range<String.Index>
区间是很容易的。注意,这个构造方法没有成功完成初始化,因此结果是一个可选类型。
let textInput = "You have traveled 483.2 miles."
let pattern = "[0-9]+(\\.([0-9])?)?"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
nsRange = NSRange(textInput.startIndex..., in: textInput)
let mileage = regex.rangeOfFirstMatch(in: textInput, range: nsRange)
range = Range(mileage, in: textInput)!
textInput[range]
//"483.2"
Multi-line string literals 多行字符串字面量
现在您可以创建多行字符串字面量,使其更容易生成精确的格式化输出或将输入以一种“漂亮的”(如:JSON或HTML格式)方式粘贴到你的代码中。字符串插值(interpolation)在这里仍然有效,您甚至可以对换行进行转义,这样它们就不会包含在输出的文字中了!
字面量语法由""" """
分隔开,每行的缩进取决于每一行开始你删除的空格是多少。您可以在字面量语法中使用引号,而不用对它们进行转义。
let firstVerse = """
Half a league, half a league,
Half a league onward,
All in the valley of Death
Rode the six hundred.
"Forward, the Light Brigade!
"Charge for the guns!" he said:
Into the valley of Death
Rode the six hundred.
"""
let details = """
Note that the indentation of the
closing delimiter determines
the amount of whitespace removed.
You can insert \(firstWord) and \
\(lastWord) and escape newlines too!
"""
Dictionary enhancements 字典的强化
作为一个Swift程序员,字典对你的日常生活有多么重要想必大家都清楚。Swift 4带来了一些改进,使它变得更强大、更有用、更具可操作性。
Sequence-based initializer 基于序列的构造函数
现在可以从键值对的序列中创建字典。例如,您可以创建一个有编号的项目列表。
let groceries = Dictionary(uniqueKeysWithValues: zip(1..., ["Prosciutto", "Heavy Cream", "Butter", "Parmesan",
"Small shells"])
)
// [5: "Small shells", 2: "Heavy Cream", 3: "Butter",
// 1: "Prosciutto", 4: "Parmesan"]
或者,你已经有了一个Tuple类型的数据, 可以直接使用它去创建字典:
let housePointTotals = [("Slytherin", 472),
("Ravenclaw", 426),
("Hufflepuff", 352),
("Gryffindor", 312)]
let banquetBegins = Dictionary(uniqueKeysWithValues: housePointTotals)
// ["Ravenclaw": 426, "Hufflepuff": 352, "Gryffindor": 312,
// "Slytherin": 472]
Merging
字典中现在包含一个允许您merge两个字典的构造方法。如果您想将一个字典merge到另一个字典中,那么Swift也提供了一个merge方法。两者都需要您对闭包进行操作,以解决由重复键(Key)引起的在merge过程中值(Value)的冲突。
/// - Parameters:
///- keysAndValues: 用于新字典的键值对序列
///- combine: 一个在出现重复键(key)的时候才会被调用的闭包(用于处理和并key之后value的取值问题是应该取那个,如何取的问题),
该闭包返回最终需要的值(可以在闭包内部决定是返回那个value值,冲突前的还是冲突后的)。
public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)
通常在使用上述方法的时候,闭包会以尾随闭包的形式出现
let duplicates = [("a", 1), ("b", 2), ("a", 3), ("b", 4)]
let oldest = Dictionary(duplicates) { (current, ignore) in
// print(current,ignore)
return current
}
// ["b": 2, "a": 1]
上面代码的意思:
duplicates
是遵从Sequence
协议,且内部元素是Tuple类型的序列,尾随闭包中的current
值是在内部迭代过程中首先发现的冲突Value,ignore
是后发现的冲突Value,并返回先发现的Value(即curren
的值是1,2)
您可以利用闭包来改变您的输入。下面是一个更强大的示例,它使用了一系列键值对,并将它们转换为数组的字典:
let sortingHat = [
("Gryffindor", "Harry Potter"), ("Slytherin", "Draco Malfoy"),
("Gryffindor", "Ron Weasley"),
("Slytherin", "Pansy Parkinson"),
("Gryffindor", "Hermione Granger"),
("Hufflepuff", "Hannah Abbott"),
("Ravenclaw", "Terry Boot"), ("Hufflepuff", "Susan Bones"),
("Ravenclaw", "Lisa Turpin"),
("Gryffindor", "Neville Longbottom")
]
let houses = Dictionary( sortingHat.map { ($0.0, [$0.1]) },
uniquingKeysWith: { (current, new) in
print(current, new)
return current + new
})
// ["Ravenclaw": ["Terry Boot", "Lisa Turpin"],
// "Hufflepuff": ["Hannah Abbott", "Susan Bones"],
// "Slytherin": ["Draco Malfoy", "Pansy Parkinson"],
// "Gryffindor": ["Harry Potter", "Ron Weasley",
// "Hermione Granger", "Neville Longbottom"]]
正如您所看到的,您可以做的不仅仅是创建一个字典。如果您想知道每一个字母在字符串中的出现次数,对于这种问题的解决可以说是它的拿手好戏。
let spell = "I solemnly swear I am up to no good"
var frequencies: [Character: Int] = [:]
let baseCounts = zip(
spell.filter { $0 != " " }.map { $0 },
repeatElement(1, count: Int.max))
frequencies = Dictionary(baseCounts, uniquingKeysWith: +)
// ["w": 1, "p": 1, "n": 2, "o": 5, "I": 2, "u": 1, "t": 1,
// "d": 1, "a": 2, "r": 1, "m": 2, "s": 2, "e": 2, "l": 2,
// "g": 1, "y": 1]
如果您有一组默认值,并希望将它们与用户设置组合在一起的话,对于这种问题的解决将是 merge 方法的完美用途。
将别的给定字典合并到此字典中,使用闭包来确定任何重复键(key)的值。
/// - Parameters:
/// - other: A dictionary to merge.
/// - combine: A closure that takes the current and new values for any
/// duplicate keys. The closure returns the desired value for the final
/// dictionary.为任何重复键接受当前和新值的闭包。闭包返回最终字典所需的值。
public mutating func merge(_ other: [Dictionary.Key : Dictionary.Value], uniquingKeysWith combine: (Dictionary.Value, Dictionary.Value) throws -> Dictionary.Value) rethrows
merge方法中的尾随闭包用法和上面构造方法的尾随闭包用法一致。
let defaultStyling: [String: UIColor] = [
"body": .black, "title": .blue, "byline": .green
]
var userStyling: [String: UIColor] = [
"body": .purple, "title": .blue
]
userStyling.merge(defaultStyling) { (user, _) -> UIColor in
user }
// ["body": .purple, "title": .blue, "byline": .green]
Default value for subscript 下标的默认值
对字典的进行取值时,经常会出现可选项的情况,这就要求您采取尽可能简单的代码,可以使用可选绑定、强制解包或optional chaining
对其解包。在过去,解决这个问题的一种常见方法是使用空合运算符(? ?)来提供默认值,并使结果是非可选的(non-optional)。
Swift 4增加了在下标中指定默认值的能力:
let swift3 = banquetBegins["House elves"] ?? 0
let swift4 = banquetBegins["House elves", default: 0]
// both are 0; both are Int and not Int?
let housePoints = banquetBegins["Hufflepuff", default: 0]
// value is 352 with or without the default.
// Without, type is Int?; with, Int.
这种特性同时提供了另一种方法来实现您先前看到的频率计数问题。
frequencies.removeAll()
print(frequencies)
let dict = spell.filter { $0 != " " }
spell.filter { $0 != " " }.map {
frequencies[$0, default: 0] += 1
}
Filtering and mapping
在Swift 4中,对字典执行filter
操作, 其会在结果中保存字典的结构和类型。
let oddGroceries = groceries.filter { $0.key % 2 == 1 }
// [5: "Small shells", 3: "Butter", 1: "Prosciutto"]
filter
方法同时也可以用于Set类型:
let set: Set = ["a", "b", "c", "d", "e"]
let filteredSet = set.filter { $0.hashValue % 2 == 0 }
// ["b", "d"]
map
函数必定返回一个数组结果。通常,在使用字典的时候,这样的返回值会让你很难过! Swift 4添加了mapValues
的方法,以允许您保留字典的结构和类型。
let mirroredGroceries = oddGroceries.mapValues {
String($0.reversed())
}
// [5: "sllehs llamS", 3: "rettuB", 1: "ottuicsorP"]
Grouping
Dictionary最强大的新特性之一是,它可以基于任意谓词对数据进行分区,创建组或存储类似数据的buckets。最简单的例子是对名单的第一个字母进行分组展示。
let names = ["Harry", "ron", "Hermione", "Hannah",
"neville", "pansy", "Padma"].map { $0.capitalized }
let nameList = Dictionary(grouping: names) { $0.prefix(1) }
// ["H": ["Harry", "Hermione", "Hannah"], "R": ["Ron"],
// "N": ["Neville"], "P": ["Pansy", "Padma"]]
你可以指定一个任意的谓词,意味着您能更有创造力。
enum Multiples {
case threes, notThrees
}
let numbers = 1...18
let predicate: (Int) -> Multiples = { $0 % 3 == 0 ? .threes : .notThrees }
let multiplesOfThree = Dictionary(grouping: numbers,by: predicate)
// [.threes: [3, 6, 9, 12, 15, 18],
// [.notThrees: [1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17]]
type(of: multiplesOfThree)
// [Multiples: [Int]]
在一个更实际的例子中,您可能希望基于一个结构体中的值来分组。将grouping与Swift 4的新keyPath特性组合起来,将使这成为一种轻而易举的事情。
// 1
struct Student {
let firstName: String
let lastName: String
}
// 2
let classRoll = sortingHat.map { $0.1.split(separator: " ") }
.map { Student(firstName: String($0[0]),
// 3
lastName: String($0[1])) }
let lastNameKeypath = \Student.lastName
// 4
let contactList = Dictionary(grouping: classRoll) {
$0[keyPath: lastNameKeypath].prefix(1)
}
上面的代码做了以下几个方面的事情:
- 定义一个学生的结构体。
- 使用之前的数组来创建一个Student数组的值。
- 使用新的keyPath语法引用Student中lastName字段。
- 用last name的第一个字母来对Student进行分组。
Generic subscripts and associated typeconstraints 泛型下标和关联类型约束
在Swift 3中使用混合数据类型的字典是相当痛苦的,因为在使用它之前需要对每个值进行类型转换。Swift 4现在允许下标返回一个泛型类型。
struct Grade {
private var data: [String: Any]
init(data: [String: Any]) {
self.data = data
}
subscript<T>(key: String) -> T? {
return data[key] as? T
}
}
let gradebook = Grade(data: ["name": "Neil Armstrong",
"exam": "LEM Landing",
"grade": 97])
let who: String? = gradebook["name"]
let grade: Int? = gradebook["grade"]
// No need to coerce the type with "as?"
//不需要使用as?对类型进行强制转换
这绝对不是实现结构体的最佳方式。但是,如果您使用的是新的可Decodable
工具,这个概念可能会简化您的自定义init(from:)
。
下标本身也可以是泛型。例如,您可以实现一种使用Sequence
协议从集合类型中获取一个数组的方法。
extension Grade {
subscript<Keys: Sequence>(keys: Keys) -> [Any]
where Keys.Element == String {
var values: [Any] = []
for key in keys {
if let value = data[key] {
values.append(value)
}
}
return values
}
}
gradebook[["name", "grade"]]
gradebook[Set(["name", "grade"])]
// 上面都返回 ["Neil Armstrong", 97]
您还可以使用该特性向标准库类型中添加新功能以供自己使用。
extension Collection {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Index {
var result: [Element] = []
for index in indices {
result.append(self[index])
}
return result
}
}
let words = "It was the best of times it was the worst of times"
.split(separator: " ")
words[[3, 9, 11]]
// ["best", "worst", "times"]
前面两个例子都利用了另一个新特性:关联类型约束。关联类型现在可以用where
子句约束。这极大地简化了使用泛型的工作。
例如,在扩展Grade
的时候,Swift 3要求您指定where Keys.Iterator.Element == String
。
允许对关联类型进行约束可以更容易地扩展标准库。
extension Sequence where Element: Numeric {
var product: Element {
return self.reduce(1, *)
}
}
[2,4,6,8].product
// 384
[1.5, 3.0, -2.0].product
// -9 (a Double)
Limiting @objc inference 限制@objc引用
为了使Objective-C代码编写的功能能够在Swift语言中使用,Swift编译器必须生成一种称为“thunk”的方法,以便在Objective-C和Swift调用约定之间进行转换。这些thunks只适用于来自Objective-C语言的调用,而不是Swift。为了减轻现有的Objective-C代码之间的相互协作的负担,Swift 3(以及更早的版本)在许多情况下需要这些thunks代码。
不幸的是,经验表明,许多这样的thunks是不需要的,因为这会导致应用程序二进制文件的体积增大。在很多情况下,应用程序大小的6%-8%是由这种“胶水”代码组成的。为了消除这种浪费,Swift 4做出了改善。
例如,从NSObject
派生的类不再被Objective-C自动访问。因为您必须显式地使用@objc
关键字来让Swift中的方法对Objective-C可见。
在计算机编程中,一个thunk代表一个创建的子程序,通常它是自动创建的,以帮助调用另一个子程序。Thunks主要用于表示子程序需要执行的额外计算,或者调用不支持一般调用机制的程序。Thunks有许多其他的应用程序来生成编译代码和模块编程。
class MyClass: NSObject {
func print() { Swift.print("hello") } // not visible to Obj-C
@objc func show() { print() } // visible to Obj-C
}
苹果建议你将所有需要对Objective-C可见的方法进行分组并放置在一个Extension
内。如果您将@objc
关键字添加到Extension
本身,那么它所包含的一切都将是可访问的。如果您需要在这个Extension
内排除一个方法,不让其被Objective-C可见,使用@nonobjc
关键字对其单独标注。
@objc extension MyClass {
func f(_ foo: String?) {}
@nonobjc func g(_ goo: Int?) {}
}
上面如果没有关键字@nonobjc
,编译器会对方法g(_:)
报出一个错误:
error: method cannot be in an @objc extension of a class (without @nonobjc) because the type of the parameter cannot be represented in Objective-C
如果你需要一个类连同它的所有extensions
,及其子类和子类的extensions
可以在Objective-C中被访问到,你需要使用关键字@objcMembers
对这个类进行标识.
@objcMembers
class MySecondClass: NSObject {
func f() {}
// can't be called from ObjC but no error
func g() -> (Int, Int) {
return (1, 1)
}
}
使用关键字@nonobjc
对你不想要被Objective-C访问的方法进行标识
@nonobjc extension MySecondClass {
func h() -> Int? { return nil }
func j(_ value: Double?) {}
}
Swift 4 inference
Swift 4中对@objc
的的自动推导,苹果对其规则进行了明确的定义。
如果声明是对使用关键字@objc
方法声明的重写,它可以推导出这个重写的方法和@objc
关键字标识的方法一样。
class Super {
@objc func foo() {}
}
class Sub: Super {
// inferred @objc
override func foo() {}
}
如果一个方法是对被@objc
标识的协议进行实现,它可以推导出这个实现的方法和@objc
关键字标识的方法一样。
@objc protocol MyDelegate {
func bar() }
class MyThirdClass: MyDelegate {
// inferred @objc
func bar() {}
}
在这种情况下,这种推导是必需的,因为对MyDelegate.bar()
的调用,不管是来自Objective-C还是Swift,都是通过Objective-C的消息发送机制,所以对协议遵守也需要其能被Objective-C所访问到。
基于同样的原因,Swift 4也会对任何声明有一个或多个以下属性进行推导:
- @IBAction
- @IBOutlet
- @IBInspectable
- @GKInspectable
- @NSManaged
最后,虽然dynamic
关键字是使用objective-C消息机制来实现的,但它不再引起@objc
推导。您必须显式地添加注释。
class Piggy {
@objc dynamic var dynamicallyDispatchedString: String = ""
@objc dynamic func dynamicallyDispatchedFunction() {
}
}
Swift 3中dynamic关键字,它用于修饰变量或函数,它的意思与OC不同。它告诉编译器使用动态分发而不是静态分发。OC区别于其他语言的一个特点在于它的动态性,任何方法调用实际上都是消息分发,而Swift则尽可能做到静态分发。
因此,Swift 3中标记为dynamic的变量/方法会隐式的加上@objc关键字,它会使用OC的runtime机制。
Other changes 其他的变化
还有许多其他更小的变化值得注意。
Private access in extensions
Swift 3引入了fileprivate
,破坏了大量的代码,但这些代码已被正确地划分为extensions
。Swift 4改善了这种状况。现在,声明为private
的方法和变量可以在同一个源文件中进行扩展。fileprivate
仍然有其“单一文件作用域”的含义,但它应该尽可能不被使用。
struct Person {
private let firstName: String
private let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
var name: String {
return "\(firstName) \(lastName)"
}
}
extension Person {
func greeting(with message: String) -> String {
return "\(message), \(firstName)!"
}
}
let dumbledore = Person(firstName: "Albus",
lastName: "Dumbledore")
dumbledore.greeting(with: "Good mornafterevening")
swapAt(::)
在为未来做准备时,Swift 4引入了一个新概念,即对内存的独占访问,这样只有一个参与者可以一次写入一个对象所占用的内存。这将中断方法swap(_:_:)
的使用。现在,您可以使用swapAt(_:_:)
来代替它的作用。
var numbers = Array(1...5)
//swap(&numbers[1], &numbers[3]) // Illegal in Swift 4
numbers.swapAt(1, 3)
// [1, 4, 3, 2, 5]
NSNumber bridging
Swift 3没有检查存储在NSNumber中的整数值是否可以用Swift标量类型(scalar type)来表示。例如:
let n = NSNumber(value: 603)
let v = n as? Int8
常量v将会被赋值为91。这显然是错误的和不安全的。在Swift 4中,常量 v将是nil。引用Swift发展建议,“as?”对于NSNumber,应该意味着“我可以将NSNumber中的数值安全地存储在这里吗吗?” 其同样适用于关键字is
:
if n is Int8 {
print("n must be pretty small")
} else {
print("nope, doesn't fit. Get a bigger bucket!")
}
Composing classes and protocols
在Swift 4之前,没有办法指定一个对象必须是一个特定的类型,并且符合一个协议。现在可以了!
protocol MySpecialDelegateProtocol {}
class MySpecialView: UIView {}
class MyController {
var delegate: (UIView & MySpecialDelegateProtocol)?
}
这中方式也在Swift标准库中以有趣的方式使用
public typealias Codable = Decodable & Encodable
Migrating to Swift 4
将项目迁移到Swift 4,是一个简单的过程。在Xcode 9中,选择Edit\Convert\To Current Swift Syntax... 。有一个例外, migrator将询问您想要什么类型的@objc
推导。推荐选择:Minimize Inference。
如果你按照建议,选了Minimize Inference, 会出现以下警告:
The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please
address deprecated @objc inference warnings, test your code with "Use of
deprecated Swift 3 @objc inference" logging enabled, and then disable
inference by changing the “Swift 3 @objc Inference” build setting to
“Default” for the “<your project>” target.
一旦您对您的源代码中所有必要的注释感到满意,就打开Target 的 build settings,并将Swift 3的@objc Inference更改为Default,就像在警告消息中指出的那样。
翻译自:Ray Wenderlich 新书 iOS 11 by Tutorials 中的 What's New in Swift 4 。