当前位置: 首页 > news >正文

iOS八股文之 Runtime

一、Runtime是啥

这个看苹果文档的描述即可:

Objective-C 语言尽可能将诸多决策从编译时和链接时推迟到运行时。只要有可能,它都会以动态方式处理事务。
这意味着该语言不仅需要编译器,还需要一个运行时系统来执行已编译的代码。
运行时系统就像是Objective-C 语言的一种操作系统,正是它让这种语言能够正常运行。

runtime 主要用 C 语言编写(如objc.h、runtime.h中的 API),核心逻辑(如objc_msgSend)用汇编编写,目的是提升性能(汇编能直接操作寄存器,减少函数调用开销)。

总之,OC动态处理事务的方式决定了运行时的诞生;本质上是一套基于 C语言的 API 和 数据结构,负责在程序运行时处理类、对象、方法调用等底层操作。

二、Runtime有啥东西

1. 关于实例(instance)、类、(Class)、元类(Meta-Class)的认识

  • 关于isa指针
    – 他们的 isa指针 指向:实例 → 类 → 元类 → 根元类(NSObject 的元类),(根元类指向自己);
    – 这样形成 isa 链表。这是 “对象能找到自己方法” 的基础。
  • 关于类的结构定义,内部包含 3 个关键部分
    super_class:指向父类,用于继承链查找;
    cache:缓存最近调用的方法,避免每次查找都遍历方法列表,提升性能;
    bits:存储类的方法列表(method_list)、属性列表(ivar_list)、协议列表(protocol_list)等。
  • 关于元类,类方法的 “归属地”
    – 实例方法存储在 “类对象” 中,类方法存储在 “元类” 中;
    – 当调用类方法[Class doClassMethod]时,runtime 会通过 “类对象的 isa” 找到元类,再从元类的方法列表中查找该类方法。

2. 消息机制:OC 的 “调用方法” 本质是 “发消息”

OC 中[obj doSomething]的本质,不是直接调用方法,而是通过 runtime 发送消息,流程分 3 步:

  • 消息发送(objc_msgSend):通过obj的isa找到类对象,先查cache,再查method_list;若没找到,通过super_class向上遍历父类,直到根类(NSObject)。
  • 动态方法解析(resolve):若遍历完没找到方法,会先调用+resolveInstanceMethod:(实例方法)或+resolveClassMethod:(类方法),允许开发者 “动态添加” 该方法(比如给分类动态补方法)。
  • 消息转发(forwarding):若解析失败,会进入转发流程:先通过 -forwardingTargetForSelector: 找 “替代对象” 处理消息;若没找到,再通过 -methodSignatureForSelector:和-forwardInvocation: 手动处理消息(比如打印日志、返回默认值),若这步也没处理,才会崩溃(unrecognized selector)

3. 动态操作 API:开发者能主动修改的行为

这是 runtime 最常被使用的部分,核心 API 分 3 类:

  • 动态操作方法:class_addMethod(添加方法)、class_replaceMethod(替换方法)、method_exchangeImplementations(交换方法实现,即 “Method Swizzling”)。
  • 动态操作属性:class_addIvar(添加成员变量,仅能在 “动态创建类” 时用)、objc_setAssociatedObject/objc_getAssociatedObject(关联对象,给分类加 “伪属性”)。
  • 动态操作类:objc_allocateClassPair(创建类)、objc_registerClassPair(注册类)、objc_getClass/object_getClass(获取类对象)。

三、Runtime有啥用(应用举例)

1. 分类添加 “属性”:关联对象(Associated Object)

问题:OC 分类默认不能添加成员变量,直接写@property只会生成 getter/setter 声明,没有实现;
解决方案:用objc_setAssociatedObjectobjc_getAssociatedObject实现属性存储;

比如:给 UIButton 加 “点击回调 Block”:
objc
// UIButton+Block.h
@interface UIButton (Block)
@property (nonatomic, copy) void(^clickBlock)(UIButton *);
@end// UIButton+Block.m
@implementation UIButton (Block)
- (void)setClickBlock:(void (^)(UIButton *))clickBlock {objc_setAssociatedObject(self, @selector(clickBlock), clickBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);[self addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
}
- (void(^)(UIButton *))clickBlock {return objc_getAssociatedObject(self, @selector(clickBlock));
}
- (void)btnClick {if (self.clickBlock) {self.clickBlock(self);}
}
@end

2. 方法交换:埋点、防崩溃、统一拦截

场景 1:给所有 UIViewController 的viewDidAppear:加页面曝光埋点,无需每个 VC 都写代码;

@implementation UIViewController (Track)
+ (void)load {// 确保只执行一次(load方法会被父类/子类多次调用,需过滤)static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 1. 获取原方法和自定义方法Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_viewDidAppear:));// 2. 交换方法实现method_exchangeImplementations(originalMethod, swizzledMethod);});
}
// 自定义方法:加埋点逻辑
- (void)swizzled_viewDidAppear:(BOOL)animated {// 先调用原方法(此时swizzled方法已经和原方法交换,调用swizzled方法等于调用原方法)[self swizzled_viewDidAppear:animated];// 埋点逻辑NSString *pageName = NSStringFromClass([self class]);[TrackManager logPageExpose:pageName];
}
@end

3. JSON 模型转换:自动映射属性

主流框架(如 MJExtension、YYModel)的核心原理:通过 runtime 遍历模型类的ivar_list(成员变量列表),获取属性名,再和 JSON 的 key 匹配,自动赋值;
关键 API:class_copyIvarList(获取成员变量列表)、ivar_getName(获取成员变量名)、object_setIvar(给成员变量赋值)。

4. KVO 底层支撑:动态生成 “中间类”

当给对象添加 KVO 时,runtime 会动态生成一个 “中间类”(如NSKVONotifying_XXX),让原对象的isa指向这个中间类;
中间类会重写setter方法,在赋值时触发observeValueForKeyPath:ofObject:change:context:,这是 KVO 能监听属性变化的底层逻辑。

四、Runtime在实践中常见问题(持续收集中)

1. 方法交换(Method Swizzling)的 “失效” 或 “重复交换”

Bug 原因:
没在+load方法中执行交换(+initialize会延迟调用,可能导致交换时机晚于方法调用);
没加dispatch_once_t,导致子类 / 父类重复交换,覆盖原实现;
解决方案:
必须在+load方法中执行交换(+load会在类加载时调用,且每个类只调用一次);
用dispatch_once确保交换逻辑只执行一次(如上文实例中的代码)。

2. 关联对象的 “内存泄漏” 或 “野指针”

Bug 原因:
关联对象的内存策略选错,比如用OBJC_ASSOCIATION_RETAIN关联 “self”,导致循环引用(如给 VC 加关联对象,值是 VC 的 Block);
关联对象没主动移除,导致对象释放后仍持有资源;
解决方案:
关联 Block 时用OBJC_ASSOCIATION_COPY_NONATOMIC(Block 拷贝后才能安全存储);
若关联对象是 “临时资源”,在对象销毁时(如 VC 的dealloc)用objc_removeAssociatedObjects(self)移除;
避免关联 “self”,若必须关联,用__weak typeof(self) weakSelf = self打破循环引用。

3. 消息转发未处理导致的 “隐性崩溃”

Bug 原因:
动态调用方法时(如performSelector:withObject:),没判断对象是否能响应该方法,导致消息转发流程走到最后仍未处理,触发unrecognized selector sent to instance;
解决方案:
调用前用respondsToSelector:判断对象是否能响应方法;
若需动态处理,重写+resolveInstanceMethod:或-forwardingTargetForSelector:,给消息一个 “兜底” 处理(如返回空值、打印日志)。

4. KVO 未移除导致的 “崩溃”

Bug 原因:
KVO 底层依赖 runtime 动态生成的中间类,若观察者销毁前没调用removeObserver:forKeyPath:,中间类仍会尝试给观察者发通知,导致野指针崩溃;
解决方案:
在观察者的dealloc方法中强制移除 KVO 监听;
用@try @catch包裹removeObserver(避免重复移除导致崩溃)。

五、Runtime 的一些其他相关(持续补充中)

1. runtime 与 Swift 的关系

  • Swift 有自己的运行时(Swift Runtime),但当 Swift 代码中用@objc修饰类、方法、属性时,会桥接到 Objective-C runtime,支持动态特性(如performSelector、KVO);
  • 纯 Swift 类(不继承 NSObject,且不加@objc)不支持 Objective-C runtime,无法动态修改行为。

2. runtime 的性能影响

动态操作(如objc_msgSend、关联对象)比静态调用(如 C 函数、Swift 直接调用方法)慢,因为需要额外的查找、遍历逻辑;所以避免在 “高频调用场景”(如tableView:cellForRowAtIndexPath:)中频繁使用 runtime 动态操作,优先用静态方法;若必须用,尽量缓存结果(如缓存遍历到的属性列表)。

3. App Store 审核风险

动态创建类(objc_allocateClassPair)、修改私有方法实现等操作,可能被 App Store 判定为 “违规修改系统行为”,存在拒审风险;所以要注意仅在 “必要场景”(如模型转换、埋点)使用 runtime,避免修改系统类(如 NSObject、UIView)的私有方法。

http://www.dtcms.com/a/487060.html

相关文章:

  • Transformer ViT 架构(转载)
  • 算法学习 05
  • 注册网站空间邵阳 网站开发 招聘
  • 技术准备一:gflags
  • 高端个性化网站开发如何避免网站被攻击
  • 怎样创建网站或者网址网页设计的模板
  • 深圳网站定制建设网站推广运营
  • 力扣2401. 最长优雅子数组
  • 广州网站设计营销公司中国领导班子级别顺序图
  • 外贸网站建设石家庄烟台网络推广
  • ​HTTPS是如何确保安全的
  • SQL Server实战指南:从基础CRUD到高并发处理的完整面试题库
  • 快速上手大模型:机器学习2
  • 自助房申请网站股票查询网站模板 wordpress
  • 网站建设炫彩图片会员卡管理系统设计
  • Lombok使用指南(上)
  • 机器宠物建模的第一步:基础形体搭建(Blocking)
  • Linux服务器编程实践22-TCP头部选项解析:MSS、窗口扩大因子与SACK
  • wordpress 中文 seo烟台网站seo
  • 建设网站实训报告书企业门户是什么意思
  • labview2018中文版安装步骤(免费永久使用)
  • 映射公式在基分析中的应用
  • 液态镜头在工业机器视觉检测中的应用
  • C++语言编程规范-程序效率
  • 在线网站优化公司涿州网站开发
  • 官方网站如何建设wordpress jq
  • 危化品安全员证核心考点:危险化学品管理专项
  • HTML+CSS+JavaScript
  • 【实时Linux实战系列】实时 Linux 的自动化基准测试框架
  • 栈和队列笔记2025-10-12