objective c 动态语言

99ANYc3cd6
预计阅读时长 19 分钟
位置: 首页 C语言 正文

Objective-C 是一门动态语言,这个特性是其与 C++、Java 等静态语言最根本的区别之一,它的“动态性”体现在程序的很多方面,允许开发者在运行时(而非编译时)检查和修改程序的结构和行为。

objective c 动态语言
(图片来源网络,侵删)

这种动态性主要源于其三个核心机制:消息传递运行时系统动态类型


核心思想:消息传递 vs. 函数调用

这是理解 Objective-C 动态性的基石。

在 C++、Java 等静态语言中,调用一个方法本质上是函数调用,编译器在编译时会确定调用哪个函数的地址,并将其硬编码到指令中,如果调用了一个不存在的方法,编译器会直接报错。

// C++ 的静态函数调用
obj->someMethod(); // 编译器在编译时就知道 someMethod 的地址

而在 Objective-C 中,调用一个方法被称为发送消息,它不关心接收对象具体是什么类型,只关心这个对象是否能“响应”这个消息。

objective c 动态语言
(图片来源网络,侵删)
// Objective-C 的动态消息传递
[obj someMethod]; // 编译器不关心 obj 是否有 someMethod 方法

这个过程由运行时系统负责:

  1. 编译时:编译器会将 [obj someMethod] 转换成一条底层的 objc_msgSend 函数调用,objc_msgSend(obj, @selector(someMethod)),它只是打包了“接收者”、“选择器”(方法名)和参数,然后发送给运行时系统。
  2. 运行时:当程序执行到这一行时,运行时系统接管,它会按照以下步骤查找方法:
    • 缓存查找:首先在接收对象的缓存方法列表中查找,如果找到,则直接执行,这是最快的路径。
    • 方法列表查找:如果缓存中没有,运行时系统会沿着继承链,在接收对象及其父类的“方法列表”(Method Lists)中逐个查找与 @selector(someMethod) 对应的函数指针。
    • 动态方法解析:如果在整个继承链中都找不到,运行时系统会询问:“嘿,这个对象不能响应这个消息,你有什么办法吗?” 这会触发 +resolveInstanceMethod:+resolveClassMethod: 方法,你可以在这个方法中动态地为该类添加一个方法实现,如果这里返回 NO,流程继续。
    • 消息转发:如果动态方法解析也失败了,运行时系统会进入最后一步:消息转发,它会依次调用两个方法:
      • +forwardingTargetForSelector::你可以返回一个能处理该消息的“替代”对象,这是一种“代理”模式。
      • 如果上面返回 nil,则会调用 -methodSignatureForSelector:forwardInvocation:,你可以在这里完全自定义消息的处理逻辑,甚至可以修改参数、返回值,或者将多个消息合并成一个。

这种机制带来的好处是:

  • 极大的灵活性:可以在运行时决定调用哪个方法,实现插件化架构、AOP(面向切面编程)等高级功能。
  • 健壮性:即使一个对象没有某个方法,程序也不会立即崩溃,而是有机会通过动态方法解析或消息转发来优雅地处理。

关键运行时特性

Objective-C 的动态性主要通过运行时系统的一系列 API 来暴露给开发者。

a. 动态类型

可以使用 id 类型来指向任何 Objective-C 对象。id 是一个指向对象内存的指针,编译器在编译时不会检查它具体是什么类。

objective c 动态语言
(图片来源网络,侵删)
id dynamicObject = [[SomeClass alloc] init];
[dynamicObject someMethod]; // 编译器不检查 someMethod 是否存在

b. 反射 - 方法交换

这是 Objective-C 最著名、最强大的动态特性之一,你可以在运行时交换两个方法的实现。

实现原理: 每个类都有一个方法列表,这是一个 Method 结构体数组。Method 结构体包含了方法名(SEL)和方法实现(IMP,一个函数指针),交换方法,就是交换这两个 Method 结构体中的 IMP

常用场景

  • AOP(面向切面编程):在不修改原有代码的情况下,为方法添加额外功能,如日志、性能监控、权限检查等。
  • Mock 测试:在单元测试中,替换掉一个依赖的方法,使其返回预设的值。

代码示例:为 UIViewsetBackgroundColor: 方法添加日志。

#import <objc/runtime.h>
// 定义一个用于交换的类别
@implementation UIView (Swizzling)
+ (void)load {
    // load 方法在类被加载到内存时调用,且只会调用一次,是进行方法交换的最佳时机
    Method originalMethod = class_getInstanceMethod(self, @selector(setBackgroundColor:));
    Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzle_setBackgroundColor:));
    // 交换方法实现
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
// 自定义的方法
- (void)swizzle_setBackgroundColor:(UIColor *)backgroundColor {
    NSLog(@"Background color is being set to: %@", backgroundColor);
    // 注意:这里调用的是交换后的方法,但由于已经交换,实际上会调用原来的 setBackgroundColor:
    // 这形成了一个无限循环,所以我们需要调用交换前的那个方法的实现
    // 由于我们交换了,原来的方法已经变成了 swizzle_setBackgroundColor:
    // 所以正确的调用方式是:[self swizzle_setBackgroundColor:backgroundColor];
    // method_exchangeImplementations 已经帮我们做好了这件事,我们只需要在实现中调用原始逻辑即可。
    // 我们直接调用 [super setBackgroundColor:backgroundColor] 是不行的,因为 super 的方法可能也被交换了。
    // 最安全的方式是获取原始方法的实现并调用它,但这里为了简单,我们直接调用。
    // 在这个例子中,因为交换了,[self swizzle_setBackgroundColor:...] 会调用原始的 setBackgroundColor:
    // 正确的做法是获取原始的 IMP 并调用
    Method originalMethod = class_getInstanceMethod(self, @selector(setBackgroundColor:));
    IMP originalIMP = method_getImplementation(originalMethod);
    void (*originalSetBackgroundColor)(id, SEL, UIColor *) = (void (*)(id, SEL, UIColor *))originalIMP;
    originalSetBackgroundColor(self, @selector(setBackgroundColor:), backgroundColor);
}
@end

c. 动态添加方法

如果运行时发现一个对象无法响应某个消息,你可以通过 class_addMethod 在运行时为这个类动态地添加一个方法实现。

// 在某个地方,+initialize 或动态方法解析中
IMP newIMP = (IMP)customMethodImplementation;
BOOL success = class_addMethod([MyClass class], @selector(unexpectedMethod:), newIMP, "v@:"); // "v@:" 是方法类型编码

d. 关联对象

有时我们想为某个类的已有实例添加一个“扩展属性”,但又不想通过继承或修改原始类来实现,关联对象就是为此而生。

它允许你将任意数量的任意类型的 C 语言值或对象“关联”到任何一个对象上。

实现原理: 关联对象存储在一个全局的 AssociationsManager 中,它维护了一个从对象地址到其关联对象表的映射,每个关联对象表又是一个从 objc_AssociationPolicy(内存管理策略)和 key(一个不透明的指针)到关联值的映射。

代码示例:为 UIView 添加一个 tagString 属性。

#import <objc/runtime.h>
static const char *kTagStringKey = "kTagStringKey";
@implementation UIView (TagString)
- (void)setTagString:(NSString *)tagString {
    // 使用 objc_setAssociatedObject 关联对象
    // 参数:对象、key、值、内存管理策略
    objc_setAssociatedObject(self, kTagStringKey, tagString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)tagString {
    // 使用 objc_getAssociatedObject 获取关联对象
    return objc_getAssociatedObject(self, kTagStringKey);
}
@end

动态性的优缺点

优点

  1. 极高的灵活性:可以实现很多在静态语言中难以实现的设计模式,如插件化、AOP、消息路由等。
  2. 运行时检查:可以处理未知的选择器,使程序更加健壮。
  3. 开发效率:一些功能(如关联对象)可以快速实现,而无需修改原有代码或进行繁琐的继承。

缺点

  1. 性能开销:消息传递比函数调用有额外的开销(缓存查找、方法列表遍历等),虽然现代运行时系统的缓存机制已经极大地优化了性能,但在性能敏感的代码路径中,直接调用 C 函数或使用 static 内联函数仍然更快。
  2. 编译时检查减弱:由于是动态类型和消息传递,很多错误(如方法名拼写错误)要到运行时才能发现,增加了调试难度,Xcode 的现代特性(如“Fix-it”)和静态分析工具在一定程度上缓解了这个问题。
  3. 代码可读性:过度使用动态特性(如方法交换、关联对象)会使代码变得难以理解和维护,因为它隐藏了对象的真实行为。

Objective-C 的动态语言特性是其设计的精髓,它赋予了这门语言无与伦比的灵活性和表达能力,虽然这些特性在 Swift 的静态类型和现代编译器优化下面临挑战,但它们在构建大型、可扩展、可插拔的 macOS 和 iOS 应用程序中,尤其是在一些底层框架和工具库的开发中,依然扮演着不可或缺的角色。

理解了消息传递、运行时系统以及方法交换、关联对象等特性,才能真正掌握 Objective-C 的强大之处。

-- 展开阅读全文 --
头像
dedeampz安装织梦软件
« 上一篇 2025-12-23
c语言 union struct
下一篇 » 2025-12-23

相关文章

取消
微信二维码
支付宝二维码

目录[+]