Swift中——Mirror映射机制

36 阅读3分钟

Swift 中的 Mirror 类型,它是 Swift 提供的反射机制核心工具,核心作用是在运行时获取任意对象的结构信息(比如属性名、属性值、类型、继承关系等),即使你不知道对象的具体类型,也能通过 Mirror 解析它的内部结构。

一、核心概念

Mirror 就像一个 “运行时结构解析器”:

  • 基于反射(Reflection)  机制,突破 Swift 静态类型的限制,在运行时探索对象的结构;
  • 可解析的信息包括:对象的类型、属性(名称 + 值 + 类型)、是否是集合 / 枚举 / 类 / 结构体、继承的父类等;
  • 核心 API:Mirror(reflecting: 目标对象) —— 创建一个 Mirror 实例,用于解析目标对象;
  • 无侵入性:无需修改目标类型的代码,即可解析其结构。

二、基础用法

1. 基本语法

// 1. 创建 Mirror 实例(传入要解析的对象)
let targetObject = SomeType()
let mirror = Mirror(reflecting: targetObject)

// 2. 遍历 Mirror 的子节点(即对象的属性/元素)
for case let (label?, value) in mirror.children {
    // label:属性名(可选值,集合元素的label为nil)
    // value:属性值(类型为Any,需手动类型转换)
    print("属性名:(label),值:(value),类型:(type(of: value))")
}

// 3. 获取对象的基本信息
print("对象类型:(mirror.subjectType)") // 目标对象的类型(如 LevelIcon.Type)
print("是否是集合:(mirror.displayStyle == .collection)") // 判断类型类别

2. 核心属性 / 方法说明

属性 / 方法作用
children子节点集合(属性 / 元素),类型为 AnyCollection<Mirror.Child>
subjectType目标对象的类型(Any.Type
displayStyle对象的类型类别(枚举值:.struct/.class/.enum/.collection等)
superclassMirror父类的 Mirror(仅类对象有,结构体 / 枚举为 nil)

三、实战示例

1. 解析结构体

struct LevelIcon{
    let type: String
    let url: String
    let priority: Int
    let isVip: Bool = true
}

// 封装的Mirror 解析模型
    func parseModel<T>(_ model: T){
        let mirror = Mirror(reflecting: model)
        print("===== 解析 \(mirror.subjectType) 模型 =====")
        
        for item in mirror.children {
            print("属性名:\(item.label)= value:\(item.value)")
        }
        
        print("获取类型 \(mirror.displayStyle)")
        // 判断模型类型
       if mirror.displayStyle == .struct {
           print("该对象是结构体类型")
       }
    }
  // 创建模型对象
  let nobleIcon = LevelIcon(type: "noble", url: "noble_icon.png", priority: 1)
 //调用
 parseModel(nobleIcon)
  //结果
    ===== 解析 LevelIcon 模型 =====
    属性名:Optional("type")= value:noble
    属性名:Optional("url")= value:noble_icon.png
    属性名:Optional("priority")= value:1
    属性名:Optional("isVip")= value:true
    获取类型 Optional(Swift.Mirror.DisplayStyle.struct)
    该对象是结构体类型

2、解析数组 / 集合

    let iconUrs = ["noble_icon.webp", "svip_icon.webp", "wealth_icon.webp"]
    let collectionMirror = Mirror(reflecting: iconUrs)
    print("集合类型:\(collectionMirror.subjectType)")
    print("是否是集合:\(collectionMirror.displayStyle == .collection)")
    for (index,value) in collectionMirror.children.enumerated() {
        print("索引\(index) = \(value.label) - \(value.value)")
    }
    //结果 
    集合类型:Array<String>
    是否是集合:true
    索引0 = nil - noble_icon.webp
    索引1 = nil - svip_icon.webp
    索引2 = nil - wealth_icon.webp

3、解析类对象(含父类属性)

如果你的模型是类(Class),Mirror 还能解析父类的属性

// 子类
    class VipLevel: BaseLevel {
        let vipExp: Int = 1000
        let iconUrl: String = "vip_icon.webp"
    }

    // 解析子类(含父类属性)
    func parseClassWithSuper<T>(_ object: T) {
        var currentMirror: Mirror? = Mirror(reflecting: object)

        // 循环解析自身 + 父类属性
        while let mirror = currentMirror {
            print("===== 解析 \(mirror.subjectType) =====")
            for case let (label?, value) in mirror.children {
                print("属性:\(label) = \(value)")
            }
            // 切换到父类的 Mirror
            currentMirror = mirror.superclassMirror
        }
    }

    // 调用
    let vip = VipLevel()
    parseClassWithSuper(vip)
       
===== 解析 VipLevel =====
属性:vipExp = 1000
属性:iconUrl = vip_icon.webp
===== 解析 BaseLevel =====
属性:id = 0
属性:name = 基础等级

四、关键注意事项(避坑)

1. 私有属性无法解析

Mirror 只能解析公开(public/internal)  的属性,私有(private/fileprivate)属性不会出现在 children 中:

struct Test {
    public let pubProp = "公开属性"
    private let priProp = "私有属性"
}

let mirror = Mirror(reflecting: Test())
for child in mirror.children {
    print(child.label!) // 仅输出:pubProp(私有属性被忽略)
}

2. 值类型转换需谨慎

Mirror 的 value 是 Any 类型,强制类型转换(as!)可能崩溃,需用可选绑定(as?):

// 错误示例:强制转换非字符串类型会崩溃
    // let str = child.value as! String

    // 正确示例:安全转换
    if let str = child.value as? String {
    // 处理字符串
    } else if let int = child.value as? Int {
    // 处理整数
    }

3. 集合 / 枚举的解析特点

  • 集合(数组 / 字典 / Set)的 children 是其元素,但 label 为 nil,需通过 enumerated() 手动加索引;
  • 枚举的 displayStyle 为 .enum,若枚举带关联值,children 会包含关联值(label 为关联值名称)。

4. 性能注意

反射是运行时操作,性能远低于静态代码,不要在高频场景(如循环遍历上万条数据)  中使用,适合工具类场景(日志、序列化)。