Swift Runtime、特殊类型

1,234 阅读2分钟

Swift Runtime探索

  • 运行下面一段代码
class XQTeacher {

    var age = 18
    
    func teach() {
        print("teach")
    }
}

func test() {
    var properCount : UInt32 = 0
    let properList = class_copyPropertyList(XQTeacher.self, &properCount)
    for i in 0..<numericCast(properCount) {
        if let proper = properList?[i] {
            let properName = property_getName(proper)
            print("属性:\(String(utf8String: properName)!)")
        }else{
            print("没有属性")
        }
    }
    
    var methodCount : UInt32 = 0
    let methodList = class_copyMethodList(XQTeacher.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodList?[i] {
            let methodName = method_getName(method)
            print("方法名:\(String(describing: methodName))")
        }else{
            print("没有方法")
        }
    }
    
}

test()

print("end")
  • 运行这段代码你会发现,当前不管是我们的方法列表还是我们的属性列表,此次此刻都是为空的
  • Swift值类型和引用类型、方法调度中我们知道@objc的作用,如果这个时候我们将我们当前的方法和属性添加上@objc,会发生什么?
  • 此刻代码会输出我们当前的teach方法和age属性。但是此刻对于我们的oc来说是没有办法使用的,还得将class继承自NSObject

总结

  • 对于纯Swift类来说,是不具备动态特性。方法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的runtime特性了。
  • 对于纯Swift类,方法和属性添加@objc标识的情况下,当前我们可以通过Runtime API拿到,但是在oc中是没法进行调度的。
  • 对于继承自NSObject类来说,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加@objc关键字,方法交换: dynamic的标识。否则也是没有办法通过Runtime API获取的。

SwiftObject

  • 通过源码调试,在class_copyPropertyList方法执行的时候打下断点,运行进入断点
  • 进到class_copyPropertyList方法
  • 进入到data方法,可以看到swift有一个默认的基类_SwiftObject
  • Swift源码里面搜索_SwiftObject
  • TargetAnyClassMetadata里面的成员
  • Swift为了和oc交互,在底层存储的数据结构上是和oc保持了部分一致。
  • objc源码中也有swift_class_t的定义,它是一个继承自objc_class的结构体,也就是在objc_class的成员变量的基础上加上自己的成员变量

反射

  • 反射就是可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。上面我们分析过了,对于一个纯Swift类来说,并不支持我们直接像oc那样操作;但是Swift标准库依然提供了反射机制让我们访问成员信息, 反射的用法非常简单。
class XQTeacher {
    var age = 18
}
var t = XQTeacher()

let mir = Mirror(reflecting: t)

for pro in mir.children {
    print("\(String(describing: pro.label)):\(pro.value)")
}
  • 通过反射来实现一个简单的json解析
func test(_ obj:Any) -> Any {
    let mirror = Mirror(reflecting: obj)
    
    guard !mirror.children.isEmpty else {
        return obj;
    }
    
    var keyValue : [String:Any] = [:]
    
    for child in mirror.children {
        if let keyName = child.label {
            keyValue[keyName] = test(child.value)
        }else{
            print("label为空")
        }
    }
    print(keyValue)
    return keyValue
}

test(t)
  • 将它封装成一个协议
protocol CustomJSONMap {
    func jsonMap() -> Any
}

extension CustomJSONMap {
    func jsonMap() -> Any {
        let mirror = Mirror(reflecting: self)
        
        guard !mirror.children.isEmpty else {
            return self;
        }
        
        var keyValue : [String:Any] = [:]
        
        for child in mirror.children {
            if let value = child.value as? CustomJSONMap {
                if let keyName = child.label {
                    keyValue[keyName] = value.jsonMap()
                }else{
                    print("label为空")
                }
            }else{
                print("没有遵守协议")
            }
            
        }
        print(keyValue)
        return keyValue
    }
}


class XQTeacher : CustomJSONMap{

    @objc var age = 18
    
    @objc func teach() {
        print("teach")
    }
}

extension Int : CustomJSONMap {}

var t = XQTeacher()

let result = t.jsonMap()

---------
// 执行结果:["age": 18]

错误处理

  • 在上文的json解析中,我们没有对错误进行处理只是打印了,定义一个JSONMapError,错误的抛出我们一般使用throw
enum JSONMapError : Error {
    case emptyKey
    case notConformProtocol
}
protocol CustomJSONMap {
    func jsonMap() throws -> Any
}

extension CustomJSONMap {
    func jsonMap() throws -> Any {
        let mirror = Mirror(reflecting: self)
        
        guard !mirror.children.isEmpty else {
            return self;
        }
        
        var keyValue : [String:Any] = [:]
        
        for child in mirror.children {
            if let value = child.value as? CustomJSONMap {
                if let keyName = child.label {
                    keyValue[keyName] = try? value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
//                print("没有遵守协议")
                throw JSONMapError.notConformProtocol
            }
            
        }
        return keyValue
    }
}


class XQTeacher : CustomJSONMap{

    @objc var age = 18
    
    var height = 1.98
 
    @objc func teach() {
        print("teach")
    }
}

extension Int : CustomJSONMap {}

var t = XQTeacher()

let result = try? t.jsonMap()
print(result)
  • Swift中除了使用try处理错误,还可以使用do...catch
    • 使用try关键字,是最简单的,也是我们最喜欢的,使用tey有两个注意点一个是try?一个是try!
      • try?返回的是一个可选类型,这里的结果就是两类,一类是成功,返回具体的字典值;一类就是错误,但是具体哪一类错误我们不关心,统一返回了一个nil
      • try!表示你对这段代码有绝对的自信,这行代码绝对不会发生错误
    • do...catch使用方式
    do {
    	try t.jsonMap()
    }catch {
       print(error)
    }
    
  • 有时候Error并不能达到你要详尽表达的错误信息,可以使用LocalErrorJSONMapError,使用方式如下

元类型、AnyClass、Self

  • AnyObject:代表任意类的instance,类的类型,仅类遵守的协议
  • Any:代表任意类型,包括funcation类型或者Optional类型
  • AnyClass:代表任意实例的类型
  • T.self:如果T是实例对象,返回的就是它本身;如果T是类,返回的就是元类型(MetaData)
  • T.Type:一种类型,T.selfT.Type类型
  • type(of:):用来获取一个值的动态类型