Swift Runtime探索

1,318 阅读5分钟

一、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) {
}