上一节,我们分析了闭包 & Runtime & Any等类型。
介绍Runtime运行时时,我们知道swift是静态语言,但可兼容OC类实现objc_msgSend消息发送机制。
-
swift中dynamic声明的函数可在extension中进行函数替换。不过这个替换在编译期就完成了。
那,swift有没有自己的运行时机制呢?
- 有,
Mirror反射机制。虽然没有OC运行时那么强大。但支持运行时获取对象的类型和成员变量(HandyJSON就是基于Mirror思想,直接从内存读取来实现的😃)
本节,我们先体验一下Mirror,下一节,我们深入研究Mirror底层原理
- Mirror体验
- protocol协议
- Error错误机制(try、throws、rethrows)
- defer
- assert
1. Mirror体验
-
Mirror(反射),可以动态获取类型、成员变量,在运行时可以调用方法、属性等行为的特性。
对于一个纯swift类来说,并不支持我们直接像OCRuntime那样操作。但Swift标准库给我们提供了简单实用的反射机制。
- 测试案例:
class HTPerson { var name = "ht" var age = 18 } let p = HTPerson() let mirror = Mirror(reflecting: p.self) for pro in mirror.children { print("\(pro.label ?? ""):\(pro.value)") }
- 打印结果:
image.png
- 进入
Mirror内部可以看到:
Mirror是一个
struct结构体
image.pngMirror 初始化时,入参为
Any类型
image.png
children属性为(label: String?, value: Any)元组类型
image.png
-
Mirror的用途很多,最常用的是用于JSON解析:
class HTPerson {
var name = "ht"
var age = 18
var student = HTStudent() // 持有对象
}
class HTStudent {
var double = 10.0
}
func test(_ obj: Any) -> Any{
let mirror = Mirror(reflecting: obj)
// 递归终止条件
guard !mirror.children.isEmpty else { return obj }
var keyValue: [String: Any] = [:] // 记录属性名和属性内容
for children in mirror.children{
if let keyName = children.label {
keyValue[keyName] = test(children.value) // 递归(继续遍历person中的对象属性)
}else{
print("children.label ")
}
}
return keyValue
}
let t = HTPerson()
print(test(t))
-
打印结果:
image.png
分析:
mirror成功读取对象的属性名和属性值,可使用字典存储和输出;每个
属性都会默认当作对象来处理,通过mirror.children可判断当前对象是否拥有属性,没有就跳出当前属性的递归流程。 这样可保证所有读取children的完整层级信息。(ps:
递归实际是利用栈特性,直接或间接调用自身实现层级读取的需求,递归需要有明确的终止条件)
- 上面代码有个
不好的点:错误结果是print打印,外界并不知道。
在swift中,错误信息一般使用Error进行表示。
2. Protocol协议
在介绍
Error之前,我们先介绍一下Swift的协议。
Swift的协议非常强大,不仅可以声明属性、函数,支持可选实现,还可以在extension中完成属性和函数的默认实现。以上面案例为例:
如果每次
读取属性,我们都需要调用test函数,会显得非常麻烦。
协议可以帮我们默认实现这个方法,只要对象遵守这个协议,都可以直接使用。
【思路】
- 创建一个协议(
CustomJSONMap),声明jsonMap函数,并在extension中默认实现jsonMap。- 创建一个枚举
JSONMapError,对所有错误类型进行列举。jsonMap内部支持Mirror所有遵循CustomJSONMap协议的属性。对未遵循协议的属性和属性名为空的属性返回相应的错误类型。
// 错误枚举
enum JSONMapError {
case emptyError // 空
case notConformProtocol // 未遵守协议
}
// 协议(自带jsonMap函数)
protocol CustomJSONMap {
func jsonMap() ->Any
}
// extension中默认jsonMap实现
extension CustomJSONMap {
func jsonMap() -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else { return self }
var keyValue: [String: Any] = [:]
for children in mirror.children{
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
keyValue[keyName] = value.jsonMap() // 递归
} else {
return JSONMapError.emptyError // 属性名为空
}
} else {
return JSONMapError.notConformProtocol // children未遵守CustomJSONMap协议
}
}
return keyValue
}
}
class HTPerson: CustomJSONMap {
var name = "ht"
var age = 18
}
let t = HTPerson()
print(t.jsonMap())
-
运行上述案例,可以看到打印了
notConformProtocol
因为name和age属性分别是String和Int类型,他们没有遵守CustomJSONMap协议。
image.png 既然如此,我们让
String和Int都遵守CustomJSONMap协议,再运行。
extension String: CustomJSONMap {}
extension Int: CustomJSONMap {}

-
完美打印所有内容
在我们的开发中,
protocol协议是一大利器,使用非常方便。
- 遇到一些
通用的函数,我们可以使用协议包装起来,只要遵循这个协议,就可以直接调用相应的属性和函数。很好的隔离代码,而且让代码看起来非常简洁。
- 上面的
错误信息,我们只是使用枚举统一罗列,return时,常规结果和错误类型混在一起返回,让我们很不舒服。
如何解决?
- 可以通过系统
Error协议搭配throw关键字,优雅的抛出错误或返回常规结果,让开发者自己选择处理方式。
3. Error机制(try、throws、rethrows)
swift中,系统提供Error协议来表示当前应用程序发生错误的情况,并支持使用throw关键字,优雅的抛出错误。
3.1 基础Error协议
public protocol Error { }
- 系统的
Error协议,就是一个空的公共协议, 不管是class、struct还是enum,都可以通过遵守这个协议,来表达错误。
以 enum为例:
// 错误枚举,遵循Error
enum JSONMapError: Error {
case emptyError // 空
case notConformProtocol // 未遵守协议
}
// 协议(自带jsonMap函数)
protocol CustomJSONMap {
func jsonMap() throws ->Any
}
// extension中默认jsonMap实现
extension CustomJSONMap {
func jsonMap() throws -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else { return self }
var keyValue: [String: Any] = [:]
for children in mirror.children{
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
keyValue[keyName] = try value.jsonMap() // 加了throws后,需要使用try执行 jsonmap()
} else {
throw JSONMapError.emptyError // 属性名为空
}
} else {
throw JSONMapError.notConformProtocol // children未遵守CustomJSONMap协议
}
}
return keyValue
}
}
class HTPerson: CustomJSONMap {
var name = "ht" // String未继承CustomJSONMap,会error
var age = 18 // Int未继承CustomJSONMap,会error
}
let t = HTPerson()
// do...catch 分开处理,catch中有默认参数error。
do {
// 处理正常结果
print(try t.jsonMap())
} catch {
// 处理错误类型(其中error为Error类型)
print(error)
}
//print(try t.jsonMap()) // try : 向上甩锅,将错误抛给上层函数处理(上层没处理,就crash)
print(try? t.jsonMap()) // try?: 只关注正常结果,忽略错误(如果错误,就为nil, 不执行后续流程)
//print(try! t.jsonMap()) // try!: 对代码绝对自信,一定不会发生错误(如果发生,直接crash)
分析:
- 让
JSONMapError枚举遵守Error协议- 将
jsonMap函数的return 错误,全部改为throw抛出错误,只有正常结果才使用return返回。- 使用
throw抛出错误会发现,函数需要使用throws标注,这是告诉使用者,这个函数可能抛出error,需要开发者决定是否处理。- 使用
throw后,发现不能直接调用jsonMap,需要使用try关键字来尝试调用。
- 那
try什么意思呢?如何使用呢?
try有三种使用方式:
- try : 向上甩锅,将
错误抛给上层函数处理(最上层都没处理,就crash)- try?: 只关注
正常结果,忽略所有错误(如果错误,就为nil, 不执行后续流程)- try!: 程序员对代码
绝对自信,一定不会发生错误(如果发生,直接crash)
- 上面三种方式,只是说到了
抛出和不管,那如何正确处理throw抛出的错误呢?
do...catch:
- 我们可以通过
do...catch来处理。其中do处理正确结果,catch处理error,catch有个隐藏参数,就是error(Error类型)do { // 处理正常结果 try t.jsonMap() // 这里try不会报错了,因为错误都分给了catch } catch { // 处理错误类型(其中error为Error类型) print(error) }
-
上面案例中,HTPerosn内的
name和age的类型都未遵循``CustomJSONMap协议,所以会抛出error
image.png 当前使用
throw,成功将错误和正常结果进行分离,并了解如何通过try和do-catch对结果进行处理。
3.2 LocalizedError
在
正常业务开发中,我们有时候并不满足于知道错误类型,我还希望得到关于这个错误的描述,以及其他信息。
(比如网络错误,我们希望获取error能记录errCode、errMsg等信息)系统基于
Error协议,再公开了一个LocalizedError协议。
public protocol LocalizedError : Error {
/// 错误的描述
var errorDescription: String? { get }
/// 错误的原因
var failureReason: String? { get }
/// 恢复的建议
var recoverySuggestion: String? { get }
/// 可提供的帮助
var helpAnchor: String? { get }
}
-
所以我们可使用
LocalizedError,把错误的信息表达得更详细一些。
(枚举类型的错误,可以使用switch每个属性都独立给error错误描述)
image.png 我们知道
LocalizedError就是遵守Error的一个协议。在业务开发中,我们完全可仿照LocalizedError的思维,直接自定义一个协议遵守Error协议。然后自己写一些属性、函数等。
3.3 CustomError (继承自Error)
- OC中,系统提供了
NSError错误类,NSError遵守Error协议,可以携带更多错误信息。
open class NSError : NSObject, NSCopying, NSSecureCoding {
public init(domain: String, code: Int, userInfo dict: [String : Any]? = nil)
open var domain: String { get }
open var code: Int { get }
open var userInfo: [String : Any] { get }
open var localizedDescription: String { get }
open var localizedFailureReason: String? { get }
open var localizedRecoverySuggestion: String? { get }
open var recoveryAttempter: Any? { get }
open var helpAnchor: String? { get }
}
// NSError遵守Error协议
extension NSError : Error { }
- 在我们
swift中,对接OCNSError的是CustomNSError。结构都一样。
public protocol CustomNSError : Error {
static var errorDomain: String { get }
var errorCode: Int { get }
var errorUserInfo: [String : Any] { get }
}
- 其实
了解到这,我相信你,只要基于Error协议,你想要的,都可自定义协议来实现。
3.4 rethorws
当我们把函数作为另一个函数入参时,如果入参函数包含throws声明,不处理会报错:

-
【处理方式一】在
函数内处理错误情况,外部可直接调用函数
image.png -
【处理方式二】如果
不想在函数内处理错误情况,可以加上rethorws声明,由外部处理。
image.png
rethrows适用于链式调用时,函数内部不需要关心异常情况,最终再统一处理异常情况。
4. defer(延后处理)
-
defer:用来定义以任何方式(throw、return)离开代码块前必须执行的代码:
image.png -
多个defer时,执行顺序是反序执行(与创建顺序相反)。
image.png
5. assert(断言)
- 很多
编程语言都有断言机制,不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断

- 默认情况下,
Swift断言只会在debug模式下生效,release模式下忽略:
image.png
本节内容较多,涉及到Mirror的深层探索,放到下一节:Mirror源码探索












