这篇是对一文鉴定是Swift的王者,仍是青铜文章中问题的解答。这些问题仅仅是表层概念,属于知识点,在我看来即便都很清楚也并不能表明上了王者,若是非要用段位类比的话,黄金仍是合理的😄。html
Swift是一门上手容易,可是精通较难的语言。即便下面这些内容都不清楚也不妨碍你开发业务需求,可是了解以后它可以帮助咱们写出更加Swifty的代码。编程
1、 协议 Protocol
ExpressibleByDictionaryLiteral
ExpressibleByDictionaryLiteral
是字典的字面量协议,该协议的完整写法为:json
public protocol ExpressibleByDictionaryLiteral { /// The key type of a dictionary literal. associatedtype Key /// The value type of a dictionary literal. associatedtype Value /// Creates an instance initialized with the given key-value pairs. init(dictionaryLiteral elements: (Self.Key, Self.Value)...) }
首先字面量(Literal)的意思是:用于表达源代码中一个固定值的表示法(notation)。swift
举个例子,构造字典咱们能够经过如下两种方式进行:api
// 方法一: var countryCodes = Dictionary<String, Any>() countryCodes["BR"] = "Brazil" countryCodes["GH"] = "Ghana" // 方法二: let countryCodes = ["BR": "Brazil", "GH": "Ghana"]
第二种构造方式就是经过字面量方式进行构造的。数组
其实基础类型基本都是经过字面量进行构造的:安全
let num: Int = 10 let flag: Bool = true let str: String = "Brazil" let array: [String] = ["Brazil", "Ghana"]
而这些都有对应的字面量协议:网络
ExpressibleByNilLiteral // nil字面量协议 ExpressibleByIntegerLiteral // 整数字面量协议 ExpressibleByFloatLiteral // 浮点数字面量协议 ExpressibleByBooleanLiteral // 布尔值字面量协议 ExpressibleByStringLiteral // 字符串字面量协议 ExpressibleByArrayLiteral // 数组字面量协议
Sequence
Sequence翻译过来就是序列,该协议的目的是一系列相同类型的值的集合,而且提供对这些值的迭代能力,这里的迭代能够理解为遍历,也即for-in
的能力。能够看下该协议的定义:数据结构
protocol Sequence { associatedtype Iterator: IteratorProtocol func makeIterator() -> Iterator }
Sequence
又引入了另外一个协议IteratorProtocol
,该协议就是为了提供序列的迭代能力。闭包
public protocol IteratorProtocol { associatedtype Element public mutating func next() -> Self.Element? }
咱们一般用for-in
实现数组的迭代:
let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"] for animal in animals { print(animal) }
这里的for-in
会被编译器翻译成:
var animalIterator = animals.makeIterator() while let animal = animalIterator.next() { print(animal) }
Collection
Collection译为集合,其继承于Sequence。
public protocol Collection : Sequence { associatedtype Index : Comparable var startIndex: Index { get } var endIndex: Index { get } var isEmpty: Bool { get } var count: Int { get } subscript(position: Index) -> Element { get } subscript(bounds: Range<Index>) -> SubSequence { get } }
是一个元素能够反复遍历而且能够经过索引的下标访问的有限集合,注意Sequence
能够是无限的,Collection
必须是有限的。
Collection
在Sequence
的基础上扩展了下标访问、元素个数能特性。咱们经常使用的集合类型Array
,Dictionary
,Set
都遵循该协议。
CustomStringConvertible
这个协议表示自定义类型输出的样式。先来看下它的定义:
public protocol CustomStringConvertible { var description: String { get } }
只有一个description
的属性。它的使用很简单:
struct Point: CustomStringConvertible { let x: Int, y: Int var description: String { return "(\(x), \(y))" } } let p = Point(x: 21, y: 30) print(p) // (21, 30) //String(describing: <#T##CustomStringConvertible#>) let s = String(describing: p) print(s) // (21, 30)
若是不实现CustomStringConvertible
,直接打印对象,系统会根据默认设置进行输出。咱们能够经过CustomStringConvertible
对这一输出行为进行设置,还有一个协议是CustomDebugStringConvertible
:
public protocol CustomDebugStringConvertible { var debugDescription: String { get } }
跟CustomStringConvertible
用法同样,对应debugPrint
的输出。
Hashable
咱们经常使用的Dictionary
,Set
均实现了Hashable
协议。Hash的目的是为了将查找集合某一元素的时间复杂度下降到O(1),为了实现这一目的须要将集合元素与存储地址之间建议一种尽量一一对应的关系。
咱们再看Hashable`协议的定义:
public protocol Hashable : Equatable { var hashValue: Int { get } func hash(into hasher: inout Hasher) } public protocol Equatable { static func == (lhs: Self, rhs: Self) -> Bool }
注意到func hash(into hasher: inout Hasher)
,Swift 4.2 经过引入 Hasher
类型并采用新的通用哈希函数进一步优化 Hashable
。
若是你要自定义类型实现 Hashable
的方式,能够重写 hash(into:)
方法而不是 hashValue
。hash(into:)
经过传递了一个 Hasher
引用对象,而后经过这个对象调用 combine(_:)
来添加类型的必要状态信息。
// Swift >= 4.2 struct Color: Hashable { let red: UInt8 let green: UInt8 let blue: UInt8 // Synthesized by compiler func hash(into hasher: inout Hasher) { hasher.combine(self.red) hasher.combine(self.green) hasher.combine(self.blue) } // Default implementation from protocol extension var hashValue: Int { var hasher = Hasher() self.hash(into: &hasher) return hasher.finalize() } }
Codable
Codable
是可Decodable
和Encodable
的类型别名。它可以将程序内部的数据结构序列化成可交换数据,也可以将通用数据格式反序列化为内部使用的数据结构,大大提高对象和其表示之间互相转换的体验。处理的问题就是咱们常常遇到的JSON转模型,和模型转JSON。
public typealias Codable = Decodable & Encodable public protocol Decodable { init(from decoder: Decoder) throws } public protocol Encodable { func encode(to encoder: Encoder) throws }
这里只举一个简单的解码过程:
//json数据 { "id": "1283984", "name": "Mike", "age": 18 } // 定义对象 struct Person: Codable{ var id: String var name: String var age: Int } // json为网络接口返回的Data类型数据 let mike = try! JSONDecoder().decode(Person.self, from: json) print(mike) //输出:Student(id: "1283984", name: "Mike", age: 18)
是否是很是简单,Codable还支持各类自定义解编码过程,彻底能够取代SwiftyJSON
,HandyJSON
等编解码库。
Comparable
这个是用于实现比较功能的协议,它的定义以下:
public protocol Comparable : Equatable { static func < (lhs: Self, rhs: Self) -> Bool static func <= (lhs: Self, rhs: Self) -> Bool static func >= (lhs: Self, rhs: Self) -> Bool static func > (lhs: Self, rhs: Self) -> Bool }
其继承于Equatable
,即判等的协议。能够很清楚的理解实现了各类比较的定义就具备了比较的功能。这个不作比较。
RangeReplaceableCollection
RangeReplaceableCollection
支持用另外一个集合的元素替换元素的任意子范围的集合。
看下它的定义:
public protocol RangeReplaceableCollection : Collection where Self.SubSequence : RangeReplaceableCollection { associatedtype SubSequence mutating func append(_ newElement: Self.Element) mutating func insert<S>(contentsOf newElements: S, at i: Self.Index) where S : Collection, Self.Element == S.Element /* 拼接、插入、删除、替换的方法,他们都具备对组元素的操做能力 */ override subscript(bounds: Self.Index) -> Self.Element { get } override subscript(bounds: Range<Self.Index>) -> Self.SubSequence { get } }
举个例子,Array支持该协议,咱们能够进行以下操做:
var bugs = ["Aphid", "Damselfly"] bugs.append("Earwig") bugs.insert(contentsOf: ["Bumblebee", "Cicada"], at: 1) print(bugs) // Prints "["Aphid", "Bumblebee", "Cicada", "Damselfly", "Earwig"]"
这里附一张Swift中Array遵循的协议关系图,有助于你们理解上面讲解的几个协议之间的关系:
图像来源:https://swiftdoc.org/v3.1/type/array/hierarchy/
2、@propertyWrapper
阅读如下代码,print 输出什么
@propertyWrapper struct Wrapper<T> { var wrappedValue: T var projectedValue: Wrapper<T> { return self } func foo() { print("Foo") } } struct HasWrapper { @Wrapper var x = 0 func foo() { print(x) // 0 print(_x) // Wrapper<Int>(wrappedValue: 0) print($x) // Wrapper<Int>(wrappedValue: 0) } }
这段代码看似要考察对@propertyWrapper
的理解,可是有不少无用内容,致使代码很奇怪。
@propertyWrapper
的意思就是属性包装,它能够将一系列类似的属性方法进行统一处理。举个例子,若是咱们须要在UserDefaults
中加一个是否首次启动的值,正常能够这样处理:
extension UserDefaults { enum Keys { static let isFirstLaunch = "isFirstLaunch" } var isFirstLaunch: Bool { get { return bool(forKey: Keys.isFirstLaunch) } set { set(newValue, forKey: Keys.isFirstLaunch) } } }
若是咱们须要加入不少这样属性的话,就须要写大量的get
、set
方法。而@propertyWrapper
的做用就是为属性的这种设置提供一个模板写法,如下是使用属性包装的写法。
@propertyWrapper struct UserDefaultWrapper<T> { private let key: String private let defaultValue: T init(key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } extension UserDefaults { @UserDefaultWrapper(key: Keys.isFirstLaunch, defaultValue: false) var isFirstLaunch: Bool }
@propertyWrapper
约束的对象必需要定义wrappedValue
属性,由于该对象包裹的属性会走到wrappedValue
的实现。
回到实例代码,定义了wrappedValue
却并无添加任何实现,这是容许的。因此访问x的时候实际上是访问Wrapper
的wrappedValue
,由于没有给出任何实现因此直接打印出0
。而_x
和$x
对应的就是Wrapper
自身。
3、关键字
public open
public
open
为权限关键词。对于一个严格的项目来讲,精确的最小化访问控制级别对于代码的维护来讲至关重要的。完整的权限关键词,按权限大小排序以下:
open > public > internal > fileprivate > private
open
权限最大,容许外部module访问,继承,重写。public
容许外部module访问,但不容许继承,重写。internal
为默认关键词,在同一个module内能够共用。fileprivate
表示代码能够在当前文件中被访问,而不作类型限定。private
表示代码只能在当前做用域或者同一文件中同一类型的做用域中被使用。
这些权限关键词能够修饰,属性,方法和类型。须要注意:当一个类型的某一属性要用public修饰时,该类型至少要用public(或者open)权限的关键词修复。能够理解为数据访问是分层的,咱们为了获取某一属性或方法须要先获取该类型,因此外层(类型)的访问权限要知足大于等于内层(类型、方法、属性)权限。
static class final
原文中final
跟权限关键词放在一块儿了,实际上是不合理的,就将其放到这里来讨论。
static
静态变量关键词,来源于C语言。
在Swift中经常使用语如下场景:
// 仅用于类名前,表示该类不能被继承。仅支持class类型 final class Manager { // 单例的声明 static let shared = Manager() // 实例属性,可被重写 var name: String = "Ferry" // 实例属性,不可被重写 final var lastName: String = "Zhang" // 类属性,不可被重写 static var address: String = "Beijing" // 类属性,可被重写。注意只能做为计算属性,而不能做为存储属性 class var code: String { return "0122" } // 实例函数,可被重写 func download() { /* code... */ } // 实例函数,不可被重写 final func download() { /* code... */ } // 类函数,可被重写 class func removeCache() { /* code... */ } // 类函数,不可被重写 static func download() { /* code... */ } } struct Manager { // 单例的声明 static let shared = Manager() // 类属性 static var name: String = "Ferry" // 类函数 static func download() { /* code... */ } }
struct
和enum
由于不能被继承,因此也就没法使用class
和final
关键词,仅能经过static
关键词进行限定
mutating inout
mutating用于修饰会改变该类型的函数以前,基本都用于struct
对象的修改。看下面例子:
struct Point { var x: CGFloat var y: CGFloat // 由于该方法改变了struct的属性值(x),因此必需要加上mutating mutating func moveRight(offset: CGFloat) { x += offset } func normalSwap(a: CGFloat, b: CGFloat) { let temp = a a = b b = temp } // 将两个值交换,需传入对象地址。注意inout须要加载类型名前 func inoutSwap(a: inout CGFloat, b: inout CGFloat) { let temp = a a = b b = temp } } var location1: CGFloat = 10 var location2: CGFloat = -10 var point = Point.init(x: 0, y: 0) point.moveRight(offset: location1) print(point) //Point(x: 10.0, y: 0.0) point.normalSwap(a: location1, b: location2) print(location1) //10 print(location2) //-10 // 注意需带取址符& point.inoutSwap(a: &location1, b: &location2) print(location1) //-10 print(location2) //10
inout
须要传入取值符,因此它的改变会致使该对象跟着变更。能够再回看上面说的Hashable
的一个协议实现:
func hash(into hasher: inout Hasher) { hasher.combine(self.red) hasher.combine(self.green) hasher.combine(self.blue) }
只有使用inout
才能修改传入的hasher的值。
infix operator
infix operator
即为中缀操做符,还有prefix、postfix后缀操做符。
它的做用是自定义操做符。好比Python里能够用**
进行幂运算,可是Swift里面,咱们就能够利用自定义操做符来定义一个用**
实现的幂运算。
// 定义中缀操做符 infix operator ** // 实现该操做符的逻辑,中缀须要两个参数 func ** (left: Double, right: Double) -> Double { return pow(left, right) } let number = 2 ** 3 print(value) //8
同理咱们还能够定义前缀和后缀操做符:
//定义阶乘操做,后缀操做符 postfix operator ~! postfix func ~! (value: Int) -> Int { func factorial(_ value: Int) -> Int { if value <= 1 { return 1 } return value * factorial(value - 1) } return factorial(value) } //定义输出操做,前缀操做符 prefix operator << prefix func << (value: Any) { print(value) } let number1 = 4~! print(number1) // 24 <<number1 // 24 <<"zhangferry" // zhangferry
前缀和后缀仅须要一个操做数,因此只有一个参数便可。
关于操做符的更多内容能够查看这里:Swift Operators。
注意,由于该文章较早,其中对于操做符的一些定义已经改变。
@dynamicMemberLookup,@dynamicCallable
这两个关键词我确实没有用过,看到dynamic
能够知道这两个特性是为了让Swift具备动态性。
@dynamicMemberLookup
中文叫动态查找成员。在使用@dynamicMemberLookup
标记了对象后(对象、结构体、枚举、protocol),实现了subscript(dynamicMember member: String)
方法后咱们就能够访问到对象不存在的属性。若是访问到的属性不存在,就会调用到实现的 subscript(dynamicMember member: String)
方法,key 做为 member 传入这个方法。 举个例子:
@dynamicMemberLookup struct Person { subscript(dynamicMember member: String) -> String { let properties = ["nickname": "Zhuo", "city": "Hangzhou"] return properties[member, default: "undefined"] } } //执行如下代码 let p = Person() print(p.city) //Hangzhou print(p.nickname) //Zhuo print(p.name) //undefined
咱们没有定义Person的city
、nickname
,name
属性,却能够用点语法去尝试访问它。若是没有@dynamicMemberLookup
这种写法会被编译器检查出来并报错,可是加了该关键词编译器就不会管它是否是存在都予以经过。
@dynamicCallable struct Person { // 实现方法一 func dynamicallyCall(withArguments: [String]) { for item in withArguments { print(item) } } // 实现方法二 func dynamicallyCall(withKeywordArguments: KeyValuePairs<String, String>){ for (key, value) in withKeywordArguments { print("\(key) --- \(value)") } } } let p = Person() p("zhangsan") // 等于 p.dynamicallyCall(withArguments: ["zhangsan"]) p("zhangsan", "20", "男") // 等于 p.dynamicallyCall(withArguments: ["zhangsan", "20", "男"]) p(name: "zhangsan") // 等于 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan"]) p(name: "zhangsan", age:"20", sex: "男") // 等于 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan", "age": "20", "sex": "男"])
@dynamicCallable
能够理解成动态调用,当为某一类型作此声明时,须要实现dynamicallyCall(withArguments:)
或者dynamicallyCall(withKeywordArguments:)
。编译器将容许你调用并为定义的方法。
一个动态查找成员变量,一个动态方法调用,带上这两个特性Swift就能够变成彻头彻尾的动态语言了。因此做为静态语言的Swift也是能够具备动态特性的。
更多关于这两个动态标记的讨论能够看卓同窗的这篇:细说 Swift 4.2 新特性:Dynamic Member Lookup
where
where通常用做条件限定。它能够用在for-in、swith、do-catch中:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] for item in numbers where item % 2 == 1 { print("odd: \(item)") // 将输出1,3,5,7,9等数 } numbers.forEach { (item) in switch item { case let x where x % 2 == 0: print("even: \(x)") // 将输出2,4,6,8等数 default: break } }
where
也能够用于类型限定。
咱们能够扩展一个字典的merge函数,它能够将两个字典进行合并,对于相同的Key
值以要合并的字典为准。而且该方法我只想针对Key
和Value
都是String
类型的字典使用,就能够这么作:
// 这里的Key Value来自于Dictionary中定义的泛型 extension Dictionary where Key == String, Value == String { //同一个key操做覆盖旧值 func merge(other: Dictionary) -> Dictionary { return self.merging(other) { _, new in new } } }
@autoclosure
@autoclosure
是使用在闭包类型以前,作的事情就是把一句表达式自动地封装成一个闭包 (closure)。
好比咱们有一个方法接受一个闭包,当闭包执行的结果为 true
的时候进行打印,分别使用普通闭包和加上autoclosure
的闭包实现:
func logIfTrueNormal(predicate: () -> Bool) { if predicate() { print("True") } } // 注意@autoclosure加到闭包的前面 func logIfTrueAutoclosure(predicate: @autoclosure () -> Bool) { if predicate() { print("True") } } // 调用方式 logIfTrueNormal(predicate: {3 > 1}) logIfTrueAutoclosure(predicate: 3 > 1)
编译器会将logIfTrueAutoclosure
函数参数中的3 > 1
这个表达式转成{3 > 1}
这种尾随闭包样式。
那这种写法有什么用处呢?咱们能够从一个示例中体会一下,在Swift系统提供的几个短路运算符(即表达式左边若是已经肯定结果,右边将再也不运算)中均采用了@autoclosure
标记的闭包。那??
运算符举例,它的实现是这样的:
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T { switch optional { case .some(let value): return value case .none: return try defaultValue() } } // 使用 var name: String? = "ferry" let currentName = name ?? getDefaultName()
由于使用了@autoclosure
标记闭包,因此??
的defaultValue
参数咱们可使用表达式,又由于是闭包,因此当name
非空时,直接返回了该值,不会调用getDefaultName()
函数,减小计算。
参考:@AUTOCLOSURE 和 ??,注意由于Swift版本问题,实例代码没法运行。
@escaping
@escaping
也是闭包修饰词,用它标记的闭包被称为逃逸闭包,还有一个关键词是@noescape
,用它修饰的闭包叫作非逃逸闭包。在Swift3及以后的版本,闭包默认为非逃逸闭包,在这以前默认闭包为逃逸闭包。
这二者的区别主要在于声明周期的不一样,当闭包做为参数时,若是其声明周期与函数一致就是非逃逸闭包,若是声明周期大于函数就是逃逸闭包。结合示例来理解:
// 非逃逸闭包 func logIfTrueNormal(predicate: () -> Bool) { if predicate() { print("True") } } // 逃逸闭包 func logIfTrueEscaping(predicate: @escaping () -> Bool) { DispatchQueue.main.async { if predicate() { print("True") } } }
第二个函数的闭包为逃逸闭包是由于其是异步调用,在函数退出时,该闭包还存在,声明周期长于函数。
若是你没法判断出应该使用逃逸仍是非逃逸闭包,也无需担忧,由于编译器会帮你作出判断。第二个函数,若是咱们不声明逃逸闭包编译器会报错,警告咱们:Escaping closure captures non-escaping parameter 'predicate'
。固然咱们仍是应该理解二者的区别。
4、高阶函数
Filter, Map, Reduce, flatmap, compactMap
这几个高阶函数都是对数组对象使用的,咱们经过示例去了解他们吧:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] // filter 过滤 let odd = numbers.filter { (number) -> Bool in return number % 2 == 1 } print(odd) // [1, 3, 5, 7, 9] //map 转换 let maps = odd.map { (number) -> String in return "\(number)" } print(maps) // ["1", "3", "5", "7", "9"] // reduce 累计运算 let result = odd.reduce(0, +) print(result) // 25 // flatMap 1.数组展开 let numberList = [[1, 2, 3], [4, 5], [[6]]] let flatMapNumber = numberList.flatMap { (value) in return value } print(flatMapNumber) // [1, 2, 3, 4, 5, [6]] // flatMap 2.过滤数组中的nil let country = ["cn", "us", nil, "en"] let flatMap = country.flatMap { (value) in return value } print(flatMap) //["cn", "us", "en"] // compactMap 过滤数组中的nil let compactMap = country.compactMap { (value) in return value } print(compactMap) // ["cn", "us", "en"]
filter,reduce其实很好理解,map、flatMap、compactMap刚开始接触时确实容易搞混,这个须要多加使用和练习。
注意到flatMap
有两种用法,一种是展开数组,将二维数组降为一维数组,一种是过滤数组中的nil
。在Swift4.1版本已经将flatMap
过滤数组中nil的函数标位deprecated
,因此咱们过滤数组中nil的操做应该使用compactMap
函数。
参考:Swift 烧脑体操(四) - map 和 flatMap
5、几个Swift中的概念
柯里化什么意思
柯里化指的是从一个多参数函数变成一连串单参数函数的变换,这是实现函数式编程的重要手段,举个例子:
// 该函数返回类型为(Int) -> Bool func greaterThan(_ comparer: Int) -> (Int) -> Bool { return { number in return number > comparer } } // 定义一个greaterThan10的函数 let greaterThan10 = greaterThan(10) greaterThan10(13) // => true greaterThan10(9) // => false
因此柯里化也能够理解为批量生成一系列类似的函数。
POP
与 OOP
的区别
OOP(object-oriented programming)面向对象编程:
在面向对象编程世界里,一切皆为对象,它的核心思想是继承、封装、多态。
POP(protocol-oriented programming)面向协议编程:
面向协议编程则主要经过协议,又或叫作接口对一系列操做进行定义。面向协议也有继承封装多态,只不过这些不是针对对象创建的。
为何Swift演变成了一门面向协议的编程语言。这是由于面向对象存在如下几个问题:
一、动态派发的安全性(这应该是OC的困境,在Swift中Xcode是不可能让这种问题编译经过的)
二、横切关注点(Cross-Cutting Concerns)问题。面向对象没法描述两个不一样事物具备某个相同特性这一点。
三、菱形问题(好比C++中)。C++能够多继承,在多继承中,两个父类实现了相同的方法,子类没法肯定继承哪一个父类的此方法,因为多继承的拓扑结构是一个菱形,因此这个问题有被叫作菱形缺陷(Diamond Problem)。
参考文章:
Any
与AnyObject
区别
AnyObject: 是一个协议,全部class都遵照该协议,经常使用语跟OC对象的数据转换。
Any:它能够表明任何型別的类(class)、结构体 (struct)、枚举 (enum),包括函式和可选型,基本上能够说是任何东西。
rethrows
和 throws
有什么区别呢?
throws是处理错误用的,能够看一个往沙盒写入文件的例子:
// 写入的方法定义 public func write(to url: URL, options: Data.WritingOptions = []) throws // 调用 do { let data = Data() try data.write(to: localUrl) } catch let error { print(error.localizedDescription) }
将一个会有错误抛出的函数末尾加上throws
,则该方法调用时须要使用try
语句进行调用,用于提示当前函数是有抛错风险的,其中catch
句柄是能够忽略的。
rethrows
与throws
并无太多不一样,它们都是标记了一个方法应该抛出错误。可是 rethrows
通常用在参数中含有能够 throws
的方法的高阶函数中(想一下为何是高阶函数?下期给出答案)。
查看map
的方法声明,咱们能同时看到 throws
,rethrows
:
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
不知道大家第一次见到map
函数本体的时候会不会疑惑,为何map
里的闭包须要抛出错误?为何咱们调用的时候并无用try
语法也能够正常经过?
实际上是这样的,transform
是须要咱们定义的闭包,它有可能抛出异常,也可能不抛出异常。Swift做为类型安全的语言就须要保证在有异常的时候须要使用try去调用,在没有异常的时候要正常调用,那怎么兼容这两种状况呢,这就是rethrows
的做用了。
func squareOf(x: Int) -> Int {return x * x} func divideTenBy(x: Int) throws -> Double { guard x != 0 else { throw CalculationError.DivideByZero } return 10.0 / Double(x) } let theNumbers = [10, 20, 30] let squareResult = theNumbers.map(squareOf(x:)) // [100, 400, 9000] do { let divideResult = try theNumbers.map(divideTenBy(x:)) } catch let error { print(error) }
当咱们直接写let divideResult = theNumbers.map(divideTenBy(x:))
时,编译器会报错:Call can throw but is not marked with 'try'
。这样就实现了根据状况去决定是否须要用try-catch
去捕获map里的异常了。
参考:错误和异常处理
break return continue fallthough 在语句中的含义(switch、while、for)
这个比较简单,只说相对特别的示例吧,在Swift的switch语句,会在每一个case结束的时候自动退出该switch判断,若是咱们想不退出,继续进行下一个case的判断,能够加上fallthough
。