望岸 發表於 2022-7-29 10:35:00

iOS基于JSCore的混合开发

<p>&nbsp;</p>
<p>1、iOS 基于JSCore的混合开发</p>
<div>
<div>
<h3>JSCore 简介</h3>
<p>JSCore的角色是做为一个桥梁,其实就是 Bridge,来协调JS与Native通信的。浏览器内核的模块主要是由<strong>渲染引擎</strong>和 <strong>JS 引擎</strong>组成,其中 JSCore 就是一种独立的 JS 引擎。Apple 通过将 WebKit 的 JS 引擎用 OC 封装,<strong>提供了一套 JS 运行环境以及 Native 与 JS 数据类型之间的转换桥梁,常用于 OC 和 JS 代码之间的相互调用</strong>,也就是说我们可以脱离渲染单独去执行 JS。</p>
<p>JSCore 主要包括如下这些 classes、协议、类结构:</p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="1440" data-height="384"><img src="//upload-images.jianshu.io/upload_images/13180946-d28a9b50092e453c.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">image</div>
</div>
<h3>JSCore 的结构</h3>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="1440" data-height="866"><img src="//upload-images.jianshu.io/upload_images/13180946-094bf485ffe17610.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">JSCore 的框架结构图</div>
</div>
<p>在 Native 中可以开启多个线程来异步执行不同的需求,也即,我们可创建多个 JSVirtualMachine 虚拟机,相互隔离互不影响的<strong>并行地执行不同 JS 任务</strong>。在一个 JSVirtualMachine 中还可以关联多个 JSContext (JS 执行环境上下文),并通过 JSValue(值对象) 来和 Native 进行数据传递通信,同时可以通过 JSExport (协议) ,将 Native 中遵守此解析的类的方法和属性转换为 JS 的接口供其调用。</p>
<h3>JS 与 OC 的数据类型互换</h3>
<p>JSValue 可以用来让 JS 和 OC 之间无障碍的数据类型转换,对应转换类型如下:</p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="2152" data-height="1520"><img src="//upload-images.jianshu.io/upload_images/13180946-e55be17100efad82.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">&nbsp;</div>
</div>
<h3>在 iOS 中执行一段 JS 代码</h3>
<p>我们可以通过JSContext 调用<code>evaluateScript</code>方法,从而在 JSCore 中执行一段 JS 脚本:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-css"><code class="language-css">- <span class="token punctuation">(JSValue *<span class="token punctuation">)<span class="token property">evaluateScript<span class="token punctuation">:<span class="token punctuation">(NSString *<span class="token punctuation">)script<span class="token punctuation">;
</span></span></span></span></span></span></span></code></pre>
</div>
<p>利用这个特性可以来做一些<strong>多端逻辑统一</strong>的事情,比如三端(iOS、Android、H5)有一段相当复杂的但原理一样算价逻辑,三端用各自语言自己写一套不但麻烦,而且效率低而且逻辑不一定统一,同时用 OC 去实现复杂计算逻辑也没有 JS 这么灵活高效。</p>
<p>这里就可以利用<strong>执行 JS 代码</strong>这个特性,将这个逻辑抽成一个 JS 方法,只需要传入特定的入参,直接返回价格,这样的话,3 端可以同时使用这个逻辑,还可以放到远端进行动态更新维护。实现思路大概如下:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-csharp"><code class="language-csharp"><span class="token comment">// 在 iOS 里面执行 JS
JSContext <span class="token operator">*jsContext <span class="token operator">= <span class="token punctuation">[<span class="token punctuation">[<span class="token class-name">JSContext alloc<span class="token punctuation">] init<span class="token punctuation">]<span class="token punctuation">;
<span class="token punctuation"><span class="token punctuation">;
<span class="token punctuation">[jsContext evaluateScript<span class="token punctuation">:<span class="token string">@"var computePrice = function(value)
                           { return value * 2 }"<span class="token punctuation">]<span class="token punctuation">;
JSValue <span class="token operator">*<span class="token keyword">value <span class="token operator">= <span class="token punctuation"><span class="token punctuation">;
<span class="token keyword">intintVal <span class="token operator">= <span class="token punctuation">[<span class="token keyword">valuetoInt32<span class="token punctuation">]<span class="token punctuation">;
<span class="token function">NSLog<span class="token punctuation">(<span class="token string">@"计算结果为 %d"<span class="token punctuation">, intVal<span class="token punctuation">)<span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>运算结果为:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-undefined"><code class="language-undefined">2018-03-16 20:20:28.006282+0800 JSCoreDemo
========在 iOS 里面执行 JS 代码========
2018-03-16 20:20:28.006517+0800 JSCoreDemo
计算结果为 1000
</code></pre>
</div>
<h3>在 iOS 中调用 JS 方法</h3>
<p>上面是在 iOS 中执行一段 JS 代码,接下来给大家介绍下如何在 iOS 中调用 H5 中的 JS 方法。比如 H5 中有一个全局方法叫做 nativeCallJS,我们可以通过执行环境的上下文 <code>jsContext[@"nativeCallJS"]</code> 获取该方法并进行调用,类似这样:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-jsx"><code class="language-jsx"><span class="token comment">// Html 中有一个 JS 全局方法
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;script <span class="token attr-name">type<span class="token attr-value"><span class="token punctuation">=<span class="token punctuation">"text/javascript<span class="token punctuation">"<span class="token punctuation">&gt;<span class="token plain-text">
    var nativeCallJS = function (parameter) {
      alert(parameter);
    };
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/script<span class="token punctuation">&gt;
<span class="token comment">// 在 iOS 运行 JS 方法
JSContext <span class="token operator">*jsContext <span class="token operator">= <span class="token punctuation"><span class="token punctuation">;
JSValue <span class="token operator">*jsMethod <span class="token operator">= jsContext<span class="token punctuation">[@<span class="token string">"nativeCallJS"<span class="token punctuation">]<span class="token punctuation">;
jsMethod callWithArguments<span class="token punctuation">:@<span class="token punctuation">[ @<span class="token string">"Hello JS, I am iOS" <span class="token punctuation">]<span class="token punctuation">]<span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>运行就可以看到 Native 执行到了 H5 的 Alter 弹层:</p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="678" data-height="312"><img src="//upload-images.jianshu.io/upload_images/13180946-c09309de6f894f91.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/678/format/webp"></div>
</div>
<div class="image-caption">&nbsp;</div>
</div>
<p>利用这个特性我们可以<strong>让 iOS 获取到一些 H5 的信息</strong>来处理一些他想处理的东西,譬如先将信息在全局中暴露出来,通过调用方法获取到 <strong>使用的版本号、运行的环境信息、端主动处理逻辑(清除缓存、控制运行)</strong>等这些事情。</p>
<h3>在 JS 中面调用 iOS 方法</h3>
<p>这里需要和<code>@"documentView.webView.mainFrame.javaScriptContext"</code>这个 webview 相关特性结合起来,将 H5 调用的方法用 Block 以<code>jsCallNative(调用方法名)</code>为名传递给 JSCore 上下文。</p>
<p>比如我们 H5 中有一个按钮的点击回调是去调用客户端的一个方法,并在方法中输出传入参数,大致是这样实现:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-cpp"><code class="language-cpp"><span class="token comment">// Html中按钮点击调用一个OC方法
<span class="token operator">&lt;button type<span class="token operator">=<span class="token string">"button"
      onclick<span class="token operator">=<span class="token string">"jsCallNative('Hello iOS', 'I am JS');"<span class="token operator">&gt;调用OC代码<span class="token operator">&lt;<span class="token operator">/button<span class="token operator">&gt;
<span class="token comment">//Block 以”jsCallNative"为名传递给JavaScript上下文
JSContext <span class="token operator">*jsContext <span class="token operator">= <span class="token punctuation">[webView valueForKeyPath<span class="token operator">:
            @<span class="token string">"documentView.webView.mainFrame.javaScriptContext"<span class="token punctuation">]<span class="token punctuation">;
   jsContext<span class="token punctuation">[@<span class="token string">"jsCallNative"<span class="token punctuation">] <span class="token operator">= <span class="token operator">^<span class="token punctuation">(<span class="token punctuation">) <span class="token punctuation">{
       NSArray <span class="token operator">*args <span class="token operator">= <span class="token punctuation"><span class="token punctuation">;
       <span class="token keyword">for <span class="token punctuation">(JSValue <span class="token operator">*obj in args<span class="token punctuation">) <span class="token punctuation">{
         <span class="token function">NSLog<span class="token punctuation">(@<span class="token string">"%@"<span class="token punctuation">, obj<span class="token punctuation">)<span class="token punctuation">;
       <span class="token punctuation">}
    <span class="token punctuation">}<span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>最终输出是这样:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-undefined"><code class="language-undefined">2018-03-16 20:51:25.590749+0800 JSCoreDemo ========在 JS 里面调用 iOS 中方法========
2018-03-16 20:51:25.591155+0800 JSCoreDemo Hello iOS
2018-03-16 20:51:25.591370+0800 JSCoreDemo I am JS
</code></pre>
</div>
<p>这个特性真正<strong>让 H5 可以享受到很多端上的特性</strong>,比如<strong>Native 方式的跳转、Native 底层能力(震动、录音、拍照)、扫码、获取设备信息、分享、设置导航栏、调用 Native 封装组件</strong>等这些功能,此处大家可以联想 Hybrid 开发模式。</p>
<h3>通过 JSExport 暴露 iOS 方法/属性给 JS</h3>
<p>通过 JSExport 可以将 iOS 对象的属性方法暴露给 JS 环境,让其使用起来像 JS 对象一样方便。比如我们 OC 中有一个 Person 的类,包含两个属性和一个方法,此处通过让<code>fullName</code>方法使用 JSExport 协议暴露出去,这样在 JS 中是可以直接去调用的。</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-objectivec"><code class="language-objectivec"><span class="token keyword">@protocol PersonProtocol <span class="token operator">&lt;JSExport<span class="token operator">&gt;
<span class="token operator">- <span class="token punctuation">(NSString <span class="token operator">*<span class="token punctuation">)fullName<span class="token punctuation">;
<span class="token keyword">@end

<span class="token keyword">@interface Person <span class="token punctuation">: NSObject <span class="token operator">&lt;PersonProtocol<span class="token operator">&gt;
<span class="token keyword">@property <span class="token punctuation">(nonatomic<span class="token punctuation">, copy<span class="token punctuation">) NSString <span class="token operator">*firstName<span class="token punctuation">;
<span class="token keyword">@property <span class="token punctuation">(nonatomic<span class="token punctuation">, copy<span class="token punctuation">) NSString <span class="token operator">*lastName<span class="token punctuation">;
<span class="token keyword">@end

<span class="token keyword">@implementation Person
<span class="token keyword">@synthesize firstName<span class="token punctuation">, lastName<span class="token punctuation">;
<span class="token punctuation">(NSString <span class="token operator">*<span class="token punctuation">)fullName <span class="token punctuation">{
<span class="token keyword">return <span class="token punctuation"><span class="token punctuation">;
<span class="token punctuation">}
<span class="token keyword">@end
<span class="token comment">// 通过 JSExport 暴露 iOS 方法属性给 JS
Person <span class="token operator">*person <span class="token operator">= <span class="token punctuation">[<span class="token punctuation"> init<span class="token punctuation">]<span class="token punctuation">;
jsContext<span class="token punctuation">[<span class="token string">@"p"<span class="token punctuation">] <span class="token operator">= person<span class="token punctuation">;
person<span class="token punctuation">.firstName <span class="token operator">= <span class="token string">@"Fei"<span class="token punctuation">;
person<span class="token punctuation">.lastName <span class="token operator">= <span class="token string">@"Zhu"<span class="token punctuation">;
<span class="token function">NSLog<span class="token punctuation">(<span class="token string">@"========通过 JSExport 暴露 iOS 方法属性给 JS========"<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation"><span class="token punctuation">;
<span class="token punctuation"><span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>最终运行结果为:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-jsx"><code class="language-jsx"><span class="token number">2018<span class="token operator">-<span class="token number">03<span class="token operator">-<span class="token number">16 <span class="token number">20<span class="token punctuation">:<span class="token number">51<span class="token punctuation">:<span class="token number">17.437688<span class="token operator">+<span class="token number">0800 JSCoreDemo<span class="token punctuation">[<span class="token number">4970<span class="token punctuation">:<span class="token number">219193<span class="token punctuation">] <span class="token operator">===<span class="token operator">===<span class="token operator">==通过 JSExport 暴露 iOS 方法属性给 <span class="token constant">JS<span class="token operator">===<span class="token operator">===<span class="token operator">==
<span class="token number">2018<span class="token operator">-<span class="token number">03<span class="token operator">-<span class="token number">16 <span class="token number">20<span class="token punctuation">:<span class="token number">51<span class="token punctuation">:<span class="token number">17.438100<span class="token operator">+<span class="token number">0800 JSCoreDemo<span class="token punctuation">[<span class="token number">4970<span class="token punctuation">:<span class="token number">219193<span class="token punctuation">] Fei Zhu
<span class="token number">2018<span class="token operator">-<span class="token number">03<span class="token operator">-<span class="token number">16 <span class="token number">20<span class="token punctuation">:<span class="token number">51<span class="token punctuation">:<span class="token number">17.438388<span class="token operator">+<span class="token number">0800 JSCoreDemo<span class="token punctuation">[<span class="token number">4970<span class="token punctuation">:<span class="token number">219193<span class="token punctuation">] <span class="token keyword">undefined
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>为什么<code>p.firstName</code>运行后是<code>undefined</code>呢? 因为在这里没有将其暴露到 Native 环境中,所以就获取不到了。</p>
<h3>在 iOS 中处理 JS 异常</h3>
<p>通过 JSCore 中的 <code>exceptionHandler</code> 可以很好的解决这个问题,当 JS 运行异常时候,会回调 JSContext 的 exceptionHandler 中设置的 Block,这样就可以在 Block 里将错误信息上传至监控平台。</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-php"><code class="language-php"><span class="token comment">// 当JavaScript运行时出现异常
   <span class="token comment">// 会回调JSContext的exceptionHandler中设置的Block

   JSContext <span class="token operator">*jsContext <span class="token operator">= <span class="token punctuation">[<span class="token punctuation"> init<span class="token punctuation">]<span class="token punctuation">;

   jsContext<span class="token punctuation">.exceptionHandler <span class="token operator">= <span class="token operator">^<span class="token punctuation">(JSContext <span class="token operator">*context<span class="token punctuation">, JSValue<span class="token operator">*exception<span class="token punctuation">) <span class="token punctuation">{
      <span class="token function">NSLog<span class="token punctuation">(@<span class="token double-quoted-string string">"JS Error: %@"<span class="token punctuation">, exception<span class="token punctuation">)<span class="token punctuation">;
    <span class="token punctuation">}<span class="token punctuation">;

<span class="token punctuation"><span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>最后输出报错为:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-rust"><code class="language-rust"><span class="token number">2018<span class="token operator">-<span class="token number">03<span class="token operator">-<span class="token number">17 <span class="token number">11<span class="token punctuation">:<span class="token number">28<span class="token punctuation">:<span class="token number">07.248778<span class="token operator">+<span class="token number">0800 JSCoreDemo<span class="token punctuation">[<span class="token number">15007<span class="token punctuation">:<span class="token number">632219<span class="token punctuation">]
<span class="token operator">==<span class="token operator">==<span class="token operator">==<span class="token operator">==在iOS里面处理 JS 异常<span class="token operator">==<span class="token operator">==<span class="token operator">==<span class="token operator">==
<span class="token number">2018<span class="token operator">-<span class="token number">03<span class="token operator">-<span class="token number">17 <span class="token number">11<span class="token punctuation">:<span class="token number">28<span class="token punctuation">:<span class="token number">07.252255<span class="token operator">+<span class="token number">0800 JSCoreDemo<span class="token punctuation">[<span class="token number">15007<span class="token punctuation">:<span class="token number">632219<span class="token punctuation">]
JS Error<span class="token punctuation">: ReferenceError<span class="token punctuation">: Can<span class="token lifetime-annotation symbol">'t find variable<span class="token punctuation">: a
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h3>JS 和端相互通信</h3>
<p>最近给 Weex 提交了一个《More enhanced about &lt;web&gt; component》 的 PR,大概就是利用上述思路,通过实现 W3C 的 MessageEvent 规范来让组件和 Weex 之间可以进行互相通信,同时通过 loadHTMLString 直接来渲染传入的 html 源码功能。</p>
<p>具体实现为:</p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="1440" data-height="929"><img src="//upload-images.jianshu.io/upload_images/13180946-5b009e3c7e95bfa4.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">image</div>
</div>
<p>具体思路和 Demo 可见 </p>
<h3>JSPatch</h3>
<p>那能否通过 JS 直接来干预 iOS 代码的运行呢?可以通过一个简单的例子来看上述过程。</p>
<p>用 OC 写了一个蓝色的<code>Hello World</code>, 我们可以通过下发一段 JS 代码将原来蓝色的字体修改成红色并修改文字,将 JS 代码下发代码删除后,又可以恢复原来的蓝色<code>Hello World</code></p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="1388" data-height="872"><img src="//upload-images.jianshu.io/upload_images/13180946-4459809d35370ab5.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">&nbsp;</div>
</div>
<p>主要代码大致如下:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-php"><code class="language-php"><span class="token comment">// 一段显示蓝色 Hello World 的 OC 代码
@implementation ViewController
<span class="token operator">- <span class="token punctuation">(void<span class="token punctuation">)viewDidLoad <span class="token punctuation">{
      <span class="token punctuation"><span class="token punctuation">;
      <span class="token punctuation"><span class="token punctuation">;
<span class="token punctuation">}
<span class="token operator">- <span class="token punctuation">(void<span class="token punctuation">)simpleTest <span class="token punctuation">{
      self<span class="token punctuation">.label<span class="token punctuation">.text <span class="token operator">= @<span class="token double-quoted-string string">"Hello World"<span class="token punctuation">;
      self<span class="token punctuation">.label<span class="token punctuation">.textColor <span class="token operator">= <span class="token punctuation"><span class="token punctuation">;
<span class="token punctuation">}
@end

<span class="token comment">// 一段符合 JSPatch 规则的JS覆盖代码
<span class="token keyword">require<span class="token punctuation">(<span class="token single-quoted-string string">'UIColor'<span class="token punctuation">)<span class="token punctuation">;
<span class="token function">defineClass<span class="token punctuation">(<span class="token single-quoted-string string">'ViewController'<span class="token punctuation">, <span class="token punctuation">{ simpleTest <span class="token punctuation">: <span class="token keyword">function<span class="token punctuation">(<span class="token punctuation">) <span class="token punctuation">{
   self<span class="token punctuation">.<span class="token function">label<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">.<span class="token function">setText<span class="token punctuation">(<span class="token double-quoted-string string">"你的蓝色 Hello World 被我改成红色了"<span class="token punctuation">)<span class="token punctuation">;

   <span class="token keyword">var red <span class="token operator">= UIColor<span class="token punctuation">.<span class="token function">redColor<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">;
   self<span class="token punctuation">.<span class="token function">label<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">.<span class="token function">setTextColor<span class="token punctuation">(red<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}<span class="token punctuation">,
<span class="token punctuation">}<span class="token punctuation">)
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<blockquote>
<p>这里是如何做到的呢?JSPatch 是一个 iOS 动态更新框架,通过引入 JSCore,就可以使用 JS 调用任何原生接口,可以为项目动态更新模块、替换原生代码动态修复 Bug。</p>
</blockquote>
<p>原理是,JS 传递字符串给 OC,OC 通过 Runtime 接口替换甚至创建了 OC 类、方法,从而调用了相应的方法。</p>
<h3>Runtime</h3>
<p>OC 语言中大概 95% 都是 C 相关的写法,为何当时苹果不直接使用 C 来写 iOS 呢?其中一个很大的原因就是 OC 的动态性,有一个很强大的 Runtime (<strong>一套 C 语言的 API,底层基于它来实现</strong>),核心是<strong>消息分发</strong>,Runtime 会<strong>根据消息接收者是否能响应该消息而做出不同的反应</strong>。简单说,就是<strong>OC 方法的实现和调用指针的关系是在运行时才决定的,而非编译期</strong>,比如如下这个将<code>foo</code>插入到数组中的第 5 位:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-cpp"><code class="language-cpp"><span class="token punctuation"><span class="token punctuation">;
</span></span></span></span></span></span></code></pre>
</div>
<p>在底层比这个实现更加生涩,他通过<code>objc_msgSend</code>这个方法将消息搭配选择器进行发送出去:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-kotlin"><code class="language-kotlin"><span class="token function">objc_msgSend<span class="token punctuation">(array<span class="token punctuation">, <span class="token label symbol">@selector<span class="token punctuation">(insertObject<span class="token operator">:atIndex<span class="token operator">:<span class="token punctuation">)<span class="token punctuation">, foo<span class="token punctuation">, <span class="token number">5<span class="token punctuation">)<span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>运行时发消息给对象,消息是如何映射到方法的 ?</p>
<p>简单来说就是,一个对象的 class 保存了方法列表,是一个字典,key 为 selectors,IMPs 为 value,一个 IMP 是指向方法在内存中的实现,selector 和 IMP 之间的关系是在运行时才决定的,非编译时。</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-objectivec"><code class="language-objectivec"><span class="token operator">- <span class="token punctuation">(id<span class="token punctuation">)doSomethingWithInt<span class="token punctuation">:<span class="token punctuation">(<span class="token keyword">int<span class="token punctuation">)aInt<span class="token punctuation">{<span class="token punctuation">}
id <span class="token function">doSomethingWithInt<span class="token punctuation">(id <span class="token keyword">self<span class="token punctuation">, SEL _cmd<span class="token punctuation">, <span class="token keyword">int aInt<span class="token punctuation">)<span class="token punctuation">{<span class="token punctuation">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>通过看了下 Runtime 的源码,发现有如下这些常用的方法:</p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="1440" data-height="903"><img src="//upload-images.jianshu.io/upload_images/13180946-70019fcf34a1993b.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">image</div>
</div>
<p>通过上述这些方法就可以做很多意想不到的事情,比如<strong>动态的变量控制、动态给一个对象增加方法、可以把消息转发给想要的对象、甚至可以动态交换两个方法的实现</strong>。也就是说,我们可以在运行期做些事情更改原来的实现,达到热修复的目的。</p>
<h2>JSPatch &amp;&amp; Runtime</h2>
<p>正是由于 OC 语言的动态性,上所有方法的调用/类的生成都通过 OC Runtime 在运行时进行,可通过类名称和方法名的字符串获取该类和该方法,并实例化和调用:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-objectivec"><code class="language-objectivec">Class class <span class="token operator">= <span class="token function">NSClassFromString<span class="token punctuation">(<span class="token string">"UIViewController"<span class="token punctuation">)<span class="token punctuation">;
id viewController <span class="token operator">= <span class="token punctuation">[<span class="token punctuation"> init<span class="token punctuation">]<span class="token punctuation">;
SEL selector <span class="token operator">= <span class="token function">NSSelectorFromString<span class="token punctuation">(<span class="token string">"viewDidLoad"<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation"><span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>也可以<strong>替换</strong>某个类的方法为新的实现:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-kotlin"><code class="language-kotlin">static void <span class="token function">newViewDidLoad<span class="token punctuation">(id slf<span class="token punctuation">, SEL sel<span class="token punctuation">) <span class="token punctuation">{<span class="token punctuation">}
<span class="token function">class_replaceMethod<span class="token punctuation">(<span class="token keyword">class<span class="token punctuation">, selector<span class="token punctuation">, newViewDidLoad<span class="token punctuation">, @<span class="token string">""<span class="token punctuation">)<span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>还可以新注册一个类,为类<strong>添加方法</strong>:</p>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-bash"><code class="language-bash">Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);
</code></pre>
</div>
<p>JSPatch 正是利用如上这些好的特性来实现他的热修复功能。</p>
<h2>JSPatch 中 JS 如何调用 OC</h2>
<p>此处 JSPatch 中的 JS 是如何和任意修改 OC 代码联系起来的呢?大概原理如下:</p>
<ol>
<li>JSPatch 在实现中是通过 Require 调用,在 JS 全局作用域上创建一个同名变量,变量指向一个对象,对象属性 __clsName 保存类名,同时表明这个对象是一个 OC Class,通过调用<code>require(“UIView")</code>,我们就可以使用<code>UIView</code>去调用他上面对应方法了。</li>
</ol>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-php"><code class="language-php">UIView <span class="token operator">= <span class="token keyword">require<span class="token punctuation">(“UIView"<span class="token punctuation">)<span class="token punctuation">;

<span class="token keyword">var _require <span class="token operator">= <span class="token keyword">function<span class="token punctuation">(clsName<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">if <span class="token punctuation">(<span class="token operator">!<span class="token keyword">global<span class="token punctuation"><span class="token punctuation">) <span class="token punctuation">{
    <span class="token keyword">global<span class="token punctuation"> <span class="token operator">= <span class="token punctuation">{__clsName<span class="token punctuation">: clsName<span class="token punctuation">}
<span class="token punctuation">}
<span class="token keyword">return <span class="token keyword">global<span class="token punctuation">
<span class="token punctuation">}

</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<ol start="2">
<li>在 JSCore 执行前,OC 方法的调用均通过<strong>新增 Object(JS) 原型方法__c(methodName)完成调用</strong>,假如直接调用的话,需要 JS 遍历当前类的所有方法,还要循环找父类的方法直到顶层,无疑是很耗时的,通过<code>__c()</code>元函数的唯一性,可以每次调用它时候,转发给一个指定函数去执行,就很优雅了。</li>
</ol>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="2388" data-height="256"><img src="//upload-images.jianshu.io/upload_images/13180946-753a99eacb7e3e89.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"></div>
</div>
<div class="image-caption">&nbsp;</div>
</div>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-php"><code class="language-php">Object<span class="token punctuation">.prototype<span class="token punctuation">.__c <span class="token operator">= <span class="token keyword">function<span class="token punctuation">(methodName<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">return <span class="token keyword">function<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">{
<span class="token keyword">var args <span class="token operator">= <span class="token keyword">Array<span class="token punctuation">.prototype<span class="token punctuation">.slice<span class="token punctuation">.<span class="token function">call<span class="token punctuation">(arguments<span class="token punctuation">)
<span class="token keyword">return <span class="token function">_methodFunc<span class="token punctuation">(self<span class="token punctuation">.__obj<span class="token punctuation">, self<span class="token punctuation">.__clsName<span class="token punctuation">, methodName<span class="token punctuation">,
    args<span class="token punctuation">, self<span class="token punctuation">.__isSuper<span class="token punctuation">)
<span class="token punctuation">}
<span class="token punctuation">}

</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>3.处理好 JS 接口问题后,接下来只需要借助前面 JSCore 的知识就可以做到 JS 和 OC 之间的消息互传了,也即在<strong>在_c 函数中通过 JSContex 建立的桥接函数,用 Runtime 接口调用相应方法完成调用</strong>:</p>
<ul>
<li>JS 中的转发</li>
</ul>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-php"><code class="language-php"><span class="token keyword">var _methodFunc <span class="token operator">= <span class="token keyword">function<span class="token punctuation">(instance<span class="token punctuation">, clsName<span class="token punctuation">, methodName<span class="token punctuation">, args<span class="token punctuation">,isSuper<span class="token punctuation">) <span class="token punctuation">{
   <span class="token punctuation">.<span class="token punctuation">.<span class="token punctuation">.<span class="token punctuation">.
   <span class="token keyword">var ret <span class="token operator">=<span class="token function">_OC_callC<span class="token punctuation">(clsName<span class="token punctuation">, selectorName<span class="token punctuation">, args<span class="token punctuation">)
   <span class="token keyword">return <span class="token function">_formatOCToJS<span class="token punctuation">(ret<span class="token punctuation">)
<span class="token punctuation">}

</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<ul>
<li>OC 中的处理</li>
</ul>
<div class="_2Uzcx_"><button class="VJbwyy" type="button"></button>
<pre class="line-numberslanguage-objectivec"><code class="language-objectivec">context<span class="token punctuation">[<span class="token string">@"_OC_callC"<span class="token punctuation">] <span class="token operator">= <span class="token operator">^<span class="token function">id<span class="token punctuation">(NSString <span class="token operator">*className<span class="token punctuation">, NSString <span class="token operator">*selectorName<span class="token punctuation">, JSValue <span class="token operator">*arguments<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">return <span class="token function">callSelector<span class="token punctuation">(className<span class="token punctuation">, selectorName<span class="token punctuation">, arguments<span class="token punctuation">, nil<span class="token punctuation">, NO<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}<span class="token punctuation">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>原文地址:<br>
https://zhuanlan.zhihu.com/p/34646281</p>

</div>

</div><br><br>
来源:https://www.cnblogs.com/xujiahui/p/16531448.html
頁: [1]
查看完整版本: iOS基于JSCore的混合开发