—— iOS 运行时中方法的调用流程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了—— iOS 运行时中方法的调用流程相关的知识,希望对你有一定的参考价值。

参考技术A

ios运行时系统中,调用方法的本质就是利用objc_msgSend进行消息发送:

iOS 中所有的类都是继承于 NSObject,一个对象所具有的方法分为实例方法和类方法,编译完成后的对象中,存在一个实例方法链表、一个缓存方法链表。当实例调用方法经objc_msgSend时:首先,在相应操做的对象中的缓存方法列表中找调用的方法,若找到,转向相应的实现并执行;若没找到,在对象的方法列表中查找,若是找到,转向相应的实现并执行;若是没找到,则递归的去父类指针所指向的类对象方法列表中查找;以此类推,若是一直到根类都没有找到,转向拦截调用,走消息转发机制;若是没有重写拦截调用方法,程序报错;

消息转发也被称为拦截调用,就是在找不到调用的方法后,且在程序崩溃以前,有机会经过重写NSObject的四个方法来补救处理:

若以上都不中,调用 NSObject 的 doesNotRecognizeSelector 方法抛出异常:

利用以上机制,可以对resolveInstanceMethod 和 resolveClassMethod 两个方法进行方法交换,拦截可能出现的 iOS 崩溃,然后自定义处理。

消息转发机制依次的三个过程:1)动态方法解析;2)转发给其他备用的接收对象;3)消息所有相关内容封装成一个NSInvocation对象,再做最后的尝试。

第一阶段,先征询接收者所属的类,是否需要动态的添加方法,用来处理当前未找到的方法。对象在无法解读消息时会首先调用所属类的下列类方法,来判断是否能接收消息:

例:

第二阶段,如果动态方法解析没有发现添加的方法,那么尝试转发给其他对象来处理这个方法。该步骤调用的方法是:

例:

第三阶段,如果没有可用的备选者,那么系统就会把消息所有相关内容封装成一个NSInvocation对象,再做最后的尝试,启动完整的消息转发。先调用methodSignatureForSelector:获取方法签名,然后再调用forwardInvocation:进行处理,这一步的处理可以直接转发给其它对象,即和第二步的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,一般会改变消息内容,比如增加参数,改变选择子等等,具体根据实际情况而定。

例:

这里就是利用了消息转发机制的第三个阶段,将NSIvocation分发给多个代理去响应。
https://blog.csdn.net/kingjxust/article/details/49559091

http://blog.cnbang.net/tech/2808/
http://blog.cnbang.net/tech/2855/

由于OC的动态特性,在编译过程向类发送了其无法理解的消息并不会报错,因为在运行时,我们可以改变对象调用的方法、向类中添加方法。只有当程序运行起来之后,才知道要真正执行哪个函数(动态绑定)。

OC消息发送原理、方法查找过程:

简单理解:

OC、运行时初始化时机:
https://www.jianshu.com/p/4b93b40977b5
https://blog.csdn.net/weixin_30920513/article/details/100093380

参考文章:
https://www.jianshu.com/p/7e132cda35cd
https://www.cnblogs.com/feng9exe/p/10397102.html
https://www.shangmayuan.com/a/02d9b8b219b24d888ef93b97.html
https://blog.csdn.net/lin1109221208/article/details/108724965

iOS之使用NSInvocation调用方法
https://www.jianshu.com/p/e24b3420f1b4

如何在 iOS 中停止 JSContext 评估?

【中文标题】如何在 iOS 中停止 JSContext 评估?【英文标题】:How to stop JSContext evaluating in iOS? 【发布时间】:2018-06-26 01:39:01 【问题描述】:

我需要在 [context evaluateScript:js] 运行时中断它,我在文档和 Google 中找不到方法。有人可以帮忙吗?

【问题讨论】:

一种天真的方法,我猜,你可以从 JavaScript 调用一些 Objective-C 方法,它会通知 JS 代码(返回 bool 值)它应该停止执行。并定期调用此方法并从 JS 中止执行。 @BorysVerebskyi evaluateScript 方法像盲目地运行 js,直到脚本自行停止。 【参考方案1】:

我无法使用JavaScriptCore 让它工作。我试过了:

将代码包装在DispatchWorkItem 中并调用cancel() 将代码包装在 Operation 中并尝试取消它以及 nil'ing OperationQueue(这实际上会导致应用崩溃)。 使用JavaScriptCore 中的C API 以及-fno-objc-arc 标志来手动管理内存。

这些都不起作用。

所做的工作是使用WKWebView,一旦nil'ed,evaluateJavaScript 函数就会调用其完成处理程序。我发布了一个示例here。

对于任何尝试这样做的人,请记住这是未记录的行为,因此它可能会在任何即将推出的 iOS 版本中发生变化。

【讨论】:

如果需要返回更多上下文结果。将 webView 的 WKScriptMessageHandler 设置为 WKUserContentController 并在 deinit 时删除处理程序。【参考方案2】:

如果您使用JSContextevaluateJavaScript 方法。由于上下文上没有取消方法,因此您无法停止它。而且iOS不喜欢macOS可以使用XPC所以没办法停止外部运行环境。

但是,有一个解决方法。想想JSContext 喜欢浏览器。剧本在路上飞。如何通过外部事件阻止它。最方便的方法是自己构建runloop。将任务变小并放入队列中。

自定义运行循环

我每隔几秒导出一个本机 Timer 以在 downloadWithHandler 方法上调用 javascript 处理程序,并在某个时候将 isCancelled 设置为 true。 runloop 将停止,JSContext eval 到达整个脚本的末尾并返回。

本机:

    let script = viewModel.string
    let context = EAJSContext()!
    // context will be dealloc after finish if not retain here.
    // not retain it to debugging
    // viewModel.context = context 

    DispatchQueue.global(qos: .background).async 
        // inject helper metholds 
        JavaScriptCoreHelper().config(context: context)

        // export download(handler:) method
        Emulator.configure(context: context)

        let result = context.evaluateScript(script)
        print("context return result: \(result)")
    

脚本:

var emulator = Emulator.create();

var taskIsFinish = false;

const promiseA = new Promise( (resolve, reject) => 
    emulator.downloadWithHandler( (response) => 
        // native pass dictionary
        var isFinish = response["isFinish"];
        var isCancelled = response["isCancelled"];
        var date = response["date"];

        console.log("isFinish: " + isFinish);
        console.log("isCancelled: " + isCancelled);
        console.log("date: " + date);

        if (isFinish || isCancelled) 
            resolve(date);
        
    );
);

promiseA
.then( (val) => 
    console.log("asynchronous logging has val: " + val)
    taskIsFinish = true
)

while(!taskIsFinish) 
    // long task in queue
    sleep(1);
    console.log('long task');


输出:

2020-05-10 04:07:06.766592+0800 …[34354:2831281] [interaction] … runToolbarItemPressed(_:): run triggerd
console.log: isFinish: false
console.log: isCancelled: false
console.log: date: Sun May 10 2020 04:07:06 GMT+0800 (CST)
console.log: isFinish: false
console.log: isCancelled: false
console.log: date: Sun May 10 2020 04:07:06 GMT+0800 (CST)
console.log: long task
console.log: isFinish: false
console.log: isCancelled: false
console.log: date: Sun May 10 2020 04:07:07 GMT+0800 (CST)
console.log: long task
console.log: isFinish: false
console.log: isCancelled: false
console.log: date: Sun May 10 2020 04:07:08 GMT+0800 (CST)
console.log: long task
console.log: isFinish: false
console.log: isCancelled: false
console.log: date: Sun May 10 2020 04:07:10 GMT+0800 (CST)
console.log: long task
console.log: isFinish: false
console.log: isCancelled: false
console.log: date: Sun May 10 2020 04:07:11 GMT+0800 (CST)
console.log: long task
console.log: isFinish: false
console.log: isCancelled: true
console.log: date: Sun May 10 2020 04:07:12 GMT+0800 (CST)
console.log: asynchronous logging has val: Sun May 10 2020 04:07:12 GMT+0800 (CST)
console.log: long task
console.log: isFinish: false
console.log: isCancelled: true
console.log: date: Sun May 10 2020 04:07:13 GMT+0800 (CST)
context return result: Optional(undefined)
console.log: isFinish: false
console.log: isCancelled: true
console.log: date: Sun May 10 2020 04:07:13 GMT+0800 (CST)
console.log: isFinish: false
console.log: isCancelled: true
console.log: date: Sun May 10 2020 04:07:13 GMT+0800 (CST)
2020-05-10 04:07:13.391950+0800 …[34354:2831310] [debug] EAJSContext.swift[16], deinit

(如果在 eval 完成后上下文没有 deinit,内存可能会泄漏。)

其他想法

将上下文包装在本地 Worker 类中。并设置标志以使该工作人员在任务取消时以空结果快速返回回调。请勿保留和丢弃。

【讨论】:

以上是关于—— iOS 运行时中方法的调用流程的主要内容,如果未能解决你的问题,请参考以下文章

波卡链Substrate SRML框架

单击按钮时中止正在运行的功能

为啥matlabcontrol在代理创建时中断调用线程?

iOS底层探索之Runtime:运行时&方法的本质

Windows 运行时中的内存管理

访问 2010 运行时中的访问 2003 应用程序运行时错误