上一节,我们分析了闭包 & 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是一个
image.pngstruct
结构体
Mirror 初始化时,入参为
image.pngAny
类型
image.png
children
属性为(label: String?, value: Any)
元组类型
-
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())
-
运行上述案例,可以看到打印了
image.pngnotConformProtocol
因为name
和age
属性分别是String
和Int
类型,他们没有遵守
CustomJSONMap协议。
-
既然如此,我们让
String
和Int
都遵守CustomJSONMap
协议,再运行。
extension String: CustomJSONMap {}
extension Int: CustomJSONMap {}
image.png
-
完美打印
所有内容
在我们的开发中,
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内的
image.pngname
和age
的类型都未遵循``CustomJSONMap
协议,所以会抛出error
-
当前使用
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 }
}
-
所以我们可使用
image.pngLocalizedError
,把错误
的信息表达
得更详细
一些。
(枚举
类型的错误,可以使用switch
每个属性都独立
给error错误描述
)
-
我们知道
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函数内处理
错误情况,外部可直接调用函数
-
【处理方式二】如果
image.png不想
在函数内处理
错误情况,可以加上rethorws
声明,由外部处理
。
rethrows
适用于链式调用
时,函数内部
不需要关心异常情况
,最终
再统一处理
异常情况。
4. defer(延后处理)
-
image.pngdefer
:用来定义以任何方式
(throw、return)离开
代码块前
必须执行
的代码
:
-
image.png多个defer
时,执行顺序
是反序执行
(与创建顺序相反)。
5. assert(断言)
- 很多
编程语言
都有断言机制
,不符合
指定条件就抛出运行时错误
,常用于调试(Debug)
阶段的条件判断
- 默认情况下,
Swift断言
只会在debug
模式下生效
,release
模式下忽略
:
image.png
本节内容较多,涉及到Mirror
的深层探索,放到下一节:Mirror源码探索
网友评论