iOS 底层探索篇 ——Runtime-objc_msgSend流程分析 - 慢速查找流程 (下)

方法无法找到流程分析

上文说到,如果所有的父类都找完了还没找到方法,那么imp就会设为forward_imp,那么继续往下走,看到会返回imp也就是forward_imp
在这里插入图片描述
看到forward_imp在方法开头有进行赋值。
在这里插入图片描述
接着寻找_objc_msgForward_impcache,发现进入__objc_msgForward。
在这里插入图片描述
接着搜索__objc_msgForward,看到TailCallFunctionPointer x17.
在这里插入图片描述
搜索TailCallFunctionPointer,发现是跳转$0,也就是x17 __objc_forward_handler
在这里插入图片描述
接下来搜索 __objc_forward_handler。
在这里插入图片描述
发现没有找到相应的实现,那么就说明这里从汇编跑到了c语言,搜索objc_forward_handler,!objc2不看看到_objc_forward_stret_handler被赋值为objc_defaultForwardStretHandler,接下来看objc_defaultForwardStretHandler函数,发现这里会进行格式化的打印,也就是输出当方法找不到的时候的打印。这里判断是不是元类,来进行判断是不是类方法,从而判断输出+号还是-号,然后在打印通过object_getClassName里面调用obj->getIsa()返回的class,然后打印SEL,最后打印self的位置。
在这里插入图片描述
比如
在这里插入图片描述
那么发生了错误,有没有办法进行处理呢?接下来,就进入了消息处理流程。

对象方法动态方法决议

运行一个不存在的方法,并打下断点。
在这里插入图片描述
然后在lookUpImpOrForward中打下断点后进来。

在这里插入图片描述

运行起来后,输出一下cls确认消息接受者是对的,这里是LGPerson是对的。
在这里插入图片描述
在输出一下imp,发现是空的,因为没有这个方法所以是对的。
在这里插入图片描述
往下走后进入了这里,这里的behavior是个方法参数。
在这里插入图片描述
搜索一下behavior是哪里来的,并且值是多少。发现在MethodTableLookUp里面,并且值为3.
在这里插入图片描述
回到loopUpImpOrForward,点进去看LOOKUP_RESOLVER是什么
在这里插入图片描述
所以behavior & LOOKUP_RESOLVER 也就是 3 & 2 = 0010 = 2,接下来 behavior ^= LOOKUP_RESOLVER; 也就是 behavior(3) ^= 2 就是1了。当behavior为1,那么 if (slowpath(behavior & LOOKUP_RESOLVER)) 就永远为false,就永远进不来了,也就是说,这里的方法只执行一次。
接下来进入resolveMethod_locked方法。
在这里插入图片描述
这里不是元类,所以走到resolveInstanceMethod。
在这里插入图片描述
发现这里又有一个trycache,这是为什么呢?其实这里的trycache,是把方法放到缓存里面,可以防止下一次的trycache查找缓存。具体看下文这里接着流程。这里其实给了一次机会,如果实现了resolveInstanceMethod:,就会有一个容错处理。
看到这里是向方法发送消息,所以是类方法。
在这里插入图片描述
去实现一下

在这里插入图片描述
运行一下得知,在报错之前,来到了resolveInstanceMethod方法,self是 LGPerson, sel是sayasuofg。并且注意到resolveInstanceMethod调用了两次。
在这里插入图片描述
那么报错之前能来到resolveInstanceMethod方法,意味着我们能在resolveInstanceMethod里面处理而避免报错。

尝试一下在resolveInstanceMethod里面addMethod 来避免报错。

在这里插入图片描述
在这里插入图片描述

运行一下。
在这里插入图片描述
发现不报错,并且成功调用了testfunc方法。
在resolveInstanceMethod中的lookUpImpOrNilTryCache,如果没有实现resolveInstanceMethod方法,会不会return呢?
在这里插入图片描述
答案是不会的,因为在nsobjct中有默认实现,返回NO。因为如果在resolveInstanceMethod过程中返回return报错的话,系统会更加的不稳定,所以系统实现了这个方法为我们兜底。
在这里插入图片描述

类方法动态方法决议

添加一个没有实现的类方法,然后运行。

在这里插入图片描述

运行后,方法会走到else 里面,
在这里插入图片描述
接着会调用resolveClassMethod方法,和对象方法动态决议很相似。
因为类方法存在元类里面,为了防止元类没有初始化,这里对元类进行了操作。
接着看到这里的resolved 是调用resolveClassMethod进行处理的,所以应该在resolveClassMethod方法里面进行处理。那么resolveClassMethod应该写在哪里?这里的消息接受者是元类,元类的对象方法就是类的类方法,所以应该写在类里面。

在这里插入图片描述
写一下resolveClassMethod方法。

在这里插入图片描述

运行一下发现进来了。
在这里插入图片描述
接着进行方法替换。类方法在元类里面所以要获取metaclass。
在这里插入图片描述
在这里插入图片描述

运行一下发现替换成功。

往回走,发现resolveClassMethod下面调用了一次resolveInstanceMethod,这是为什么呢?这里因为类方法不仅以类方法的形式存在,还以元类对象方法的形式存在,所以也会有对象的继承链的形式存在,最终会走到NSObject里面。
在这里插入图片描述

在这里插入图片描述

既然对象方法和类方法最终都走到NSObject,那么可不可以都在NSObject处理呢?来试一下。

在这里插入图片描述
运行一下,发现方法都被替换了,说明是可行的。
在这里插入图片描述
那么为什么要有动态决议这样子的处理呢。

  1. 方法查找是通过sel找imp的过程,当通过sel找不到imp的时候,苹果给一次机会去纠正以免报错。
  2. 全局的方法找不到imp的时候,我们都可以监听。这样我们可以监听我们自己写的方法,可以进行记录下来以便更改并进行处理避免奔溃,比如跳转到首页的处理。

这种方式就叫做AOP。AOP也就是面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

为什么两次tryCache

跟着流程走一遍
lookUpImpOrNilTryCache
在这里插入图片描述
_lookUpImpTryCache
在这里插入图片描述
lookUpImpOrForward
在这里插入图片描述

log_and_fill_cache,到这里,cls的缓存里面插入了sayasuofg的imp
在这里插入图片描述

所以到lookUpImpOrForwardTryCache进来时
在这里插入图片描述
imp就不是nil,而是(libobjc.A.dylib_objc_msgForward_impcache),就不会在进行慢速查找,而是直接去done,然后返回 (libobjc.A.dylib_objc_msgForward_impcache)

在这里插入图片描述

在这里插入图片描述

instrumentObjcMessageSends辅助

可以在代码中添加instrumentObjcMessageSends查看调用了哪些方法。
添加未实现代码并运行。
在这里插入图片描述
到Macintosh HD 中的隐藏文件tmp中。
在这里插入图片描述

找到msgSends开头的文件打开。查看
在这里插入图片描述

在这里插入图片描述

汇编指令参考.

<think>嗯,用户想了解Runtime和Runloop的底层原理,这两个都是iOS开发中的核心概念。首先,我需要回忆一下自己之前学过的内容,确保理解正确。Runtime主要是Objective-C的动态运行时环境,而Runloop则与事件处理循环相关。 先从Runtime开始。记得Runtime库是用C和汇编写的,它处理对象的创建、消息转发、方法交换等等。可能需要解释一下消息发送机制,比如objc_msgSend的作用,还有方法解析和消息转发的流程。另外,类与对象的结构,如isa指针、方法列表、属性列表这些也是关键点。动态特性如方法交换(method swizzling)和关联对象(associated objects)也应该提到,这些都是Runtime的实际应用场景。 然后是Runloop。Runloop的基本概念是事件处理循环,保持程序持续运行并处理各种事件。需要区分主线程和子线程的Runloop,主线程的默认开启,子线程的需要手动启动。Runloop的模式,比如DefaultMode和TrackingMode,以及不同模式下如何处理事件。还要讲Runloop的结构,Sources、Timers、Observers这些组成部分,以及它们如何协同工作。最后,Runloop在应用中的实际作用,比如界面刷新、定时器、事件响应等,都是必须涵盖的点。 用户可能对底层原理感兴趣,但作为开发者,他们可能更关心这些机制在实际开发中的应用,比如性能优化、解决卡顿问题,或者如何利用Runtime进行动态方法添加。需要确保解释清晰,结构合理,避免过于晦涩。同时,注意使用中文,按照系统指令正确排版数学公式,不过这里可能用不上太多数学内容,但如果有涉及的地方要记得用$符号包裹。 可能需要注意用户的知识水平,如果用户是初学者,可能需要更简洁的解释,避免过多技术细节。但问题中提到了“底层原理”,所以需要深入一些。但也要保持条理,分点说明。同时,检查是否有遗漏的重要概念,比如自动释放池与Runloop的关系,或者Runloop如何与GCD交互。 最后,确保回答结构清晰,分Runtime和Runloop两部分,每部分有概述、核心机制、实际应用等小节。使用例子帮助理解,比如消息转发的步骤,或者Runloop处理事件的流程。避免错误,比如混淆Runloop和线程的关系,或者错误描述方法解析的顺序。需要确认信息的准确性,必要时参考官方文档或可靠的资料。</think>好的,我将以iOS/macOS开发为背景,分步解释Runtime和Runloop的底层原理: ### 一、Runtime(运行时系统) #### 1. 核心概念 - Objective-C的动态特性基础 - C语言和汇编实现的库(源码可见于[objc4](https://opensource.apple.com/source/objc4/)- 核心职责:实现面向对象特性和消息传递机制 #### 2. 核心机制 $$Class结构体 = isa指针 + superclass指针 + method\_list\_t + cache\_t + ...$$ **消息传递流程**: 1. 快速查找:通过`objc_msgSend`在方法缓存`cache_t`中查找 2. 慢速查找:遍历类的方法列表`method_list_t` 3. 动态解析:未找到时触发`+resolveInstanceMethod:` 4. 消息转发:经过`forwardingTargetForSelector:`和`methodSignatureForSelector:`两阶段 **关键数据结构**: - `isa指针`:实现对象与类的关联(arm64架构包含non-pointer isa优化) - `method_t`:包含SEL(方法选择器)、IMP(函数指针)、TypeEncoding(类型编码) #### 3. 实际应用 - 方法交换(Method Swizzling) - 动态添加方法(`class_addMethod`) - 关联对象(Associated Objects) - KVO实现(通过派生子类重写setter方法) --- ### 二、Runloop(运行循环) #### 1. 核心概念 - 事件处理循环:`do-while`循环保持线程活跃 - 与线程一一对应(主线程Runloop自动启动,子线程需手动启动) - 核心作用:管理事件/消息、进行线程调度 #### 2. 核心结构 ```plaintext CFRunLoop { currentMode = "UIInitializationRunLoopMode", modes = [ CFRunLoopMode { name = "kCFRunLoopDefaultMode", sources0 = [触摸事件、performSelector:], sources1 = [基于端口的系统事件], observers = [CA动画回调、AutoreleasePool清理], timers = [NSTimer、performSelector:withObject:afterDelay:] }, ... ] } ``` #### 3. 运行流程 $$进入循环 \rightarrow 处理输入源 \rightarrow 进入休眠 \rightarrow 等待唤醒 \rightarrow 处理事件$$ **详细步骤**: 1. 通知Observer:即将进入Loop(`kCFRunLoopEntry`) 2. 通知Observer:即将处理Timer(`kCFRunLoopBeforeTimers`) 3. 通知Observer:即将处理Source(`kCFRunLoopBeforeSources`) 4. 处理Blocks 5. 处理Source0(非基于Port的事件) 6. 如果有Source1待处理,立即跳转处理 7. 通知Observer:即将休眠(`kCFRunLoopBeforeWaiting`) 8. 进入休眠(通过mach_msg系统调用) 9. 被以下事件唤醒: - Port-Based事件 - Timer到时间 - Runloop超时 - 被手动唤醒 10. 通知Observer:结束休眠(`kCFRunLoopAfterWaiting`) 11. 处理唤醒时收到的事件 12. 回到步骤2循环执行 #### 4. 关键应用 - 界面刷新:CADisplayLink与VSync信号同步 - 自动释放池:Runloop休眠前执行释放操作 - 事件响应:手势识别、屏幕触摸 - GCD调度:`dispatch_main_queue`通过Runloop执行 --- ### 三、两者关联 1. **消息处理**:Runloop驱动Runtime的消息分发 2. **对象生命周期**:Runloop控制AutoreleasePool的释放 3. **线程保活**:Runloop通过`CFRunLoopRun`维持线程存活,配合Runtime进行消息处理 ### 四、调试技巧 1. Runtime调试: ```bash # 打印类方法列表 (lldb) p [MyClass _methodDescription] ``` 2. Runloop观察: ```objective-c CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopAllActivities); ``` 理解这两个机制,可以帮助开发者: - 优化滚动流畅度(通过Runloop模式控制) - 诊断消息转发导致的崩溃 - 实现高性能的异步渲染 - 合理管理线程资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值