反射(Reflection)是指程序在运行时检查、访问和修改自身结构(类型、属性、方法等)的能力。在 iOS 开发中,Objective-C 和 Swift 分别提供了不同层次的反射支持:OC 依赖 Runtime 提供完整的动态反射能力,Swift 则通过 Mirror 提供只读的内省能力。

Objective-C 的反射能力

Objective-C 的反射能力完全建立在 Runtime 之上。Runtime 在运行时维护了完整的类型元数据(类对象、元类对象、方法列表、属性列表、成员变量列表、协议列表等),并通过一系列 C 函数 API 暴露给开发者,使得程序可以在运行时动态地查询和修改几乎所有类型信息。

关于 Runtime 的消息发送、消息转发、Method Swizzling、关联对象等核心能力,请参考 runtime。本节聚焦于 Runtime 中与"反射"直接相关的能力——即运行时的类型内省和动态操作。

类型内省

类型内省(Introspection)是反射中最基础的能力——在运行时查询一个对象的类型信息。

NSObject 提供的内省方法

NSObject 定义了一组内省方法,这些方法底层都依赖 Runtime 的 isa 指针和类型元数据来实现:

id obj = [[NSMutableArray alloc] init];

// 类型判断
[obj isKindOfClass:[NSArray class]];           // YES — 判断是否是某个类或其子类的实例
[obj isMemberOfClass:[NSMutableArray class]];  // YES — 判断是否是某个类的直接实例
[obj conformsToProtocol:@protocol(NSCoding)];  // YES — 判断是否遵循某个协议

// 方法响应检查
[obj respondsToSelector:@selector(addObject:)]; // YES — 判断是否能响应某个消息

// 获取类型信息
NSStringFromClass([obj class]);          // @"__NSArrayM"(真实的私有子类名)
NSStringFromSelector(@selector(count));  // @"count"

需要注意 isKindOfClass:isMemberOfClass: 的区别:前者沿继承链向上查找,后者只比较当前类。另外,[obj class] 返回的可能是私有子类(如类簇 NSArray 的实际类是 __NSArrayI__NSArrayM),而 object_getClass(obj) 能获取真正的 isa 指向的类(KVO 场景下会是 NSKVONotifying_ 前缀的子类)。

Runtime API 的类型查询

除了 NSObject 的方法,Runtime 提供了更底层的 C 函数 API 来获取详细的类型元数据:

#import <objc/runtime.h>

Class cls = [MyClass class];

// 获取类名和继承关系
const char *name = class_getName(cls);           // "MyClass"
Class superCls = class_getSuperclass(cls);       // 父类
BOOL isMeta = class_isMetaClass(cls);            // 是否是元类
size_t size = class_getInstanceSize(cls);        // 实例大小(字节)

// 获取属性列表
unsigned int propCount;
objc_property_t *props = class_copyPropertyList(cls, &propCount);
for (unsigned int i = 0; i < propCount; i++) {
    const char *propName = property_getName(props[i]);
    const char *propAttrs = property_getAttributes(props[i]);
    NSLog(@"属性: %s, 特性: %s", propName, propAttrs);
}
free(props);

// 获取成员变量列表
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
for (unsigned int i = 0; i < ivarCount; i++) {
    const char *ivarName = ivar_getName(ivars[i]);
    const char *ivarType = ivar_getTypeEncoding(ivars[i]);
    ptrdiff_t offset = ivar_getOffset(ivars[i]);
    NSLog(@"成员变量: %s, 类型编码: %s, 偏移量: %td", ivarName, ivarType, offset);
}
free(ivars);

// 获取方法列表
unsigned int methodCount;
Method *methods = class_copyMethodList(cls, &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
    SEL sel = method_getName(methods[i]);
    const char *typeEncoding = method_getTypeEncoding(methods[i]);
    NSLog(@"方法: %@, 类型编码: %s", NSStringFromSelector(sel), typeEncoding);
}
free(methods);

// 获取协议列表
unsigned int protocolCount;
Protocol * __unsafe_unretained *protocols = class_copyProtocolList(cls, &protocolCount);
for (unsigned int i = 0; i < protocolCount; i++) {
    NSLog(@"协议: %s", protocol_getName(protocols[i]));
}
free(protocols);

这些 API 返回的都是 copy 语义的数组(内部使用 malloc 分配),调用方必须手动 free

类型编码

Runtime 使用类型编码(Type Encoding)字符串来描述类型信息。编译器会为每个方法的返回值和参数生成对应的类型编码,存储在方法元数据中。常见的编码:

编码类型编码类型
cchariint
sshortllong
qlong longffloat
ddoubleBBOOL(C++ bool)
vvoid@id(ObjC 对象)
#Class:SEL
^typetype 指针{name=...}struct

例如 "v@:" 表示:返回 void、参数为 id(self)和 SEL(_cmd),这是最常见的无参方法的类型编码。

"@24@0:8@16" 表示:返回 id、总大小 24 字节、第一个参数 id(self)在偏移 0、第二个参数 SEL(_cmd)在偏移 8、第三个参数 id 在偏移 16。

属性的特性编码(property_getAttributes)采用不同的格式。例如 "T@\"NSString\",C,N,V_name" 表示:类型是 NSString *copy 语义、nonatomic、对应的实例变量名是 _name

属性编码含义
T类型
Rreadonly
Ccopy
&retain/strong
Nnonatomic
Wweak
V实例变量名

动态操作

OC Runtime 的反射能力不仅限于"查看",还支持在运行时动态修改类型结构。

动态创建类

// 创建 NSObject 的子类 "DynamicPerson"
Class PersonClass = objc_allocateClassPair([NSObject class], "DynamicPerson", 0);

// 添加成员变量(必须在 objc_registerClassPair 之前)
class_addIvar(PersonClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
class_addIvar(PersonClass, "_age", sizeof(int), log2(sizeof(int)), @encode(int));

// 添加方法
class_addMethod(PersonClass, @selector(description), (IMP)personDescription, "@@:");

// 注册类(注册后不能再添加成员变量)
objc_registerClassPair(PersonClass);

// 使用
id person = [[PersonClass alloc] init];
[person setValue:@"Tom" forKey:@"name"];
NSLog(@"%@", person);  // 调用 personDescription

NSString *personDescription(id self, SEL _cmd) {
    Ivar nameIvar = class_getInstanceVariable([self class], "_name");
    NSString *name = object_getIvar(self, nameIvar);
    return [NSString stringWithFormat:@"DynamicPerson: %@", name];
}

注册后不能再添加成员变量,这是因为 objc_registerClassPair 会将 instanceSizeivars 列表等信息固化到 class_ro_t 中。但方法、属性、协议可以在注册后随时添加,因为它们存储在可写的 class_rw_t 中。

动态方法操作

// 添加方法
class_addMethod([MyClass class], @selector(newMethod), (IMP)newMethodIMP, "v@:");

// 替换方法实现
class_replaceMethod([MyClass class], @selector(existingMethod), (IMP)newIMP, "v@:");

// 交换两个方法的实现
Method m1 = class_getInstanceMethod([MyClass class], @selector(method1));
Method m2 = class_getInstanceMethod([MyClass class], @selector(method2));
method_exchangeImplementations(m1, m2);

// 获取和设置方法实现
IMP oldIMP = method_getImplementation(m1);
method_setImplementation(m1, (IMP)customIMP);

通过字符串动态访问

OC 的反射支持从字符串到运行时对象的动态转换:

// 字符串 → 类
Class cls = NSClassFromString(@"UIViewController");
id vc = [[cls alloc] init];

// 字符串 → SEL
SEL sel = NSSelectorFromString(@"viewDidLoad");
if ([vc respondsToSelector:sel]) {
    [vc performSelector:sel];
}

// 反向转换
NSString *className = NSStringFromClass([vc class]);   // @"UIViewController"
NSString *selName = NSStringFromSelector(sel);         // @"viewDidLoad"

这在路由系统、解耦模块间通信等场景中广泛使用。例如组件化架构中,A 模块不需要直接 #import B 模块的头文件,通过 NSClassFromStringNSSelectorFromString 就能动态创建对象和调用方法。

Objective-C 反射的局限性

虽然 OC Runtime 的反射能力非常强大,但也有明确的局限:

  1. 无法获取局部变量:Runtime 只能访问类的元数据(成员变量、方法等),函数内部的局部变量在栈上分配,不在 Runtime 的管辖范围
  2. 类型编码信息有限:泛型参数会被擦除(如 NSArray<NSString *> 的类型编码只有 @"NSArray"),无法在运行时获取集合的元素类型
  3. 安全性风险:Runtime 允许访问私有成员变量、修改方法实现,这虽然提供了极大的灵活性,但也带来了安全和稳定性风险。App Store 审核会拒绝使用私有 API 的应用
  4. Swift 类型不完全兼容:纯 Swift 的 struct、enum 没有 OC Runtime 元数据,只有继承自 NSObject 的 Swift 类才能使用 OC 的反射 API

Swift 的反射能力

Swift 的反射设计哲学与 OC 截然不同:OC 追求最大的运行时灵活性(完整的动态反射),Swift 则以编译时安全和性能为优先,只提供有限的运行时内省能力。

Mirror:Swift 的内省机制

Mirror 是 Swift 标准库提供的反射 API,它能在运行时检查任意值的结构,但仅支持只读内省——可以查看但不能修改。

基本用法

struct Person {
    let name: String
    var age: Int
    private var secret: String = "hidden"
}

let person = Person(name: "Tom", age: 25)
let mirror = Mirror(reflecting: person)

// 获取类型信息
print(mirror.subjectType)        // Person
print(mirror.displayStyle)       // Optional(.struct)

// 遍历所有存储属性(包括 private)
for child in mirror.children {
    print("\(child.label ?? "nil"): \(child.value)")
}
// 输出:
// name: Tom
// age: 25
// secret: hidden

Mirror 有几个关键特点:

  1. 可以访问 private 属性:Mirror 绕过了 Swift 的访问控制,能看到所有存储属性
  2. 只反映存储属性:计算属性不会出现在 children
  3. 值是 Any 类型child.value 的类型是 Any,需要类型转换后才能使用

Mirror.Child 结构

Mirrorchildren 属性返回 AnyCollection<Mirror.Child>,每个 Child 是一个 (label: String?, value: Any) 的命名元组:

for child in mirror.children {
    let label = child.label   // 属性名(可选,元组元素可能没有名字)
    let value = child.value   // 属性值(Any 类型)
    let valueMirror = Mirror(reflecting: value)
    print("属性: \(label ?? "?"), 值: \(value), 类型: \(valueMirror.subjectType)")
}

displayStyle

displayStyle 表示被反射值的展示风格,帮助判断值的类型分类:

displayStyle对应类型
.struct结构体
.class
.enum枚举
.tuple元组
.optionalOptional
.collectionArray
.dictionaryDictionary
.setSet
nil基础类型(Int、String 等)

继承链反射

对于类继承体系,MirrorsuperclassMirror 可以获取父类的 Mirror,从而遍历整个继承链的属性:

class Animal {
    var species: String = "Unknown"
}

class Dog: Animal {
    var name: String = "Buddy"
    var breed: String = "Labrador"
}

let dog = Dog()
var currentMirror: Mirror? = Mirror(reflecting: dog)

while let mirror = currentMirror {
    print("--- \(mirror.subjectType) ---")
    for child in mirror.children {
        print("  \(child.label ?? "?"): \(child.value)")
    }
    currentMirror = mirror.superclassMirror
}
// 输出:
// --- Dog ---
//   name: Buddy
//   breed: Labrador
// --- Animal ---
//   species: Unknown

注意:Mirror(reflecting: dog)children 只包含 Dog 自身定义的属性,不包含从 Animal 继承的属性。要获取继承的属性,需要通过 superclassMirror 逐层向上遍历。

枚举的反射

Mirror 对枚举有特殊的处理方式:

enum NetworkError {
    case timeout
    case serverError(code: Int, message: String)
    case noConnection
}

let error = NetworkError.serverError(code: 500, message: "Internal Server Error")
let mirror = Mirror(reflecting: error)

print(mirror.displayStyle)       // Optional(.enum)

for child in mirror.children {
    print("case: \(child.label ?? "?"), 关联值: \(child.value)")
}
// 输出:
// case: serverError, 关联值: (code: 500, message: "Internal Server Error")

对于有关联值的枚举 case,label 是 case 名称,value 是关联值(如果有多个关联值则是元组)。对于无关联值的 case(如 .timeout),children 为空,此时可以通过 String(describing:) 获取 case 名称。

Mirror 的底层原理

Mirror 并不是简单的"语法糖",它的底层依赖 Swift 编译器在 Mach-O 中生成的元数据。

元数据来源

Swift 编译器会在 Mach-O 的特定 Section 中生成反射所需的元数据:

Section内容用途
__swift5_fieldmd字段描述符(Field Descriptor)每个类型的存储属性名称和类型引用
__swift5_reflstr反射字符串属性名称的字符串常量
__swift5_types类型描述符(Type Descriptor)类型的名称、泛型参数、字段数量等
__swift5_typeref类型引用属性类型的 Mangled Name 引用

当创建 Mirror(reflecting:) 时,Swift Runtime 通过值的类型元数据指针(Type Metadata)找到对应的 Type Descriptor,再从中找到 Field Descriptor,逐一读取字段名称和值。

Mirror 的创建流程

Mirror(reflecting: value)
获取 value 的类型元数据(Type Metadata)
查找 Type Descriptor → Field Descriptor
遍历 Field Descriptor 中的字段记录
        ├── 读取字段名称(从 __swift5_reflstr)
        ├── 计算字段偏移量(从元数据中的 Field Offset Vector)
        └── 读取字段值(从 value 内存 + 偏移量)
构建 Mirror.Child 数组

字段偏移量存储在类型元数据结构中的 Field Offset Vector 区域。对于 struct,偏移量在编译期确定,存储在元数据的固定位置;对于 class,由于可能涉及继承和运行时调整,偏移量在类初始化时计算并存储。

CustomReflectable 协议

Swift 允许类型通过遵循 CustomReflectable 协议来自定义反射行为:

struct Credentials: CustomReflectable {
    let username: String
    let password: String
    
    var customMirror: Mirror {
        Mirror(self, children: [
            "username": username,
            "password": String(repeating: "*", count: password.count)
        ])
    }
}

let creds = Credentials(username: "admin", password: "secret123")
let mirror = Mirror(reflecting: creds)
for child in mirror.children {
    print("\(child.label ?? "?"): \(child.value)")
}
// 输出:
// username: admin
// password: *********

这在调试输出、日志记录等场景中很有用——可以隐藏敏感信息,或者将复杂的内部结构转换为更易读的形式。

Mirror(reflecting:) 检测到值遵循了 CustomReflectable 协议时,会调用 customMirror 属性获取自定义的 Mirror,而不是使用默认的元数据反射。

Swift 反射的局限性

Mirror 提供的是有限的内省能力,与 OC Runtime 的完整反射相比有明显限制:

能力OC RuntimeSwift Mirror
读取属性值支持支持
修改属性值支持(object_setIvar不支持
获取方法列表支持(class_copyMethodList不支持
调用任意方法支持(objc_msgSend不支持
动态添加方法支持(class_addMethod不支持
动态创建类型支持(objc_allocateClassPair不支持
获取协议列表支持(class_copyProtocolList不支持
访问私有属性支持支持(只读)

Swift 故意限制了反射能力,这是设计上的取舍:

  1. 编译时安全:Swift 强调类型安全,大量的类型检查在编译期完成,不鼓励运行时动态操作
  2. 性能优化:Swift 编译器依赖静态类型信息进行优化(如内联、泛型特化),如果支持完整反射,很多优化将无法进行
  3. 二进制大小:完整的反射元数据会显著增加二进制大小。实际上,-Osize 优化级别下编译器会裁剪部分反射元数据

实际应用场景

1. 通用调试输出

利用 Mirror 实现一个通用的对象描述函数,自动打印所有属性:

func dump<T>(_ value: T, indent: Int = 0) -> String {
    let mirror = Mirror(reflecting: value)
    let prefix = String(repeating: "  ", count: indent)
    
    if mirror.children.isEmpty {
        return "\(value)"
    }
    
    var lines = ["\(mirror.subjectType) {"]
    for child in mirror.children {
        let childDesc = dump(child.value, indent: indent + 1)
        lines.append("\(prefix)  \(child.label ?? "?"): \(childDesc)")
    }
    lines.append("\(prefix)}")
    return lines.joined(separator: "\n")
}

Swift 标准库已经提供了全局的 dump() 函数,内部就是基于 Mirror 实现的,它会递归打印对象的所有属性结构,日常调试时可以直接使用。

2. JSON 序列化

Mirror 可以用于实现简单的 JSON 序列化(虽然实际项目中应使用 Codable):

func toJSON<T>(_ value: T) -> Any {
    let mirror = Mirror(reflecting: value)
    
    if mirror.children.isEmpty {
        return value
    }
    
    if mirror.displayStyle == .collection {
        return mirror.children.map { toJSON($0.value) }
    }
    
    if mirror.displayStyle == .dictionary {
        var dict: [String: Any] = [:]
        for child in mirror.children {
            if let pair = child.value as? (key: AnyHashable, value: Any) {
                dict["\(pair.key)"] = toJSON(pair.value)
            }
        }
        return dict
    }
    
    var dict: [String: Any] = [:]
    for child in mirror.children {
        guard let label = child.label else { continue }
        let childMirror = Mirror(reflecting: child.value)
        if childMirror.displayStyle == .optional {
            if let firstChild = childMirror.children.first {
                dict[label] = toJSON(firstChild.value)
            }
        } else {
            dict[label] = toJSON(child.value)
        }
    }
    return dict
}

3. OC 字典转模型

这是 OC Runtime 反射最经典的应用场景,利用 class_copyIvarListclass_copyPropertyList 自动完成字典到模型的映射:

+ (instancetype)modelWithDict:(NSDictionary *)dict {
    id obj = [[self alloc] init];
    
    unsigned int count;
    objc_property_t *props = class_copyPropertyList(self, &count);
    
    for (unsigned int i = 0; i < count; i++) {
        NSString *propName = [NSString stringWithUTF8String:property_getName(props[i])];
        id value = dict[propName];
        if (value && value != [NSNull null]) {
            [obj setValue:value forKey:propName];
        }
    }
    
    free(props);
    return obj;
}

这是 MJExtension、YYModel 等 JSON 转模型框架的核心思路。完整的实现还需要处理类型转换、嵌套模型、数组泛型、属性名与 JSON key 的映射等问题,但底层都依赖 Runtime 的反射能力。

4. 路由系统

在组件化架构中,模块间通过路由进行解耦通信,核心依赖 OC 的字符串-类型转换能力:

- (UIViewController *)viewControllerForURL:(NSURL *)url {
    NSString *path = url.path;  // 例如 "/user/profile"
    
    // 路由表:URL path → 类名
    NSDictionary *routeMap = @{
        @"/user/profile": @"UserProfileViewController",
        @"/order/detail": @"OrderDetailViewController",
    };
    
    NSString *className = routeMap[path];
    if (!className) return nil;
    
    Class cls = NSClassFromString(className);
    if (!cls) return nil;
    
    UIViewController *vc = [[cls alloc] init];
    
    // 通过 URL 参数自动设置属性
    NSDictionary *params = [self parseQueryParams:url];
    for (NSString *key in params) {
        // 构造 setter 名称:key "userId" → "setUserId:"
        NSString *setterName = [NSString stringWithFormat:@"set%@%@:",
            [[key substringToIndex:1] uppercaseString],
            [key substringFromIndex:1]];
        if ([vc respondsToSelector:NSSelectorFromString(setterName)]) {
            [vc setValue:params[key] forKey:key];
        }
    }
    
    return vc;
}

这里涉及了 OC 反射的多个能力:NSClassFromString(字符串动态创建类)、NSSelectorFromString(字符串动态构造 SEL)、respondsToSelector:(运行时内省检查)、KVC 的 setValue:forKey:(通过字符串 key 动态赋值)。

5. 自动化测试辅助

利用 Mirror 验证模型对象的属性是否符合预期,无需为每个属性手写断言:

func assertEqual<T>(_ actual: T, _ expected: T, file: StaticString = #file, line: UInt = #line) {
    let actualMirror = Mirror(reflecting: actual)
    let expectedMirror = Mirror(reflecting: expected)
    
    for (actualChild, expectedChild) in zip(actualMirror.children, expectedMirror.children) {
        let actualStr = String(describing: actualChild.value)
        let expectedStr = String(describing: expectedChild.value)
        if actualStr != expectedStr {
            print("属性 \(actualChild.label ?? "?") 不匹配: 实际=\(actualStr), 期望=\(expectedStr)")
        }
    }
}

OC 与 Swift 反射的对比

维度Objective-C RuntimeSwift Mirror
设计理念最大灵活性,运行时决定一切编译时安全优先,运行时内省为辅
反射深度完整反射(读/写/增/删/改)只读内省(只能查看)
支持类型ObjC 类(继承自 NSObject)所有 Swift 类型(struct/class/enum/tuple)
方法反射支持(方法列表、动态调用)不支持
属性修改支持(object_setIvar、KVC)不支持
性能有运行时开销(哈希查找、间接跳转)有运行时开销(元数据查找、Any 装箱)
安全性低(可访问私有 API、修改内部实现)高(只读,不影响运行时行为)
元数据存储class_rw_t / class_ro_tMach-O 的 __swift5_fieldmd 等 Section

对于继承自 NSObject 的 Swift 类,两套反射系统可以共存:

class MyObject: NSObject {
    @objc var name: String = "Tom"
    var age: Int = 25    // 非 @objc 属性
}

let obj = MyObject()

// Swift Mirror:能看到所有存储属性
let mirror = Mirror(reflecting: obj)
for child in mirror.children {
    print("\(child.label ?? "?"): \(child.value)")
}
// name: Tom
// age: 25

// OC Runtime:只能看到 @objc 属性
var count: UInt32 = 0
let properties = class_copyPropertyList(type(of: obj), &count)
for i in 0..<Int(count) {
    let name = String(cString: property_getName(properties![i]))
    print("OC property: \(name)")
}
free(properties)
// OC property: name(age 不会出现,因为没有 @objc 标记)

常见面试题

Q1: iOS 中有哪些反射机制?OC 和 Swift 的反射有什么区别?

iOS 中有两套反射机制:

Objective-C Runtime 反射:提供完整的动态反射能力。通过 Runtime API 可以在运行时获取类的属性列表、方法列表、成员变量列表、协议列表等元数据,还可以动态创建类、添加方法、修改方法实现(Method Swizzling)、通过字符串创建对象(NSClassFromString)等。元数据存储在类对象的 class_rw_t / class_ro_t 结构中。只支持 ObjC 类(继承自 NSObject 的对象)。

Swift Mirror 反射:提供有限的只读内省能力。通过 Mirror(reflecting:) 可以在运行时获取任意 Swift 值的类型名称、存储属性名和值、displayStyle(struct/class/enum/optional 等),还可以通过 superclassMirror 遍历类的继承链。底层依赖编译器在 Mach-O 中生成的元数据(__swift5_fieldmd__swift5_reflstr 等)。支持所有 Swift 类型(struct、class、enum、tuple),但不能修改属性、不能获取方法列表、不能动态调用方法。

核心区别:OC 反射追求最大灵活性(可读可写可改),Swift 反射追求编译时安全(只读内省)。对于继承自 NSObject 的 Swift 类,两套系统可以共存,但 OC Runtime 只能看到 @objc 标记的属性和方法。

Q2: Swift 的 Mirror 底层是怎么实现的?

Mirror 的底层依赖 Swift 编译器在 Mach-O 中生成的反射元数据。主要涉及以下几个 Section:

  • __swift5_fieldmd(Field Descriptor):存储每个类型的字段描述信息,包括字段数量和每个字段的名称引用、类型引用
  • __swift5_reflstr(Reflection Strings):存储字段名称的字符串常量
  • __swift5_types(Type Descriptor):存储类型的描述信息,包括类型名称、泛型参数、字段数量、Field Descriptor 的相对偏移
  • __swift5_typeref(Type References):存储字段类型的 Mangled Name 引用

创建 Mirror(reflecting: value) 时,Swift Runtime 首先通过值的类型元数据指针找到 Type Descriptor,再通过其中的 Field Descriptor 引用定位到字段描述信息。然后逐一读取字段名称(从 __swift5_reflstr)和字段偏移量(从元数据中的 Field Offset Vector),最终从值的内存地址加上偏移量处读取字段值,构建出 Mirror.Child 数组。

如果值的类型遵循了 CustomReflectable 协议,Mirror 会优先调用 customMirror 属性获取自定义的反射结果,跳过默认的元数据反射流程。

Q3: OC 的字典转模型是怎么通过反射实现的?

核心思路是利用 Runtime API 获取模型类的所有属性(或成员变量),然后遍历这些属性,从字典中取出对应 key 的值,通过 KVC 赋值给模型对象。

基本实现步骤:

  1. 调用 class_copyPropertyList(或 class_copyIvarList)获取类的属性列表
  2. 遍历属性列表,通过 property_getName 获取属性名
  3. 以属性名为 key 从字典中取值
  4. 通过 setValue:forKey:(KVC)将值设置到模型对象上
  5. 调用 free 释放属性列表的内存

实际的 JSON 转模型框架(如 MJExtension、YYModel)还需要额外处理:类型转换(如字符串转数字)、嵌套模型递归转换、数组中的模型类型识别(通过约定的类方法返回数组元素类型)、属性名与 JSON key 的映射关系、NSNull 处理等。YYModel 为了追求性能,还使用了 objc_msgSend 直接调用 setter 而非 KVC,并且缓存了属性的类型信息避免重复解析。

Q4: NSClassFromString 在什么场景下使用?有什么注意事项?

NSClassFromString 将字符串转换为 Class 对象,常用于以下场景:

  1. 路由系统:组件化架构中,模块间通过 URL → 类名字符串 → NSClassFromString → 创建对象的方式实现解耦
  2. 动态加载:根据配置或服务端下发的类名,动态创建不同的视图控制器或策略对象
  3. 避免硬依赖:当需要使用某个类但不想直接 import 它的头文件时(如可选依赖的 SDK)

注意事项:

  • 如果类不存在,返回 nil,不会崩溃,因此必须做非空判断
  • Swift 类的命名空间:Swift 类在 Runtime 中的名称包含模块名前缀(如 MyApp.MyViewController),使用 NSClassFromString 时必须传入完整的 "模块名.类名" 格式,否则返回 nil。如果 Swift 类标记了 @objc(CustomName),则使用括号中的名称
  • 不要在性能敏感的路径中频繁调用,因为涉及全局类表的哈希查找