Swift底层探索:反射

反射Mirror

反射就是可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。对于纯Swift类来说,并不支持像OC那样操作;但是Swift标准库仍然提供了反射机制让我们访问成员信息。

class HotpotCat: NSObject {
    var age: Int = 18
    var height = 1.85
}

var hotpot = HotpotCat()

let mirror = Mirror(reflecting: hotpot)

//children 是个元组 typealias Mirror.Child = (label: String?, value: Any)
for pro in mirror.children {
    print("\(pro.label!):\(pro.value)")
}
age:18
height:1.85

那么是不是可以写个JSON解析?

protocol JSONMap {
    func jsonParse() -> Any
}

extension JSONMap {
    func jsonParse() -> 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? JSONMap {
                if let keyName = children.label {
                    //递归逐层解析数据
                    keyValue[keyName] = value.jsonParse()
                } else {
                    print("label is nil")
                }
            } else {
                print("not conform  JSONMap protocol")
            }
        }
        return keyValue
    }
}

调用

//遵守协议,不然无法调用jsonParse
extension Int: JSONMap {}
extension Double: JSONMap {}

class HotpotCat: JSONMap {
    var age: Int = 18
    var height = 1.85
}

var hotpot = HotpotCat()
print(hotpot.jsonParse())
["height": 1.85, "age": 18]

错误处理

Error协议

上面的例子中,错误直接打印了出来,接着处理下错误。
Swift提供了Error协议来标识当前应用程序发生错误的情况,Error定义如下:

public protocol Error {
}

无论是structClass还是enum都可以通过遵循这个协议来表示错误。
上面的解析错误可以简单表示如下:

enum JSONMapError: Error {
    case emptyKey
    case notConformProtocol
}

json解析改完错误后如下(为了区分返回值把error抛出):

protocol JSONMap {
    func jsonParse() throws -> Any
}

extension JSONMap {
    func jsonParse() 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? JSONMap {
                if let keyName = children.label {
                    //递归逐层解析数据
                    keyValue[keyName] = try value.jsonParse()//try错误抛出给上层
                } else {
                    throw JSONMapError.emptyKey
                }
            } else {
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

try

使用try关键字,这个是最简单的,可以简单理解为甩锅
try&try?&try!

  • try:抛出给上层,向上抛出。上层无法处理直接crash。
  • try?:返回可选类型,要么成功返回值,要么错误返回nil,这里不关心具体的错误。
  • try!:表示绝对自信,这行代码不会发生错误。

do...catch

可以捕获具体错误信息。
增加一个name属性,不遵守JSONMap协议

class HotpotCat: JSONMap {
    var age: Int = 18
    var height = 1.85
    var name: String = "hotpot"
}
var hotpot = HotpotCat()
print(try? hotpot.jsonParse())
do {
    try hotpot.jsonParse()
} catch {
    print(error)
}
nil
notConformProtocol

LocalizedError协议

上面的error信息还是比较粗糙的,如果想要更详细的信息可以使用LocalizedError协议,定义如下:

public protocol LocalizedError : Error {

    /// A localized message describing what error occurred.
    var errorDescription: String? { get }

    /// A localized message describing the reason for the failure.
    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 requests help.
    var helpAnchor: String? { get }
}

可以实现协议,将错误表达的更详尽:

extension JSONMapError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .emptyKey:
            return NSLocalizedString("The Object doesn't has children", comment: "JSONMap object has empty children")
        case .notConformProtocol:
            return NSLocalizedString("The Object doesn't conform JSONMap protocol", comment: "Don't conform json map")
        }
    }
}

调用:

var hotpot = HotpotCat()

do {
    try hotpot.jsonParse()
} catch {
    print(error.localizedDescription)
    print("\(String(describing: (error as? LocalizedError)?.errorDescription))")
}

输出

The Object doesn't conform JSONMap protocol
Optional("The Object doesn\'t conform JSONMap protocol")

CustomNSError

oc NSError对应的有一个CustomNSError,有三个默认属性

  • a static errorDomain
  • an errorCode integer
  • an errorUserInfo dictionary
public protocol CustomNSError : Error {

    /// The domain of the error.
    static var errorDomain: String { get }

    /// The error code within the given domain.
    var errorCode: Int { get }

    /// The user-info dictionary.
    var errorUserInfo: [String : Any] { get }
}

实现:

extension JSONMapError: CustomNSError {
    var errorCode: Int {
        switch self {
            case .emptyKey:
                return 404
            case .notConformProtocol:
                return 504
        }
    }
}

调用:

var hotpot = HotpotCat()

do {
    try hotpot.jsonParse()
} catch {
    print(error.localizedDescription)
    print("\(String(describing: (error as? LocalizedError)?.errorDescription))")
    print((error as? CustomNSError)?.errorCode as Any)
}

输出:

The Object doesn't conform JSONMap protocol
Optional("The Object doesn\'t conform JSONMap protocol")
Optional(504)

完整的解析代码:

protocol JSONMap {
    func jsonParse() throws -> Any
}

extension JSONMap {
    func jsonParse() 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? JSONMap {
                if let keyName = children.label {
                    //递归逐层解析数据
                    keyValue[keyName] = try value.jsonParse()//try错误抛出给上层
                } else {
                    throw JSONMapError.emptyKey
                }
            } else {
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

//遵守协议,不然无法调用jsonParse
extension Int: JSONMap {}
extension Double: JSONMap {}
extension String: JSONMap {}


enum JSONMapError: Error {
    case emptyKey
    case notConformProtocol
}

extension JSONMapError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .emptyKey:
            return NSLocalizedString("The Object doesn't has children", comment: "JSONMap object has empty children")
        case .notConformProtocol:
            return NSLocalizedString("The Object doesn't conform JSONMap protocol", comment: "Don't conform json map")
        }
    }
}
    
extension JSONMapError: CustomNSError {
    var errorCode: Int {
        switch self {
            case .emptyKey:
                return 404
            case .notConformProtocol:
                return 504
        }
    }
}

class HotpotCat: JSONMap {
    var age: Int = 18
    var height = 1.85
    var name: String = "hotpot"
}

var hotpot = HotpotCat()

do {
    let jsonData = try hotpot.jsonParse()
    print(jsonData)
} catch {
    print(error.localizedDescription)
    print("\(String(describing: (error as? LocalizedError)?.errorDescription))")
    print((error as? CustomNSError)?.errorCode as Any)
}

//输出
["age": 18, "height": 1.85, "name": "hotpot"]

Mirror源码解析

系统是如何通过Mirror拿到对应的value值?oc中通过runtime很容易做到。Swift是一门静态型语言,系统在底层做了什么样的事情,才让Swift有了反射的特性。

Mirror.swift

public struct Mirror {
public init(reflecting subject: Any) {
    if case let customized as CustomReflectable = subject {
      self = customized.customMirror
    } else {
      self = Mirror(internalReflecting: subject)
    }
  }
}
  • 可以看到Mirror是一个结构体
  • 有一个初始化方法,传进来是一个Any

接着看下ReflectionMirror.swift的实现

image.png

  • 通过_getNormalizedType获取subjectType
  • 通过_getChildCount获取childCount属性大小
  • 遍历将属性拼到集合当中

_getNormalizedType

_getNormalizedType查找传进来的动态类型(真正类型):

@_silgen_name("swift_reflectionMirror_normalizedType")
internal func _getNormalizedType(_: T, type: Any.Type) -> Any.Type

@_silgen_name 关键字
.c文件中写一个c方法,.h中不暴露方法声明。

int testAdd(int a, int b) {
   return a + b;
}

swift文件中调用

@_silgen_name("testAdd")
func swift_testAdd(a: Int32, b: Int32) -> Int32
var resut = swift_testAdd(a: 20, b: 30)
print(resut)//50

只在Swift中进行了方法声明,就调用到了c方法。相当于swift_testAdd调用了testAdd方法。

这里说明_getNormalizedType方法最终会调用swift_reflectionMirror_normalizedType方法,搜索下发现swift_reflectionMirror_normalizedType方法在ReflectionMirror.mm中:

SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value,
                                                      const Metadata *type,
                                                      const Metadata *T) {
  return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; });
}
  • value就是Mirror实例
  • type 就是type(of:)获取的type
  • T就是传进来的类型Mirror(reflecting: hotpot)
  • 返回值是impl->type,结构是ReflectionMirrorImpl
    call方法
template
auto call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType,
          const F &f) -> decltype(f(nullptr))
{
  //
  const Metadata *type;
  OpaqueValue *value;
  std::tie(type, value) = unwrapExistential(T, passedValue);
  //传进来的type不为空,type = passedType
  if (passedType != nullptr) {
    type = passedType;
  }

unwrapExistential方法最终会去metadata中找type,以struct为类分析下TargetStructMetadata->TargetValueMetadata(Description)->TargetMetadata(kind)

 ConstTargetMetadataPointer
  getDescription() const {
    return Description;
  }

Description记录的就是有关于元数据的描述,TargetValueTypeDescriptor -> TargetTypeContextDescriptor(name) -> TargetContextDescriptor(Flags、Parent)
TargetTypeContextDescriptor

  /// The name of the type.
 //这里name记录的就是Int、String、Hotpot类型
  TargetRelativeDirectPointer Name;

还原下Struct metadata

struct StructMetadata {
    var kind: Int
    var desc: UnsafeRawPointer
}

desc结构是什么呢?
TargetValueTypeDescriptor -> TargetTypeContextDescriptor(name) -> TargetContextDescriptor(Flags、Parent)
nameTargetRelativeDirectPointer -> TargetRelativeDirectPointer -> RelativeDirectPointer -> RelativeDirectPointerImpl
RelativeDirectPointerImpl结构如下:

template
class RelativeDirectPointerImpl {
private:
  Offset RelativeOffset;//int32_t
 //RelativeDirectPointer中有两个方法
 operator typename super::PointerTy() const & {
    return this->get();
  }

  const typename super::ValueTy *operator->() const & {
    return this->get();
  }  
};

PointerTyValueTy对应如下,为传进来的T

  using ValueTy = T;
  using PointerTy = T*;

两个方法都调用了this->get()

PointerTy get() const & {
    // Check for null.
    if (Nullable && RelativeOffset == 0)
      return nullptr;
    
    // The value is addressed relative to `this`.
    uintptr_t absolute = detail::applyRelativeOffset(this, RelativeOffset);
    return reinterpret_cast(absolute);
  }

this->get()中调用了applyRelativeOffset方法

template
static inline uintptr_t applyRelativeOffset(BasePtrTy *basePtr, Offset offset) {
  static_assert(std::is_integral::value &&
                std::is_signed::value,
                "offset type should be signed integer");

  auto base = reinterpret_cast(basePtr);
  // We want to do wrapping arithmetic, but with a sign-extended
  // offset. To do this in C, we need to do signed promotion to get
  // the sign extension, but we need to perform arithmetic on unsigned values,
  // since signed overflow is undefined behavior.
  auto extendOffset = (uintptr_t)(intptr_t)offset;
  return base + extendOffset;
}

返回值为base+extendOffset。这里是一个相对指针,本来指针里面存储一个地址,现在存储了相对地址。拿到自己的地址+地址偏移。存储的实际是'地址偏移'。
还原后descname如下:

struct StructMetadataDesc {
    var flags: Int32
    var parent: Int32
    var name: RelativeDirectPointer
}

struct RelativeDirectPointer {
    var offset: Int32
    //由于要操作self 加上 mutating
    mutating func get() -> UnsafeMutablePointer {
        let offset = self.offset
        // 模拟this + offset
        //获取self指针
        return withUnsafePointer(to: &self) { p in
            //为了指针相加,先转成raw pointer再offset绑定到T.self再转回UnsafeMutablePointer
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
    }
}

测试下:

struct Hotpot {
    var age = 18
    var name = "hotpot"
}

var h = Hotpot()
var p = Hotpot.self

//unsafeBitCast 按位强转 Hotpot.self 到 StructMetadata。8字节按位存储。不安全。
let ptr = unsafeBitCast(Hotpot.self as Any.Type, to: UnsafeMutablePointer.self)

//获取name。
/**
 1.ptr.pointer获取StructMetadata指针。
 2.desc.pointee获取desc指针。
 3.name获取type名称指针。
 4.最终通过get方法找到偏移地址存放的结构体名称。
 */
let namePtr = ptr.pointee.desc.pointee.name.get()
print(String(cString: namePtr))

输出:

Hotpot

这里就拿到了结构体的名称。
原理就是找到metadata->desc->desc中的内容
上面的分析总结下就是下面一张图:

image.png

获取属性名和值

回到ReflectionMirror.swift文件上面分析了subjectType,在ReflectionMirror.mm文件中call函数

switch (type->getKind()) {
    case MetadataKind::Tuple: {
      TupleImpl impl;
      return call(&impl);
    }

    case MetadataKind::Struct: {
      StructImpl impl;
      return call(&impl);
    }
    

    case MetadataKind::Enum:
    case MetadataKind::Optional: {
      EnumImpl impl;
      return call(&impl);
    }
      
    case MetadataKind::ObjCClassWrapper:
    case MetadataKind::ForeignClass:
    case MetadataKind::Class: {
      return callClass();
    }

    case MetadataKind::Metatype:
    case MetadataKind::ExistentialMetatype: {
      MetatypeImpl impl;
      return call(&impl);
    }

可以看到根据不同类型传入不同的impl,这里struct传入的是StructImpl:

// Implementation for structs.
struct StructImpl : ReflectionMirrorImpl {
  //找到 Description->isReflectable 是否可反射
  bool isReflectable() {
    const auto *Struct = static_cast(type);
    const auto &Description = Struct->getDescription();
    return Description->isReflectable();
  }
  //
  char displayStyle() {
    return 's';
  }
  //找到 Struct->getDescription()->NumFields
  intptr_t count() {
    if (!isReflectable()) {
      return 0;
    }

    auto *Struct = static_cast(type);
    return Struct->getDescription()->NumFields;//属性的count
  }

可以看到也是从desc中获取。
TargetTypeContextDescriptor中其它字段如下:

TargetRelativeDirectPointer Name;
TargetRelativeDirectPointer AccessFunctionPtr;
TargetRelativeDirectPointer Fields;

TargetValueTypeDescriptor中其它字段如下:

  uint32_t NumFields;
  uint32_t FieldOffsetVectorOffset;

这时候还原后的desc如下:

struct StructMetadataDesc {
    var flags: Int32
    var parent: Int32
    var name: RelativeDirectPointer //存放type的名称
    var accessFunctionPtr: RelativeDirectPointer
    var fields: RelativeDirectPointer
    var numFields: UInt32
    var fieldOffsetVectorOffset: UInt32
}

调用

print(ptr.pointee.desc.pointee.numFields)

输出

2

可以看到有2个属性。
ReflectionMirror.mm中还有一个方法subscript它用来获取属性名称和值。

//获取属性偏移值
  intptr_t childOffset(intptr_t i) {
    //获取metadata
    auto *Struct = static_cast(type);

    if (i < 0 || (size_t)i > Struct->getDescription()->NumFields)
      swift::crash("Swift mirror subscript bounds check failure");

    // Load the offset from its respective vector.
    return Struct->getFieldOffsets()[i];
  }

  const FieldType childMetadata(intptr_t i, const char **outName,
                                void (**outFreeFunc)(const char *)) {
    StringRef name;
    FieldType fieldInfo;
    std::tie(name, fieldInfo) = getFieldAt(type, i);
    assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented");
    
    *outName = name.data();
    *outFreeFunc = nullptr;
    
    return fieldInfo;
  }

//获取属性名称和值
  AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) {
    //属性的名称
    auto fieldInfo = childMetadata(i, outName, outFreeFunc);
    //当前的value
    auto *bytes = reinterpret_cast(value);
    //offset
    auto fieldOffset = childOffset(i);
    //属性的值 = value + offset
    auto *fieldData = reinterpret_cast(bytes + fieldOffset);

    return copyFieldContents(fieldData, fieldInfo);
  }

getFieldAt方法获取属性名

static std::pair
getFieldAt(const Metadata *base, unsigned index) {
  using namespace reflection;
  
  // If we failed to find the field descriptor metadata for the type, fall
  // back to returning an empty tuple as a standin.
  auto failedToFindMetadata = [&]() -> std::pair {
    auto typeName = swift_getTypeName(base, /*qualified*/ true);
    missing_reflection_metadata_warning(
      "warning: the Swift runtime found no field metadata for "
      "type '%*s' that claims to be reflectable. Its fields will show up as "
      "'unknown' in Mirrors\n",
      (int)typeName.length, typeName.data);
    return {"unknown", FieldType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))};
  };
//获取desc
  auto *baseDesc = base->getTypeContextDescriptor();
  if (!baseDesc)
    return failedToFindMetadata();
//通过desc->Fields属性的get方法获取fields
  auto *fields = baseDesc->Fields.get();
  if (!fields)
    return failedToFindMetadata();
  //获取field
  auto &field = fields->getFields()[index];
  // Bounds are always valid as the offset is constant.
  //通过field getFieldName获取name,这里就拿到了属性值的名称
  auto name = field.getFieldName();

Fields结构如下:

image.png

是一个相对指针,存储的是FieldDescriptor

class FieldDescriptor {
  const FieldRecord *getFieldRecordBuffer() const {
    return reinterpret_cast(this + 1);
  }

public:
  const RelativeDirectPointer MangledTypeName;
  const RelativeDirectPointer Superclass;

  FieldDescriptor() = delete;

  const FieldDescriptorKind Kind;
  const uint16_t FieldRecordSize;
  const uint32_t NumFields;

还原后:

struct FieldDescriptor {
    var MangledTypeName: RelativeDirectPointer
    var Superclass: RelativeDirectPointer
    var kind: UInt16
    var FieldRecordSize: UInt16
    var NumFields: UInt32
    var fields: FieldRecord //连续的存储空间,记录属性字段
}

getFieldNameRecords.h

 StringRef getFieldName() const {
    return FieldName.get();
  }

FieldNameFieldRecord中:

class FieldRecord {
  const FieldRecordFlags Flags;

public:
  const RelativeDirectPointer MangledTypeName;
  const RelativeDirectPointer FieldName;

  FieldRecord() = delete;

  bool hasMangledTypeName() const {
    return MangledTypeName;
  }

  StringRef getMangledTypeName() const {
    return Demangle::makeSymbolicMangledNameStringRef(MangledTypeName.get());
  }

  StringRef getFieldName() const {
    return FieldName.get();
  }

  bool isIndirectCase() const {
    return Flags.isIndirectCase();
  }
};

还原下:

struct FieldRecord {
    var Flags: Int32
    var MangledTypeName: RelativeDirectPointer
    var FieldName: RelativeDirectPointer
}

这个时候就可以去取到属性名称了

//获取FieldDescriptor
let fieldDescriptorPtr = ptr.pointee.desc.pointee.fields.get()
//获取recordPtr
let recordPtr = withUnsafePointer(to: &fieldDescriptorPtr.pointee.fields){
//    先把fields转为UnsafeRawPointer,然后移动
    return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to:FieldRecord.self).advanced(by:0))
}
//获取`age`名称,
print(String(cString: recordPtr.pointee.FieldName.get()))
age

总结:

  • Mirror获取metadata->desc
  • 通过desc->name获取类型
  • 通过desc->numFields获取属性个数
  • 用过desc->fields->FieldDescriptor(fields)获取属性描述->FieldName获取属性名
    image.png

    demo
    参考
    https://www.sohu.com/a/275678142_470093
    https://swift.gg/2018/11/15/how-mirror-works/

你可能感兴趣的:(Swift底层探索:反射)