一、Swift Runtime探索
1、原始代码
//
// main.swift
// TestSwCmd
//
//
import Foundation
print("Hello, World!")
class Teacher {
var age : Int = 18
func teach() {
print("a Teacher teach")
}
}
let t = Teacher()
func test() {
//获取方法列表
var methodCount:UInt32 = 0
let methodlist = class_copyMethodList(Teacher.self, &methodCount)
if methodlist != nil {
for i in 0..<numericCast(methodCount) {
if let method = methodlist?[i]{
let methodName = method_getName(method);
print("⽅法列表:\(String(describing: methodName))")
}else{
print("not found method");
}
}
}else{
print("方法-无法获取-获取不到")
}
//获取属性列表
var count:UInt32 = 0
let proList = class_copyPropertyList(Teacher.self, &count)
if proList != nil {
for i in 0..<numericCast(count) {
if let property = proList?[i]{
let propertyName = property_getName(property);
print("属性成员属性:\(String(utf8String: propertyName)! )")
}else{
print("没有找到你要的属性");
}
}
}else{
print("属性-无法获取-获取不到")
}
}
test()
Log 输出
Hello, World!
方法-无法获取-获取不到
属性-无法获取-获取不到
Program ended with exit code: 0
运⾏这段代码你会发现,当前不管是我们的⽅法列表还是我们的属性列表,此次此刻都是为空的。
2、@objc 的标识,如果这个时候我们将我们当前的⽅法和属性添加上,会发⽣什么?
此刻代码会输出我们当前的 teach ⽅法和 age 属性。
class Teacher {
@objc var age : Int = 18
@objc func teach() {
print("a Teacher teach")
}
}
Log输出:
Hello, World!
⽅法列表:age
⽅法列表:setAge:
⽅法列表:teach
属性成员属性:age
Program ended with exit code: 0
//但是此刻对于我们的 OC 来说是没有办法使⽤的:
3、继承自NSObject的类 的输出
class Teacher : NSObject{
var age : Int = 18
func teach() {
print("a Teacher teach")
}
}
继承自NSObject的类 的输出
Hello, World!
⽅法列表:init
属性-无法获取-获取不到
Program ended with exit code: 0
如果同样输出所有的方法和属性,也需要增加 @objc
4、得出来这样⼀个结论:
-
对于纯 Swift 类来说,没有 动态特性。⽅法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的 Runtime 特性了,这和我们的⽅法调度(V-Table调度)是不谋⽽合的。 dynamic (动态特性)
-
对于纯 Swift 类,⽅法和属性添加 @objc 标识的情况下,当前我们可以通过 Runtime API 拿到,但是在我们的 OC 中是没法进⾏调度的
-
对于继承⾃ NSObject 类来说,如果我们想要动态的获取当前的属性和⽅法,必须在其声明前添加 @objc 关键字,⽅法交换: dynamic的标识。否则也是没有办法通过 Runtime API 获取的
二、反射
反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。上⾯我们分析过 了,对于⼀个纯 Swift 类来说,并不⽀持我们直接像 OC 那样操作;但是 Swift 标准库依然提供了反射机制让我们访问成员信息, 反射的⽤法⾮常简单,我们⼀起来熟悉⼀下:
class Teacher {
var age : Int = 18
func teach() {
print("a Teacher teach")
}
}
func testMirror() {
let t = Teacher()
let mirror = Mirror(reflecting: t)
for pro in mirror.children{
print("\(pro.label):\(pro.value)")
}
}
testMirror()
Log输出:
Hello, World!
Optional("age"):18
Program ended with exit code: 0
那这个时候我们能⽤ Mirror 做些什么事情那?⾸先想到的应该就是 JSON 解析了:
func jsonPare(_ obj : Any ) -> Any {
let mirror = Mirror(reflecting: obj)
guard !mirror.children.isEmpty else {
print("return: \(obj)")
return obj
}
var keyValues : [String : Any] = [:]
for children in mirror.children {
if let keyName = children.label {
keyValues[keyName] = jsonPare(children.value)
}else {
print("children.label 为空")
}
}
print("result: \(keyValues)")
return keyValues
}
jsonPare(t)
Log输出:
Hello, World!
return: 18
result: ["age": 18]
Program ended with exit code: 0
上述代码中我们虽然完成了⼀个简单的 JSON 解析的Demo ,但是很多错误都是输出,如何在 Swift 中专业的表达错误那?
三、错误处理
Swift 提供 Error 协议来标识当前应用程序发生错误的情况,error 的定义如下:
public protocol Error{
}
所以不管是 struct、Class、enum 我们都可以通过遵循这个协议来表示⼀个错误。这⾥我们选择 enum
enum JSONMapError: Error {
case emptyKey
case notConformProtocol
}
但是这⾥我们使⽤ return 关键字直接接收了⼀个 Any 的结果,如何抛出错误,正确的⽅式是使⽤ throw 关键字。 于此同时,编译器会告诉我们当前的我们的 function 并没有声明成 throws ,所以修改代码之后就能得出这样的结果了:
enum JSONMapError: Error {
case emptyKey
case notConformProtocol
}
protocol CustomJSONMap {
func jsosnMap() thorws -> Any
}
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()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
我们来使⽤⼀下我们当前编写完成的代码,会发现编译器要求我们使⽤ try 关键字来处理错误。
接下来我们就来说⼀说 Swift 中错误处理的⼏种⽅式:
第一种 使⽤ try 关键字,是最简便的,也是我们最喜欢的:甩锅
使⽤ try 关键字有两个注意点:⼀个还是 try? ,⼀个是 try!
try? :返回的是⼀个可选类型,这⾥的结果就是两类,⼀类是成功,返回具体的字典值;⼀类就错误,
但是具体哪⼀类错误我们不关系,统⼀返回了⼀个nil
try! 表示你对这段代码有绝对的⾃信,这⾏代码绝对不会发⽣错误!
第⼆种 ⽅式就是使⽤ do...catch
如何你觉得仅仅使⽤ Error 并不能达到你想要详尽表达错误信息的⽅式,可以使⽤ LocalError 协议 , 定义如下:
public protocol LocalizedError : Error {
/// A localized message describing what error occurred.
var errorDescription: String? { get }
/// A localized message describing the reason for the failur e.
var failureReason: String? { get }
/// A localized message describing how one might recover from the failure.
var recoverySuggestion: String? { get }
/// A localized message providing "help" text if the user req uests help.
var helpAnchor: String? { get }
}
CustomError 有三个默认属性:
a static errorDomain an errorCode integer an errorUserInfo dictionary
四、其他内容
numericCast
代码中用到的方法, 方法定义
Returns the given integer as the equivalent value in a different integer type.
返回给定整数作为不同整数类型中的等效值。
var methodCount:UInt32 = 0
let methodlist = class_copyMethodList(Teacher.self, &methodCount)
for i in 0..<numericCast(methodCount) {
}