今天浏览了HackingWithSwift上关于Swift5.0和5.1更新内容的文章,这里记录一下感兴趣的部分。
New in Swift 5.0
Raw Strings
可以使用#-#生成不需要使用 \ 进行转译的字符串,如
let msg = #"this is a messge"#
可以在引号前添加任意多个 #,只要首尾的#数量相同,如:
let hashes = ####"this is a \"test""####
当需要在 raw string 中使用 interpolation时,需要:
let title = "John"
let msg = #"Hi, Mr \#(title)!"#
如果经常需要写正则表达式,那么这个功能无疑是一福音。
标准类型: Result
顾名思义, Result是一个用于表示返回结果的类型,主要运用在异步的API中。它是一个 enum类型,包含 success 和 failure。两者都是使用范型定义的,但是 failure 需要实现 Error 协议, 即
public enum Result<Success, Failure> where Failure : Error
它的使用也很简单,看个例子就能明白:
enum NetworkError: Error {
case badURL
}
func fetchUnreadCount1(from urlString: String, completionHandler: @escaping (Result<Int, NetworkError>) -> Void) {
guard let url = URL(string: urlString) else {
completionHandler(.failure(.badURL))
return
}
// complicated networking code here
print("Fetching \(url.absoluteString)...")
completionHandler(.success(5))
}
fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
switch result {
case .success(let count):
print("\(count) unread messages.")
case .failure(let error):
print(error.localizedDescription)
}
}
通过Result的定义,还可以发现:
- get() 方法要么返回success值,要么 throws,因此需要
try? result.get()
- 可以使用一个带throws闭包来初始化 Result
- failure可以使用库中的 Error
Result 还有一系列方法:map/flatMap/mapError/flatMapError
,其中 flatMap和map的区别是当传递给map的闭包返回一个result时, flatMap会直接返回那个result;而map会把该result包裹进一个result再返回。
String Interpolation
协议ExpressibleByStringInterpolation
在3.0 被废弃,在5.0又被引入,这个功能使得字符串的插值化更灵活更方便。为了更好地理解这个特性,笔者特地查看了 SE-0228 的说明。先来看下当前的字符串插值是如何进行:编译器会把字符串解析成一系列片段,每个片段要么包含纯的字符串(含转译字符串),要么是一个等待插入值的表达式,形如:
// Semantic expression for: "hello \(name)!"
String(stringInterpolation:
String(stringInterpolationSegment: "hello "),
String(stringInterpolationSegment: name),
String(stringInterpolationSegment: "!"))
这种设计在效率和灵活性上都有不足,比如:
- 内存管理低下:现有实现依赖String(stringInterpolationSegment:),这会导致复制内存段,极端情况下,会占有过多的内存。而对于每个segment,其位置和长度都是知道的,并不需要生成临时变量以占用额外内存
- 插值方式单一导致的灵活性不够:插值表达式没有额外的参数;segment可以是任何参数从而导致缺少参数约束;丢失segment的语义,可以从字符串解析生成各个segment但反过来却不行,这样就无法得出segment是由字符串还是表达式而来
SE-0228列举了几种适用情形:
//formatting
/// Use printf-style format strings:
"The price is $\(cost, format: "%.2f")"
//logging
log("Processing \(public: tagName) tag containing \(private: contents)")
//attributed strings
"\([.link: supportURL])Click here\([.link: nil]) to visit our support site"
//localization
// Builds a LocalizableString(key: "The document “%@” could not be saved.", arguments: [name])
let message: LocalizableString = "The document “\(name)” could not be saved."
alert.messageText = String(localized: message)
接下来,我们跟随hackingWithSwift看一下这个更新带来的便利。
首先就是灵活的格式化了,这也是最基本的用法。
extension String.StringInterpolation {
mutating func appendInterpolation(_ number: Int, style: NumberFormatter.Style) {
let formatter = NumberFormatter()
formatter.numberStyle = style
if let result = formatter.string(from: number as NSNumber) {
appendLiteral(result)
}
}
}
let number = Int.random(in: 0...100)
let lucky = "The lucky number this week is \(number, style: .spellOut)."
除此之外,对于需要构造正则表达式,html/xml字符串或者sqlite statement语句的情形,ExpressibleByStringInterpolation
可以帮助你便捷地实现。HTMLComponent
是一个很好的构建html字符串的例子,这里就不做搬运。值得一提的是,对于 ExpressibleByStringInterpolation
协议,如果需要实现自定义的插值,必须构建一个实现 StringInterpolationProtocol
的类型。如果没有这种类型的话,系统会自动使用内建的 DefaultStringInterpolation
,但其通常是无法满足自定义的插值情形的。对于语句
let text: HTMLComponent = "You should follow me on Twitter \(twitter: "twostraws"), or you can email me at \(email: "paul@hackingwithswift.com")."
其执行顺序:
init(literalCapacity: Int, interpolationCount: Int)
appendLiteral(_ literal: String)
appendInterpolation(twitter: String)
appendLiteral(_ literal: String)
appendInterpolation(email: String)
appendLiteral(_ literal: String)
dynamically callable types
这个更新是一种语法糖,先来看一下例子:
@dynamicCallable
struct RandomNumberGenerator1 {
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double {
let numberOfZeroes = Double(args.first?.value ?? 0)
let maximum = pow(10, numberOfZeroes)
return Double.random(in: 0...maximum)
}
}
let random1 = RandomNumberGenerator1()
let result1 = random1(number: 0)
let result2 = random1(1, 2)
首先要对model声明 dynamicCallable
,然后实现函数
dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>)->T
于是,对于 result1会调用dynamicallyCall
,输入参数是一组键值对。对于result2,输入参数也是一组键值对,只不过其中每个key是空字符串。
如果同时实现了函数: dynamicallyCall(withArguments args: [Int]) -> T
,result2 就会调用该函数。
因此,从技术上而言,要实现 dynamically call,需要实现以上两个函数。
这个更新,提供的便利性在于可以通过实例调用一个函数,从而实现了一个算子的功能。而从[SE-0216]的描述来看,这个更新想把Swift与C/ObjC之优越的交互拓展到类如Python的语言。
handle future enum cases
这是一个实用的功能。在实际中,随着业务或者功能的拓展,往往会对enum类型进行扩展,这就会对已有代码带来影响:如果之前枚举了每一个类型,那么编译器会报错;如果之前添加了 default,那么就缺失对新增类型的处理。
Swift5.0对这种情形的处理是在 default 分支前添加 @unknown
。当有新类型添加的时候,编译器不会报错,而是会给出提示,由你决定如何处理。
flatten nested optional
简单地讲,这个更新就是把嵌套的optional展开成普通的optional,形如:
optional(optional(type)) -> optional(type)
整数因子判断
添加了 ‘isMultiple’,增加语句的可读性。
compactMapValues
对于Dictionary,compactMap
的 结果形如:
(key: "Paul", value: Optional(38)
使用 compactMapValues
则可以:
paul:38
如果value是一个nil的话, compactMapValues
会直接将该key移除。所以该函数的操作流程:
- 变形
- 解包
- 移除值为nil的dictionary
在实际中,可以免去了对nil的判断,提高执行效率。
New in Swift 5.1
成员初始化
先看例子:
struct User {
var name: String
var loginCount: Int = 0
}
let piper = User(name: "Piper Chapman", loginCount: 0)
let suzanne = User(name: "Suzanne Warren")
在5.1中,suzanne 也是合法的。
这个更新,私以为不是很妥当。设想Swift中设计了 designated 和 convenience initialize,来确保每个成员变量能得到初始化。因此在设计初始化函数的时候,需要很好地设计接口。5.1中引入的这种增强,势必会破坏这种习惯。
implicit return
对于只含有一个表达式的函数,如果有返回值的话,可以省略 return
func double(_ number: Int) -> Int {
number * 2
}
Self
设想一组父子类:
class NetworkManager {
class var maximumActiveRequests: Int {
return 4
}
func printDebugData() {
print("Maximum network requests: \(NetworkManager.maximumActiveRequests).")
}
}
class ThrottledNetworkManager: NetworkManager {
override class var maximumActiveRequests: Int {
return 1
}
}
let manager = ThrottledNetworkManager()
manager.printDebugData()
对于子类实例 manager 其 maximumActiveRequests
为1,但打印的时候却为4。Self就是为了解决这个问题:在class中,Self
就是动态地代表当前的类,主要用于静态成员变量。Self
简化了 self.dynamicType
的写法,使得书写更通用和一致。
opaque type
在 SwiftUI 中,经常可以看到 some view
。使用 some
可以使得一个类型变成 opaque type,考虑如下代码:
protocol Fighter { }
struct XWing: Fighter { }
func launchFighter() -> Fighter {
return XWing()
}
func launchOpaqueFighter() -> some Fighter {
return XWing()
}
launchFighter
返回一个 Flight,但是如果想要判断这个 Flight具体是哪个时(是XWing或YWing),就无能无力了。想要实现这个功能,你能想到的解决方案是让 Flighter 遵循 Equatable
,但是马上你就会遇到问题:
Protocol ‘Fighter’ can only be used as a generic constraint because it has Self or associated type requirements
错误原因:对于 Equatable 编译器需要知道左右两边的类型是否相同。
于是 some 登场了,launchOpaqueFighter
返回的就是一个 opaque 类型的 Flighter,在这种情况下对于调用方得到的还是一个 Flighter 类型,但是编译器却知道该类型是 XWing。因此,和范型不一样的地方在于:
1.范型是由调用方决定最终的类型,opaque type
是函数内部自己决定最终的类型
2.范型返回的是一组容器,效率上存在问题
3.在编译阶段,opaque type
是确定的
好了,我们接着看下面的内容:
func makeInt() -> some Equatable {
Int.random(in: 1...10)
}
func makeString() -> some Equatable {
"Red"
}
makeInt() == makeString() //build error
虽然上述两个函数都返回 some Equatable
,但编译器知道一个返回 Int
另一个返回String
,两者是无法比较的。
另外,some
除了可以用在返回类型,还可以用在属性和下标上。
static and class subscripts
这是一种语法的改进,可以使用以下的方式访问类静态变量:
public enum NewSettings {
private static var values = [String: String]()
public static subscript(_ name: String) -> String? {
get {
return values[name]
}
set {
print("Adjusting \(name) to \(newValue ?? "nil")")
values[name] = newValue
}
}
}
NewSettings["Captain"] = "Gary"
NewSettings["Friend"] = "Mooncake"
print(NewSettings["Captain"] ?? "Unknown")
warnings for ambiguous none
Swift的optional类型是一个 enum,包含 none
和 some
。因此,自定义的enum如果使用了 none
关键字,就会产生歧义。5.1会改善对此类问题的警告。
enum optional
对于optional enum类型,在switch的时候, case中也要使用optional。在5.1中,可以不需要使用optional。
ordered collection diffing
在实际刷新数据的时候,经常会遇到数据源发生变化而需要刷新UI的情形,这时往往要求对移除和添加的数据按序实现并伴随动画。为了实现这个需求,需要知道哪些数据是要被移除,哪些是被添加的,5.1增加了 difference
:
let diff = scores2.difference(from: scores1)
for change in diff {
switch change {
case .remove(let offset, _, _):
scores1.remove(at: offset)
case .insert(let offset, let element, _):
scores1.insert(element, at: offset)
}
}
print(scores1)
//or
let result = scores1.applying(diff) ?? []
creating uninitialized array
顾名思义,就是创建array的时候,允许只分配内存但不初始化, 比如:
let randomNumbers = Array<Int>(unsafeUninitializedCapacity: 10) { buffer, initializedCount in
for x in 0..<5 {
buffer[x] = Int.random(in: 0...10)
}
initializedCount = 5//must set
}
这里,要求分配10个Int的Array,但在初始化的闭包中初始化了5个。
这个改进主要用于内置库中高效算法的实现,比如需要根据array计算新array,这时就不需要分配并初始化内存来存放新array。
Comments