<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>yulingtianxia&#39;s blog</title>
  
  <subtitle>玉令天下的博客</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://yulingtianxia.com/"/>
  <updated>2022-12-11T18:58:42.255Z</updated>
  <id>http://yulingtianxia.com/</id>
  
  <author>
    <name>杨萧玉</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Flutter 官方终于出手了，DartNative 将何去何从?</title>
    <link href="http://yulingtianxia.com/blog/2022/12/12/DartNative-Interface/"/>
    <id>http://yulingtianxia.com/blog/2022/12/12/DartNative-Interface/</id>
    <published>2022-12-11T17:46:52.000Z</published>
    <updated>2022-12-11T18:58:42.255Z</updated>
    
    <content type="html"><![CDATA[<p>2022 年 8 月底，Flutter 发布了 3.3 稳定版，随之发布的 Dart 2.18 宣布<a href="https://flutter.cn/posts/dart-2-18#dart-与-objective-c-和-swift-互调" target="_blank" rel="noopener">支持 Dart 与 Objective-C 和 Swift 互调</a>，而 <a href="https://github.com/dart-lang/sdk/issues/49674" target="_blank" rel="noopener">Java 与 Java/Kotlin 的互调也在开发中</a>。整体思路跟 DartNative 三年前的思路类似，走的也是跨语言 API 直接调用(但官方目前只支持同步)，然后通过工具链生成接口绑定。发布当天就有人给我提 Issue 了：<a href="https://github.com/dart-native/dart_native/issues/105" target="_blank" rel="noopener">老哥，考虑一下这个库未来何去何从吧，官方有了</a>，竟如此『不讲武德』</p><p><img src="http://yulingtianxia.com/resources/DartNative/20221212-025211@2x.png" alt></p><p>联想到之前 5 月份 Flutter 3.0(Dart 2.17) 发布时官方支持了 Dart Finalizer，跟 DartNative 一年前就支持的 Finalizer 冲突了，看来是『有备而来』啊:</p><p><img src="http://yulingtianxia.com/resources/DartNative/14035340227.png" alt></p><a id="more"></a><h2 id="ffigen-与-DartNative"><a href="#ffigen-与-DartNative" class="headerlink" title="ffigen 与 DartNative"></a>ffigen 与 DartNative</h2><p>Flutter 官方为了支持 Dart 与 Objective-C 和 Swift 互调，基于 ffigen 工具生成了大量的模板代码，缺点是可读性差，优点是性能好一些；DartNative 基于 Native Runtime 动态调用任意 Objective-C 和 Swift 方法，codegen 只是锦上添花，缺点是首次调用性能有所牺牲（有 cache），优点是动态性强且生成代码可读性高，即便手写代码也很少。</p><p>目前二者的实现细节差异也蛮大，比如官方的代码生成是基于 clang 的，比 DartNative codegen 基于的 antlr 更严谨一些，但是使用成本也高很多。官方的 ffigen 目前虽然从 Sample 示例上虽然还没看到对异步调用和回调等能力的支持，不过从整体上官方投入力度还是蛮大的，比我这种利用空闲时间断断续续搞的 sideproject 强多了，后续的能力补齐只是时间问题。</p><p>这三年来我曾一直怀疑 DartNative 的设计路线是否正确，现在官方亲自下场了，那说明这个思路还是有前瞻性的。不过在此之前我也一直在反思这个设计的缺点：把抹平各语言 API 的工作交给了 dart 这一层，需要写平台判断的代码，这与主流的 Channel 接口绑定方案使用上差异很大，增加了理解成本和门槛。为了补齐这块短板，我和另一位贡献者 <a href="https://github.com/hui19" target="_blank" rel="noopener">hui19</a> 参考了 JSI 等跨语言 bridge 接口绑定的设计，提出了开发 DartNative Interface（简称 DNI?），并在 2022 年 5 月份 DartNative 的 0.7.x 版本开始支持（比官方 8 月份发布 Dart 2.18 还要早哈哈）。</p><p>于是有意思的事情出现了：Channel/ffigen 和 DartNative 正在朝着对方的设计思路演进，但实现上却有很大差别。这里没有吹嘘 Flutter 官方是参考了 DartNative 的意思，毕竟这种只有不到 900 Star 的小项目根本不会受到 Google 官方关注，我相信这只是巧合罢了。而且 ffigen 和 DartNative 都是基于官方的 dart:ffi 实现的，所以 Google 永远都是爸爸。</p><h2 id="什么是-DartNative-Interface"><a href="#什么是-DartNative-Interface" class="headerlink" title="什么是 DartNative Interface"></a>什么是 DartNative Interface</h2><p>DartNative Interface 实现了跨语言接口之间的绑定和双向调用。相比于 Channel，无需针对 method 写一堆 if-else，也不用把参数挤在一坨序列化和反序列化。DartNative Interface 会将参数列表和返回值自动转换，并支持同步调用和异步协程（这 Channel 它能比吗？它不可以）。iOS/macOS/Android 目前支持的数据类型：num/String/List/Map/Set/NativeByte/NativeObject，支持双向互相调用。iOS/macOS 额外支持 Function/Pointer，支持 Swift。</p><p>相比于 DartNative 之前的设计，接口绑定意味着减少了对 native 调用的动态性，但也提供了抹平多端接口差异的标准化方案。继承了 DartNative 强大的类型转换和生命周期管理能力，更加易用。</p><p><img src="https://github.com/dart-native/dart_native/blob/master/images/dartnative.png?raw=true" alt></p><h3 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h3><h4 id="Dart-调用-Native"><a href="#Dart-调用-Native" class="headerlink" title="Dart 调用 Native"></a>Dart 调用 Native</h4><p>Dart 代码：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">interface</span> = <span class="title">Interface</span>("<span class="title">MyFirstInterface</span>");</span></span><br><span class="line"><span class="class">// <span class="title">Example</span> <span class="title">for</span> <span class="title">string</span> <span class="title">type</span>.</span></span><br><span class="line"><span class="class"><span class="title">String</span> <span class="title">helloWorld</span>() </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="class"><span class="keyword">interface</span>.<span class="title">invokeMethodSync</span>('<span class="title">hello</span>', <span class="title">args</span>: ['<span class="title">world</span>']);</span></span><br><span class="line"><span class="class">&#125;</span></span><br><span class="line"><span class="class">// <span class="title">Example</span> <span class="title">for</span> <span class="title">num</span> <span class="title">type</span>.</span></span><br><span class="line"><span class="class"><span class="title">Future</span>&lt;<span class="title">int</span>&gt; <span class="title">sum</span>(<span class="title">int</span> <span class="title">a</span>, <span class="title">int</span> <span class="title">b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="class"><span class="keyword">interface</span>.<span class="title">invokeMethod</span>('<span class="title">sum</span>', <span class="title">args</span>: [<span class="title">a</span>, <span class="title">b</span>]);</span></span><br><span class="line"><span class="class">&#125;</span></span><br></pre></td></tr></table></figure><p>对应的 Objective-C 代码：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">DNInterfaceDemo</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Register interface name.</span></span><br><span class="line">InterfaceEntry(MyFirstInterface)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Register method "hello".</span></span><br><span class="line">InterfaceMethod(hello, myHello:(<span class="built_in">NSString</span> *)str) &#123;</span><br><span class="line">    <span class="keyword">return</span> [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"hello %@!"</span>, str];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Register method "sum".</span></span><br><span class="line">InterfaceMethod(sum, addA:(int32_t)a withB:(int32_t)b) &#123;</span><br><span class="line">    <span class="keyword">return</span> @(a + b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><p>对应的 Java 代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// load libdart_native.so</span></span><br><span class="line">DartNativePlugin.loadSo();</span><br><span class="line"></span><br><span class="line"><span class="meta">@InterfaceEntry</span>(name = <span class="string">"MyFirstInterface"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InterfaceDemo</span> <span class="keyword">extends</span> <span class="title">DartNativeInterface</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@InterfaceMethod</span>(name = <span class="string">"hello"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(String str)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"hello "</span> + str;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@InterfaceMethod</span>(name = <span class="string">"sum"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">sum</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> a + b;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Native-调用-Dart"><a href="#Native-调用-Dart" class="headerlink" title="Native 调用 Dart"></a>Native 调用 Dart</h4><p>Dart 代码：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span>.<span class="title">setMethodCallHandler</span>('<span class="title">totalCost</span>',</span></span><br><span class="line"><span class="class">        (<span class="title">double</span> <span class="title">unitCost</span>, <span class="title">int</span> <span class="title">count</span>, <span class="title">List</span> <span class="title">list</span>) <span class="title">async</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">'totalCost: <span class="subst">$&#123;unitCost * count&#125;</span>'</span>: list&#125;;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>对应的 Objective-C 代码：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[<span class="keyword">self</span> invokeMethod:<span class="string">@"totalCost"</span></span><br><span class="line">         arguments:@[@<span class="number">0.123456789</span>, @<span class="number">10</span>, @[<span class="string">@"testArray"</span>]]</span><br><span class="line">            result:^(<span class="keyword">id</span> _Nullable result, <span class="built_in">NSError</span> * _Nullable error) &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>, result);</span><br><span class="line">&#125;];</span><br></pre></td></tr></table></figure><p>对应的 Java 代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">invokeMethod(<span class="string">"totalCost"</span>, <span class="keyword">new</span> Object[]&#123;<span class="number">0.123456789</span>, <span class="number">10</span>, Arrays.asList(<span class="string">"hello"</span>, <span class="string">"world"</span>)&#125;,</span><br><span class="line">             <span class="keyword">new</span> DartNativeResult() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onResult</span><span class="params">(@Nullable Object result)</span> </span>&#123;</span><br><span class="line">                    Map retMap = (Map) result;</span><br><span class="line">                    <span class="comment">// do something</span></span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">error</span><span class="params">(@Nullable String errorMessage)</span> </span>&#123;</span><br><span class="line">                    <span class="comment">// do something</span></span><br><span class="line">                &#125;</span><br><span class="line">              &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h4 id="Dart-Finalizer"><a href="#Dart-Finalizer" class="headerlink" title="Dart Finalizer"></a>Dart Finalizer</h4><p>Flutter 3.0(Dart 2.17) 开始支持 Dart Finalizer，但是使用 DartNative，只需要 Flutter 2.2(Dart 2.13) 就可以了：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> foo = Bar(); <span class="comment">// A custom instance.</span></span><br><span class="line">unitTest.addFinalizer(() &#123; <span class="comment">// register a finalizer callback.</span></span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'The instance of \'foo\' has been destroyed!'</span>); <span class="comment">// When `foo` is destroyed by GC, this line of code will be executed.</span></span><br><span class="line">&#125;);</span><br><span class="line">``` </span><br><span class="line"></span><br><span class="line">如果想在 Native 监听一个 Dart 对象被销毁，做法是 Dart 从 Native 获取一个对象，并作为另一个想要被监听的 Dart 对象的属性：</span><br><span class="line"></span><br><span class="line">```dart</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DartLifecycleObject</span> </span>&#123;</span><br><span class="line">  late <span class="keyword">final</span> <span class="built_in">dynamic</span> finalizer;</span><br><span class="line">  DartLifecycleObject() &#123;</span><br><span class="line">    finalizer = <span class="class"><span class="keyword">interface</span>.<span class="title">invokeMethodSync</span>('<span class="title">finalizer</span>');</span></span><br><span class="line"><span class="class">  &#125;</span></span><br><span class="line"><span class="class">&#125;</span></span><br></pre></td></tr></table></figure><p>这里以 OC 语言为例，可以返回一个自定义类（DNDartFinalizer）的对象：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">InterfaceMethod(finalizer, finalizerObject) &#123;</span><br><span class="line">    <span class="keyword">return</span> [[DNDartFinalizer alloc] init];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当 Dart 对象（DartLifecycleObject）被释放后，DNDartFinalizer 对象也会被释放，所以在 dealloc 方法中可以监听到：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">DNDartFinalizer</span></span></span><br><span class="line">- (<span class="keyword">void</span>)dealloc &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@"DartLifecycleObject dead!"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><h2 id="何去何从"><a href="#何去何从" class="headerlink" title="何去何从?"></a>何去何从?</h2><p>DartNative 的最近一次技术分享是在 2021 年底的 GMTC 深圳站：<a href="https://gmtc.infoq.cn/2021/shenzhen/presentation/4010" target="_blank" rel="noopener">Flutter 自研通道 DartNative 的探索与实现</a>。不过那次分享的内容有所保留，不仅没有提到 DartNative 的 Interface 开发计划，连当时已经做了的一些新特性都没有讲，比如支持 Swift 和 macOS、适配 Dart nullsafety、支持 muti-isolates、支持 OC 方法和 block 返回 Future 给 Dart 等。原因是篇幅要精简，况且总得攒点东西留着后续分享不是嘛？</p><p>当时我还是 Flutter 专题的出品人，title 长度虽然比不过龙妈，但依然被黑惨了：</p><p><img src="http://yulingtianxia.com/resources/DartNative/IMG_4853.JPG" alt></p><p>接下来的故事比较有意思，手 Q 后来下掉了 Flutter，业务就缺乏了落地验证场景（iOS App 是否使用 Flutter 并不是机密，扒一下安装包就看得出来，网上也有人统计过各大 App 使用 Flutter 的情况）。考虑到腾讯 Flutter Oteam 未来的发展需要落地到具体业务，于是我把 Oteam 负责人的职位交接给了其他大佬。DartNative 依旧在修修补补，毕竟公司还有其他的 App 在使用，GitHub 也偶尔有提 Issue。下掉 Flutter 后我又接手了小程序，之前有用 Flutter 写的业务如今运行在了小程序上。缘，妙不可言。</p><p>最后就是官方宣布 ffigen 支持直接调用 Objective-C/Swift 之后，Java/Kotlin 也在进行中。我估计再给 Flutter 官方一年时间，ffigen 应该可以补齐能力。剩下的就是 Channel 与 DartNative Interface 的差异了，我只能抽业余时间尽量缝缝补补吧，也期待官方能力不断拓展，毕竟总有那么一天的。目前 DartNative 的硬伤是不支持 Windows，如果有 Windows 大佬欢迎共建。我是肝不动了，否则博客能断更两年么（手动狗头）？</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;2022 年 8 月底，Flutter 发布了 3.3 稳定版，随之发布的 Dart 2.18 宣布&lt;a href=&quot;https://flutter.cn/posts/dart-2-18#dart-与-objective-c-和-swift-互调&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;支持 Dart 与 Objective-C 和 Swift 互调&lt;/a&gt;，而 &lt;a href=&quot;https://github.com/dart-lang/sdk/issues/49674&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Java 与 Java/Kotlin 的互调也在开发中&lt;/a&gt;。整体思路跟 DartNative 三年前的思路类似，走的也是跨语言 API 直接调用(但官方目前只支持同步)，然后通过工具链生成接口绑定。发布当天就有人给我提 Issue 了：&lt;a href=&quot;https://github.com/dart-native/dart_native/issues/105&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;老哥，考虑一下这个库未来何去何从吧，官方有了&lt;/a&gt;，竟如此『不讲武德』&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://yulingtianxia.com/resources/DartNative/20221212-025211@2x.png&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;联想到之前 5 月份 Flutter 3.0(Dart 2.17) 发布时官方支持了 Dart Finalizer，跟 DartNative 一年前就支持的 Finalizer 冲突了，看来是『有备而来』啊:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://yulingtianxia.com/resources/DartNative/14035340227.png&quot; alt&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>CPP-Summit 2020</title>
    <link href="http://yulingtianxia.com/blog/2020/12/06/CPP-Summit-2020/"/>
    <id>http://yulingtianxia.com/blog/2020/12/06/CPP-Summit-2020/</id>
    <published>2020-12-05T17:16:56.000Z</published>
    <updated>2020-12-06T15:36:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>2020 年是 <a href="http://cpp-summit.org" target="_blank" rel="noopener">CPP-Summit</a> 第一次来到深圳，也是赶在了 2020 的尾巴。感谢公司让我有机会参加这种会议见见世面，在此记录下这两天的经历和想法，就当是记笔记交作业了。</p><a id="more"></a><p>简单介绍下这个会议，中文名是『全球 C++ 及系统软件技术大会』。虽然它的英文名里省略了『全球』，但从演讲嘉宾阵容上来看，也算是对得起『全球』二字了：有将近三分之一的演讲者来自国外。官网上的大会主席是『C++ 之父』Bjarne Stroustrup，他今年演讲的主题是<a href="http://cpp-summit.org/speaker/502?uid=c1026" target="_blank" rel="noopener">《C++ 20 与C++的持续演化》</a>。顺便说下，这个是『真·西佳佳之父』。会议的主办方是一家叫 Boolan 的技术咨询和培训机构，所以会议开始会植入一波硬广，这也算是常规操作了。大会的演讲嘉宾有极少部分是来自这家机构以及大会金主爸爸的，大部分嘉宾都是来自行业内有一定经验的开发者，阵容还算可以。</p><p>先说下总体感受：C++ 作为一门亘古不衰的语言，在各行各业都存在着经验丰富的开发者。随着各种更加『现代化』的语言的兴起，C++ 也迎来了更多的变化。不过很多新增语法特性的改进更像是戴着脚镣跳舞，保证对存量代码的向前兼容。在大会上也深深感受到互联网行业对传统软件行业的冲击，甚至讨论了 C++ 这碗饭还香不香的问题。大会的议题总体分为三大类：</p><ol><li>新特性的研究和布道</li><li>性能和安全的实践经验</li><li>研发模式、测试和工程化等方法论</li></ol><p>一般这种大会都是分为多个分会场同时演讲的，我只能选其中一些我比较感兴趣的议题去参加，大部分集中在前两类。国外开发者分享的议题大部分集中在第三类，由于今年疫情的原因，都改为线上会议了。国内开发者的分享更侧重于性能和安全，或者尝鲜新特性，尤其是大厂更强调业务落地。这里按时间顺序列出我参加的分享，并附上我的一句话短评：</p><ul><li><a href="http://cpp-summit.org/speaker/502?uid=c1026" target="_blank" rel="noopener">Bjarne Stroustrup  * C++ 20 与 C++ 的持续演化</a>：介绍了 C++ 历史和 C++ 20 的新特性，以及工作组如何讨论和制定标准，后续的规划。</li><li><a href="http://cpp-summit.org/speaker/601?uid=c1026" target="_blank" rel="noopener">John Lakos  * C++ 大规模软件开发方法</a>：作者写了一本书，这里是第一章，有关组件化相关的内容。</li><li><a href="http://cpp-summit.org/speaker/590?uid=c1026" target="_blank" rel="noopener">Charley Bay  * 系统架构师的三个视角：开发、架构与设计视角</a>：架构师要做的事情，各种方法论。</li><li><a href="http://cpp-summit.org/speaker/201?uid=c1026" target="_blank" rel="noopener">吴咏炜  C++ 性能调优纵横谈</a>：结合编译器选项和工具介绍了性能测试的正确姿势以及优化经验，列举了很多实践案例和测试数据，干货满满。</li><li><a href="http://cpp-summit.org/speaker/584?uid=c1026" target="_blank" rel="noopener">马骏 (良斌)  C++ 协程在阿里的推广和大规模应用</a>：阿里的编译器工作组对协程的支持，技术实现和业务落地。</li><li>分论坛  下一代软件架构与工程变革</li><li><a href="http://cpp-summit.org/speaker/588?uid=c1026" target="_blank" rel="noopener">刘新铭  如何检测业务逻辑导致的安全漏洞</a>：他们做了一款基于 open64 编译器实现的 C++ 代码静态扫描工具。</li><li><a href="http://cpp-summit.org/speaker/571?uid=c1026" target="_blank" rel="noopener">陈峰  腾讯广告系统大规模 C++ 工程实践</a>：腾讯广告系统的代码库组织方式、构建系统等实践。</li><li><a href="http://cpp-summit.org/speaker/311?uid=c1026" target="_blank" rel="noopener">张银奎  从纳秒级优化谈 CPU 眼里的好代码</a>：总结了高性能编程的经验，结合 CPU 底层原理和编译器选项调优，干货满满，硬核分享。</li><li><a href="http://cpp-summit.org/speaker/603?uid=c1026" target="_blank" rel="noopener">谈静国  渗透视角下的 C/C++ 安全编码实践</a>：列举了一些编码安全隐患以及渗透原理，介绍了常用的解决方案，其中也涉及静态扫描工具。</li><li><a href="http://cpp-summit.org/speaker/549?uid=c1026" target="_blank" rel="noopener">冉昕  低延迟场景下的性能优化实践</a>：介绍了低延迟场景下性能测试的测不准问题，从方方面面一点点抠性能，列举了大量常见 API 的 benchmark。</li><li><a href="http://cpp-summit.org/speaker/593?uid=c1026" target="_blank" rel="noopener">Ivan Čukić  * 如何设计优雅的API 同时不牺牲性能</a>：国外开发者少有的深入编码实践的分享，利用 C++ 11 的右值引用特性梳理了大量实践，并通过比较汇编代码的差异分析性能。</li><li><a href="http://cpp-summit.org/speaker/540?uid=c1026" target="_blank" rel="noopener">张汉东  Rust系统级开发的优势与劣势</a>：业界戏称『Rust 之父』前来踢馆，安利一波 Rust 的好与坏。</li></ul><p><img src="http://yulingtianxia.com/resources/CPP-Summit/IMG_3152.JPG" alt></p><ul><li><a href="http://cpp-summit.org/speaker/575?uid=c1026" target="_blank" rel="noopener">张超  C++ Modules 与大规模物理设计</a>：对 C++ Modules 新特性的介绍，还算是不错的培训机构课程。</li></ul><p>接下来胡扯下我感受到的几个点。</p><h2 id="性能与安全"><a href="#性能与安全" class="headerlink" title="性能与安全"></a>性能与安全</h2><p>如果要分析性能，那就一定要涉及到编译器开启的优化选项，不同级别的优化会导致差异很大的结果。有些代码会因为过于简单而被编译器优化没了，从而导致测试结果的偏差。这时候往往需要对照生成的汇编代码来看，推荐一个在线工具：<a href="https://godbolt.org" target="_blank" rel="noopener">Compiler Explorer</a></p><p>安全方面能做的大都是自动化代码静态扫描工具。C++ 除了接入扫描工具检查之外，也在努力添加新特性，想把一些事情放在编译期去做。好像很多现代化语言都喜欢在编译期保证空值安全，类型安全等等，甚至是编译期就已经计算好了一些常量，推导出了一些结果。想要在编译期做更多事情，就需要语法特性上做更多要求。带来的负面影响就是学习成本高，很难做动态化。比如 Rust，初学者想让自己的代码编译通过都有点难。</p><h2 id="C-的生命力"><a href="#C-的生命力" class="headerlink" title="C++ 的生命力"></a>C++ 的生命力</h2><p>C++ 对嵌入式、培训和咨询等行业影响很深，但是移动互联网时代大家更偏向于使用 Go、Python、Rust 等语言。比如有人提到招 C++ 的应届生很难，由于前一阵子深度学习很火，好多都是只会 Python 的候选人。接着也有人回应说深度学习的应用层的确是 Python，但是各种引擎和框架还是需要 C++来实现，这恰恰反映出 C++ 高性能的优势。甚至有人说要是以后大家不学 C++ 了，那么在座的各位待遇就会翻倍了，因为有大量现存的代码是用 C++ 写的，只有我们才看得懂。</p><p>大会上好多拥有二三十年 C++ 开发经验的大佬，我这种毕业四年的萌新只能在一旁打 call。大量熟悉底层软硬件的资深开发努力分享经验，做咨询和教育。大家并不希望 C++ 因为过时而构起了壁垒，而是积极拥抱变化，布道更现代化的 C++。</p><p>但话又说回来，C++ 的这些新特性很多要照顾已有的旧语法，用起来还是少了内味儿。比如各种现代语言都具有的 Modules 概念，C++至今也并未普及，这也导致 C++ 没有一个公认的制品库管理工具。站在一些现代语言的开发者的视角，C++ 不过是在努力变革求生罢了。尴尬之处就在于，维护 C++ 老代码时没必要升级到新特性（有工作量和风险），若是写新业务还不如直接用上 Go，Java，Scala 等。一门语言的命运啊，当然要靠自我奋斗，但是也考虑到历史的行程。</p><h2 id="国内与国外"><a href="#国内与国外" class="headerlink" title="国内与国外"></a>国内与国外</h2><p>在一次午餐的时候，我听到隔壁桌聊天说国外开发者讲的东西太虚了。我作为一线开发者深表赞同，也是在『踩坑』后避开了大部分国外开发者的分享。可能这里不仅仅是分享内容不被大家讨喜的原因，主办方的实时翻译效果也不太好。建议主办方考虑下，不要为了追求『全球』国际化而刻意安排国外开发者分享的比例。</p><p>为主办方点赞的是现场可以通过贴小心心的方式为分享者打 call。（主办方的小姐姐给了我三颗小心心让我贴给心心比较少的几位，然而我比较耿直，还是送给了我想打 call 的几位分享者）</p><p><img src="http://yulingtianxia.com/resources/CPP-Summit/IMG_3153.jpeg" alt></p><p><img src="http://yulingtianxia.com/resources/CPP-Summit/IMG_3155.jpeg" alt></p><p>最受欢迎的当属『C++ 之父』的分享，我觉得这不仅仅是分享内容受欢迎了，『教父』自带的人气和信仰也是很重要的。提问环节甚至有一位华为的项目经理上来就是一句 “Dear father!”，然后在主持人的一通喊停之下来了一通自我介绍，并邀请 Bjarne Stroustrup 来甚至华为参观，想来个拥抱交个朋友。最后说自己没有问题要提问，单纯想膜拜下。（非原话，差不多这个意思）反正把大伙儿都逗笑了。更逗的是线上提问，亮点自寻：</p><p><img src="http://yulingtianxia.com/resources/CPP-Summit/IMG_3136.jpeg" alt></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;2020 年是 &lt;a href=&quot;http://cpp-summit.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CPP-Summit&lt;/a&gt; 第一次来到深圳，也是赶在了 2020 的尾巴。感谢公司让我有机会参加这种会议见见世面，在此记录下这两天的经历和想法，就当是记笔记交作业了。&lt;/p&gt;
    
    </summary>
    
    
      <category term="开发者大会" scheme="http://yulingtianxia.com/tags/%E5%BC%80%E5%8F%91%E8%80%85%E5%A4%A7%E4%BC%9A/"/>
    
  </entry>
  
  <entry>
    <title>实现 Native 异步回调 Flutter</title>
    <link href="http://yulingtianxia.com/blog/2020/10/25/Asynchronous-Callback-for-Flutter/"/>
    <id>http://yulingtianxia.com/blog/2020/10/25/Asynchronous-Callback-for-Flutter/</id>
    <published>2020-10-25T05:15:24.000Z</published>
    <updated>2020-10-25T09:39:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>看到标题的你可能已经充满疑问：Channel 不是本来就支持 Native 调用 Flutter 的么？别着急，先往下看。<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 要实现的是一个用 Channel 无法做到的回调场景：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/async_callback_block_code.png" alt></p><p>为什么说 Flutter Channel 无法做到呢，有两点：</p><ol><li>使用 Channel 从 Native 调用 Dart 时，想获取返回值就只能通过在 Channel API 在主线程的异步回调 <code>FlutterResult</code>。上面的例子是在 Native 的主线程调用 Flutter 并可以<strong>同步</strong>获取到返回值，如果用 Channel 会直接导致死锁。</li><li>Flutter Channel 需要写额外的胶水代码，而上面的例子简单清爽，跨语言调用无缝衔接。</li></ol><a id="more"></a><p>PS: 考虑到性能问题，在 Native 主线程调用 Flutter 并同步等待返回值这种场景，可能会引起卡顿。实事求是地说，这可能本身是个不该考虑到的场景，但不代表 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 就不去做。毕竟 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 在实现 Flutter Channel 没覆盖到的场景的同时，也在尝试不断替代它。</p><p>除了 Block 的回调场景，还有 iOS 里常见的 Delegate：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">RuntimeStub</span></span></span><br><span class="line">- (<span class="keyword">void</span>)fooDelegate:(<span class="keyword">id</span>&lt;SampleDelegate&gt;)delegate &#123;</span><br><span class="line">    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<span class="number">1</span> * <span class="built_in">NSEC_PER_SEC</span>)), dispatch_get_main_queue(), ^&#123;</span><br><span class="line">        <span class="built_in">NSObject</span> *result = [delegate callback];</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><p>使用 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 是这么玩的：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">DelegateStub delegate = DelegateStub();</span><br><span class="line">RuntimeSon stub = RuntimeSon();</span><br><span class="line">stub.fooDelegate(delegate);</span><br></pre></td></tr></table></figure><p>当 OC 的 <code>[delegate callback]</code> 被执行时，Dart 类 <code>DelegateStub</code> 实例的 <code>callback()</code> 方法会被调用：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DelegateStub</span> <span class="keyword">extends</span> <span class="title">NSObject</span> <span class="title">with</span> <span class="title">SampleDelegate</span> </span>&#123;</span><br><span class="line">  DelegateStub() : <span class="keyword">super</span>(Class(<span class="string">'DelegateStub'</span>, type(of: NSObject))) &#123;</span><br><span class="line">    <span class="keyword">super</span>.registerSampleDelegate();</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  callback() &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'callback succeed!'</span>);</span><br><span class="line">    <span class="keyword">return</span> NSObject();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>类似 Native 需要回调给 Dart 的场景还有很多，比如 <code>NSNotification</code>、<code>dealloc</code> 等渠道。这些回调需求无法直接用 Channel 来实现，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 基于 DartVM 提供的能力实现了一套回调机制，支持阻塞和非阻塞两种方式。</p><p>下面以 Block 回调为例来讲下具体实现，在这之前先熟悉下 Dart Function 是如何变成 Block 并传递给 OC 的。这部分原理在之前的<a href="http://yulingtianxia.com/blog/2020/03/28/Using-Objective-C-Block-in-Flutter/">《在 Flutter 中玩转 Objective-C Block》</a>也有讲过：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/async_callback_block_workflow.png" alt></p><p>Block 能够回调到对应的 Dart Function 的前提是能建立起绑定关系，也就是上图右侧的『指针绑定』那里。这里是通过 DartFFI 提供的能力来将 Dart Function 转为函数指针 <code>callback</code>，更加具体的关联逻辑实现如下图所示：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/async_callback_block_correlative.png" alt></p><p>绑定好 OC Block 和 Dart Function 后，重点来了：如何在 Native 调用 <code>callback</code> 函数指针指向的函数？要知道，这个函数肯定要在 Dart Function 传入时的线程来调用的（一般是 <code>flutter-ui</code> 线程），而且还不能位经由 <code>isolate</code> 直接切到对应线程去调用。</p><p>这里使用的 Port 相关 API，在 Dart isolate 库中可以找到。不过在 Native 侧 Flutter 框架并未暴露这些 API，但是可以在 Dart VM 源码中找到。Native 异步调用到 Flutter 的实现流程如下，具体函数实现可以查阅 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 源码：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/async_callback_block_port.png" alt></p><p>上面讲完了 Block 执行后回调 Dart。Block <code>dealloc</code> 后回调 Dart 并释放资源的原理跟上面大同小异，只是异步回调的时候不会阻塞等待返回值罢了。更详细流程可以看之前我写的<a href="http://yulingtianxia.com/blog/2020/08/22/[DartNative](https://github.com/dart-native/dart_native)-Automatic-Memory-Management/">《<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 内存自动管理》</a>对 Block 内存的处理。</p><p>而 Delegate 和 Notification 回调的调用原理都跟 Block 回调相同，只是在上层类型封装和参数列表上有少许差异，这里不再赘述。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;看到标题的你可能已经充满疑问：Channel 不是本来就支持 Native 调用 Flutter 的么？别着急，先往下看。&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DartNative&lt;/a&gt; 要实现的是一个用 Channel 无法做到的回调场景：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://yulingtianxia.com/resources/DartObjC/async_callback_block_code.png&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;为什么说 Flutter Channel 无法做到呢，有两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 Channel 从 Native 调用 Dart 时，想获取返回值就只能通过在 Channel API 在主线程的异步回调 &lt;code&gt;FlutterResult&lt;/code&gt;。上面的例子是在 Native 的主线程调用 Flutter 并可以&lt;strong&gt;同步&lt;/strong&gt;获取到返回值，如果用 Channel 会直接导致死锁。&lt;/li&gt;
&lt;li&gt;Flutter Channel 需要写额外的胶水代码，而上面的例子简单清爽，跨语言调用无缝衔接。&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>如何实现 Flutter 同步调用 Native API</title>
    <link href="http://yulingtianxia.com/blog/2020/09/28/Synchronous-Channel-for-Flutter/"/>
    <id>http://yulingtianxia.com/blog/2020/09/28/Synchronous-Channel-for-Flutter/</id>
    <published>2020-09-28T09:53:16.000Z</published>
    <updated>2020-09-30T14:17:54.000Z</updated>
    
    <content type="html"><![CDATA[<p>Flutter Channel 是一个异步调用通道，如果想在 Dart 侧同步获取到 Native 返回的结果，调用的时候加上 <code>await</code> 就可以了：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="built_in">int</span> result = <span class="keyword">await</span> platform.invokeMethod(<span class="string">'hello channel'</span>);</span><br></pre></td></tr></table></figure><p>所以这篇文章到此为止了？</p><p>不！上面这行代码其实是个『假同步』，因为它只保证了 Dart 代码的同步执行，而 Native 代码与 Dart 并不在同一条线程执行。试想下，如果你通过 Flutter Channel 打日志，但由于打日志的消息是异步传递到 Native 的，最后日志顺序可能是错的。而通过日志来排查一些时序性相关的 Bug 时，日志的顺序很重要。</p><p>因为 Flutter Channel 设计之初就是异步的，使用 <code>await</code> 来回切换线程所带来的开销不小。而且协程的 <code>await</code> 语法具有传递性，上层调用方也需要使用 <code>await</code>，层层传递。</p><p>而 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 设计之初就是同步调用的，且也支持异步调用：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// new DNTest instance and call hello method.</span></span><br><span class="line">DNTest().hello(<span class="string">'DartNative'</span>);</span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="Why-DartNative"><a href="#Why-DartNative" class="headerlink" title="Why DartNative?"></a>Why DartNative?</h2><ol><li><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 是『真同步』，保证了执行顺序。同时也支持异步调用。</li><li>一行代码实现同步调用，告别 Flutter Channel 胶水代码带来的开发成本。</li><li>同步调用性能是 Flutter Channel 的数倍。分别使用 Flutter Channel 和 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 调用 <code>fooNSString:</code> 方法，<strong>耗时相差三到四倍</strong>。性能数据可能在不同场景下有波动，可以通过执行 <a href="https://github.com/dart-native/dart_native/blob/3af52f7d3cfa0d93fd9fc04a10a05d4a2e0d5398/dart_native/example/lib/ios/ios_main.dart" target="_blank" rel="noopener">Benchmark 代码</a> 来对比结果。</li></ol><h2 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h2><p>下图以 Dart 同步调用 iOS Objective-C API 为例，描述了 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 同步调用的原理。以一个字符串参数为例，讲述了从 Dart <code>String</code> 自动转为 Objective-C <code>NSString</code> 并传递给 <code>hello:</code> 方法的过程。返回值也是自动转换类型的，由于篇幅原因没在图片中描述。</p><p><img src="http://yulingtianxia.com/resources/DartObjC/sync_call_whole.png" alt></p><p>在实现了基本的同步调用后，开发重点也转向了性能优化。</p><h2 id="方法签名的优化"><a href="#方法签名的优化" class="headerlink" title="方法签名的优化"></a>方法签名的优化</h2><p>在 Dart 同步调用 Native 时，为了实现跨语言调用时参数和返回值类型的自动转换，需要先获取到 Native 的方法签名。这里做了两方面的性能优化：</p><ol><li>通过 DartFFI 调用 OC Runtime 获取方法签名占据了一定耗时。可以在 Dart 侧加一层 Cache 来减少通信和反射次数。</li><li>方法签名字符串的构成是 “TypeEncoding+offset” 的组合，跨语言之间传递字符串的编解码的耗时较多，而只有 TypeEncoding 那部分才是类型自动转换所需要的。绝大部分类型对应的 TypeEncoding 都是固定的，于是只需要传递 TypeEncoding 的指针即可。</li></ol><p><img src="http://yulingtianxia.com/resources/DartObjC/sync_call_optimize.png" alt></p><h2 id="字符串转换的优化"><a href="#字符串转换的优化" class="headerlink" title="字符串转换的优化"></a>字符串转换的优化</h2><p>Dart <code>String</code> 在与 Objective-C <code>NSString</code> 相互转换的过程中，数据传输的格式的选择至关重要。因为 Dart <code>String</code> 是使用 UTF16 编码的，所以 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 使用 <code>Uint16List</code> 作为数据传输的格式。通过性能测试，使用 UTF16 来回传输字符串的总耗时（包含 Native 方法自身耗时）相比 UTF8 <a href="https://github.com/dart-native/dart_native/issues/22" target="_blank" rel="noopener">减少了 35% 左右</a>，如果只计算通道自动类型转换耗时减少的比例会更多。</p><h3 id="转换-Dart-String-为-Objective-C-NSString"><a href="#转换-Dart-String-为-Objective-C-NSString" class="headerlink" title="转换 Dart String 为 Objective-C NSString:"></a>转换 Dart <code>String</code> 为 Objective-C <code>NSString</code>:</h3><p>使用 DartFFI 在堆上创建 <code>uint16_t</code> 数组，将 Dart <code>String</code> 转为 UTF16 格式后装载进去。最终通过 <code>perform</code> 方法反射调用 <code>stringWithCharacters:length:</code> 方法来创建 <code>NSString</code> 对象。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> units = value.codeUnits;</span><br><span class="line"><span class="keyword">final</span> Pointer&lt;Uint16&gt; charPtr = allocate&lt;Uint16&gt;(count: units.length + <span class="number">1</span>);</span><br><span class="line"><span class="keyword">final</span> Uint16List nativeString = charPtr.asTypedList(units.length + <span class="number">1</span>);</span><br><span class="line">nativeString.setAll(<span class="number">0</span>, units);</span><br><span class="line">nativeString[units.length] = <span class="number">0</span>;</span><br><span class="line">NSObject result = Class(<span class="string">'NSString'</span>).perform(</span><br><span class="line">    SEL(<span class="string">'stringWithCharacters:length:'</span>),</span><br><span class="line">    args: [charPtr, units.length]);</span><br><span class="line">free(charPtr);</span><br></pre></td></tr></table></figure><h3 id="转换-Objective-C-NSString-为-Dart-String"><a href="#转换-Objective-C-NSString-为-Dart-String" class="headerlink" title="转换 Objective-C NSString 为 Dart String:"></a>转换 Objective-C <code>NSString</code> 为 Dart <code>String</code>:</h3><p><code>NSString</code> 转为 UTF16 稍微麻烦一点。这里的方案是先转为 UTF16 的 <code>NSData</code>，然后将 <code>uint16_t</code> 数组的地址和字符长度（不是字节长度）返回给 Dart 侧。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">void</span> *</span><br><span class="line">native_convert_nsstring_to_utf16(<span class="built_in">NSString</span> *string, <span class="built_in">NSUInteger</span> *length) &#123;</span><br><span class="line">    <span class="built_in">NSData</span> *data = [string dataUsingEncoding:<span class="built_in">NSUTF16StringEncoding</span>];</span><br><span class="line">    <span class="comment">// UTF16, 2-byte per unit</span></span><br><span class="line">    *length = data.length / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">return</span> data.bytes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Dart 拿到 <code>uint16_t</code> 数组后会转为 <code>Uint16List</code> 类型，并用它初始化一个 <code>String</code> 对象。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Pointer&lt;Uint64&gt; length = allocate&lt;Uint64&gt;();</span><br><span class="line">Pointer&lt;Void&gt; result = convertNSStringToUTF16(ptr, length);</span><br><span class="line">Uint16List list = result.cast&lt;Uint16&gt;().asTypedList(length.value);</span><br><span class="line">free(length);</span><br><span class="line"><span class="built_in">String</span> str = <span class="built_in">String</span>.fromCharCodes(list);</span><br></pre></td></tr></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>写了这么多 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 的相关文章，终于轮到了介绍最基础最核心的同步调用功能。其实异步调用也是支持的，看来用 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 来替换 Flutter Channel 的理由又多了。</p><p>这篇文章主要讲的是 iOS 的同步调用实现以及性能优化，Android 也已经实现同步调用中基本类型的自动转换。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Flutter Channel 是一个异步调用通道，如果想在 Dart 侧同步获取到 Native 返回的结果，调用的时候加上 &lt;code&gt;await&lt;/code&gt; 就可以了：&lt;/p&gt;
&lt;figure class=&quot;highlight dart&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;int&lt;/span&gt; result = &lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt; platform.invokeMethod(&lt;span class=&quot;string&quot;&gt;&#39;hello channel&#39;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;所以这篇文章到此为止了？&lt;/p&gt;
&lt;p&gt;不！上面这行代码其实是个『假同步』，因为它只保证了 Dart 代码的同步执行，而 Native 代码与 Dart 并不在同一条线程执行。试想下，如果你通过 Flutter Channel 打日志，但由于打日志的消息是异步传递到 Native 的，最后日志顺序可能是错的。而通过日志来排查一些时序性相关的 Bug 时，日志的顺序很重要。&lt;/p&gt;
&lt;p&gt;因为 Flutter Channel 设计之初就是异步的，使用 &lt;code&gt;await&lt;/code&gt; 来回切换线程所带来的开销不小。而且协程的 &lt;code&gt;await&lt;/code&gt; 语法具有传递性，上层调用方也需要使用 &lt;code&gt;await&lt;/code&gt;，层层传递。&lt;/p&gt;
&lt;p&gt;而 &lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DartNative&lt;/a&gt; 设计之初就是同步调用的，且也支持异步调用：&lt;/p&gt;
&lt;figure class=&quot;highlight dart&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// new DNTest instance and call hello method.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;DNTest().hello(&lt;span class=&quot;string&quot;&gt;&#39;DartNative&#39;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>DartNative 内存自动管理</title>
    <link href="http://yulingtianxia.com/blog/2020/08/22/DartNative-Automatic-Memory-Management/"/>
    <id>http://yulingtianxia.com/blog/2020/08/22/DartNative-Automatic-Memory-Management/</id>
    <published>2020-08-22T15:05:38.000Z</published>
    <updated>2020-09-28T09:06:45.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 可以让开发者一行代码实现调用 Native 代码，且支持高性能同步调用。之前曾经写过一篇文章讲述 Dart 与 Objective-C 对象的生命周期管理，当时的实现是『半自动』的解决方案。如今 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 更新到 0.3 后实现了生命周期的自动管理，也就是『全自动』的解决方案。</p><a id="more"></a><h2 id="新版本的变化"><a href="#新版本的变化" class="headerlink" title="新版本的变化"></a>新版本的变化</h2><p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 0.3 版本基于 Flutter 1.20.2，Dart 1.9.0。我不得不提的是，Flutter 和 Dart 对 API 兼容性设计的确很糟糕，不仅没有 API Available 的文档或语法标注，也经常会发生 breaking change。<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 也因此表现的同样『激进』，毕竟我们还没有发布 1.0 版本。</p><p>新版本不再需要手动管理内存，Objective-C 对象会被对应的 Dart 对象持有，当 Dart 对象析构时就不再持有 Objective-C 对象。于是 iOS 侧直接干掉了 <code>NSObject</code> 和各种 <code>struct</code> 的 <code>retain()</code> 和 <code>release()</code> 等手动操作引用计数的方法。如果从旧版本升级过来，发现编译失败，直接删掉对这些方法的调用就可以。</p><h2 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h2><p>这里只讲下对象的生命周期管理，非对象类型基本上依然复用<a href="http://yulingtianxia.com/blog/2020/01/31/DartNative-Memory-Management-Cpp-Non-Object/">之前的策略</a>。</p><h3 id="Dart-封装的-NSObject-对象"><a href="#Dart-封装的-NSObject-对象" class="headerlink" title="Dart 封装的 NSObject 对象"></a>Dart 封装的 NSObject 对象</h3><p>之前我写过一篇文章《<a href="http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/">DartNative Memory Management: NSObject</a>》，其中讲述了对象类型的内存管理，并且埋了个坑：</p><blockquote></blockquote><p>如果 Dart VM 支持了 finalize，那么现在的『半自动』内存管理就成了『全自动』了，不过那样的话，内存管理方案也会改变。</p><p>嗯，下图简要描述如何手动实现 Dart Finalizer，并将 Objective-C 对象的生命周期『部分绑定』到 Dart 对象上的。这里之所以是『部分绑定』，是考虑到 Objective-C 对象不仅可以被 Dart 对象持有，也可以被其他 Objective-C 对象持有。Dart 对象的构造和析构只是对关联的 Objective-C 对象引用计数加一和减一：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/DartNative_Object_Memory_Management.png" alt></p><p>Flutter 内嵌的 Dart VM 中也内嵌了一些 C 的 API 来供 Native Extension 调用。虽然可以在二进制文件中链接到函数符号，但开发时依然需要引入 Dart SDK 源码中相应的头文件来进行编译。</p><h3 id="由-Dart-Function-创建的-Block-对象"><a href="#由-Dart-Function-创建的-Block-对象" class="headerlink" title="由 Dart Function 创建的 Block 对象"></a>由 Dart Function 创建的 Block 对象</h3><p>还有一种特殊场景，就是 Dart 调用带有 Block 回调的 API。此时是 Native 的 Block 来回调 Dart 函数，需要保证回调的时候 Dart 函数及上下文依然存在。此时的 Dart Function 会被 Dart Block 持有，而 Dart Block 的生命周期被绑定到了对应的 Objective-C Block 上：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/DartNative_Block_Memory_Management.png" alt></p><p>Objective-C 的 Block 析构时，会通过 isolate 注册好的 Port 去异步回调 Dart。实际的实现其实比上图描述的还要复杂得多，这里为了阐述思路，简化了很多细节。其实像 <code>dealloc</code> 这种无需阻塞的异步回调可以直接用 Flutter Channel 来完成，但因为 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 基于 Dart VM API 和 FFI 等技术搭建了通用的异步回调机制，这里直接复用这个能力了。至此 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 彻底告别了 Flutter Channel。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 0.3 版本虽然没有新的 Feature，但其实是在修内功。在 iOS 侧的内存管理和异步回调上修复了一系列问题，后续还会将对应能力同步到 Android 侧。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a></li><li><a href="https://github.com/dart-lang/sdk/issues/35770" target="_blank" rel="noopener">dart:ffi GC finalizers</a></li><li><a href="https://github.com/dart-lang/sdk/issues/37022#issuecomment-671310270" target="_blank" rel="noopener">Support asynchronous callbacks</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DartNative&lt;/a&gt; 可以让开发者一行代码实现调用 Native 代码，且支持高性能同步调用。之前曾经写过一篇文章讲述 Dart 与 Objective-C 对象的生命周期管理，当时的实现是『半自动』的解决方案。如今 &lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DartNative&lt;/a&gt; 更新到 0.3 后实现了生命周期的自动管理，也就是『全自动』的解决方案。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>如何实现一行命令自动生成 Flutter 插件</title>
    <link href="http://yulingtianxia.com/blog/2020/07/25/How-to-Implement-Codegen/"/>
    <id>http://yulingtianxia.com/blog/2020/07/25/How-to-Implement-Codegen/</id>
    <published>2020-07-24T16:04:27.000Z</published>
    <updated>2020-09-28T09:06:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>在上一篇文章<a href="http://yulingtianxia.com/blog/2020/06/25/Codegen-for-DartNative/">《告别 Flutter Channel，调用 Native API 仅需一行代码！》</a> 发出后，收到了很多关注。仔细想想，其实不是仅仅只需一行代码的，还需要敲一行 <a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">codegen</a> 命令来生成 Dart 代码。这回就简单讲下自动生成代码这块的设计和实现原理。</p><a id="more"></a><h2 id="为何要做代码生成工具"><a href="#为何要做代码生成工具" class="headerlink" title="为何要做代码生成工具"></a>为何要做代码生成工具</h2><p>一开始技术架构的搭建自底向上的。当我做出 Flutter 与 Native 之间的<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">高性能通道</a>后，自然而然地去想要提升易用性，降低开发者的使用门槛。当你从使用者角度去审视自己的产品时，就会自顶向下去设计一些 Feature 去满足目标用户的诉求。</p><p>最终我决定开发一款命令行工具。它可以解析 Native 代码中的 API，生成对应的 Dart 代码，再进而支持生成 Flutter Plugin/Package 工程。生成的 Flutter 工程会通过 pub 依赖 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a>:</p><p><img src="http://yulingtianxia.com/resources/DartObjC/codegen_workflow.png" alt></p><p>工具需要满足以下需求：</p><ol><li>兼容性：能够支持 Native 多种语言代码的转换，如 Objective-C，Java，Swift，Kotlin 等</li><li>易用性：开发者能够很方便地安装和使用工具</li></ol><h2 id="技术方案"><a href="#技术方案" class="headerlink" title="技术方案"></a>技术方案</h2><p>为了满足兼容性，在实现『解析代码生成 AST』的技术方案上，是基于第三方开源框架 <a href="https://www.antlr.org" target="_blank" rel="noopener">antlr</a> 来实现。命令行工具使用 NodeJS 实现，开发者可以通过 npm 很方便地安装工具。</p><p>如果基于 Clang 来实现的话，性能和稳定性都有保证，但是需要 link 到所需的代码或 framework。且 Clang 对多语言的支持比较局限，也不方便提供给开发者去安装。</p><p>关于 antlr 的使用，这里不再赘述，可以直接查看官方文档。（毕竟作者是靠卖书赚钱的）。这里需要将源语言的 grammer 生成 JS 版本的 Runtime 文件，在遍历 AST 的 callback 中收集所需的元数据，转换成为生成 Dart 自定义格式的 AST。最后遍历 Dart AST，生成 Dart 代码。</p><p>这里的方案概括为如下步骤：</p><ol><li>查找出包含客户端 API 的 Native 代码文件，如 <code>.h</code> 和 <code>.java</code> 文件</li><li>通过 antlr parser 生成 Native 语言的 AST</li><li>将 Native AST 转换成 Dart 语言所需的 AST</li><li>通过 Dart AST 生成 dart 代码</li></ol><p><img src="http://yulingtianxia.com/resources/DartObjC/codegen_theory.png" alt></p><h2 id="遇到的坑"><a href="#遇到的坑" class="headerlink" title="遇到的坑"></a>遇到的坑</h2><h3 id="词法分析失败"><a href="#词法分析失败" class="headerlink" title="词法分析失败"></a>词法分析失败</h3><p>官方提供的 Objective-C 的 <a href="https://github.com/antlr/grammars-v4" target="_blank" rel="noopener">grammer</a> 有很多问题，生成的 lexer 还是 parser 都会在词法分析阶段就抛异常。这里就比较坑了，需要修改 lexer 和 parser 的 g4 文件。可能是 grammer 太久没更新了，很多分词阶段就抛异常了，比如 <code>@import</code> 都不支持，真是一言难尽。而且这种只针对单个文件的词法分析程序，很难像 Clang 那样可以 link 其他文件做到的严谨性。经过一系列的修复工作，已经可以 parse iOS Foundation 库的所有头文件。</p><h3 id="语法特性映射"><a href="#语法特性映射" class="headerlink" title="语法特性映射"></a>语法特性映射</h3><p>这一步发生在 AST 的 transform。虽说不同语言之间有很多语法设计都是想通的，但是依然会有一些难以映射的语法特性。比如 Java 的重载方法，OC 奇葩的方法名。转换成 Dart 的方法命名应该遵循哪方的语言规范？OC 的 <code>Protocol</code> 在 Dart 中如何表示？Dart 类的静态方法不会被继承，那么 <code>Protocol</code> 中的类方法怎么办？Dart 的 <code>enum</code> 不支持自定义 <code>int</code> 值，来自 OC 的 <code>enum</code> 如何转换成 Dart？区分何时生成 Dart 的 <code>import</code> 或 <code>export</code>？。。。</p><p>类似这些操蛋的问题数不胜数。。。需要不断细化，思考，打磨。。。</p><h3 id="批量处理"><a href="#批量处理" class="headerlink" title="批量处理"></a>批量处理</h3><p>当要处理的文件有很多时，一个一个地串行处理显然会让开发者等得不耐烦。NodeJS 本来不是为 CPU 密集型操作服务的，但是在 v10.5.0 引入 <a href="https://nodejs.org/api/worker_threads.html" target="_blank" rel="noopener">worker_threads</a> 后，实现了真·多线程，解决了 CPU 密集型操作的痛点。</p><p>在处理一些稍大点的文件时，需要注意上调 NodeJS VM 老生代内存的阈值。</p><h3 id="多种使用场景"><a href="#多种使用场景" class="headerlink" title="多种使用场景"></a>多种使用场景</h3><p>考虑到不同的使用场景，<a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">codegen</a> 所生成的策略也会不一样：</p><ol><li>将 App 的 Native 代码转为 Dart 代码，直接在 Flutter 中使用</li><li>将 Native 系统库转成 Flutter Package</li><li>将 Native 第三方库转成 Flutter Plugin</li></ol><p>这三种场景的共同点：如果 Native 代码依赖了其他库，也需要向 pubspec.yaml 中插入这个依赖库的 Dart 版本。</p><p>第 2 和 3 个场景需要用 <code>flutter create</code> 命令生成新的 Flutter 工程，并将生成的 Dart 代码挪到工程里。<br>更进一步是将生成代码所需的 Native 文件也挪到 Flutter 工程里，并更新 podspec 和 gradle （PS：待实现）。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在做 <a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">codegen</a> 之前，我一直觉得有 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 超级通道就足够了。毕竟是自己开发的轮子自己熟悉，认为手写一层 Dart Wrapper 也没啥麻烦的，其实并不然。当给未接触过的开发者使用后，的确是有上手门槛和开发量的。在调研的过程中也发现 bang 神的 JSPatch 也有个 Converter 工具用来把 OC 代码转为 JS 代码，同样用的也是 antlr 解析 AST，在此也十分感谢 JSPatch 提供的思路。超级通道加工具辅助方可实现了<strong>运行性能和开发效率的双提升</strong>。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://www.antlr.org" target="_blank" rel="noopener">antlr</a><br><a href="https://github.com/antlr/grammars-v4" target="_blank" rel="noopener">grammers-v4</a><br><a href="https://blog.cnbang.net/tech/2915/" target="_blank" rel="noopener">JSPatch Convertor 实现原理详解</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在上一篇文章&lt;a href=&quot;http://yulingtianxia.com/blog/2020/06/25/Codegen-for-DartNative/&quot;&gt;《告别 Flutter Channel，调用 Native API 仅需一行代码！》&lt;/a&gt; 发出后，收到了很多关注。仔细想想，其实不是仅仅只需一行代码的，还需要敲一行 &lt;a href=&quot;https://github.com/dart-native/codegen&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;codegen&lt;/a&gt; 命令来生成 Dart 代码。这回就简单讲下自动生成代码这块的设计和实现原理。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
      <category term="NodeJS" scheme="http://yulingtianxia.com/tags/NodeJS/"/>
    
  </entry>
  
  <entry>
    <title>告别 Flutter Channel，调用 Native API 仅需一行代码！</title>
    <link href="http://yulingtianxia.com/blog/2020/06/25/Codegen-for-DartNative/"/>
    <id>http://yulingtianxia.com/blog/2020/06/25/Codegen-for-DartNative/</id>
    <published>2020-06-25T08:48:18.000Z</published>
    <updated>2020-11-07T02:42:06.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 自研超级通道的性能已经数倍优于 Flutter Channel 之后，我将目光转向了开发成本的优化。于是 <a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">Codegen</a> 应运而生，开发者可以用它很方便地将 Native API 转为 Dart 封装，直接拿来用就可以了！从而优化 Flutter 调用 Native API 的开发体验，实现『<strong>运行性能和开发效率的双提升</strong>』：</p><ul><li><input checked disabled type="checkbox"> 无需编写 Flutter Channel 的胶水代码</li><li><input checked disabled type="checkbox"> 无需跨 IDE 联调 Channel 两边的代码</li><li><input checked disabled type="checkbox"> Native API 也被赋予了热重载功能</li><li><input checked disabled type="checkbox"> 支持同步调用，打日志顺序不再错乱</li></ul><a id="more"></a><p>如果你还一脸懵逼，来看一段<a href="https://www.bilibili.com/video/BV1Bt4y197Jg" target="_blank" rel="noopener">实操视频</a>吧（第一次做 UP 主，跪求一键三连）：</p><iframe src="//player.bilibili.com/player.html?aid=626168423&bvid=BV1Bt4y197Jg&cid=205348003&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><p>视频中为了演示方便，对 <a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">Codegen</a> 代码有些特殊处理：去掉了自动生成 <code>import</code> 的代码。</p><h2 id="DartNative-整体解决方案-amp-展望"><a href="#DartNative-整体解决方案-amp-展望" class="headerlink" title="DartNative 整体解决方案&amp;展望"></a>DartNative 整体解决方案&amp;展望</h2><ol><li><strong>DartNative Bridge</strong>: 自研超级通道，性能甩开官方 Flutter Channel 好几倍，支持 Native 绝大部分类型</li><li><strong>DartNative Codegen</strong>: 将 Native API 转为 Dart API，可在 Flutter 工程中直接调用</li><li><strong>DartNative Dispatch</strong>: 各平台 API 终究是有差异的，且只能靠开发者手动抹平。通过分发机制为开发者提供一个抹平平台差异代码的地方。</li><li><strong>DartNative Component Market</strong>：基于 DartNative 的开源组件市场，未来会有越来越多的 Native 组件通过 DartNative 转为 Flutter 组件。</li></ol><p><img src="http://yulingtianxia.com/resources/DartObjC/DartNative%20Future.png" alt></p><p>从 2019 年的九月份开始做 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 的第一个版本（那时候还叫 DartObjC），到如今初具规模并在线上小范围使用，可谓是有些漫长。漫长的原因有两点：</p><ol><li>为了追求性能与效率双提升，技术方案上走了 Hard 模式。抛弃 Channel 是一条没人走过的路，虽说 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 开源后陆陆续续出现了一些相同 idea 的项目，但都没有走我这条最艰难的路。不仅技术有难度，设计方案也要反复推翻，打磨，优化。。。做新的解决方案就是很漫长，我最然做的很早，但是战线拉得太长。</li><li>这是一个利用打游戏剩下的业余时间搞出来的 side project，全凭自身兴趣和满腔热血。有时候也羡慕那些有 KPI 的开源项目，起码有排期的保证，能够快速推进项目进度。</li></ol><p>个人的力量终究是有限的，尤其是 Android 我一窍不通。还好后来也有更多感兴趣的小伙伴加入这个项目，补齐 Android 侧的超级通道能力，继续推进 <a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">Codegen</a> 的完成度。</p><h2 id="Codegen-的用法"><a href="#Codegen-的用法" class="headerlink" title="Codegen 的用法"></a>Codegen 的用法</h2><p><a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">Codegen</a> 是一个 CLI 工具，可以很方便地使用 npm 来安装：</p><figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">npm</span> install -g @dartnative/codegen</span><br></pre></td></tr></table></figure><p>跟其他标准的 CLI 工具一样，通过 <code>-h</code> 选项可以查看用法：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Usage</span>: codegen [options] &lt;input&gt;</span><br><span class="line"></span><br><span class="line">Generate dart code from native API.</span><br><span class="line"></span><br><span class="line"><span class="attribute">Options:</span></span><br><span class="line">  -V, --version              output the version number</span><br><span class="line">  -l, --language &lt;language&gt;  [objc, java, auto(default)]</span><br><span class="line">  -o, --output &lt;output&gt;      Output directory</span><br><span class="line">  -p, --package &lt;package&gt;    Generate a shareable Flutter project containing modular Dart code.</span><br><span class="line">  -h, --help                 display help for command</span><br></pre></td></tr></table></figure><p><a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">Codegen</a> 默认会自动监测输入源代码的语言，目前还只支持 Objective-C 语言。默认的 Dart 文件输出目录是当前目录，也可以通过 <code>-o</code> 选项来指定输出目录。生成的 Dart 代码会通过 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 的超级通道(Bridge)来调用 Native API。</p><p><img src="https://github.com/dart-native/codegen/blob/master/images/introduction.png?raw=true" alt></p><p><a href="https://github.com/dart-native/codegen" target="_blank" rel="noopener">Codegen</a> 还支持将一个 Native SDK 转成 Flutter 组件，不过此功能尚在完善中，也就是 <code>-p</code> 选项。</p><p><img src="https://github.com/dart-native/codegen/blob/master/images/login_sample.png?raw=true" alt></p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>曾经有两位大佬看了 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 后问我有没有计划将它 Merge 到 Flutter 官方仓库里，我那时候觉得这个想法真的很大胆。现在看起来，如果完成了上述解决方案的大部分，好像也并不是不可以。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在 &lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DartNative&lt;/a&gt; 自研超级通道的性能已经数倍优于 Flutter Channel 之后，我将目光转向了开发成本的优化。于是 &lt;a href=&quot;https://github.com/dart-native/codegen&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Codegen&lt;/a&gt; 应运而生，开发者可以用它很方便地将 Native API 转为 Dart 封装，直接拿来用就可以了！从而优化 Flutter 调用 Native API 的开发体验，实现『&lt;strong&gt;运行性能和开发效率的双提升&lt;/strong&gt;』：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input checked disabled type=&quot;checkbox&quot;&gt; 无需编写 Flutter Channel 的胶水代码&lt;/li&gt;
&lt;li&gt;&lt;input checked disabled type=&quot;checkbox&quot;&gt; 无需跨 IDE 联调 Channel 两边的代码&lt;/li&gt;
&lt;li&gt;&lt;input checked disabled type=&quot;checkbox&quot;&gt; Native API 也被赋予了热重载功能&lt;/li&gt;
&lt;li&gt;&lt;input checked disabled type=&quot;checkbox&quot;&gt; 支持同步调用，打日志顺序不再错乱&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
      <category term="NodeJS" scheme="http://yulingtianxia.com/tags/NodeJS/"/>
    
  </entry>
  
  <entry>
    <title>BlockHook and Memory Safety</title>
    <link href="http://yulingtianxia.com/blog/2020/05/30/BlockHook-and-Memory-Safety/"/>
    <id>http://yulingtianxia.com/blog/2020/05/30/BlockHook-and-Memory-Safety/</id>
    <published>2020-05-30T09:25:58.000Z</published>
    <updated>2020-05-30T11:07:28.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 最近修复了一些内存安全方面的问题，记录下这些问题的解决思路：</p><ol><li>微信项目使用 BlockHook 时的 MRC 兼容问题</li><li>GlobalBlock 在某些场景下的 VM Protection 没有写权限</li><li>如何检测带有 Private Data 的 block</li></ol><a id="more"></a><h2 id="修复-BlockHook-在-MRC-上的问题"><a href="#修复-BlockHook-在-MRC-上的问题" class="headerlink" title="修复 BlockHook 在 MRC 上的问题"></a>修复 BlockHook 在 MRC 上的问题</h2><p>ARC 下将 StackBlock 赋值时，会自动 copy 成 MallocBlock。不过这个编译器帮我们做的隐式行为的前提是代码里显示声明为 Block 类型。而 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 为了能够传入各种签名的 <code>aspectBlock</code>，恰恰用的是 <code>id</code>：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">nullable</span> BHToken *)block_hookWithMode:(BlockHookMode)mode</span><br><span class="line">                              usingBlock:(<span class="keyword">id</span>)aspectBlock;</span><br></pre></td></tr></table></figure><p>如果调用方用的是 MRC，即便 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 是用 ARC 实现的，那么拿到的 <code>aspectBlock</code> 依然是 StackBlock。当被 Hook 的 Block 异步执行时，<code>aspectBlock</code> 也需要异步执行，但它早已经在栈上被释放，进而由于野指针而 crash。</p><p>这就是在微信项目里使用 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 时遇到的问题。当劳动节的下午我正出门去吃饭路上，微信的同事在企业微信上找到了我反馈了这个 bug。我由于路上匆忙没仔细看手机，一开始以为是我另一个同事找我。看问题截图上 Xcode 工程名我还以为他逆向调试微信用了 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 干啥坏事嘞，于是回了一句『你是真的牛逼』。再定神一看我擦是微信巨佬，虽然贼尴尬但只好装作没事一样继续看问题。。。扯远了。。。</p><p>微信巨佬果然是巨佬，还给了我解决方案。我照着巨佬给的思路，<code>copy</code> 了传入的 <code>aspectBlock</code>：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// If aspectBlock is a NSStackBlock and invoked asynchronously, it will cause a wild pointer. We copy it.</span></span><br><span class="line">_aspectBlock = [aspectBlock <span class="keyword">copy</span>];</span><br></pre></td></tr></table></figure><h2 id="解决-GlobalBlock-没有写权限的问题"><a href="#解决-GlobalBlock-没有写权限的问题" class="headerlink" title="解决 GlobalBlock 没有写权限的问题"></a>解决 GlobalBlock 没有写权限的问题</h2><p>用 Xcode 11 编译时，将 Deployment Info 中的 target 选择 iOS 13 后，GlobalBlock 对象所占的内存是只读的，这就导致 Hook 过程中无法对 <code>invoke</code> 函数指针做写操作，直接 crash。</p><p>首先需要判断下 <code>invoke</code> 指针对应的地址有没有写权限，如果没有写权限则需要提权。这涉及到 VM Region 和 Protection 的一些操作，在获取内存地址的基本信息时也要注意区分下 64 位和 32 位：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> vm_prot_t ProtectInvokeVMIfNeed(<span class="keyword">void</span> *address) &#123;</span><br><span class="line">    vm_address_t addr = (vm_address_t)address;</span><br><span class="line">    vm_size_t vmsize = <span class="number">0</span>;</span><br><span class="line">    mach_port_t object = <span class="number">0</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(__LP64__) &amp;&amp; __LP64__</span></span><br><span class="line">    vm_region_basic_info_data_64_t info;</span><br><span class="line">    mach_msg_type_number_t infoCnt = VM_REGION_BASIC_INFO_COUNT_64;</span><br><span class="line">    kern_return_t ret = vm_region_64(mach_task_self(), &amp;addr, &amp;vmsize, VM_REGION_BASIC_INFO, (vm_region_info_t)&amp;info, &amp;infoCnt, &amp;object);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">    vm_region_basic_info_data_t info;</span><br><span class="line">    mach_msg_type_number_t infoCnt = VM_REGION_BASIC_INFO_COUNT;</span><br><span class="line">    kern_return_t ret = vm_region(mach_task_self(), &amp;addr, &amp;vmsize, VM_REGION_BASIC_INFO, (vm_region_info_t)&amp;info, &amp;infoCnt, &amp;object);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    <span class="keyword">if</span> (ret != KERN_SUCCESS) &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@"vm_region block invoke pointer failed! ret:%d, addr:%p"</span>, ret, address);</span><br><span class="line">        <span class="keyword">return</span> VM_PROT_NONE;</span><br><span class="line">    &#125;</span><br><span class="line">    vm_prot_t protection = info.protection;</span><br><span class="line">    <span class="keyword">if</span> ((protection&amp;VM_PROT_WRITE) == <span class="number">0</span>) &#123;</span><br><span class="line">        ret = vm_protect(mach_task_self(), (vm_address_t)address, <span class="keyword">sizeof</span>(address), <span class="literal">false</span>, protection|VM_PROT_WRITE);</span><br><span class="line">        <span class="keyword">if</span> (ret != KERN_SUCCESS) &#123;</span><br><span class="line">            <span class="built_in">NSLog</span>(<span class="string">@"vm_protect block invoke pointer VM_PROT_WRITE failed! ret:%d, addr:%p"</span>, ret, address);</span><br><span class="line">            <span class="keyword">return</span> VM_PROT_NONE;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> protection;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在修改 <code>invoke</code> 指针后，还需要恢复原来的权限。相当于我只是在需要替换 <code>invoke</code> 指针的时候临时开了写权限：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="built_in">BOOL</span> ReplaceBlockInvoke(<span class="keyword">struct</span> _BHBlock *block, <span class="keyword">void</span> *replacement) &#123;</span><br><span class="line">    <span class="keyword">void</span> *address = &amp;(block-&gt;invoke);</span><br><span class="line">    vm_prot_t origProtection = ProtectInvokeVMIfNeed(address);</span><br><span class="line">    <span class="keyword">if</span> (origProtection == VM_PROT_NONE) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NO</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    block-&gt;invoke = replacement;</span><br><span class="line">    <span class="keyword">if</span> ((origProtection&amp;VM_PROT_WRITE) == <span class="number">0</span>) &#123;</span><br><span class="line">        kern_return_t ret = vm_protect(mach_task_self(), (vm_address_t)address, <span class="keyword">sizeof</span>(address), <span class="literal">false</span>, origProtection);</span><br><span class="line">        <span class="keyword">if</span> (ret != KERN_SUCCESS) &#123;</span><br><span class="line">            <span class="built_in">NSLog</span>(<span class="string">@"vm_protect block invoke pointer REVERT failed! ret:%d, addr:%p"</span>, ret, address);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然我还没花时间去追查苹果爸爸为啥要在 Xcode 11 上 iOS 13 target 编译时给 GlobalBlock 只读权限，但理论上我的这个操作并不是对非法内存地址的提权，应该是被允许的，毕竟线上检测是否越狱等功能也会用到这些 API。但我还是不放心，请教了页面仔大佬后，答复是可以上架，终于安心了，也期待下个版本可以试试。</p><p>如果有大佬知道苹果爸爸为何会这样做，或者有更优雅更安全的方案，请给小弟赐教，欢迎指出缺陷，一起开源共建。</p><h2 id="优化-BlockHook-检测-Private-Data-的方式"><a href="#优化-BlockHook-检测-Private-Data-的方式" class="headerlink" title="优化 BlockHook 检测 Private Data 的方式"></a>优化 BlockHook 检测 Private Data 的方式</h2><p>在 <a href="http://yulingtianxia.com/blog/2019/06/19/BlockHook-with-Private-Data/">BlockHook with Private Data</a> 这篇文章里我曾经介绍过一种『骨骼惊奇』的 Block，不能直接替换 <code>invoke</code> 函数指针来 Hook。当时判断这类带有 Private Data 的 Block 的依据是直接用 Private Data 中的 <code>dbpd_magic</code> 字段与 <code>DISPATCH_BLOCK_PRIVATE_DATA_MAGIC</code> 判等：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> dispatch_block_private_data_t</span><br><span class="line">bh_dispatch_block_get_private_data(<span class="keyword">struct</span> _BHBlock *block)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// Keep in sync with _dispatch_block_create implementation</span></span><br><span class="line">    uint8_t *x = (uint8_t *)block;</span><br><span class="line">    <span class="comment">// x points to base of struct Block_layout</span></span><br><span class="line">    x += <span class="keyword">sizeof</span>(<span class="keyword">struct</span> _BHBlock);</span><br><span class="line">    <span class="comment">// x points to base of captured dispatch_block_private_data_s object</span></span><br><span class="line">    dispatch_block_private_data_t dbpd = (dispatch_block_private_data_t)x;</span><br><span class="line">    <span class="keyword">if</span> (dbpd-&gt;dbpd_magic != DISPATCH_BLOCK_PRIVATE_DATA_MAGIC) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dbpd;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我知道这种暴力 Memory Overflow 的行为有潜在隐患，而且<a href="https://github.com/yulingtianxia/BlockHook/issues/11" target="_blank" rel="noopener">调试时开启了 Address Sanitizer 后会必现 crash</a>。当时这么做的原因我也在<a href="http://yulingtianxia.com/blog/2019/06/19/BlockHook-with-Private-Data/">文章</a>里写了，GCD 源码中会检查 Block 的 <code>invoke</code> 指针是否为 <code>_dispatch_block_special_invoke</code>，以此判断 Block 是否包含 Private Data。而这个标志位指针是私有的，我无法在没有符号表的场景下获取到。现在想想当时的自己真是个 SB，当初这么简单的问题，其实现在换个思路不就解决了：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> dispatch_block_private_data_t</span><br><span class="line">bh_dispatch_block_get_private_data(<span class="keyword">struct</span> _BHBlock *block) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!blockWithPrivateData) &#123;</span><br><span class="line">        blockWithPrivateData = dispatch_block_create(<span class="number">0</span>, ^&#123;&#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (block-&gt;invoke != ((__bridge <span class="keyword">struct</span> _BHBlock *)blockWithPrivateData)-&gt;invoke) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Keep in sync with _dispatch_block_create implementation</span></span><br><span class="line">    uint8_t *privateData = (uint8_t *)block;</span><br><span class="line">    <span class="comment">// privateData points to base of struct Block_layout</span></span><br><span class="line">    privateData += <span class="keyword">sizeof</span>(<span class="keyword">struct</span> _BHBlock);</span><br><span class="line">    <span class="comment">// privateData points to base of captured dispatch_block_private_data_s object</span></span><br><span class="line">    dispatch_block_private_data_t dbpd = (dispatch_block_private_data_t)privateData;</span><br><span class="line">    <span class="keyword">if</span> (dbpd-&gt;dbpd_magic != DISPATCH_BLOCK_PRIVATE_DATA_MAGIC) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dbpd;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>既然无法直接拿到 <code>_dispatch_block_special_invoke</code> 指针，那我干脆创建一个带有 Private Data 的 Block 然后取它的 <code>invoke</code> 指针不就搞定了吗！现在看看当初的自己好傻啊。</p><h2 id="最后谈谈-BlockHook"><a href="#最后谈谈-BlockHook" class="headerlink" title="最后谈谈 BlockHook"></a>最后谈谈 BlockHook</h2><p>其实 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的诞生纯属偶然，起初是我本想做些其他关于 Block 的事情，但技术太菜一直没搞成。一顿瞎折腾失败后，剩余的代码就是 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的雏形。然后业余时间不断踩坑和填坑，收到用户反馈后不断打磨，最终搞出了个能用的版本。我本以为打磨了这么久，应该没啥大问题了，然而还是不断有新的问题和挑战出现。毕竟自己曾经吹下了牛皮，含着泪也要继续打磨下去。有时候兴趣带来的动力真的远超 KPI 的压力，让人干劲十足，哈哈。</p><p>我曾经吹牛说 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 『（应该是）填补了 Objective-C 业界在 Hook Block 技术领域的空白』，后来五子棋跟我说之前肯定有人做过这件事，不过记不清是哪个项目了。我也很想知道在这之前是否有人 Hook 过 Objective-C 的 Block，也跪求打脸并虚心接受。但我对 Hook 的理解并不是局限于替换个函数指针 IMP 就可以了，我个人觉得能配得上是 Hook/AOP 的框架，至少要满足下面几个要求中的大部分吧：</p><ol><li>用同一个 Hook 框架多次 Hook，能够有完整的 Hook 调用链。甚至能兼容其他框架。</li><li>兼容 90% 以上的使用场景，经得住大规模验证（不一定线上，也可以是作为测试工具）。</li><li>不能为了『轻量级』和高性能而去牺牲兼容性、鲁棒性和易用性，否则就是实现度不够。</li><li>支持 Revert Hook，最好能 Revert Hook 链的中间节点，甚至能完美还原现场。</li></ol><p>其实替换个函数指针并用 libffi 调用任意函数之类的事情随便找个人都会很快上手，如果就只做了这点事情我个人是不敢称其为 Hook/AOP 框架的。<a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的大部分内容都是解决上面所列出的几点要求，并且自认为解决的还算不错。所以 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 是否填补了业界空白，就看大佬们如何看待 Hook 这件事情的定义了。PS: 可能会误伤一些人，千万别对号入座啊。我也曾经搞过『轻量级』的轮子，性能也牛逼，其实问题一堆实现度很低。我其实在吐槽我自己。。。</p><p>最后，跪求苹果爸爸别搞事情了。。。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; 最近修复了一些内存安全方面的问题，记录下这些问题的解决思路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;微信项目使用 BlockHook 时的 MRC 兼容问题&lt;/li&gt;
&lt;li&gt;GlobalBlock 在某些场景下的 VM Protection 没有写权限&lt;/li&gt;
&lt;li&gt;如何检测带有 Private Data 的 block&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Runtime" scheme="http://yulingtianxia.com/tags/Runtime/"/>
    
      <category term="BlockHook" scheme="http://yulingtianxia.com/tags/BlockHook/"/>
    
  </entry>
  
  <entry>
    <title>Passing Out Parameter in DartNative</title>
    <link href="http://yulingtianxia.com/blog/2020/04/25/Passing-Out-Parameter-in-DartNative/"/>
    <id>http://yulingtianxia.com/blog/2020/04/25/Passing-Out-Parameter-in-DartNative/</id>
    <published>2020-04-25T07:55:41.000Z</published>
    <updated>2020-09-28T09:07:08.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 作为一条比 Channel 性能更高开发成本更低的超级通道，通过 C++ 调用 Native 的 API，深入底层且考虑全面。很多 Objective-C 接口含有 <code>NSError **</code> 这种 out parameter，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 也对这种场景做了支持。</p><a id="more"></a><h2 id="封装-Objective-C-里的-Out-Parameter"><a href="#封装-Objective-C-里的-Out-Parameter" class="headerlink" title="封装 Objective-C 里的 Out Parameter"></a>封装 Objective-C 里的 Out Parameter</h2><p>说白了用的最多的就是 “A pointer to a pointer” 啊！<code>NSError **</code> 啊！</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSError</span> *error;</span><br><span class="line">[<span class="keyword">self</span> fooWithError:&amp;error];</span><br></pre></td></tr></table></figure><p>那换成 Dart 语言该咋表示呢？？？首先要知道 Dart 是不支持 out parameter 的，只能另辟蹊径，在语法上做一些妥协，最终跑通流程实现目的。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSObjectRef&lt;NSObject&gt; ref = NSObjectRef&lt;NSObject&gt;();</span><br><span class="line">fooWithError(ref);</span><br></pre></td></tr></table></figure><p>还记得之前 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 是如何封装 <code>NSObject *</code> 的么？用一个同名的 Dart 类包一个 OC 对象的指针就行了。那想封装 out parameter 的话，在此基础之上再套一层不就行了！只要用泛型，就能一层层套下去。。。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NSObjectRef</span>&lt;<span class="title">T</span> <span class="keyword">extends</span> <span class="title">id</span>&gt; </span>&#123;</span><br><span class="line">  T value;</span><br><span class="line">  Pointer&lt;Pointer&lt;Void&gt;&gt; _ptr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着要考虑如何初始化 out parameter 了。在 OC 里只需要在栈上的一个地址就够了，也就是声明一个变量。但 Dart 的对象并没有对应指针的概念，但是可以通过 dart ffi 手动创建一个指向指针的指针。不过它指向的内存是在堆上，需要手动释放。此时可以通过<a href="http://yulingtianxia.com/blog/2020/01/31/DartNative-Memory-Management-Cpp-Non-Object/">我之前讲内存管理的文章</a>里讲到的 <code>PointerWrapper</code> 来实现临时指针变量的自动释放，简单来说就是把 dart ffi 创建的内存交给 OC ARC 管理。</p><p>加上构造方法和自动释放后的 <code>NSObjectRef</code> 实现如下：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NSObjectRef</span>&lt;<span class="title">T</span> <span class="keyword">extends</span> <span class="title">id</span>&gt; </span>&#123;</span><br><span class="line">  T value;</span><br><span class="line">  Pointer&lt;Pointer&lt;Void&gt;&gt; _ptr;</span><br><span class="line">  Pointer&lt;Pointer&lt;Void&gt;&gt; <span class="keyword">get</span> pointer =&gt; _ptr;</span><br><span class="line"></span><br><span class="line">  NSObjectRef() &#123;</span><br><span class="line">    _ptr = allocate&lt;Pointer&lt;Void&gt;&gt;();</span><br><span class="line">    _ptr.value = nullptr;</span><br><span class="line">    PointerWrapper wrapper = PointerWrapper(_dealloc);</span><br><span class="line">    wrapper.value = _ptr.cast&lt;Void&gt;();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  NSObjectRef.fromPointer(<span class="keyword">this</span>._ptr);</span><br><span class="line">  </span><br><span class="line">  _dealloc() &#123;</span><br><span class="line">    _ptr = <span class="keyword">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="从-Out-Parameter-取值"><a href="#从-Out-Parameter-取值" class="headerlink" title="从 Out Parameter 取值"></a>从 Out Parameter 取值</h2><p>Dart 侧把一个指针传给 OC 后，OC 会创建另一个指针，并把后者赋值给前者指向的内存。还是拿 <code>NSError</code> 举例子：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)fooWithError:(<span class="keyword">out</span> <span class="built_in">NSError</span> **)error &#123;</span><br><span class="line">    <span class="keyword">if</span> (error) &#123;</span><br><span class="line">        *error = [<span class="built_in">NSError</span> errorWithDomain:<span class="string">@"com.dartnative.test"</span> code:<span class="number">-1</span> userInfo:<span class="literal">nil</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下一步是要将上例中 OC 的 <code>NSError</code> 对象转成 Dart 的对象，并赋值给 <code>NSObjectRef</code> 的 <code>value</code> 属性上。</p><h3 id="建立泛型与初始化的映射"><a href="#建立泛型与初始化的映射" class="headerlink" title="建立泛型与初始化的映射"></a>建立泛型与初始化的映射</h3><p>面对不同泛型的 <code>NSObjectRef</code> 声明，要转成其封装类型的对象。而 Flutter 禁用的 Dart 的反射，即不能通过 <code>NSObjectRef</code> 声明的泛型来初始化对应的类。我维护了个 <code>Map</code> 来建立起 <code>Type</code> 到初始化调用的映射，并提供注册方法：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="built_in">dynamic</span> ConvertorFromPointer(Pointer&lt;Void&gt; ptr);</span><br><span class="line"></span><br><span class="line"><span class="built_in">Map</span>&lt;<span class="built_in">String</span>, ConvertorFromPointer&gt; _convertorCache = &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> registerTypeConvertor(<span class="built_in">String</span> type, ConvertorFromPointer convertor) &#123;</span><br><span class="line">  <span class="keyword">if</span> (_convertorCache[type] == <span class="keyword">null</span>) &#123;</span><br><span class="line">    _convertorCache[type] = convertor;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样调用 <code>registerTypeConvertor</code> 函数就可以很方便地建立起 Native 封装类型到初始化闭包的映射：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">registerTypeConvertor(<span class="string">'NSString'</span>, (ptr) &#123;</span><br><span class="line">    <span class="keyword">return</span> NSString.fromPointer(ptr);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>接着实现 <code>convertFromPointer</code> 函数，用来调用之前注册的闭包，这样就实现用类名和指针来获取到对应的 Dart 对象了：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">dynamic</span> convertFromPointer(String type, <span class="keyword">dynamic</span> arg) &#123;</span><br><span class="line">  Pointer&lt;<span class="built_in">Void</span>&gt; ptr;</span><br><span class="line">  <span class="keyword">if</span> (arg <span class="keyword">is</span> NSObject) &#123;</span><br><span class="line">    ptr = arg.pointer;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arg <span class="keyword">is</span> Pointer) &#123;</span><br><span class="line">    ptr = arg;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (ptr == nullptr) &#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ConvertorFromPointer convertor = _convertorCache[type];</span><br><span class="line">  <span class="keyword">if</span> (convertor != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> convertor(ptr);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arg <span class="keyword">is</span> Pointer) &#123;</span><br><span class="line">    <span class="keyword">return</span> NSObject.fromPointer(arg);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后在 <code>NSObjectRef</code> 里添加了个 <code>syncValue</code> 方法，将转换好的 Dart 对象赋值给 <code>value</code> 属性：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">syncValue() &#123;</span><br><span class="line">    <span class="keyword">if</span> (_ptr != <span class="keyword">null</span> &amp;&amp; _ptr.value != nullptr) &#123;</span><br><span class="line">        value = convertFromPointer(T.toString(), _ptr.value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自动生成注册代码"><a href="#自动生成注册代码" class="headerlink" title="自动生成注册代码"></a>自动生成注册代码</h3><p>那么多 Native 类型，总不能手写代码一个个去调用 <code>registerTypeConvertor</code> 吧。<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 提供了 Annotation 用于自动生成这些注册代码，只需要在封装 Native 类的上面加一个 <code>@native</code> 即可：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@native</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NSString</span> <span class="keyword">extends</span> <span class="title">NSSubclass</span>&lt;<span class="title">String</span>&gt; </span>&#123;</span><br><span class="line">  NSString.fromPointer(Pointer&lt;Void&gt; ptr) : <span class="keyword">super</span>.fromPointer(ptr) &#123;</span><br><span class="line">    value = perform(SEL(<span class="string">'UTF8String'</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样只需要在项目目录里运行下面的命令，所有加了 <code>@native</code> 的类都会在同一个 dart 文件中生成注册初始化闭包的代码：</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flutter packages pub <span class="built_in">run</span> build_runner build <span class="comment">--delete-conflicting-outputs</span></span><br></pre></td></tr></table></figure><p>建议在运行上面的 <code>build</code> 之前先 <code>clean</code> 下：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flutter packages pub <span class="keyword">run</span><span class="bash"> build_runner clean</span></span><br></pre></td></tr></table></figure><p>这是 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 里带的一份自动生成的文件 ``：</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GENERATED CODE - DO NOT MODIFY BY HAND</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// **************************************************************************</span></span><br><span class="line"><span class="comment">// DartNativeGenerator</span></span><br><span class="line"><span class="comment">// **************************************************************************</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/dart_native.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/collection/nsarray.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/collection/nsdictionary.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/collection/nsset.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/nsvalue.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/nsnumber.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/notification.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:dart_native/src/ios/foundation/nsstring.dart'</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">runDartNative</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  registerTypeConvertor(<span class="string">'NSArray'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSArray.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  registerTypeConvertor(<span class="string">'NSDictionary'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSDictionary.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  registerTypeConvertor(<span class="string">'NSSet'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSSet.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  registerTypeConvertor(<span class="string">'NSValue'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSValue.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  registerTypeConvertor(<span class="string">'NSNumber'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSNumber.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  registerTypeConvertor(<span class="string">'NSNotification'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSNotification.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  registerTypeConvertor(<span class="string">'NSString'</span>, (ptr) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">return</span> NSString.<span class="title">fromPointer</span><span class="params">(ptr)</span></span>;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>考虑到 Flutter 的 plugin 和 App 都可能会用到 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a>，那么各自的 Native 类就都要生成对应的注册代码。所以这里的入口函数名是根据 package 名生成的，不用担心重名问题。</p><p>利用 Annotation 自动生成代码的实现原理就不细说了，网上文章很多，可以参考闲鱼的 <a href="https://github.com/alibaba-flutter/annotation_route" target="_blank" rel="noopener">annotation_route</a>。我只是做了一点微小的优化工作，可能以后也不会单开一片文章来讲。</p><p>PS: 自动生成代码这块一开始是给 callback 功能用的，这里写下，只是蹭了蹭篇幅。</p><h3 id="自动取值"><a href="#自动取值" class="headerlink" title="自动取值"></a>自动取值</h3><p><code>syncValue()</code> 方法实现后就比较简单了，下一步就只是找个合理的时机调用的问题了。这只需要在 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 的 <code>msgSend</code> 方法中加入对参数类型的判断。如果是 <code>NSObjectRef</code> 类型，则需要在调用完 Native 侧的方法后再次调用它的 <code>syncValue()</code> 方法。</p><p>这里仅截取一段相关的实现代码：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 省略部分逻辑</span></span><br><span class="line">List&lt;NSObjectRef&gt; outRefArgs = [];</span><br><span class="line"><span class="comment">// 省略部分逻辑</span></span><br><span class="line"><span class="keyword">if</span> (args != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="comment">// 省略部分逻辑</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; argCount; i++) &#123;</span><br><span class="line">      <span class="keyword">var</span> arg = args[i];</span><br><span class="line">      <span class="keyword">if</span> (arg == <span class="literal">null</span>) &#123;</span><br><span class="line">        arg = nil;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arg <span class="keyword">is</span> NSObjectRef) &#123;</span><br><span class="line">        outRefArgs.<span class="keyword">add</span>(arg);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 省略部分逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">outRefArgs.forEach((<span class="keyword">ref</span>) =&gt; <span class="keyword">ref</span>.syncValue());</span><br></pre></td></tr></table></figure><p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 中的<code>msgSend</code> 方法顾名思义，虽然表面上是复刻 OC 的实现，实则接口和原理差很多。这里也不详细展开讲，感兴趣的可以直接去看代码。</p><h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p><code>NSObjectRef</code> 目前只考虑了对 <code>NSObject</code> 及其子类的 out parameter 的封装，理论上对其他基本类型和结构体也是可以支持的，不过使用场景可能没 <code>NSError **</code> 那么多，等遇到的时候再搞吧。</p><p>内行看门道，外行看热闹。我这么简单的内容都能水出一篇文章，跪求大佬们轻喷，不嘲笑就好。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 作为一条比 Channel 性能更高开发成本更低的超级通道，通过 C++ 调用 Native 的 API，深入底层且考虑全面。很多 Objective-C 接口含有 &lt;code&gt;NSError **&lt;/code&gt; 这种 out parameter，&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 也对这种场景做了支持。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>在 Flutter 中玩转 Objective-C Block</title>
    <link href="http://yulingtianxia.com/blog/2020/03/28/Using-Objective-C-Block-in-Flutter/"/>
    <id>http://yulingtianxia.com/blog/2020/03/28/Using-Objective-C-Block-in-Flutter/</id>
    <published>2020-03-28T07:57:55.000Z</published>
    <updated>2020-09-28T09:07:13.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 作为一条比 Channel 性能更高开发成本更低的超级通道，通过 C++ 调用 Native 的 API，深入底层且考虑全面。很多 Objective-C 接口的参数和返回值是 Block，所以这就需要支持用 Dart 语言创建和调用 Objective-C Block。</p><a id="more"></a><h2 id="Dart-调用-Objective-C-带-Block-的-API"><a href="#Dart-调用-Objective-C-带-Block-的-API" class="headerlink" title="Dart 调用 Objective-C 带 Block 的 API"></a>Dart 调用 Objective-C 带 Block 的 API</h2><p>Dart 语言支持协程，这样就无需传递闭包来作为异步调用的回调。而 Objective-C 大量 API 都使用 Block 作为回调，当 Dart 调用这类异步 API 的时候，就需要 Dart 侧创建 Block 并传递给 Objective-C。</p><p>Dart 语言中的 Function 可以当做闭包，可以实现下面这样的效果：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">stub.fooBlock((NSObject a) &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'hello block! <span class="subst">$&#123;a.toString()&#125;</span>'</span>);</span><br><span class="line">    <span class="keyword">return</span> a;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>而对应的 Objective-C 接口如下：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="built_in">NSObject</span> *(^BarBlock)(<span class="built_in">NSObject</span> *a);</span><br><span class="line">- (<span class="keyword">void</span>)fooBlock:(BarBlock)block;</span><br></pre></td></tr></table></figure><p>下面就讲下 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 是如何做到把 Dart Function 当做 Block 传给 Objective-C 的。</p><h3 id="函数签名"><a href="#函数签名" class="headerlink" title="函数签名"></a>函数签名</h3><p>首先要确保的是 Dart Function 的签名跟 Objective-C Block 是一致的，这样二者才能转换。在 Dart 里一切皆为对象，Function 也不例外。那么拿到 Function 的 <code>runtimeType</code> 即可，然后解析其内容。不过 <code>runtimeType</code> 的内容都是 Dart 类名，如何能与 Objective-C 类型对应上呢？<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 的策略是提供与 Native 同名的类，这样使用这些同名类定义 Dart Function，就可以把函数签名映射到 Native 上了。</p><p>列举一些 Dart 声明的基础类型：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">unsigned_char</span> = <span class="title">char</span> <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">short</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">unsigned_short</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">unsigned_int</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">long</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">unsigned_long</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">long_long</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">unsigned_long_long</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">size_t</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">NSInteger</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">NSUInteger</span> = <span class="title">NativeBox</span>&lt;<span class="title">int</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">float</span> = <span class="title">NativeBox</span>&lt;<span class="title">double</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">CGFloat</span> = <span class="title">NativeBox</span>&lt;<span class="title">double</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">CString</span> = <span class="title">NativeBox</span>&lt;<span class="title">String</span>&gt; <span class="title">with</span> <span class="title">_ToAlias</span>;</span></span><br></pre></td></tr></table></figure><h3 id="动态创建-Block"><a href="#动态创建-Block" class="headerlink" title="动态创建 Block"></a>动态创建 Block</h3><p>有了函数签名，如何构造对应的 Block 对象呢？首先要知道 Block 是什么，而这是就又个老生常谈的话题了。我十分建议你先了解下 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 及其相关文章，这样会对理解这部分内容有很大帮助。</p><p>废话不多说，上硬核：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)initBlock &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *typeString = <span class="keyword">self</span>.typeString.UTF8String;</span><br><span class="line">    int32_t flags = (BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_SIGNATURE);</span><br><span class="line">    <span class="comment">// Struct return value on x86(32&amp;64) MUST be put into pointer.(On heap)</span></span><br><span class="line">    <span class="keyword">if</span> (typeString[<span class="number">0</span>] == <span class="string">'&#123;'</span> &amp;&amp; (TARGET_CPU_X86 || TARGET_CPU_X86_64)) &#123;</span><br><span class="line">        flags |= BLOCK_HAS_STRET;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Check block encoding types valid.</span></span><br><span class="line">    <span class="built_in">NSUInteger</span> numberOfArguments = [<span class="keyword">self</span> _prepCIF:&amp;_cif withEncodeString:typeString flags:flags];</span><br><span class="line">    <span class="keyword">if</span> (numberOfArguments == <span class="number">-1</span>) &#123; <span class="comment">// Unknown encode.</span></span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">self</span>.numberOfArguments = numberOfArguments;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">self</span>.hasStret) &#123;</span><br><span class="line">        <span class="keyword">self</span>.numberOfArguments--;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    _closure = ffi_closure_alloc(<span class="keyword">sizeof</span>(ffi_closure), (<span class="keyword">void</span> **)&amp;_blockIMP);</span><br><span class="line">    </span><br><span class="line">    ffi_status status = ffi_prep_closure_loc(_closure, &amp;_cif, DNFFIBlockClosureFunc, (__bridge <span class="keyword">void</span> *)(<span class="keyword">self</span>), _blockIMP);</span><br><span class="line">    <span class="keyword">if</span> (status != FFI_OK) &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@"ffi_prep_closure returned %d"</span>, (<span class="keyword">int</span>)status);</span><br><span class="line">        abort();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">struct</span> _DNBlockDescriptor descriptor = &#123;</span><br><span class="line">        <span class="number">0</span>,</span><br><span class="line">        <span class="keyword">sizeof</span>(<span class="keyword">struct</span> _DNBlock),</span><br><span class="line">        (<span class="keyword">void</span> (*)(<span class="keyword">void</span> *dst, <span class="keyword">const</span> <span class="keyword">void</span> *src))copy_helper,</span><br><span class="line">        (<span class="keyword">void</span> (*)(<span class="keyword">const</span> <span class="keyword">void</span> *src))dispose_helper,</span><br><span class="line">        typeString</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    _descriptor = malloc(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> _DNBlockDescriptor));</span><br><span class="line">    memcpy(_descriptor, &amp;descriptor, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> _DNBlockDescriptor));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">struct</span> _DNBlock simulateBlock = &#123;</span><br><span class="line">        &amp;_NSConcreteStackBlock,</span><br><span class="line">        flags,</span><br><span class="line">        <span class="number">0</span>,</span><br><span class="line">        _blockIMP,</span><br><span class="line">        _descriptor,</span><br><span class="line">        (__bridge <span class="keyword">void</span>*)<span class="keyword">self</span></span><br><span class="line">    &#125;;</span><br><span class="line">    _signature = [<span class="built_in">NSMethodSignature</span> signatureWithObjCTypes:typeString];</span><br><span class="line">    _block = (__bridge <span class="keyword">id</span>)Block_copy(&amp;simulateBlock);</span><br><span class="line">    SEL selector = <span class="built_in">NSSelectorFromString</span>(<span class="string">@"autorelease"</span>);</span><br><span class="line">    <span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic push</span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Warc-performSelector-leaks"</span></span></span><br><span class="line">    _block = [_block performSelector:selector];</span><br><span class="line">    <span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic pop</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简单来说，动态创建 Block 的流程封装在了一个 Wrapper 类中，步骤如下：</p><ol><li>用 libffi 动态创建相同签名的函数，</li><li>准备好创建 Block 需要的 <code>flag</code>、<code>description</code>、<code>signature</code> 和 <code>wrapper</code> 对象等</li><li>根据 Block 的内存模型创建对应的结构体（栈上）</li><li>把 Block 对象 <code>copy</code> 到堆上，并发送 <code>autorelease</code> 消息</li></ol><p>这上面每一步其实都不简单，单独拆出来都能写一段。但因为 <a href="http://blog.cnbang.net/tech/3332/" target="_blank" rel="noopener">bang 大佬已经写过文章</a>介绍过了，我这里就不再赘述了。我只是站在巨人的肩膀上，增加了一些改进和对 Dart 的适配（如支持结构体、<code>x86</code> 兼容等）。很惭愧，就做了一点微小的工作。</p><h3 id="映射-Block-和-Dart-Function"><a href="#映射-Block-和-Dart-Function" class="headerlink" title="映射 Block 和 Dart Function"></a>映射 Block 和 Dart Function</h3><p>Block 对象创建好了，需要跟 Dart Function 映射起来，然后当 Block 被执行的时候才会调用到对应的 Dart 逻辑。</p><p>关于回调这块，我在 Dart 侧维护一个 <code>Map</code> 来管理 Native 到 Dart 的回调映射。基本思路是，Key 为 Native 对象的地址，Value 为 Dart 侧的 Block 类。</p><p>Dart 版的 <code>Block</code> 类构造方法里会将映射建立起来：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">factory</span> Block(<span class="built_in">Function</span> function) &#123;</span><br><span class="line">    <span class="built_in">List</span>&lt;<span class="built_in">String</span>&gt; dartTypes = _dartTypeStringForFunction(function);</span><br><span class="line">    <span class="built_in">List</span>&lt;<span class="built_in">String</span>&gt; nativeTypes = _nativeTypeStringForDart(dartTypes);</span><br><span class="line">    Pointer&lt;Utf8&gt; typeStringPtr = Utf8.toUtf8(nativeTypes.join(<span class="string">', '</span>));</span><br><span class="line">    NSObject blockWrapper =</span><br><span class="line">        NSObject.fromPointer(blockCreate(typeStringPtr, _callbackPtr));</span><br><span class="line">    <span class="built_in">int</span> blockAddr = blockWrapper.perform(SEL(<span class="string">'blockAddress'</span>));</span><br><span class="line">    Block result = Block._internal(Pointer.fromAddress(blockAddr));</span><br><span class="line">    free(typeStringPtr);</span><br><span class="line">    result.types = dartTypes;</span><br><span class="line">    result._wrapper = blockWrapper;</span><br><span class="line">    result.function = function;</span><br><span class="line">    _blockForAddress[result.pointer.address] = result;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>Block</code> 类的 <code>dealloc</code> 方法里会移除映射，防止造成 Dart 版的『野指针』。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">dealloc() &#123;</span><br><span class="line">    _wrapper = <span class="keyword">null</span>;</span><br><span class="line">    _blockForAddress.remove(pointer.address);</span><br><span class="line">    <span class="keyword">super</span>.dealloc();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Dart-调用-Objective-C-返回的-Block"><a href="#Dart-调用-Objective-C-返回的-Block" class="headerlink" title="Dart 调用 Objective-C 返回的 Block"></a>Dart 调用 Objective-C 返回的 Block</h2><p>结合对 Block 的理解以及实践过 Dart 调用 OC 方法的经验，很容易在 Dart 版的 <code>Block</code> 中实现个 <code>invoke</code> 方法：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dynamic</span> invoke([<span class="built_in">List</span> args]) &#123;</span><br><span class="line">    <span class="keyword">if</span> (pointer == nullptr) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    Pointer&lt;Utf8&gt; typesEncodingsPtr = _blockTypeEncodeString(pointer);</span><br><span class="line">    Pointer&lt;Int32&gt; countPtr = allocate&lt;Int32&gt;();</span><br><span class="line">    Pointer&lt;Pointer&lt;Utf8&gt;&gt; typesPtrPtr =</span><br><span class="line">        nativeTypesEncoding(typesEncodingsPtr, countPtr, <span class="number">0</span>);</span><br><span class="line">    <span class="built_in">int</span> count = countPtr.value;</span><br><span class="line">    free(countPtr);</span><br><span class="line">    <span class="comment">// typesPtrPtr contains return type and block itself.</span></span><br><span class="line">    <span class="keyword">if</span> (count != (args?.length ?? <span class="number">0</span>) + <span class="number">2</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="string">'Args Count NOT match'</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Pointer&lt;Pointer&lt;Void&gt;&gt; argsPtrPtr = nullptr.cast();</span><br><span class="line">    <span class="keyword">if</span> (args != <span class="keyword">null</span>) &#123;</span><br><span class="line">      argsPtrPtr = allocate&lt;Pointer&lt;Void&gt;&gt;(count: args.length);</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; args.length; i++) &#123;</span><br><span class="line">        <span class="keyword">var</span> arg = args[i];</span><br><span class="line">        <span class="keyword">if</span> (arg == <span class="keyword">null</span>) &#123;</span><br><span class="line">          arg = nil;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">String</span> encoding = Utf8.fromUtf8(typesPtrPtr.elementAt(i + <span class="number">2</span>).value);</span><br><span class="line">        storeValueToPointer(arg, argsPtrPtr.elementAt(i), encoding);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    Pointer&lt;Void&gt; resultPtr = blockInvoke(pointer, argsPtrPtr);</span><br><span class="line">    <span class="keyword">if</span> (argsPtrPtr != nullptr.cast()) &#123;</span><br><span class="line">      free(argsPtrPtr);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">String</span> encoding = Utf8.fromUtf8(typesPtrPtr.elementAt(<span class="number">0</span>).value);</span><br><span class="line">    <span class="built_in">dynamic</span> result = loadValueFromPointer(resultPtr, encoding);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简单来说上面的实现做了如下几步：</p><ol><li>获取 Block 的函数签名</li><li>校验 Dart 测传入的参数列表是否符合函数签名</li><li>将 Dart 参数转为 Native 对应的类型，写入堆中</li><li>调用 C 函数 <code>blockInvoke</code>，将 Block 指针和参数列表二级指针传过去</li><li>释放二级指针（其指向的对象类型和堆上的结构体会自动释放）</li><li>将 <code>blockInvoke</code> 返回的指针内容转为 Dart 对象</li></ol><h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p>关于 Block 这块其实还有很多技术细节没有叙述完整，包括 <code>copy</code> 方法的实现，回调映射的细节，类型自动转换的细节等。因为篇幅原因，感兴趣的可以直接看源码：<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">https://github.com/dart-native/dart_native</a></p><p>其实我期望的是使用 Dart 的协程来完成处理异步回调，这样更现代更优雅。日后会基于此方案再次封装上层接口，支持协程。</p><p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 作为一条深入底层且考虑全面的 Dart 到 Native 超级通道，未来还要做的事情还有很多。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 作为一条比 Channel 性能更高开发成本更低的超级通道，通过 C++ 调用 Native 的 API，深入底层且考虑全面。很多 Objective-C 接口的参数和返回值是 Block，所以这就需要支持用 Dart 语言创建和调用 Objective-C Block。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>DartNative Struct</title>
    <link href="http://yulingtianxia.com/blog/2020/02/24/DartNative-Struct/"/>
    <id>http://yulingtianxia.com/blog/2020/02/24/DartNative-Struct/</id>
    <published>2020-02-23T16:15:12.000Z</published>
    <updated>2020-09-28T09:07:17.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 基于 Dart FFI，通过 C++ 调用 Native 的 API。很多 Objective-C 接口的参数和返回值都有 Struct，比如最常见的 <code>CGSize</code> 等。这就需要能够用 Dart 语言表示 Struct 类型，尤其是 Cocoa 内建的这些常用结构体。</p><a id="more"></a><p>结构体的存储需要一段连续的内存，可以是栈也可以是堆上。而 Dart 与 Objective-C 跨语言调用时只能传递一个指针大小的数据，这就使得 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 的结构体需要在堆上创建，并通过指针传递。</p><p>Dart FFI 虽然提供了构建结构体的 API，但是目前还不支持<a href="https://github.com/dart-lang/sdk/issues/37271" target="_blank" rel="noopener">结构体的嵌套</a>，所以像 <code>CGRect</code> 包含 <code>CGPoint</code> 和 <code>CGSize</code> 这种结构，还不能通过嵌套的方式复用实现代码。此外，<code>CGFloat</code> 和 <code>NSUInteger</code> 也可能有 32bit 和 64bit 两种情况，Dart 只能在运行时去区分该用哪种。这些原因导致目前使用 Dart FFI 构建 Struct 时不得不采用排列组合式的笨方法。</p><p>下面就以实现一个 <code>CGSize</code> 为例，看看这种方式有多笨。</p><p>首先 <code>CGSize</code> 是由两个 <code>CGFloat</code> 组成，而 <code>CGFloat</code> 又有可能是 32bit 或 64bit。所以现需要分别实现这两种情况，也就是 <code>CGFloat32x2</code> 和 <code>CGFloat64x2</code>，分别表示两个 <code>float</code> 和两个 <code>double</code>：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CGFloat32x2</span> <span class="keyword">extends</span> <span class="title">Struct</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Float</span>()</span><br><span class="line">  <span class="built_in">double</span> a;</span><br><span class="line">  <span class="meta">@Float</span>()</span><br><span class="line">  <span class="built_in">double</span> b;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">factory</span> CGFloat32x2(<span class="built_in">double</span> a, <span class="built_in">double</span> b) =&gt; allocate&lt;CGFloat32x2&gt;().ref</span><br><span class="line">    ..a = a</span><br><span class="line">    ..b = b;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">factory</span> CGFloat32x2.fromPointer(Pointer&lt;CGFloat32x2&gt; ptr) &#123;</span><br><span class="line">    <span class="keyword">return</span> ptr.ref;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CGFloat64x2</span> <span class="keyword">extends</span> <span class="title">Struct</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Double</span>()</span><br><span class="line">  <span class="built_in">double</span> a;</span><br><span class="line">  <span class="meta">@Double</span>()</span><br><span class="line">  <span class="built_in">double</span> b;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">factory</span> CGFloat64x2(<span class="built_in">double</span> a, <span class="built_in">double</span> b) =&gt; allocate&lt;CGFloat64x2&gt;().ref</span><br><span class="line">    ..a = a</span><br><span class="line">    ..b = b;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">factory</span> CGFloat64x2.fromPointer(Pointer&lt;CGFloat64x2&gt; ptr) &#123;</span><br><span class="line">    <span class="keyword">return</span> ptr.ref;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CGFloat64x2</code> 初始化时会在堆上开辟内存，并填充数据。而使用 <code>fromPointer</code> 类方法初始化时则是传入一个指针，并将指针指向的内存按照内存模型映射到 Dart 这边的属性。而从 Native 那边传过来的指针肯定也是指向由 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 开辟的内存，所以使用这两种初始化方法后，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">DartNative</a> 都需要负责释放内存。</p><p>我也很想把这一长串代码合并下，可惜目前 Dart FFI 的语法还很弱，Dart 的类型安全编译检查也使得一些事情做不了。考虑到 Flutter 禁用了反射，所以只能按部就班写一坨一坨长得很像但又不完全一样的代码了。</p><p>基于两种情况之上再封装一层 <code>CGFloatx2Wrapper</code>，内部判断该用哪种。由于 Dart 不支持宏，无法在编译器静态判断是否是 64bit，所以封装了个 <code>LP64</code> 在运行时判断。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CGFloatx2Wrapper</span> <span class="keyword">extends</span> <span class="title">NativeStruct</span> </span>&#123;</span><br><span class="line">  CGFloat32x2 _value32;</span><br><span class="line">  CGFloat64x2 _value64;</span><br><span class="line"></span><br><span class="line">  CGFloatx2Wrapper(<span class="built_in">double</span> a, <span class="built_in">double</span> b) &#123;</span><br><span class="line">    <span class="keyword">if</span> (LP64) &#123;</span><br><span class="line">      _value64 = CGFloat64x2(a, b);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      _value32 = CGFloat32x2(a, b);</span><br><span class="line">    &#125;</span><br><span class="line">    wrapper;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Pointer <span class="keyword">get</span> addressOf =&gt; LP64 ? _value64.addressOf : _value32.addressOf;</span><br><span class="line"></span><br><span class="line">  CGFloatx2Wrapper.fromPointer(Pointer&lt;Void&gt; ptr) &#123;</span><br><span class="line">    <span class="keyword">if</span> (LP64) &#123;</span><br><span class="line">      _value64 = CGFloat64x2.fromPointer(ptr.cast());</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      _value32 = CGFloat32x2.fromPointer(ptr.cast());</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CGFloatx2Wrapper</code> 继承了 <code>NativeStruct</code>，后者内部维护了一个 <code>PointerWrapper</code> 来实现 Struct 堆内存的自动释放。<code>PointerWrapper</code> 本质上只是包装了下指针，并在自己释放的时候 <code>free</code> 指针指向的内存。<code>NativeStruct</code> 提供 <code>retain</code> 和 <code>release</code> 方法，并在释放时回调 <code>dealloc</code> 接口，使得 Struct 在 Dart 上可以像对象类型一样使用。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">NativeStruct</span> </span>&#123;</span><br><span class="line">  Pointer <span class="keyword">get</span> addressOf;</span><br><span class="line"></span><br><span class="line">  PointerWrapper _wrapper;</span><br><span class="line">  PointerWrapper <span class="keyword">get</span> wrapper &#123;</span><br><span class="line">    <span class="keyword">if</span> (_wrapper == <span class="keyword">null</span>) &#123;</span><br><span class="line">      _wrapper = PointerWrapper(dealloc);</span><br><span class="line">    &#125;</span><br><span class="line">    Pointer&lt;Void&gt; result = addressOf.cast&lt;Void&gt;();</span><br><span class="line">    _wrapper.value = result;</span><br><span class="line">    <span class="keyword">return</span> _wrapper;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  NativeStruct retain() &#123;</span><br><span class="line">    wrapper.retain();</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  release() =&gt; wrapper.release();</span><br><span class="line"></span><br><span class="line">  dealloc() &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>继续回到 <code>CGFloatx2Wrapper</code>，这层封装内部维护两个属性 <code>a</code> 和 <code>b</code> 及其存取方法，只是简单的透传而已:</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">double</span> <span class="keyword">get</span> a =&gt; LP64 ? _value64.a : _value32.a;</span><br><span class="line"><span class="keyword">set</span> a(<span class="built_in">double</span> a) &#123;</span><br><span class="line">  <span class="keyword">if</span> (LP64) &#123;</span><br><span class="line">    _value64.a = a;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    _value32.a = a;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">double</span> <span class="keyword">get</span> b =&gt; LP64 ? _value64.b : _value32.b;</span><br><span class="line"><span class="keyword">set</span> b(<span class="built_in">double</span> b) &#123;</span><br><span class="line">  <span class="keyword">if</span> (LP64) &#123;</span><br><span class="line">    _value64.b = b;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    _value32.b = b;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CGFloatx2Wrapper</code> 也通过重写操作符实现了 Struct 判等的功能，这样就不需要使用 Objective-C 里繁琐的 <code>CGSizeEqualToSize</code> 等函数了：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">bool</span> <span class="keyword">operator</span> ==(other) &#123;</span><br><span class="line">  <span class="keyword">if</span> (other == <span class="keyword">null</span>) <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  <span class="keyword">return</span> a == other.a &amp;&amp; b == other.b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@override</span></span><br><span class="line"><span class="built_in">int</span> <span class="keyword">get</span> hashCode =&gt; a.hashCode ^ b.hashCode;</span><br></pre></td></tr></table></figure><p>真不敢相信如此难堪的代码出自我之手，只能说各位大佬们有懂 Dart 的可以指点下小弟有没有更优雅的方式。最后基于这个 <code>CGFloatx2Wrapper</code> 就可以封装出 <code>CGSize</code>,<code>CGPoint</code>,<code>CGVector</code> 和 <code>UIOffset</code> 等 Struct 了，它们均由两个 <code>CGFloat</code> 组成。也就只有这一步复用代码了。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CGSize</span> <span class="keyword">extends</span> <span class="title">CGFloatx2Wrapper</span> </span>&#123;</span><br><span class="line">  <span class="built_in">double</span> <span class="keyword">get</span> width =&gt; a;</span><br><span class="line">  <span class="keyword">set</span> width(<span class="built_in">double</span> width) &#123;</span><br><span class="line">    a = width;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">double</span> <span class="keyword">get</span> height =&gt; b;</span><br><span class="line">  <span class="keyword">set</span> height(<span class="built_in">double</span> height) &#123;</span><br><span class="line">    b = height;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  CGSize(<span class="built_in">double</span> width, <span class="built_in">double</span> height) : <span class="keyword">super</span>(width, height);</span><br><span class="line">  CGSize.fromPointer(Pointer&lt;Void&gt; ptr) : <span class="keyword">super</span>.fromPointer(ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还有其他类型需要封装，比如 <code>CGRect</code> 由 4 个 <code>CGFloat</code> 组成，且由于 Dart FFI 不支持 Struct 嵌套，所以无法复用 <code>CGPoint</code> 和 <code>CGSize</code> 这两个类的代码，只能重复上面的过程重起炉灶。就这样最终将 Objective-C Cocoa API 内建的 Struct 基本都包装成了 Dart 里的类。目前支持的类型有：<code>CGSize</code>,<code>CGPoint</code>,<code>CGVector</code>,<code>CGRect</code>,<code>NSRange</code>,<code>UIOffset</code>,<code>UIEdgeInsets</code>,<code>NSDirectionalEdgeInsets</code> 和 <code>CGAffineTransform</code>。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 基于 Dart FFI，通过 C++ 调用 Native 的 API。很多 Objective-C 接口的参数和返回值都有 Struct，比如最常见的 &lt;code&gt;CGSize&lt;/code&gt; 等。这就需要能够用 Dart 语言表示 Struct 类型，尤其是 Cocoa 内建的这些常用结构体。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>DartNative Memory Management: C++ Non-Object</title>
    <link href="http://yulingtianxia.com/blog/2020/01/31/DartNative-Memory-Management-Cpp-Non-Object/"/>
    <id>http://yulingtianxia.com/blog/2020/01/31/DartNative-Memory-Management-Cpp-Non-Object/</id>
    <published>2020-01-31T15:12:26.000Z</published>
    <updated>2020-09-28T09:07:22.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 基于 Dart FFI，通过 C++ 调用 Native 的 API。这种跨多语言的 bridge 就需要考虑到内存管理的问题。<a href="http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/">上一篇文章</a> 介绍了 Objective-C 对象类型的管理，本篇算是它的续篇，讲下对 <code>struct</code> 和 <code>char *</code> 内存的管理。</p><a id="more"></a><p>如果你还不了解 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 是什么，建议先看下我之前的两篇文章：</p><ul><li><a href="http://yulingtianxia.com/blog/2019/10/27/Write-Objective-C-Code-using-Dart/">用 Dart 来写 Objective-C 代码</a></li><li><a href="http://yulingtianxia.com/blog/2019/11/28/DartObjC-Design/">谈谈 dart_native 混合编程引擎的设计</a></li><li><a href="http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/">DartNative Memory Management: NSObject</a></li></ul><p>PS：dart_objc 已经更名为 dart_native。</p><h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><p>Cocoa(Touch) 中的好多 API 都用到了系统内建的 <code>struct</code> 或 <code>UTF8String</code>(<code>char *</code>) 类型，它们不像 Objective-C 对象那样只存在于堆上（Block 除外），既可以存在堆上也可以在栈上。<strong>如果能将 <code>struct</code> 和 <code>char *</code> 用对象的形式包一层</strong>，那么就可以<strong>将堆上非对象类型的生命周期转换为对象类型，交由 ARC 来管理</strong>。由此继续借助<a href="http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/">上一篇文章</a>的经验和流程，自动释放存储在堆上的 <code>struct</code> 和 <code>char *</code> 类型。</p><h2 id="何时销毁非对象类型"><a href="#何时销毁非对象类型" class="headerlink" title="何时销毁非对象类型"></a>何时销毁非对象类型</h2><p>首先要确定非对象类型传递的方式。这里的解决方案是全都存储于堆上，并用一个 Wrapper 对象包一层来传递。下面说说为何这么做。</p><p>非对象类型如果存储在栈上，那么当调用结束返回后就会被销毁。在跨语言异步调用时，栈上的内存也会被回收，Dart 侧无法长期持有并访问这些数据。Objective-C 大多使用 Block 的方式来实现异步调用逻辑，由于 Block 会去捕获外部变量，所以可以正常运行。</p><p>这个 <code>PointerWrapper</code> 类也很简单，它包了个 <code>void *pointer</code> 属性，在析构的时候会释放 <code>pointer</code> 指向的内存：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)dealloc &#123;</span><br><span class="line">    <span class="built_in">free</span>(_pointer);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样就可以把一个非对象类型先 copy 到堆上，然后封装成对象类型来传递了。确保其不会过早被释放，且在同步或异步调用完成后由 ARC 自动释放。</p><h2 id="Dart-从-C-获取非对象类型"><a href="#Dart-从-C-获取非对象类型" class="headerlink" title="Dart 从 C++ 获取非对象类型"></a>Dart 从 C++ 获取非对象类型</h2><p>这里分两种情况：</p><ol><li>Dart 创建新的 <code>struct</code> 或 <code>char *</code>（<code>Pointer&lt;Utf8&gt;</code>）。会通过 Dart FFI 的 <code>allocate</code> 在堆上开辟新的内存，<strong>需要释放</strong>。</li><li>Dart 调用 C++ 函数或 Objective-C Block 时获取的返回值。<code>struct</code> 会被拷贝到新创建的堆内存上，<strong>需要释放</strong>；<code>char *</code> 会自动转换成 <code>String</code>，<strong>不需要释放</strong>。</li><li>C++ 调用 Dart callback 时传入的参数。<code>struct</code> 会被拷贝到新创建的堆内存上，<strong>需要释放</strong>；<code>char *</code> 会自动转换成 <code>String</code>，<strong>不需要释放</strong>。</li></ol><p>至于如何在 Dart 侧创建诸如 <code>CGRect</code> 之类的 <code>struct</code>，可能又能单开一篇文章来讲了，这里不细说了。Dart 侧并不会直接从 Objective-C/C++ 侧拿到 <code>struct</code> 类型，而是拿到一份 <code>malloc</code> 并拷贝后的指针。</p><p>上面这些需要释放的 <code>struct</code> 均可以通过 <code>PointerWrapper</code> 来自动释放，也就是默认创建的是临时变量，用完会自动销毁。Dart 的 <code>struct</code> 都以 <code>NativeStruct</code> 作为基类。其中 <code>addressOf</code> 为指向 <code>struct</code> 的指针，<code>wrapper</code> 接管了 <code>struct</code> 的生命周期。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">NativeStruct</span> </span>&#123;</span><br><span class="line">  Pointer <span class="keyword">get</span> addressOf;</span><br><span class="line"></span><br><span class="line">  PointerWrapper _wrapper;</span><br><span class="line">  PointerWrapper <span class="keyword">get</span> wrapper &#123;</span><br><span class="line">    <span class="keyword">if</span> (_wrapper == <span class="literal">null</span>) &#123;</span><br><span class="line">      _wrapper = PointerWrapper();</span><br><span class="line">    &#125;</span><br><span class="line">    Pointer&lt;<span class="built_in">Void</span>&gt; result = addressOf.cast&lt;<span class="built_in">Void</span>&gt;();</span><br><span class="line">    _wrapper.value = result;</span><br><span class="line">    <span class="keyword">return</span> _wrapper;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  NativeStruct retain() &#123;</span><br><span class="line">    wrapper.retain();</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  release() =&gt; wrapper.release();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>也就是 Dart 获取到的 <code>struct</code> 是个临时变量，不用就会自动销毁。如果需要长期持有，则需要手动 <code>retain</code> 和 <code>release</code>。而 Dart 获取到的 <code>char *</code> 则会被自动转为 <code>String</code> 类型，无需关心内存管理。</p><h2 id="C-从-Dart-获取非对象类型"><a href="#C-从-Dart-获取非对象类型" class="headerlink" title="C++ 从 Dart 获取非对象类型"></a>C++ 从 Dart 获取非对象类型</h2><p>这里分两种情况：</p><ol><li>Dart 调用 C++ 函数或 Objective-C Block 时传入的参数。</li><li>Objective-C 调用 Dart callback 时获取的返回值。</li></ol><p>Dart 侧的 <code>struct</code> 早已由 <code>PointerWrapper</code> 交给 ARC 来接管生命周期，<strong>在调用完成后自动释放</strong>。不过需要注意的一点是，Dart 的 <code>String</code> 自动转换为 C++ 的 <code>char *</code>（<code>Pointer&lt;Utf8&gt;</code>）时属于新创建 <code>char *</code>，<strong>需要交给 <code>PointerWrapper</code> 自动释放</strong>:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">dynamic</span> <span class="title">storeCStringToPointer</span>(<span class="params"><span class="keyword">dynamic</span> <span class="keyword">object</span>, Pointer&lt;Pointer&lt;Void&gt;&gt; ptr</span>)</span> &#123;</span><br><span class="line">  Pointer&lt;Utf8&gt; charPtr = Utf8.toUtf8(<span class="keyword">object</span>);</span><br><span class="line">  PointerWrapper wrapper = PointerWrapper();</span><br><span class="line">  wrapper.<span class="keyword">value</span> = charPtr.cast&lt;Void&gt;();</span><br><span class="line">  ptr.cast&lt;Pointer&lt;Utf8&gt;&gt;().<span class="keyword">value</span> = charPtr;</span><br><span class="line">  <span class="keyword">return</span> wrapper;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Dart-向-C-传参"><a href="#Dart-向-C-传参" class="headerlink" title="Dart 向 C++ 传参"></a>Dart 向 C++ 传参</h3><ol><li>由于字符串比较特殊，即便在函数调用结束后，字符串很多以常量的形式被继续使用。所以传递 <code>char *</code> 的时候，即便已经通过传递 <code>PointerWrapper</code> 来保证调用过程中不被释放，但还需要利用 <code>NSTaggedPointerString</code> 将其生命周期交给 Foundation 管理。</li><li>原本传递结构体现在改成了传递结构体的指针。因为跨语言调用时，使用 Dart FFI 传递单个数据最大为 64bit，可以为整型、浮点型或指针等。所以可能无法容纳下比较大的结构体，需要传递指向结构体的指针。</li><li>其余类型照常传递。</li></ol><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span></span><br><span class="line">_fillArgsToInvocation(<span class="built_in">NSMethodSignature</span> *signature, <span class="keyword">void</span> **args, <span class="built_in">NSInvocation</span> *invocation, <span class="built_in">NSUInteger</span> offset) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">NSUInteger</span> i = offset; i &lt; signature.numberOfArguments; i++) &#123;</span><br><span class="line">        <span class="keyword">const</span> <span class="keyword">char</span> *argType = [signature getArgumentTypeAtIndex:i];</span><br><span class="line">        <span class="built_in">NSUInteger</span> argsIndex = i - offset;</span><br><span class="line">        <span class="keyword">if</span> (argType[<span class="number">0</span>] == <span class="string">'*'</span>) &#123;</span><br><span class="line">            <span class="comment">// Copy CString to NSTaggedPointerString and transfer it's lifecycle to ARC. Orginal pointer will be freed after function returning.</span></span><br><span class="line">            <span class="keyword">const</span> <span class="keyword">char</span> *temp = [<span class="built_in">NSString</span> stringWithUTF8String:(<span class="keyword">const</span> <span class="keyword">char</span> *)args[argsIndex]].UTF8String;</span><br><span class="line">            <span class="keyword">if</span> (temp) &#123;</span><br><span class="line">                args[argsIndex] = (<span class="keyword">void</span> *)temp;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (argType[<span class="number">0</span>] == <span class="string">'&#123;'</span>) &#123;</span><br><span class="line">            <span class="comment">// Already put struct in pointer on Dart side.</span></span><br><span class="line">            [invocation setArgument:args[argsIndex] atIndex:i];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            [invocation setArgument:&amp;args[argsIndex] atIndex:i];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Objective-C-调用-Dart-callback-时获取的返回值"><a href="#Objective-C-调用-Dart-callback-时获取的返回值" class="headerlink" title="Objective-C 调用 Dart callback 时获取的返回值"></a>Objective-C 调用 Dart callback 时获取的返回值</h3><p>由于 Dart callback 所对应的 C++ Function 由 libffi 动态创建，而基于动态创建的 C++ Function 又动态创建了 Objective-C Block 和方法。所以这一切都是我们创建的，尽在掌控之中。而这个动态创建的过程又有点复杂，可以再单独开一篇文章来讲了。</p><p>Objective-C 中方法和 Block 的返回值如果是比较大的 <code>struct</code>，运行在 x86 架构上时，实际上调用更底层函数时的参数列表会有变化。此时第一个参数是指向返回结构体的指针，其余参数依次后移一位。这在 Objective-C 中缩写为 stret，也就是 struct return 的意思。</p><p>如果没有触发 <code>stret</code> 条件，此时的策略是 Dart callback 返回非对象类型锁对应的 <code>PointerWrapper</code>，然后 Objective-C 侧再从 <code>wrapper</code> 中取出对非对象类型，并塞入到 libffi 提供的 <code>ret</code> 指针里：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (wrapper.hasStret) &#123;</span><br><span class="line">    <span class="comment">// synchronize stret value from first argument.</span></span><br><span class="line">    [invocation setReturnValue:*(<span class="keyword">void</span> **)args[<span class="number">0</span>]];</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> ([wrapper.typeString hasPrefix:<span class="string">@"&#123;"</span>]) &#123;</span><br><span class="line">    DOPointerWrapper *pointerWrapper = *(DOPointerWrapper *__<span class="keyword">strong</span> *)ret;</span><br><span class="line">    memcpy(ret, pointerWrapper.pointer, invocation.methodSignature.methodReturnLength);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> ([wrapper.typeString hasPrefix:<span class="string">@"*"</span>]) &#123;</span><br><span class="line">    DOPointerWrapper *pointerWrapper = *(DOPointerWrapper *__<span class="keyword">strong</span> *)ret;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *origCString = (<span class="keyword">const</span> <span class="keyword">char</span> *)pointerWrapper.pointer;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *temp = [<span class="built_in">NSString</span> stringWithUTF8String:origCString].UTF8String;</span><br><span class="line">    *(<span class="keyword">const</span> <span class="keyword">char</span> **)ret = temp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>非对象类型的内存管理要比对象类型复杂得多，光是把 <code>struct</code> 在 Dart 中转换出来就已经有些麻烦了。好在大部分问题都已经克服过去了，最终实现了一套半自动化的内存管理系统，也实现了跨语言的类型自动转换。后续可能还会对 stret 的情况进行优化，甚至对方案进行大改。</p><p>哎真是太难了，我还是继续骑着小摩托去找人马吧。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 基于 Dart FFI，通过 C++ 调用 Native 的 API。这种跨多语言的 bridge 就需要考虑到内存管理的问题。&lt;a href=&quot;http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/&quot;&gt;上一篇文章&lt;/a&gt; 介绍了 Objective-C 对象类型的管理，本篇算是它的续篇，讲下对 &lt;code&gt;struct&lt;/code&gt; 和 &lt;code&gt;char *&lt;/code&gt; 内存的管理。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>DartNative Memory Management: NSObject</title>
    <link href="http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/"/>
    <id>http://yulingtianxia.com/blog/2019/12/26/DartObjC-Memory-Management-Object/</id>
    <published>2019-12-26T04:49:13.000Z</published>
    <updated>2020-09-28T09:07:26.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 基于 Dart FFI，通过 C++ 调用 Native 的 API。这种跨多语言的 bridge 就需要考虑到内存管理的问题。由于篇幅有限，会分开来讲，本篇文章只涉及 Objective-C 对象类型的管理。</p><a id="more"></a><p>如果你还不了解 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 是什么，建议先看下我之前的两篇文章：</p><ul><li><a href="http://yulingtianxia.com/blog/2019/10/27/Write-Objective-C-Code-using-Dart/">用 Dart 来写 Objective-C 代码</a></li><li><a href="http://yulingtianxia.com/blog/2019/11/28/DartObjC-Design/">谈谈 dart_native 混合编程引擎的设计</a></li></ul><h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><p>先看看不同语言是如何管理内存与对象的生命周期的。</p><ul><li>Dart VM 使用 GC 来管理内存，且 Dart 语言一切皆为对象。</li><li>C++ 在堆上手动开辟的内存需要手动释放。</li><li>Objective-C 上的对象普遍使用 ARC 来管理，但也可以使用 MRC。其余跟 C++ 一样。</li></ul><p>GC 和引用计数都是常见的内存管理方式，这里就不科普具体算法的细节了。两者差别固然很大，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 在这里做了一些事情，尽量让开发者写 Dart 时少关心内存问题。</p><p>由于 Dart 对象的生命周期实际完全由 VM 的 GC 决定，所以这里没有可操作性的空间，只能调整 Objective-C 对象的生命周期。Objective-C 对象都是存储在堆上的，跨语言之间传递的都是指针。而使用栈上的一个 64 位空间也足够存储大部分基本类型数据，足够覆盖到各种长度精度的整型和浮点数类型。</p><p>跨语言之间的方法调用，更多关注的是方法返回值给到另一种语言时的生命周期，以及对象被销毁后的处理。</p><h2 id="Objective-C-对象销毁后的处理"><a href="#Objective-C-对象销毁后的处理" class="headerlink" title="Objective-C 对象销毁后的处理"></a>Objective-C 对象销毁后的处理</h2><p>读过我之前文章的人可能会对 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 的使用方式稍有了解，其实就是自定义 Dart 类来把 Objective-C 类封装了一层。比如我写了个 Dart 类叫 <code>NSObject</code>，封装了大部分基本的 API。打通了方法的调用时类型的自动转换，支持所有基本类型。</p><p>Dart 的 <code>NSObject</code> 类有个指向 Objective-C 对象的指针 <code>_ptr</code>，当这个 Objective-C 对象被销毁时，那么对应的 Dart 对象各种状态也需要置空。虽然 Dart 对象没被及时销毁，但是对其的任何操作都是无效的了。当然，这很容易导致难以发现的 bug。所以需要有效地措施来让开发者知道这个 Dart 对象已经失效了。</p><p>首先是提供 <code>dealloc</code> 方法，让开发者自己清理子类中的内容，这跟写 MRC 代码很像。<br>这是基类中 <code>dealloc</code> 方法的实现（简略版），它清空了 <code>_ptr</code> 指针。当 Objective-C 对象被销毁后，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 框架会负责调用 <code>dealloc</code> 方法，开发者不能手动调用。篇幅原因，这部分的实现原理就不展开讲了。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// Clean NSObject instance.</span></span><br><span class="line"><span class="comment">/// Subclass can override this method and call release on its dart properties.</span></span><br><span class="line">dealloc() &#123;</span><br><span class="line">    _ptr = <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当 <code>dealloc</code> 方法被调用后，需要有能够对 Dart 对象判空的能力。于是我创造了个 Dart 版本的 <code>nil</code>，其实就是一个指向 <code>nullptr</code> 的 Dart 对象。</p><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">final id <span class="literal">nil</span> = id(<span class="name">nullptr</span>)<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>然后重写了 Dart <code>NSObject</code> 的 <code>==</code> 判等方法，使得 <code>NSObject</code> 的判等变成了指针之间的判等。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">bool <span class="keyword">operator</span> ==(other) &#123;</span><br><span class="line">    <span class="keyword">if</span> (other == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">return</span> pointer == other.pointer;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如此一来，一旦 Dart 对象内部指向的 Objective-C 对象被销毁，它就等于 <code>nil</code> 了。</p><h2 id="Dart-从-Objective-C-获取对象"><a href="#Dart-从-Objective-C-获取对象" class="headerlink" title="Dart 从 Objective-C 获取对象"></a>Dart 从 Objective-C 获取对象</h2><p>从 Objective-C 获取对象的方式可能是新创建的，也可能是某个普通方法的返回值。从形式上二者都是调用方法返回对象，但是内存引用计数却不一样。以 <code>new</code>, <code>alloc</code>, <code>copy</code> 和 <code>mutableCopy</code> 开头的方法会被认为引用计数加一，这样就相当于把 Objective-C 对象的管理权交给了 Dart。而普通方法返回的 Objective-C 对象的管理权并不归属 Dart。</p><p>为了简化操作，让这两种获取方式的结果统一，我会在 Dart 侧 <code>NSObject</code> 基类的这四个相关方法中调用一次 <code>autorelease</code>。这样就又把带 <code>new</code>, <code>alloc</code>, <code>copy</code> 和 <code>mutableCopy</code> 前缀的方法返回的 Objective-C 对象的管理权交由 ARC，而又不会过早释放导致 crash。</p><p>这里从使用方式可分两种情况：</p><ol><li>临时使用 Objective-C 对象，当为局部变量：Dart 侧编写代码时无需关心内存管理</li><li>长期使用 Objective-C 对象，作为属性持有：Dart 侧需手动 <code>retain</code> 和 <code>release</code></li></ol><p>针对第二种情况，写过 MRC 代码的会很熟悉。这是对应的 Dart 代码，是不是很像。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_MyAppState</span> <span class="keyword">extends</span> <span class="title">State&lt;MyApp&gt;</span> </span>&#123;</span><br><span class="line">  <span class="type">NSObject</span> <span class="class"><span class="keyword">object</span> </span>= <span class="type">NSObject</span>().retain();</span><br><span class="line">  ...</span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  void dispose() &#123;</span><br><span class="line">    <span class="class"><span class="keyword">object</span>.<span class="title">release</span>(<span class="params"></span>)</span>;</span><br><span class="line">    <span class="keyword">super</span>.dispose();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果 Dart VM 支持了 <code>finalize</code>，那么现在的『半自动』内存管理就成了『全自动』了，不过那样的话，内存管理方案也会改变。这里就不谈 Plan B 了。</p><h2 id="Objective-C-从-Dart-获取对象"><a href="#Objective-C-从-Dart-获取对象" class="headerlink" title="Objective-C 从 Dart 获取对象"></a>Objective-C 从 Dart 获取对象</h2><p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 是支持传入回调方法的，也就是 Objective-C 是可以直接调用 Dart 方法的。当 Objective-C 从 Dart 方法的返回值是对象，需要处理好它的生命周期。</p><p>当 Dart 返回给 Objective-C 一个对象时，其内部指向的 Objective-C 对象是交给 ARC 管理的。当 Dart 与 Objective-C 在同一线程时倒还好，切了不同线程后 Objective-C 对象很可能被销毁了，那么就会 crash。此时就需要在 Dart 侧记录下要返回的 Objective-C 对象，这里用到了线程局部存储（TLS）。利用 Dart FFI 调用下面这个 C++ 函数，它在当前线程下持有了 Dart 要返回的 Objective-C 对象，防止被提前销毁。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">void</span><br><span class="line">native_mark_autoreleasereturn_object(id object) &#123;</span><br><span class="line">    int64_t<span class="built_in"> address </span>= (int64_t)object;</span><br><span class="line">    [NSThread.currentThread do_performWaitingUntilDone:YES block:^&#123;</span><br><span class="line">        NSThread.currentThread.threadDictionary[@(address)] = object;</span><br><span class="line">    &#125;];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然还需要在 Objective-C 侧调用完 Dart 方法后，将 TLS 置空，确保不会造成内存泄露。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>这篇文章依然没有讲 Dart 如何调用 Objective-C API，没有贴很多代码晒技术细节，满篇都是讲思路和方法。可能是我觉得这些都是 Runtime 的基础，没太多自己思考的东西。写出来也只是简单的科普知识罢了。</p><p>张小龙说『思辨大于执行』，当大家都有很强的执行力的时候，先理清思路就显得很重要。</p><p>主要还是技术细节太多，几篇文章的篇幅都讲不完，我也懒得一次写完。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 基于 Dart FFI，通过 C++ 调用 Native 的 API。这种跨多语言的 bridge 就需要考虑到内存管理的问题。由于篇幅有限，会分开来讲，本篇文章只涉及 Objective-C 对象类型的管理。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>谈谈 dart_native 混合编程引擎的设计</title>
    <link href="http://yulingtianxia.com/blog/2019/11/28/DartObjC-Design/"/>
    <id>http://yulingtianxia.com/blog/2019/11/28/DartObjC-Design/</id>
    <published>2019-11-28T09:07:22.000Z</published>
    <updated>2020-09-28T09:07:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>我之前在 『<a href="http://yulingtianxia.com/blog/2019/10/27/Write-Objective-C-Code-using-Dart/">用 Dart 来写 Objective-C 代码</a>』 这篇文章讲了下我在解决 Flutter 三端开发问题的一个思路和方案，并给出了 Demo 和简单的对比。这次讲下 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 的设计，这包含了上层使用方式和底层技术方案的设计。由于涉及到的技术点很多，这次不会深入太多技术实现细节，不过后续可能会分篇讲下。</p><a id="more"></a><h2 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h2><h3 id="宇宙真理①：Native-平台接口随版本变化，差异随时间增长。"><a href="#宇宙真理①：Native-平台接口随版本变化，差异随时间增长。" class="headerlink" title="宇宙真理①：Native 平台接口随版本变化，差异随时间增长。"></a>宇宙真理①：Native 平台接口随版本变化，差异随时间增长。</h3><ul><li>iOS 有太多的平台独有框架的 CloudKit、PhotoKit、StoreKit …</li><li>同理安卓也是，且这些差异都跟 UI 无关，无法通过图形引擎统一。</li><li>随着版本发布，不断有新增和废弃的 API，平台差异只会越来越大。</li></ul><h3 id="宇宙真理②：任何跨平台开发框架，Native-API-该用还得用，可能只是换一种语言封装调用，逃不掉的。"><a href="#宇宙真理②：任何跨平台开发框架，Native-API-该用还得用，可能只是换一种语言封装调用，逃不掉的。" class="headerlink" title="宇宙真理②：任何跨平台开发框架，Native API 该用还得用，可能只是换一种语言封装调用，逃不掉的。"></a>宇宙真理②：任何跨平台开发框架，Native API 该用还得用，可能只是换一种语言封装调用，逃不掉的。</h3><p>无论是现今炙手可热的 Flutter，还是之前的 RN 和 Weex，都逃不掉这条真理。</p><p>还有些跨平台框架不通过 Bridge 或 Channel 调用 Native，而是直接将某种语言代码编译成对应平台的二进制。比如最近出的 Kotlin/Native，或是古老的 Xamarin，也都逃不掉这条真理。</p><p><img src="http://yulingtianxia.com/resources/DartObjC/flutter_rn.png" alt="Flutter vs RN/Weex"></p><p>Flutter 通过图形引擎的跨平台帮我们抹平了 UI 层面的平台差异，这在跨平台开发框架中已经是个突破了。但其余的部分仍然需要开发者编写很多 Channel 代码来抹平不同平台的差异。不妨将二者结合下，取其精华去其糟粕，于是有了一种新的开发方式：</p><p><img src="http://yulingtianxia.com/resources/DartObjC/dart_native.png" alt="DartNative"></p><h3 id="为何这样设计"><a href="#为何这样设计" class="headerlink" title="为何这样设计"></a>为何这样设计</h3><ol><li>Native API 很多，逐个用 Channel 封装的话要多写很多代码。而这里可以借鉴其他跨平台框架『用同一种语言调用不同平台 API』的成熟经验，以 Dart 语言的形式将 Native API 暴露给 Flutter 来调用。将『三端开发』切换语言和开发环境的场景消灭到最低。</li><li>通过 Native Runtime 来应对不同版本 API 变化问题，以不变应万变。搭配 Dart API 自动化生成工具提升效率，解放手写 Channel 带来的一系列开发成本。</li></ol><h2 id="技术指标"><a href="#技术指标" class="headerlink" title="技术指标"></a>技术指标</h2><p><strong>一句话：运行性能和研发效率都要吊打 Flutter Channel。</strong></p><h3 id="研发效率"><a href="#研发效率" class="headerlink" title="研发效率"></a>研发效率</h3><p>以『判断是否安装某 App』为例，针对代码行数进行对比：</p><table><thead><tr><th></th><th>代码行数</th><th>调试成本</th></tr></thead><tbody><tr><td>DartObjC</td><td>Native 1 行/Dart 1 行</td><td><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 一行代码直接返回 bool 类型，无需调试 Native 和 Dart 逻辑。</td></tr><tr><td>Channel</td><td>Native 30 行/Dart 15 行</td><td>Channel 需定义返回数据格式，手动转换 BOOL 与 int，判断 channel 和 methodName，需要调试 Native 和 Dart 逻辑</td></tr></tbody></table><p>由于 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 帮开发者完成了类型自动转换，省去了多余的 Channel 逻辑，也就无需调试这部分代码。只需调试 Dart 代码，统一开发环境和语言。</p><p>其实使用 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 后，理论上是不需要写 Native 代码的。</p><h3 id="性能数据"><a href="#性能数据" class="headerlink" title="性能数据"></a>性能数据</h3><p>分别测试了两个 Native 接口在相同环境下执行 1 万次的耗时情况(ms)：</p><table><thead><tr><th>接口案例</th><th>总耗时对比（Channel/dart_native）</th><th>仅通道耗时对比（Channel/dart_native）</th></tr></thead><tbody><tr><td>判断是否安装某 App</td><td>5202/<strong>4166</strong></td><td>919/<strong>99</strong></td></tr><tr><td>打日志</td><td>2480/<strong>2024</strong></td><td>1075/<strong>432</strong></td></tr></tbody></table><p>严格来讲，对比性能时需要刨除 Native 方法自身的执行耗时，剩下的就是通道的耗时了。在这方面 Flutter Channel 的耗时是 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 的好几倍。在测试打日志这个案例时，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 耗时瓶颈在于将 Dart <code>String</code> 转为 Objective-C <code>NSString</code>，所以耗时仅仅比 Flutter Channel 少了 60% 左右。</p><p>而在真实场景下，总耗时就更加有意义。由于 Native 方法本身执行的耗时占比较大，所以最终二者的耗时对比并不是几倍的关系，但 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 依然有着性能上的优势。</p><h3 id="支持的特性"><a href="#支持的特性" class="headerlink" title="支持的特性"></a>支持的特性</h3><p>为了在 Flutter 中使用，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 无法用到 Dart 反射特性，但依然最大限度地实现了对 Objective-C 语法特性的支持。</p><h4 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h4><p>Dart 和 Objective-C 的内存管理方式差异很大。前者使用 GC，后者使用 ARC。目前的解决方案是『半自动引用计数』的内存管理方式，大多数场景下无需关注内存问题。待 Dart 支持 <code>finalizer</code> 可优化为『全自动』。这其中用到了一些算不上黑科技的土方子，暂且奏效。</p><p>Dart 中临时使用和创建的 Objective-C 对象、C-String 或结构体无需关注内存问题，但如果想长期持有，需要调用 <code>retain()</code> 方法，并在不用的时候（比如页面销毁时）调用 <code>release()</code> 方法。</p><h4 id="Native-Callback"><a href="#Native-Callback" class="headerlink" title="Native Callback"></a>Native Callback</h4><p>有很多 Native API 的参数一个 Callback。这类方法大多是一些异步返回的方法，传入参数的方式大多是 Block 或 Delegate。为了让 Dart 能够调用这些 API，<a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 实现了『用 Dart 语法写 Block 和 Delegate』。这需要实现动态创建任意函数签名的 Block 对象和 Objective-C 方法，甚至当 Dart 类并没有对应的 Objective-C 类时，需要动态创建这个类。这其中又涉及到大量内建类型的自动转换和边界问题处理。</p><h4 id="多线程-GCD"><a href="#多线程-GCD" class="headerlink" title="多线程 / GCD"></a>多线程 / GCD</h4><p>Flutter 中运行时，VM 会开辟一些内建的线程来维持 Flutter 的运行。我们编写的 Dart 代码大多跑在 flutter.ui 线程，但这不是 Native 系统的主线程。而有些 API 要求必须在主线程调用，所以 <a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 也支持指定线程和队列调用。</p><p>对于 GCD 的 API 仅有部分支持，且计划为 Swift 风格语法。等 dart:ffi 1.1 支持 async callback 后，这部分的功能会得到加强。</p><h4 id="方法调用时的类型自动转换"><a href="#方法调用时的类型自动转换" class="headerlink" title="方法调用时的类型自动转换"></a>方法调用时的类型自动转换</h4><p><a href="https://github.com/dart-native/dart_native" target="_blank" rel="noopener">dart_native</a> 会自动转换 Dart 与 Objective-C 类型。大部分 Objective-C 类型在 Dart 中都有对应的封装类，或者是可以映射到 Dart 基本类型。目前有的转换是单项的，比如 Dart Function 可以转为 Objective-C Block，反之则不行。</p><p>已支持以下类型的自动转换：</p><table><thead><tr><th>Dart</th><th>Objective-C</th></tr></thead><tbody><tr><td>int</td><td>int8_t</td></tr><tr><td>int</td><td>int16_t</td></tr><tr><td>int</td><td>int32_t</td></tr><tr><td>int</td><td>int64_t</td></tr><tr><td>int</td><td>uint8_t</td></tr><tr><td>int</td><td>uint16_t</td></tr><tr><td>int</td><td>uint32_t</td></tr><tr><td>int</td><td>uint64_t</td></tr><tr><td>char/int/String</td><td>char</td></tr><tr><td>unsigned_char/int/String</td><td>unsigned char</td></tr><tr><td>short/int</td><td>short</td></tr><tr><td>unsigned_short/int</td><td>unsigned short</td></tr><tr><td>long/int</td><td>long</td></tr><tr><td>unsigned_long/int</td><td>unsigned long</td></tr><tr><td>long_long/int</td><td>long long</td></tr><tr><td>unsigned_long_long/int</td><td>unsigned long long</td></tr><tr><td>NSInteger/int</td><td>NSInteger</td></tr><tr><td>NSUInteger/int</td><td>NSUInteger</td></tr><tr><td>size_t/int</td><td>size_t</td></tr><tr><td>float/double</td><td>float</td></tr><tr><td>double</td><td>double</td></tr><tr><td>double</td><td>CGFloat</td></tr><tr><td>bool</td><td>BOOL/bool/_Bool</td></tr><tr><td>CGSize</td><td>CGSize</td></tr><tr><td>CGPoint</td><td>CGPoint</td></tr><tr><td>CGVector</td><td>CGVector</td></tr><tr><td>CGRect</td><td>CGRect</td></tr><tr><td>NSRange</td><td>NSRange/_NSRange</td></tr><tr><td>UIOffset</td><td>UIOffset</td></tr><tr><td>UIEdgeInsets</td><td>UIEdgeInsets</td></tr><tr><td>NSDirectionalEdgeInsets</td><td>NSDirectionalEdgeInsets</td></tr><tr><td>CGAffineTransform</td><td>CGAffineTransform</td></tr><tr><td>NSObject</td><td>NSObject</td></tr><tr><td>NSObjectProtocol</td><td>NSObjectProtocol</td></tr><tr><td>Block/Function</td><td>NSBlock</td></tr><tr><td>Class</td><td>Class</td></tr><tr><td>Selector/SEL</td><td>Selector/SEL</td></tr><tr><td>Protocol</td><td>Protocol</td></tr><tr><td>NSString/String</td><td>NSString</td></tr><tr><td>String</td><td>char *</td></tr><tr><td>Pointer<void></void></td><td>void *</td></tr><tr><td>void</td><td>void</td></tr><tr><td>NSValue</td><td>NSValue</td></tr><tr><td>NSNumber</td><td>NSNumber</td></tr><tr><td>NSArray/List</td><td>NSArray</td></tr><tr><td>NSDictionary/Map</td><td>NSDictionary</td></tr><tr><td>NSSet/Set</td><td>NSSet</td></tr></tbody></table>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;我之前在 『&lt;a href=&quot;http://yulingtianxia.com/blog/2019/10/27/Write-Objective-C-Code-using-Dart/&quot;&gt;用 Dart 来写 Objective-C 代码&lt;/a&gt;』 这篇文章讲了下我在解决 Flutter 三端开发问题的一个思路和方案，并给出了 Demo 和简单的对比。这次讲下 &lt;a href=&quot;https://github.com/dart-native/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt; 的设计，这包含了上层使用方式和底层技术方案的设计。由于涉及到的技术点很多，这次不会深入太多技术实现细节，不过后续可能会分篇讲下。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
      <category term="Flutter" scheme="http://yulingtianxia.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>用 Dart 来写 Objective-C 代码</title>
    <link href="http://yulingtianxia.com/blog/2019/10/27/Write-Objective-C-Code-using-Dart/"/>
    <id>http://yulingtianxia.com/blog/2019/10/27/Write-Objective-C-Code-using-Dart/</id>
    <published>2019-10-27T09:59:40.000Z</published>
    <updated>2020-09-28T09:07:35.000Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章不是讲 Flutter Channel 入门的，可能让你失望了。不过继续往下读可能也会有点收获。</p><p>Flutter 提升了客户端开发的效率，但在跟 Native 代码配合开发时也带来了不好的体验。于是我写了个 Flutter 插件 <a href="https://pub.dev/packages/dart_native" target="_blank" rel="noopener">dart_native</a>，使开发者可以用 Dart 的语法来写 Objective-C 代码。借助于 Flutter 的热重载，也可以更高效的动态调试 Native 代码，从此告别在两个工程和 IDE 中切来切去。方法调用性能相比 Channel 也提升很多。</p><p>尚在开发中，开源地址：<a href="https://github.com/yulingtianxia/dart_objc" target="_blank" rel="noopener">https://github.com/yulingtianxia/dart_objc</a></p><a id="more"></a><h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>先说说为什么会有开发效率的问题。Flutter 的跨平台多适用于 UI 等上层需求，本来是可以提升开发效率的。但是诸如 LBS、系统和设备信息、获取相册等常用功能都需要两端去写很多 Native 代码。<strong>最终原本的『两端开发』最后成了『三端开发』</strong>。很少会有完全用 Flutter 开发的 App，原因如下：</p><ol><li>一些跟系统和设备强相关的功能只能靠调用 API 来实现</li><li>旧项目引入 Flutter 后需调用已有的 Native 模块代码</li></ol><p>既然『三端开发』无法避免，那么增加了哪些成本呢？</p><ol><li>开发过程中需要在至少两个 IDE 打开的工程中来回切换，需单独运行，无论是写代码还是 Debug 都体验不连贯，降低效率</li><li>如果 Flutter 和 Native 代码由不同的人来开发和维护，增加了沟通成本</li><li>Flutter 需要通过编写 channel 代码来与 Native 层交互，需要两端开发时统一数据传输协议。不仅 channel 调用性能较差，Model 数据在 Native 与 Flutter 之间传递过程的序列化和反序列化也降低性能。</li><li>通过 channel 在 Flutter 和 Native 之间调用时只支持异步回调</li></ol><h2 id="分析问题"><a href="#分析问题" class="headerlink" title="分析问题"></a>分析问题</h2><p>既然无法避免调用 Native 的 API，那么就要面对这个事实。下一步是如何能让调用 Native API 的这个过程效率更高。具体体现如下：</p><ol><li>开发效率提高：直接用 Dart 语言在 Flutter 工程里编写和调试代码，无需切换到 Xcode 等其他 IDE 打开的 Native 工程</li><li>运行效率提高：channel 的调用性能差一直被诟病</li></ol><p>所以思路就是：</p><ol><li>将 Native API 封装成对应的 Dart 语言，解决一系列语言之间的类型转换和语法兼容问题</li><li>通过一个更高效的方式来调用 Native API，这里使用 dart:ffi 调用 C 函数，再通过 Runtime 机制调用 Native</li></ol><h2 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h2><p>提供一个 Flutter 库来提供 Dart 语言的 API，通过 dart:ffi 作为 Flutter 与 Native 之间的桥。对比 Dart 与 Native 的语法特性，对一些类型进行内存级别的底层转换。</p><p><a href="https://pub.dev/packages/dart_native" target="_blank" rel="noopener">dart_native</a> 由于采用指针地址直接传递的方式，方法调用性能相比 channel 提升了<strong>几倍甚至一个数量级</strong>。（测试接口为获取系统版本，如涉及复杂参数的序列化可能差异更大）</p><p>由于 <a href="https://pub.dev/packages/dart_native" target="_blank" rel="noopener">dart_native</a> 组件还在基于 dev 版本的 Dart 开发，可能后续还会有比较大的变动，甚至是 API 的变化。所以没有过多展开讲实现细节，感兴趣可以去自己看代码：<a href="https://github.com/yulingtianxia/dart_objc" target="_blank" rel="noopener">https://github.com/yulingtianxia/dart_objc</a></p><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><p>假如你写了个 Objective-C 的类叫 <code>RuntimeStub</code>，并实现了个 <code>fooBlock:</code> 方法，参数和返回值都是个 block 对象。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">RuntimeStub</span> ()</span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">RuntimeStub</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">int</span>(^BarBlock)(<span class="built_in">NSObject</span> *a);</span><br><span class="line">- (BarBlock)fooBlock:(BarBlock)block &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><p>利用 <a href="https://pub.dev/packages/dart_native" target="_blank" rel="noopener">dart_native</a> 写 Dart 代码调用过程如下：</p><p>初始化一个 <code>NSObject</code> 对象，传入类名就可以 <code>new</code> 任意类型的对象。<code>perform()</code> 方法可以调用任意对象的任何方法，跟 Objective-C 的用法基本一致。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSObject stub = NSObject(<span class="string">'RuntimeStub'</span>);</span><br><span class="line">Block block = stub.perform(Selector(<span class="string">'fooBlock:'</span>), args: [barFunc]);</span><br></pre></td></tr></table></figure><p>Objective-C 中 Block 这种匿名函数或闭包的概念在 Dart 中其实就是 Function，所以当参数是 Block 对象的时候，可以直接传入一个与之函数签名一样的 Dart Function 对象。<a href="https://pub.dev/packages/dart_native" target="_blank" rel="noopener">dart_native</a> 会自动完成参数类型转换和调用等一系列底层细节。所以用 Dart 实现的 <code>barFunc</code> 与 Objective-C 接口 <code>BarBlock</code> 的签名需要一致：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span> barFunc = (NSObject a) &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'hello block! <span class="subst">$&#123;a.toString()&#125;</span>'</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">101</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Dart 调用 Block 也很简单，调用 <code>invoke</code> 方法就行：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">int</span> result = block.invoke([stub]);</span><br></pre></td></tr></table></figure><p>最后也可以用 Dart 封装下 <code>RuntimeStub</code> 类，这样调用代码更简洁。这种模板代码后续会做成自动生成的，而不用手写。</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RuntimeStub</span> <span class="keyword">extends</span> <span class="title">NSObject</span> </span>&#123;</span><br><span class="line">  RuntimeStub() : <span class="keyword">super</span>(<span class="string">'RuntimeStub'</span>);</span><br><span class="line"></span><br><span class="line">  Block fooBlock(<span class="built_in">Function</span> func) &#123;</span><br><span class="line">    <span class="keyword">return</span> perform(Selector(<span class="string">'fooBlock:'</span>), args: [func]);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p>目前的 Cocoa API 封装打算参考 Swift 版本的文档，毕竟 Dart 有些语法跟 Swift 还有点像。</p><p>Android 平台的实现也在规划中，最终将会结束 Flutter 三端开发现状，实现真正的前端大一统。</p><p>不吹了，还要做的事情真的太多了。。。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;这篇文章不是讲 Flutter Channel 入门的，可能让你失望了。不过继续往下读可能也会有点收获。&lt;/p&gt;
&lt;p&gt;Flutter 提升了客户端开发的效率，但在跟 Native 代码配合开发时也带来了不好的体验。于是我写了个 Flutter 插件 &lt;a href=&quot;https://pub.dev/packages/dart_native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dart_native&lt;/a&gt;，使开发者可以用 Dart 的语法来写 Objective-C 代码。借助于 Flutter 的热重载，也可以更高效的动态调试 Native 代码，从此告别在两个工程和 IDE 中切来切去。方法调用性能相比 Channel 也提升很多。&lt;/p&gt;
&lt;p&gt;尚在开发中，开源地址：&lt;a href=&quot;https://github.com/yulingtianxia/dart_objc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/yulingtianxia/dart_objc&lt;/a&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Runtime" scheme="http://yulingtianxia.com/tags/Runtime/"/>
    
      <category term="Dart" scheme="http://yulingtianxia.com/tags/Dart/"/>
    
      <category term="DartNative" scheme="http://yulingtianxia.com/tags/DartNative/"/>
    
  </entry>
  
  <entry>
    <title>App 二进制文件重排已经被玩坏了</title>
    <link href="http://yulingtianxia.com/blog/2019/09/01/App-Order-Files/"/>
    <id>http://yulingtianxia.com/blog/2019/09/01/App-Order-Files/</id>
    <published>2019-08-31T16:47:39.000Z</published>
    <updated>2019-09-01T04:07:37.000Z</updated>
    
    <content type="html"><![CDATA[<p>『二进制文件重排优化启动速度』本是一项上古 PC 时代就玩过的东东，前一阵子借助某宇宙大厂重新火了一把。不过令我惊讶的是：这么简单个事情竟然搞得如此复杂，而且还声称『开拓性的探索、在没有业界经验可供参考』。。。</p><p><del>说真话可能会得罪人，但是我怕过吗？</del> 我怂了，这段掐了。</p><p>其实二进制文件重排很简单啊，重点在于生成 order 文件。我基于 Clang SanitizerCoverage 和业界已有的经验，整了个 <a href="https://github.com/yulingtianxia/AppOrderFiles" target="_blank" rel="noopener">AppOrderFiles</a>，一个调用搞定！Enjoy it！</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">AppOrderFiles(^(<span class="built_in">NSString</span> *orderFilePath) &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@"OrderFilePath:%@"</span>, orderFilePath);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="苹果官方文档的古老方案"><a href="#苹果官方文档的古老方案" class="headerlink" title="苹果官方文档的古老方案"></a>苹果官方文档的古老方案</h2><p>苹果的官方文档很早就给了二进制文件重排的方案：<a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeFootprint/Articles/ImprovingLocality.html#//apple_ref/doc/uid/20001862-117091-BCIBJEBH" target="_blank" rel="noopener">Improving Locality of Reference</a>，『早』到甚至被苹果提示这份文档已经年久失修，部分工具和链接失效了。文档的过时不仅体现在还是 GCC 时代，连工具链比如像 <code>gprof</code> 也不能用了，不过 Google 也给出了 macOS 上的替代品，有兴趣的可以去研究下。</p><h2 id="Facebook-的-hfsort"><a href="#Facebook-的-hfsort" class="headerlink" title="Facebook 的 hfsort"></a>Facebook 的 hfsort</h2><p>需要先用 hf-prod-collect.sh 收集数据，然后塞给 <a href="https://github.com/facebook/hhvm/tree/master/hphp/tools/hfsort" target="_blank" rel="noopener">hfsort</a> 生成 hotfuncs.txt 文件。很好很强大，不过对于编程小白来说有一定的使用成本。</p><p>PS：此方案来自于我写了这篇文章后，jmpews 大神丢给我了个链接，受益匪浅。（其实我啥都看不懂）</p><h2 id="基于-Clang-SanitizerCoverage-的方案"><a href="#基于-Clang-SanitizerCoverage-的方案" class="headerlink" title="基于 Clang SanitizerCoverage 的方案"></a>基于 Clang SanitizerCoverage 的方案</h2><p>在 <a href="https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs" target="_blank" rel="noopener">Clang 10 documentation</a> 中可以看到 LLVM 官方对 SanitizerCoverage 的详细介绍，包含了示例代码。</p><p>简单来说 SanitizerCoverage 是 Clang 内置的一个代码覆盖工具。它把一系列以 <code>__sanitizer_cov_trace_pc_</code> 为前缀的函数调用插入到用户定义的函数里，借此实现了全局 AOP 的大杀器。其覆盖之广，包含 Swift/Objective-C/C/C++ 等语言，Method/Function/Block 全支持。</p><p>开启 SanitizerCoverage 的方法是：在 build settings 里的 “Other C Flags” 中添加 <code>-fsanitize-coverage=func,trace-pc-guard</code>。如果含有 Swift 代码的话，还需要在 “Other Swift Flags” 中加入 <code>-sanitize-coverage=func</code> 和 <code>-sanitize=undefined</code>。所有链接到 App 中的二进制都需要开启 SanitizerCoverage，这样才能完全覆盖到所有调用。</p><p>基于 Clang SanitizerCoverage 我写了个工具 AppOrderFiles。CocoaPods 接入，一行调用生成 Order File。啥也不说了，全在 GayHub 里了：<a href="https://github.com/yulingtianxia/AppOrderFiles" target="_blank" rel="noopener">https://github.com/yulingtianxia/AppOrderFiles</a></p><p>当然这也不完全是我的原创，对照着 Clang 文档的同时，还参考了 <a href="https://medium.com/@michael.eisel/improving-app-performance-with-order-files-c7fff549907f" target="_blank" rel="noopener">Improving App Performance with Order Files</a> 这篇文章的代码。人家这篇文章虽然早就给出了，不过还是有一些 bug 和优化空间的。</p><p>原理就是在 SanitizerCoverage 的回调函数里将地址先收集到队列里，调用 <code>AppOrderFiles()</code> 后会停止收集，并将队列中的 PC 地址依次翻译符号，最后去重。反正代码也不多，直接贴核心代码：</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> OSQueueHead <span class="built_in">queue</span> = OS_ATOMIC_QUEUE_INIT;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> BOOL collectFinished = NO;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">void</span> *pc;</span><br><span class="line">    <span class="keyword">void</span> *next;</span><br><span class="line">&#125; PCNode;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The guards are [start, stop).</span></span><br><span class="line"><span class="comment">// This function will be called at least once per DSO and may be called</span></span><br><span class="line"><span class="comment">// more than once with the same values of start/stop.</span></span><br><span class="line"><span class="keyword">void</span> __sanitizer_cov_trace_pc_guard_init(<span class="keyword">uint32_t</span> *start,</span><br><span class="line">                                         <span class="keyword">uint32_t</span> *<span class="built_in">stop</span>) &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">uint32_t</span> N;  <span class="comment">// Counter for the guards.</span></span><br><span class="line">    <span class="keyword">if</span> (start == <span class="built_in">stop</span> || *start) <span class="keyword">return</span>;  <span class="comment">// Initialize only once.</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"INIT: %p %p\n"</span>, start, <span class="built_in">stop</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">uint32_t</span> *x = start; x &lt; <span class="built_in">stop</span>; x++)</span><br><span class="line">        *x = ++N;  <span class="comment">// Guards should start from 1.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// This callback is inserted by the compiler on every edge in the</span></span><br><span class="line"><span class="comment">// control flow (some optimizations apply).</span></span><br><span class="line"><span class="comment">// Typically, the compiler will emit the code like this:</span></span><br><span class="line"><span class="comment">//    if(*guard)</span></span><br><span class="line"><span class="comment">//      __sanitizer_cov_trace_pc_guard(guard);</span></span><br><span class="line"><span class="comment">// But for large functions it will emit a simple call:</span></span><br><span class="line"><span class="comment">//    __sanitizer_cov_trace_pc_guard(guard);</span></span><br><span class="line"><span class="keyword">void</span> __sanitizer_cov_trace_pc_guard(<span class="keyword">uint32_t</span> *guard) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!*guard) <span class="keyword">return</span>;  <span class="comment">// Duplicate the guard check.</span></span><br><span class="line">    <span class="keyword">if</span> (collectFinished) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// If you set *guard to 0 this code will not be called again for this edge.</span></span><br><span class="line">    <span class="comment">// Now you can get the PC and do whatever you want:</span></span><br><span class="line">    <span class="comment">//   store it somewhere or symbolize it and print right away.</span></span><br><span class="line">    <span class="comment">// The values of `*guard` are as you set them in</span></span><br><span class="line">    <span class="comment">// __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive</span></span><br><span class="line">    <span class="comment">// and use them to dereference an array or a bit vector.</span></span><br><span class="line">    *guard = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">void</span> *PC = __builtin_return_address(<span class="number">0</span>);</span><br><span class="line">    PCNode *node = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(PCNode));</span><br><span class="line">    *node = (PCNode)&#123;PC, <span class="literal">NULL</span>&#125;;</span><br><span class="line">    OSAtomicEnqueue(&amp;<span class="built_in">queue</span>, node, offsetof(PCNode, next));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>苹果官方也提供了 PGO 的详细文档，而且操作很简单。不过它跟二进制文件重排还是有区别的，这里不展开讲了。毕竟相对于对业务代码加载优先级的优化来说，PGO 对启动优化性价比没那么高，应该就是高频调用函数内联之类的（这句纯属瞎扯）。</p><p>我为啥过了这么久才发此文呢？猜猜原因是啥：</p><p>A. 不爱蹭热度<br>B. 喜欢炒冷饭<br>C. 忙准备答辩<br>D. 8 月已经发过文章了，这篇得等到 9 月发，这样才不浪费</p><p>碰到不会的题，我一般三短一长选最长。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;『二进制文件重排优化启动速度』本是一项上古 PC 时代就玩过的东东，前一阵子借助某宇宙大厂重新火了一把。不过令我惊讶的是：这么简单个事情竟然搞得如此复杂，而且还声称『开拓性的探索、在没有业界经验可供参考』。。。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;说真话可能会得罪人，但是我怕过吗？&lt;/del&gt; 我怂了，这段掐了。&lt;/p&gt;
&lt;p&gt;其实二进制文件重排很简单啊，重点在于生成 order 文件。我基于 Clang SanitizerCoverage 和业界已有的经验，整了个 &lt;a href=&quot;https://github.com/yulingtianxia/AppOrderFiles&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AppOrderFiles&lt;/a&gt;，一个调用搞定！Enjoy it！&lt;/p&gt;
&lt;figure class=&quot;highlight objc&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;AppOrderFiles(^(&lt;span class=&quot;built_in&quot;&gt;NSString&lt;/span&gt; *orderFilePath) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;NSLog&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;@&quot;OrderFilePath:%@&quot;&lt;/span&gt;, orderFilePath);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="瞎折腾" scheme="http://yulingtianxia.com/tags/%E7%9E%8E%E6%8A%98%E8%85%BE/"/>
    
  </entry>
  
  <entry>
    <title>BlockHook with Invocation(2)</title>
    <link href="http://yulingtianxia.com/blog/2019/08/11/BlockHook-with-Invocation-2/"/>
    <id>http://yulingtianxia.com/blog/2019/08/11/BlockHook-with-Invocation-2/</id>
    <published>2019-08-11T14:18:51.000Z</published>
    <updated>2020-08-22T11:03:57.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="http://yulingtianxia.com/blog/2019/07/27/BlockHook-with-Invocation/">上一篇文章</a> 简单介绍了下 <code>retainArguments</code> 和 <code>block_interceptor</code> 实现的思路，本文会详细讲解下 <code>BHInvocation</code> 的接口设计与实现，并与系统的 <code>NSInvocation</code> 作对比。</p><a id="more"></a><h2 id="接口设计"><a href="#接口设计" class="headerlink" title="接口设计"></a>接口设计</h2><p><code>BHInvocation</code> 相当于是参照 <code>NSInvocation</code> 的接口并改造了下，以承载 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的一些元数据。</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">BHInvocation</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>, <span class="keyword">weak</span>) BHToken *token;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>) BlockHookMode mode;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">getter</span>=isArgumentsRetained, <span class="keyword">readonly</span>) <span class="built_in">BOOL</span> argumentsRetained;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>, <span class="keyword">readonly</span>) <span class="built_in">NSMethodSignature</span> *methodSignature;</span><br><span class="line">- (<span class="keyword">void</span>)invokeOriginalBlock; <span class="comment">// 替代 invoke 和 invokeWithTarget:</span></span><br><span class="line">- (<span class="keyword">void</span>)retainArguments;</span><br><span class="line">- (<span class="keyword">void</span>)getReturnValue:(<span class="keyword">void</span> *)retLoc;</span><br><span class="line">- (<span class="keyword">void</span>)setReturnValue:(<span class="keyword">void</span> *)retLoc;</span><br><span class="line">- (<span class="keyword">void</span>)getArgument:(<span class="keyword">void</span> *)argumentLocation atIndex:(<span class="built_in">NSInteger</span>)idx;</span><br><span class="line">- (<span class="keyword">void</span>)setArgument:(<span class="keyword">void</span> *)argumentLocation atIndex:(<span class="built_in">NSInteger</span>)idx;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><h3 id="BlockHook-相关的接口"><a href="#BlockHook-相关的接口" class="headerlink" title="BlockHook 相关的接口"></a>BlockHook 相关的接口</h3><p>为了存储 Hook 相关的信息，需要在 <code>NSInvocation</code> 的接口基础上新增 <code>token</code> 属性和 <code>mode</code> 属性。不过 <code>BHToken</code> 其实已经存储了 <code>BlockHookMode</code>，为何还要再在 <code>BHInvocation</code> 中加一个 <code>mode</code> 呢？</p><p><code>BHToken</code> 存储的是一次 Hook 行为的元数据；<code>BHInvocation</code> 存储的是 Hook 后 Block 执行时的元数据。<code>BHToken</code> 存储的 <code>mode</code> 是 Hook 的模式，可能包含了多种模式；而 <code>BHInvocation</code> 存储的 <code>mode</code> 则是当前这次 Hook 执行回调所处的时机。</p><p>例如同时 Hook Block 执行的前后，此时传入的 <code>mode</code> 值为 <code>BlockHookModeBefore|BlockHookModeAfter</code>，生成的 <code>BHToken</code> 的值也是一样。而 Block 执行前后会有两次回调，传入的 <code>BHInvocation</code> 参数内容却不太一样：其 <code>mode</code> 分别为 <code>BlockHookModeBefore</code> 和 <code>BlockHookModeAfter</code>。但这两次传入的 <code>BHInvocation</code> 中的 <code>token</code> 确是完全一样。</p><p><code>BHToken</code> 也是初始化 <code>BHInvocation</code> 所用到的唯一参数。</p><p>由于是 Hook，所以执行 Block 时需要注意是调用原始实现还是新的实现。 加入了 <code>invokeOriginalBlock</code> 接口来调用原始实现，这也是所有 AOP 工具的必要设计。</p><h3 id="NSInvocation-相关的接口"><a href="#NSInvocation-相关的接口" class="headerlink" title="NSInvocation 相关的接口"></a><code>NSInvocation</code> 相关的接口</h3><p>为了降低使用者的学习成本，<a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的接口设计上会尽量参照一些已有的 AOP 工具。在 Invocation 这块，能参照的最好的例子就是系统提供的 <code>NSInvocation</code>。其提供了<strong>读、写和 <code>retian</code> 参数列表/返回值</strong>的接口，以及方法签名等。</p><p>而 <code>NSInvocation</code> 有些接口在 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 中是用不到的，比如 <code>selector</code> 属性没什么意义，再比如 <code>invoke</code> 和 <code>invokeWithTarget:</code> 这两个接口在 AOP 场景下也不必存在。</p><h2 id="接口实现"><a href="#接口实现" class="headerlink" title="接口实现"></a>接口实现</h2><p>在<a href="http://yulingtianxia.com/blog/2019/07/27/BlockHook-with-Invocation/">上一篇文章</a>中介绍了过了 <code>retainArguments</code> 的实现思路，针对每个指向参数或返回值的指针都需要经历 “Copy” 和 “Retain” 两步：</p><p><img src="http://yulingtianxia.com/resources/BlockHook/retainArguments.png" alt></p><h3 id="Copy-Pointer"><a href="#Copy-Pointer" class="headerlink" title="Copy Pointer"></a>Copy Pointer</h3><p>无论 <code>pointer</code> 指向的内容是一个 <code>struct</code> 还是 <code>NSObject *</code>，都需要将 <code>pointer</code> 的内容拷贝，防止原始内存被修改或者释放。在拷贝前需要开辟新的内存，其生命周期与 <code>BHInvocation</code> 绑定在一起。</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span> *)_copyPointer:(<span class="keyword">void</span> **)pointer encode:(<span class="keyword">const</span> <span class="keyword">char</span> *)encode key:(<span class="built_in">NSNumber</span> *)key</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">NSUInteger</span> pointerSize;</span><br><span class="line">    <span class="built_in">NSGetSizeAndAlignment</span>(encode, &amp;pointerSize, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="built_in">NSMutableData</span> *pointerData = [<span class="built_in">NSMutableData</span> dataWithLength:pointerSize];</span><br><span class="line">    <span class="keyword">self</span>.mallocMap[key] = pointerData;</span><br><span class="line">    <span class="keyword">void</span> *pointerBuf = pointerData.mutableBytes;</span><br><span class="line">    memcpy(pointerBuf, pointer, pointerSize);</span><br><span class="line">    <span class="keyword">return</span> pointerBuf;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Retain-Pointer"><a href="#Retain-Pointer" class="headerlink" title="Retain Pointer"></a>Retain Pointer</h3><p>如果 <code>pointer</code> 指向的内容依然是个指针，比如 <code>NSObject *</code> 或 <code>char *</code>，还需要防止其内容提前被释放，产生野指针。这里相当于是对 Objective-C 对象和 C-String 的特殊处理，以参数和返回值的 index 作为 key，利用字典 <code>retainMap</code> 强引用 Objective-C 对象；对于 Block 对象还需调用 <code>copy</code> 方法，将栈上的 Block 拷贝到堆上防止被提早释放；对于 C-String 则是开辟新内存并拷贝字符串内容，然后放入 <code>retainMap</code> 中；</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)_retainPointer:(<span class="keyword">void</span> **)pointer encode:(<span class="keyword">const</span> <span class="keyword">char</span> *)encode key:(<span class="built_in">NSNumber</span> *)key</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">void</span> *p = *pointer;</span><br><span class="line">    <span class="keyword">if</span> (!p) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (encode[<span class="number">0</span>] == <span class="string">'@'</span>) &#123;</span><br><span class="line">        <span class="keyword">id</span> arg = (__bridge <span class="keyword">id</span>)p;</span><br><span class="line">        <span class="keyword">if</span> (strcmp(encode, <span class="string">"@?"</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">self</span>.retainMap[key] = [arg <span class="keyword">copy</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">self</span>.retainMap[key] = arg;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (encode[<span class="number">0</span>] == <span class="string">'*'</span>) &#123;</span><br><span class="line">        <span class="keyword">char</span> *arg = p;</span><br><span class="line">        <span class="built_in">NSMutableData</span> *data = [<span class="built_in">NSMutableData</span> dataWithLength:<span class="keyword">sizeof</span>(<span class="keyword">char</span>) * strlen(arg)];</span><br><span class="line">        <span class="keyword">self</span>.retainMap[key] = data;</span><br><span class="line">        <span class="keyword">char</span> *str = data.mutableBytes;</span><br><span class="line">        strcpy(str, arg);</span><br><span class="line">        *pointer = str;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="读写参数和返回值"><a href="#读写参数和返回值" class="headerlink" title="读写参数和返回值"></a>读写参数和返回值</h3><p><a href="http://yulingtianxia.com/blog/2019/07/27/BlockHook-with-Invocation/">上一篇文章</a>讲述了 <code>BHInvocation</code> 存储参数列表和返回值上的一些处理策略，这里来讲讲如何读写。</p><p>在实现读写参数列表和返回值接口时，不仅仅是对 <code>args</code> 和 <code>retValue</code> 指针的读写操作，还要考虑到 Copy Pointer 和 Retain Pointer。</p><p>Copy Pointer 这步无需自行开辟内存了，原因是写入时 <code>retainArguments</code> 的时候已经开辟好了，读取时直接使用传入的指针。</p><p>Retain Pointer 接口使用 <code>idx</code> 作为 key，写入新的值时会替换字典 <code>retainMap</code> 中的旧值。这样既可以释放旧值，也能重新 retain 新值。</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)getArgument:(<span class="keyword">void</span> *)argumentLocation atIndex:(<span class="built_in">NSInteger</span>)idx</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!argumentLocation || !<span class="keyword">self</span>.args || !<span class="keyword">self</span>.args[idx]) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">void</span> *arg = <span class="keyword">self</span>.args[idx];</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *type = [<span class="keyword">self</span>.methodSignature getArgumentTypeAtIndex:idx];</span><br><span class="line">    <span class="built_in">NSUInteger</span> argSize;</span><br><span class="line">    <span class="built_in">NSGetSizeAndAlignment</span>(type, &amp;argSize, <span class="literal">NULL</span>);</span><br><span class="line">    memcpy(argumentLocation, arg, argSize);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)setArgument:(<span class="keyword">void</span> *)argumentLocation atIndex:(<span class="built_in">NSInteger</span>)idx</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!argumentLocation || !<span class="keyword">self</span>.args || !<span class="keyword">self</span>.args[idx]) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">void</span> *arg = <span class="keyword">self</span>.args[idx];</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *type = [<span class="keyword">self</span>.methodSignature getArgumentTypeAtIndex:idx];</span><br><span class="line">    <span class="built_in">NSUInteger</span> argSize;</span><br><span class="line">    <span class="built_in">NSGetSizeAndAlignment</span>(type, &amp;argSize, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">self</span>.isArgumentsRetained) &#123;</span><br><span class="line">        [<span class="keyword">self</span> _retainPointer:argumentLocation encode:type key:@(idx)];</span><br><span class="line">    &#125;</span><br><span class="line">    memcpy(arg, argumentLocation, argSize);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 ARC 下从 <code>NSInvocation</code> 读取参数或返回值时，如果类型为 Objective-C 对象，则需要避免默认的强引用。<a href="https://stackoverflow.com/questions/16928299/get-block-argument-from-nsinvocation-with-arc" target="_blank" rel="noopener">Stack Overflow</a> 上有具体解决方案，其中的一种方案如下：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSObject * __unsafe_unretained <span class="keyword">arg</span>;</span><br><span class="line">[invocation getArgument:&amp;<span class="keyword">arg</span> atIndex:<span class="number">1</span>];</span><br></pre></td></tr></table></figure><p><code>BHInvocation</code> 由于高仿了 <code>NSInvocation</code> 的接口和实现，所以也需要注意此问题。究其原因在于 <code>memcpy</code> 只是内存拷贝，不是直接向 <code>strong</code> 类型变量赋值，并不会参与到 ARC 的引用计数中。而出了作用域后 ARC 会自动对 <code>strong</code> 类型 <code>release</code> 一次，导致读取到的对象过度释放，导致 crash。（PS：ARC 真实的实现机制会更复杂些，为了描述方便这里对原理进行了简化）</p><p>其实还有一种更好的方式读参数，那就是直接在 <code>aspectBlock</code> 中取参数。<code>aspectBlock</code> 中的参数是可以随意写的，但需要跟 Block 的参数列表对应上。写法可以参照下面这个测试用例，直接获取参数，然后修改参数：</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)testObjectArg &#123;</span><br><span class="line">    <span class="built_in">NSObject</span> *argOrig = [<span class="built_in">NSObject</span> new];</span><br><span class="line">    <span class="built_in">NSObject</span> *argFixed = [<span class="built_in">NSObject</span> new];</span><br><span class="line">    <span class="keyword">void</span> (^ObjectArgBlock)(<span class="built_in">NSObject</span> *) = ^(<span class="built_in">NSObject</span> *test)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">NSAssert</span>(test == argFixed, <span class="string">@"Modify struct member failed!"</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    [ObjectArgBlock block_hookWithMode:BlockHookModeBefore usingBlock:^(BHInvocation *invocation, <span class="built_in">NSObject</span> *test)&#123;</span><br><span class="line">        <span class="built_in">NSAssert</span>(test == argOrig, <span class="string">@"Wrong arg!"</span>);</span><br><span class="line">        <span class="comment">// Hook 改参数</span></span><br><span class="line">        [invocation setArgument:(<span class="keyword">void</span> *)&amp;argFixed atIndex:<span class="number">1</span>];</span><br><span class="line">    &#125;];</span><br><span class="line">    </span><br><span class="line">    ObjectArgBlock(argOrig);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>最初 <code>BHInvocation</code> 还不够完善时，读写 Block 的参数/返回值只能用二级指针之类的晦涩语法直接操作 <code>args</code> 和 <code>retValue</code>，门槛较高而且还不够安全。<code>BHInvocation</code> 接口设计和实现上尽量参考已有的成熟案例，降低开发者学习成本，快速上手。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;http://yulingtianxia.com/blog/2019/07/27/BlockHook-with-Invocation/&quot;&gt;上一篇文章&lt;/a&gt; 简单介绍了下 &lt;code&gt;retainArguments&lt;/code&gt; 和 &lt;code&gt;block_interceptor&lt;/code&gt; 实现的思路，本文会详细讲解下 &lt;code&gt;BHInvocation&lt;/code&gt; 的接口设计与实现，并与系统的 &lt;code&gt;NSInvocation&lt;/code&gt; 作对比。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Runtime" scheme="http://yulingtianxia.com/tags/Runtime/"/>
    
      <category term="BlockHook" scheme="http://yulingtianxia.com/tags/BlockHook/"/>
    
  </entry>
  
  <entry>
    <title>BlockHook with Invocation(1)</title>
    <link href="http://yulingtianxia.com/blog/2019/07/27/BlockHook-with-Invocation/"/>
    <id>http://yulingtianxia.com/blog/2019/07/27/BlockHook-with-Invocation/</id>
    <published>2019-07-27T09:14:27.000Z</published>
    <updated>2020-08-22T11:04:32.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 在业界已经率先解决了在<strong>同步</strong>调用场景下对 Objective-C Block 的 AOP 问题。但也有很多场景是需要先调用一段自己的逻辑，然后再<strong>异步延时</strong>执行 Block。</p><p>比如从外部跳转到 App 某个页面前需要检查下登录态，如果未登录则需要走完登录流程后才能继续跳转页面，而几乎所有基于 Block callback 的路由组件都没提供路由拦截器的功能。不同的路由组件内部实现不同，想要实现拦截器就需要针对不同的内部实现来修改路由组件源码。</p><p>因此我实现了 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的异步拦截功能，所有基于 Block 的路由组件就都有了通用的路由拦截器！</p><p>当然，Block 拦截器的应用场景不仅于此。只要是需要『同步改异步执行』 Block 的场景都可以用到。</p><p>让子弹再飞一会儿！</p><a id="more"></a><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><p><a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 拦截器用法很简单，在已有 <code>BHInvocation</code> 参数的基础上，增加了一个 <code>completion</code> 回调。当拦截器的逻辑异步执行完后，调用 <code>completion</code> 即可继续执行原来的 Block。如果拦截器的逻辑是同步的，也依然可以用这个接口，只是没必要罢了，推荐直接用原来的 <code>block_hookWithMode:usingBlock:</code> 接口。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">void</span>(^IntercepterCompletion)(<span class="keyword">void</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> Interceptor for blocks. When your interceptor completed, call `completion` callback.</span></span><br><span class="line"><span class="comment"> You can call `completion` asynchronously!</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"> @param interceptor You **MUST** call `completion` callback in interceptor, unless you want to cancel invocation.</span></span><br><span class="line"><span class="comment"> @return BHToken instance.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">- (BHToken *)block_interceptor:(<span class="keyword">void</span> (^)(BHInvocation *invocation, IntercepterCompletion completion))interceptor;</span><br></pre></td></tr></table></figure><p>举个例子，拦截时修改传入的参数，并延迟 0.5 秒再执行 Block：</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSObject</span> *testArg = [<span class="built_in">NSObject</span> new];</span><br><span class="line"><span class="built_in">NSObject</span> *testArg1 = [<span class="built_in">NSObject</span> new];</span><br><span class="line">    </span><br><span class="line"><span class="built_in">NSObject</span> *(^testblock)(<span class="built_in">NSObject</span> *) = ^(<span class="built_in">NSObject</span> *a) &#123;</span><br><span class="line">    <span class="keyword">return</span> [<span class="built_in">NSObject</span> new];</span><br><span class="line">&#125;;</span><br><span class="line">    </span><br><span class="line">[testblock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion  _Nonnull completion) &#123;</span><br><span class="line">    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<span class="number">0.5</span> * <span class="built_in">NSEC_PER_SEC</span>)), dispatch_get_main_queue(), ^&#123;</span><br><span class="line">        *(<span class="keyword">void</span> **)(invocation.args[<span class="number">1</span>]) = (__bridge <span class="keyword">void</span> *)(testArg1);</span><br><span class="line">        completion();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;];</span><br><span class="line">    </span><br><span class="line">testblock(testArg);</span><br></pre></td></tr></table></figure><h2 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h2><p>首先想想如果要延迟一个 Objective-C 方法的执行，需要怎么做？</p><p>答案是利用消息转发机制，<code>NSInvocation</code> 调用 <code>retainArguments</code> 将方法执行所需的上下文持有，这样才能保证方法执行时所需的参数、<code>target</code> 等不会被释放。</p><p>对于 Block 来说，虽然也能通过 <code>NSInvocation</code> 来进行调用，但是经过 Hook 过后已经不再适用。因为 <code>NSInvocation</code> 的实现机制以及生命周期管理是个黑盒，且无法承载 Hook 相关的信息，需要自己来实现个 <code>BHInvocation</code>。</p><p><img src="http://yulingtianxia.com/resources/BlockHook/BlockInterceptor.png" alt></p><h3 id="BHInvocation-结构"><a href="#BHInvocation-结构" class="headerlink" title="BHInvocation 结构"></a>BHInvocation 结构</h3><p>我之前的 <a href="http://yulingtianxia.com/blog/2019/04/27/BlockHook-with-Struct/">BlockHook with Struct</a> 这篇文章提到了个技术点：在 x86 架构下，当 Block 返回值是大于 16 Byte 的 <code>struct</code> 时，参数列表有些变化：</p><p><img src="http://yulingtianxia.com/resources/BlockHook/realArgs.png" alt></p><p>为了兼容这种情况，需要两套 <code>args</code> 和 <code>retValue</code>。一套『真的』用于传给 libffi 调用原始函数指针，另一套『假的』提供给使用方读写参数和返回值。这样使用方无需关心底层特殊逻辑，直接用就行了。</p><p><code>BHInvocation</code> 主要结构如下：</p><p><img src="http://yulingtianxia.com/resources/BlockHook/BHInvocation.png" alt></p><p>PS：<code>BHInvocation</code> 与 <code>NSInvocation</code> 的场景和用法有些不同，所以实现上也会有差异。<code>NSInvocation</code> 没有公开源码，想了解原理的可以看看 mikeash 的实现： <a href="https://github.com/mikeash/MAInvocation" target="_blank" rel="noopener">MAInvocation</a>。但我并没有参考过 mikeash 的源码，因为等我写完了才发现它。。。</p><h3 id="retainArguments-实现"><a href="#retainArguments-实现" class="headerlink" title="retainArguments 实现"></a><code>retainArguments</code> 实现</h3><p><code>retainArguments</code> 实现策略：</p><ol><li>拷贝 <code>void **args</code> 指针数组和返回值指针</li><li><code>retain</code> 指针内容类型为 Objective-C 对象的参数</li><li>如果参数中也有其他 Block 对象，则 <code>copy</code> 过来</li><li>如果参数中有 C-string，则 <code>strcpy</code> 过来</li></ol><p><img src="http://yulingtianxia.com/resources/BlockHook/retainArguments.png" alt></p><p>需要注意的是这里依然要考虑两套 <code>args</code> 和 <code>retValue</code> 的问题。代码就不贴了，有兴趣的可以自己去看。</p><h3 id="block-interceptor-实现"><a href="#block-interceptor-实现" class="headerlink" title="block_interceptor 实现"></a><code>block_interceptor</code> 实现</h3><p>解决了 <code>retainArguments</code> 的实现，一切都好说了。只要基于原有的 <code>block_hookWithMode:usingBlock:</code> 接口稍加改装即可：</p><figure class="highlight erlang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">- <span class="params">(BHToken *)</span>block_interceptor:<span class="params">(void (^)</span><span class="params">(BHInvocation *invocation, IntercepterCompletion completion)</span>)interceptor &#123;</span><br><span class="line">    return [self block_hookWithMode:BlockHookModeInstead usingBlock:^<span class="params">(BHInvocation *invocation)</span> &#123;</span><br><span class="line">        if <span class="params">(interceptor)</span> &#123;</span><br><span class="line">            IntercepterCompletion completion = ^<span class="params">()</span> &#123;</span><br><span class="line">                [invocation invokeOriginalBlock];</span><br><span class="line">            &#125;;</span><br><span class="line">            interceptor<span class="params">(invocation, completion)</span>;</span><br><span class="line">            [invocation retainArguments];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>写了这么多关于 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的文章，我越来越发现自己在苹果爸爸面前所表现出的无知。几乎每一步都要去踩很多坑，看很多源码。而这次是看着苹果爸爸的文档脑补如何实现，业界也没有能参考的先例。</p><p>这种感觉犹如自己在黑暗中不断探索，并享受着这种孤独。</p><p>标题暗示着，这篇文章可能会有后续的。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; 在业界已经率先解决了在&lt;strong&gt;同步&lt;/strong&gt;调用场景下对 Objective-C Block 的 AOP 问题。但也有很多场景是需要先调用一段自己的逻辑，然后再&lt;strong&gt;异步延时&lt;/strong&gt;执行 Block。&lt;/p&gt;
&lt;p&gt;比如从外部跳转到 App 某个页面前需要检查下登录态，如果未登录则需要走完登录流程后才能继续跳转页面，而几乎所有基于 Block callback 的路由组件都没提供路由拦截器的功能。不同的路由组件内部实现不同，想要实现拦截器就需要针对不同的内部实现来修改路由组件源码。&lt;/p&gt;
&lt;p&gt;因此我实现了 &lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; 的异步拦截功能，所有基于 Block 的路由组件就都有了通用的路由拦截器！&lt;/p&gt;
&lt;p&gt;当然，Block 拦截器的应用场景不仅于此。只要是需要『同步改异步执行』 Block 的场景都可以用到。&lt;/p&gt;
&lt;p&gt;让子弹再飞一会儿！&lt;/p&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Runtime" scheme="http://yulingtianxia.com/tags/Runtime/"/>
    
      <category term="BlockHook" scheme="http://yulingtianxia.com/tags/BlockHook/"/>
    
  </entry>
  
  <entry>
    <title>BlockHook with Private Data</title>
    <link href="http://yulingtianxia.com/blog/2019/06/19/BlockHook-with-Private-Data/"/>
    <id>http://yulingtianxia.com/blog/2019/06/19/BlockHook-with-Private-Data/</id>
    <published>2019-06-19T14:50:39.000Z</published>
    <updated>2020-08-22T11:04:42.000Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> Hook 所有 Block 对象时，发现有些 Block 被 Hook 后会 Crash。究其原因发现是它们骨骼惊奇，夹带了很多『私货』，不能直接 Hook！本文讲述 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 在处理这种 Block 时的技术原理，解开含有 Private Data 的 Block 的神秘面纱。</p><a id="more"></a><p>由于关于 Block Private Data 的资料几乎没有，所以我完全可以当回标题党，把这篇文章的标题叫做『你真的了解 Block 么？』或者『这才是 Hook Block 的正确姿势』之类的。但想想还是算了吧，怕被大佬们嘲笑称又『改变业界』了啊。</p><h2 id="Block-为何会有-Private-Data"><a href="#Block-为何会有-Private-Data" class="headerlink" title="Block 为何会有 Private Data"></a>Block 为何会有 Private Data</h2><p>首先来看一段代码：</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">dispatch_block_t </span><span class="keyword">block </span>= <span class="keyword">dispatch_block_create(0, </span>^&#123;</span><br><span class="line">    NSLog(@<span class="string">"I'm dispatch_block_t"</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>用 <code>dispatch_block_create</code> 创建的 Block 都很特殊，返回的 Block 包含了参数里传入的 Block。此时 <code>dispatch_block_t</code> 虽然表面上是一种普通的 Block，但它的构造暗藏玄机，含有 Private Data，下面会详细解读。</p><h3 id="特殊的-invoke-函数"><a href="#特殊的-invoke-函数" class="headerlink" title="特殊的 invoke 函数"></a>特殊的 invoke 函数</h3><p>这种 Block 的 <code>invoke</code> 函数指针是固定的，函数名为 <code>___dispatch_block_create_block_invoke</code>。在 linux 系统下，函数名为 <code>__dispatch_block_create_block_invoke</code>，嗯少了个下划线。这个函数的定义来自 libdispatch.dylib，也就是我们常用的 GCD。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> &#123;</span><br><span class="line"><span class="comment">// The compiler hides the name of the function it generates, and changes it if</span></span><br><span class="line"><span class="comment">// we try to reference it directly, but the linker still sees it.</span></span><br><span class="line"><span class="function"><span class="keyword">extern</span> <span class="keyword">void</span> <span class="title">DISPATCH_BLOCK_SPECIAL_INVOKE</span><span class="params">(<span class="keyword">void</span> *)</span></span></span><br><span class="line"><span class="function"><span class="meta">#<span class="meta-keyword">ifdef</span> __linux__</span></span></span><br><span class="line"><span class="function"><span class="title">asm</span><span class="params">(<span class="string">"___dispatch_block_create_block_invoke"</span>)</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="keyword">asm</span>(<span class="string">"____dispatch_block_create_block_invoke"</span>);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="keyword">void</span> (*<span class="keyword">const</span> _dispatch_block_special_invoke)(<span class="keyword">void</span>*) = DISPATCH_BLOCK_SPECIAL_INVOKE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>libdispatch 会通过判断 Block 的 <code>invoke</code> 指针是否为 <code>_dispatch_block_special_invoke</code>，来知道这个 Block 是否含有 Private Data。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">bool</span></span><br><span class="line">_dispatch_block_has_private_data(<span class="keyword">const</span> <span class="keyword">dispatch_block_t</span> block)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> (_dispatch_Block_invoke(block) == _dispatch_block_special_invoke);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不幸的是，<code>_dispatch_block_special_invoke</code> 是私有的。在非调试场景下是无法通过 <code>dladdr</code> 等方式来获取它的函数名的。也就无法用类似上面的代码来判断 Block 是否含有 Private Data 了。</p><h3 id="获取-Private-Data"><a href="#获取-Private-Data" class="headerlink" title="获取 Private Data"></a>获取 Private Data</h3><p>使用 <code>dispatch_block_create</code> 创建的 <code>dispatch_block_t</code> 只是个『壳』，真正执行的是其内部包含的 Block。再加上 GCD 所需的一些数据（queue，group，thread，priority 等），这些数据都需要作为 Private Data 追加在 Block 上。对实现 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 来说最需要关注的就是 <code>dbpd_magic</code> 和 <code>dbpd_block</code>。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">OS_OBJECT_DECL_CLASS(voucher);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">dispatch_block_private_data_s</span> &#123;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> dbpd_magic;</span><br><span class="line">    <span class="keyword">dispatch_block_flags_t</span> dbpd_flags;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="keyword">volatile</span> dbpd_atomic_flags;</span><br><span class="line">    <span class="keyword">int</span> <span class="keyword">volatile</span> dbpd_performed;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> dbpd_priority;</span><br><span class="line">    <span class="keyword">voucher_t</span> dbpd_voucher;</span><br><span class="line">    <span class="keyword">dispatch_block_t</span> dbpd_block;</span><br><span class="line">    <span class="keyword">dispatch_group_t</span> dbpd_group;</span><br><span class="line">    <span class="keyword">dispatch_queue_t</span> dbpd_queue;</span><br><span class="line">    <span class="keyword">mach_port_t</span> dbpd_thread;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dispatch_block_private_data_s</span> *<span class="title">dispatch_block_private_data_t</span>;</span></span><br></pre></td></tr></table></figure><p>既然无法用 <code>_dispatch_block_special_invoke</code> 来判断 Block 是否含有 Private Data，可以使用 <code>dbpd_magic</code> 魔数来判断。当其值为 <code>0xD159B10C</code> 时（DisBloc 的意思），则表明含有 Private Data。<strong>当然这种溢出的方式同样是有风险的，但触及到 PAGEZERO 概率很低</strong>。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DISPATCH_BLOCK_PRIVATE_DATA_MAGIC 0xD159B10C <span class="comment">// 0xDISPatch_BLOCk</span></span></span><br><span class="line"></span><br><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">dispatch_block_private_data_t</span></span><br><span class="line">bh_dispatch_block_get_private_data(struct _BHBlock *block)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// Keep in sync with _dispatch_block_create implementation</span></span><br><span class="line">    <span class="keyword">uint8_t</span> *x = (<span class="keyword">uint8_t</span> *)block;</span><br><span class="line">    <span class="comment">// x points to base of struct Block_layout</span></span><br><span class="line">    x += <span class="keyword">sizeof</span>(struct _BHBlock);</span><br><span class="line">    <span class="comment">// x points to base of captured dispatch_block_private_data_s object</span></span><br><span class="line">    <span class="keyword">dispatch_block_private_data_t</span> dbpd = (<span class="keyword">dispatch_block_private_data_t</span>)x;</span><br><span class="line">    <span class="keyword">if</span> (dbpd-&gt;dbpd_magic != DISPATCH_BLOCK_PRIVATE_DATA_MAGIC) &#123;</span><br><span class="line">        <span class="keyword">return</span> nil;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dbpd;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后真正执行的其实是 <code>dbpd_block</code> 这个 Block，<code>dispatch_block_t</code> 只是个保存各种元数据的壳。</p><h2 id="适配-BlockHook"><a href="#适配-BlockHook" class="headerlink" title="适配 BlockHook"></a>适配 BlockHook</h2><p>虽然说 Private Data 本身并不是 Block 实现中必要的一环，它只是 GCD 对 Block 数据结构的一种『魔改』扩充。但由于 GCD 内部的一些保护机制，会在修改了 Block 的 <code>invoke</code> 指针后触发 crash（<code>__builtin_trap</code>），所以不能直接对含有 Private Data 的 Block 进行 Hook。这就需要 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 组件做一些适配工作。</p><h3 id="Hook-真正要执行的-Block"><a href="#Hook-真正要执行的-Block" class="headerlink" title="Hook 真正要执行的 Block"></a>Hook 真正要执行的 Block</h3><p>既然 <code>dbpd_block</code> 才是真正要执行的 Block，那么 Hook 的时候需要先获取 Private Data，然后对其 <code>dbpd_block</code> 进行 Hook:</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">- (BHToken *)block_hookWithMode:(BlockHookMode)mode</span><br><span class="line">                     usingBlock:(<span class="keyword">id</span>)aspectBlock</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!aspectBlock || ![<span class="keyword">self</span> block_checkValid]) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">struct</span> _BHBlock *bh_block = (__bridge <span class="keyword">void</span> *)<span class="keyword">self</span>;</span><br><span class="line">    <span class="keyword">if</span> (!_bh_Block_descriptor_3(bh_block)) &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@"Block has no signature! Required ABI.2010.3.16. %@"</span>, <span class="keyword">self</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Handle blocks have private data.</span></span><br><span class="line">    dispatch_block_private_data_t dbpd = bh_dispatch_block_get_private_data(bh_block);</span><br><span class="line">    <span class="keyword">if</span> (dbpd &amp;&amp; dbpd-&gt;dbpd_block) &#123;</span><br><span class="line">        <span class="keyword">return</span> [dbpd-&gt;dbpd_block block_hookWithMode:mode usingBlock:aspectBlock];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> [[BHToken alloc] initWithBlock:<span class="keyword">self</span> mode:mode aspectBlockBlock:aspectBlock];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="获取-Block-当前-Hook-Token"><a href="#获取-Block-当前-Hook-Token" class="headerlink" title="获取 Block 当前 Hook Token"></a>获取 Block 当前 Hook Token</h3><p>因为 Hook 的是 <code>dbpd_block</code>，所以获取 Token 的时候也需要额外处理下。要在 <code>dbpd_block</code> 上通过 AssociatedObject 来获取 Token，而不是 <code>dispatch_block_t</code> 上。</p><figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">BHToken </span>*)<span class="keyword">block_currentHookToken</span></span><br><span class="line"><span class="keyword">&#123;</span></span><br><span class="line"><span class="keyword"> </span>   <span class="meta">if</span> (![<span class="keyword">self </span><span class="keyword">block_checkValid]) </span>&#123;</span><br><span class="line">        return nil<span class="comment">;</span></span><br><span class="line">    &#125;</span><br><span class="line">    dispatch_block_private_data_t dbpd = <span class="keyword">bh_dispatch_block_get_private_data((__bridge </span><span class="keyword">struct </span>_BHBlock *)(<span class="keyword">self));</span></span><br><span class="line"><span class="keyword"> </span>   <span class="meta">if</span> (dbpd &amp;&amp; dbpd-&gt;dbpd_block) &#123;</span><br><span class="line">        return [dbpd-&gt;dbpd_block <span class="keyword">block_currentHookToken];</span></span><br><span class="line"><span class="keyword"> </span>   &#125;</span><br><span class="line">    void *invoke = [<span class="keyword">self </span><span class="keyword">block_currentInvokeFunction];</span></span><br><span class="line"><span class="keyword"> </span>   return objc_getAssociatedObject(<span class="keyword">self, </span>invoke)<span class="comment">;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>代码地址: <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">https://github.com/yulingtianxia/BlockHook</a></p><p>一图以蔽之。</p><p><img src="http://yulingtianxia.com/resources/BlockHook/BlockHook%20PrivateData.png" alt></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在使用 &lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; Hook 所有 Block 对象时，发现有些 Block 被 Hook 后会 Crash。究其原因发现是它们骨骼惊奇，夹带了很多『私货』，不能直接 Hook！本文讲述 &lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; 在处理这种 Block 时的技术原理，解开含有 Private Data 的 Block 的神秘面纱。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="Runtime" scheme="http://yulingtianxia.com/tags/Runtime/"/>
    
      <category term="BlockHook" scheme="http://yulingtianxia.com/tags/BlockHook/"/>
    
  </entry>
  
  <entry>
    <title>BlockHook with Revocation</title>
    <link href="http://yulingtianxia.com/blog/2019/05/26/BlockHook-with-Revocation/"/>
    <id>http://yulingtianxia.com/blog/2019/05/26/BlockHook-with-Revocation/</id>
    <published>2019-05-26T09:20:10.000Z</published>
    <updated>2019-08-11T14:21:07.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 开创性地解决了 Objective-C 语言界 Hook Block 的问题，但也迎来了新的问题：</p><ol><li>如何知道某个 Block 对象被谁 Hook 过？</li><li>多次 Hook 的先后顺序？</li><li>如何处理好多次 Hook 同一个 Block 后对任意一次 Hook 的撤销？</li></ol><p>这些问题也是开发者在使用时向我反馈过的问题，在这篇文章里，这些问题都将会解决。</p><p>关于 <a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 的原理，可以先阅读之前的文章：</p><ul><li><a href="http://yulingtianxia.com/blog/2018/02/28/Hook-Objective-C-Block-with-Libffi/">Hook Objective-C Block with Libffi</a></li><li><a href="http://yulingtianxia.com/blog/2019/04/27/BlockHook-with-Struct/">BlockHook with Struct</a></li></ul><a id="more"></a><h2 id="按顺序构造『虚拟的』-Hook-链表"><a href="#按顺序构造『虚拟的』-Hook-链表" class="headerlink" title="按顺序构造『虚拟的』 Hook 链表"></a>按顺序构造『虚拟的』 Hook 链表</h2><p>首先要有一个链表来按先后顺序记录一个 Block 对象上所有的 Hook。这个链表的格式以及持有关系也需要考虑在内。</p><p>为此我构造了一个虚拟的链表来记录 Hook 的先后关系，而不是单独创建一个链表显式的记录。首先介绍下 Block 与 token 之间的引用关系：</p><p><img src="https://raw.githubusercontent.com/yulingtianxia/Blog-Hexo-Source/master/source/resources/BlockHook/BlockHook_Token_List.png" alt></p><p>可以看出每个 <code>BHToken</code> 记录了原始和替换后的 <code>invoke</code> 函数指针，那么先后两次 Hook 就靠 <code>invoke</code> 函数指针来关联了：<strong>每个 tokne 的 <code>originalInvoke</code> 就是上一次 Hook 的 token 的 <code>replacementInvoke</code></strong>。而拿到 token 又是靠 Block 对象上的 <code>AssociatedObject</code>，且 key 为 <code>replacementInvoke</code>。这样就构造了一条虚拟的链表：想要获得上次 Hook 的 token，只需在 Hook 的 Block 对象上使用 <code>originalInvoke</code> 作为 key 即可。</p><p>下面的代码展示了获取下个 token 的 <code>next</code> 实现。因为链表可能会有新的插入和删除节点操作，所以需确保线程安全。</p><figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">BHToken </span>*)next</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">BHLock </span>*lock = [<span class="keyword">self.block </span><span class="keyword">bh_lockForKey:@selector(next)];</span></span><br><span class="line"><span class="keyword"> </span>   [lock lock]<span class="comment">;</span></span><br><span class="line">    <span class="meta">if</span> (!_next) &#123;</span><br><span class="line">        _next = objc_getAssociatedObject(<span class="keyword">self.block, </span><span class="keyword">self.originInvoke);</span></span><br><span class="line"><span class="keyword"> </span>   &#125;</span><br><span class="line">    <span class="keyword">BHToken </span>*result = _next<span class="comment">;</span></span><br><span class="line">    [lock unlock]<span class="comment">;</span></span><br><span class="line">    return result<span class="comment">;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里使用 <code>replacementInvoke</code> 来作为 <code>AssociatedObject</code> 的 key 真是<strong>一举多得</strong>：</p><ol><li>将 token 的生命周期绑定到 Block 对象上，实现 self-managed</li><li>因为函数指针地址是唯一的，确保 Block 上关联每个 token 的 key 不会冲突</li><li>Block 的 <code>invoke</code> 指针作为 key，可以找到最后一次 Hook 的 token。进而按 Hook 先后顺序遍历出所有的 token。</li></ol><p>下面的代码展示了如何获取最后一次 Hook 的 token。在读取 <code>invoke</code> 函数指针的时候，注意保证线程安全。</p><figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">BHToken </span>*)<span class="keyword">block_currentHookToken</span></span><br><span class="line"><span class="keyword">&#123;</span></span><br><span class="line"><span class="keyword"> </span>   <span class="meta">if</span> (![<span class="keyword">self </span><span class="keyword">block_checkValid]) </span>&#123;</span><br><span class="line">        return nil<span class="comment">;</span></span><br><span class="line">    &#125;</span><br><span class="line">    void *invoke = [<span class="keyword">self </span><span class="keyword">block_currentInvokeFunction];</span></span><br><span class="line"><span class="keyword"> </span>   return objc_getAssociatedObject(<span class="keyword">self, </span>invoke)<span class="comment">;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (void *)<span class="keyword">block_currentInvokeFunction</span></span><br><span class="line"><span class="keyword">&#123;</span></span><br><span class="line"><span class="keyword"> </span>   <span class="keyword">struct </span>_BHBlock *<span class="keyword">bh_block </span>= (__bridge void *)<span class="keyword">self;</span></span><br><span class="line"><span class="keyword"> </span>   <span class="keyword">BHLock </span>*lock = [<span class="keyword">self </span><span class="keyword">bh_lockForKey:_cmd];</span></span><br><span class="line"><span class="keyword"> </span>   [lock lock]<span class="comment">;</span></span><br><span class="line">    void *invoke = <span class="keyword">bh_block-&gt;invoke;</span></span><br><span class="line"><span class="keyword"> </span>   [lock unlock]<span class="comment">;</span></span><br><span class="line">    return invoke<span class="comment">;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="撤销-Hook"><a href="#撤销-Hook" class="headerlink" title="撤销 Hook"></a>撤销 Hook</h2><p>俗话说『请神容易送神难』。好多 Hook 框架只解决的如何 Hook，但是却无法撤销恢复原样，留下一堆烂摊子。</p><p>在搭建了 Hook 链表的基础上，多次 Hook 的链表可以简化成 <code>invoke</code> 函数指针之间的关系：</p><p><img src="https://raw.githubusercontent.com/yulingtianxia/Blog-Hexo-Source/master/source/resources/BlockHook/BlockHook_invoke_call.png" alt></p><p>那么撤销 Hook 就可以从链表头部开始遍历，找到当前要 <code>remove</code> 的 token。接着链表上删除这个 token，而这又可以分为两个子问题：</p><ol><li>移除最后一次 Hook：需要将 Block 的 <code>invoke</code> 指针指向 token 的 <code>originalInvoke</code>。</li><li>移除<em>非</em>最后一次 Hook：需要将上一次 Hook token 的 <code>originalInvoke</code> 指向当前 token 的 <code>originalInvoke</code>。</li></ol><p>最后肯定还要解除 Block 对象对 token 的持有。</p><p>这部分逻辑的实现代码如下，在操作 Block 的 <code>invoke</code> 指针时依然需要注意线程安全问题：</p><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">BOOL</span>)remove</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">self</span>.isStackBlock) &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@"Can't remove token for StackBlock!"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NO</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">self</span>.deadBlock = <span class="literal">nil</span>;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">self</span>.originInvoke) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">self</span>.block) &#123;</span><br><span class="line">            BHToken *current = [<span class="keyword">self</span>.block block_currentHookToken];</span><br><span class="line">            BHToken *last = <span class="literal">nil</span>;</span><br><span class="line">            <span class="keyword">while</span> (current) &#123;</span><br><span class="line">                <span class="keyword">if</span> (current == <span class="keyword">self</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (last) &#123; <span class="comment">// remove middle token</span></span><br><span class="line">                        last.originInvoke = <span class="keyword">self</span>.originInvoke;</span><br><span class="line">                        last.next = <span class="literal">nil</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> &#123; <span class="comment">// remove head(current) token</span></span><br><span class="line">                        BHLock *lock = [<span class="keyword">self</span>.block bh_lockForKey:<span class="keyword">@selector</span>(block_currentInvokeFunction)];</span><br><span class="line">                        [lock lock];</span><br><span class="line">                        ((__bridge <span class="keyword">struct</span> _BHBlock *)<span class="keyword">self</span>.block)-&gt;invoke = <span class="keyword">self</span>.originInvoke;</span><br><span class="line">                        [lock unlock];</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                last = current;</span><br><span class="line">                current = [current next];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">self</span>.originInvoke = <span class="literal">NULL</span>;</span><br><span class="line">        objc_setAssociatedObject(<span class="keyword">self</span>.block, _replacementInvoke, <span class="literal">nil</span>, OBJC_ASSOCIATION_RETAIN);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NO</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><a href="https://github.com/yulingtianxia/BlockHook" target="_blank" rel="noopener">BlockHook</a> 还在不断完善每一个细节，尽可能做到有始有终，至善尽美。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; 开创性地解决了 Objective-C 语言界 Hook Block 的问题，但也迎来了新的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如何知道某个 Block 对象被谁 Hook 过？&lt;/li&gt;
&lt;li&gt;多次 Hook 的先后顺序？&lt;/li&gt;
&lt;li&gt;如何处理好多次 Hook 同一个 Block 后对任意一次 Hook 的撤销？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些问题也是开发者在使用时向我反馈过的问题，在这篇文章里，这些问题都将会解决。&lt;/p&gt;
&lt;p&gt;关于 &lt;a href=&quot;https://github.com/yulingtianxia/BlockHook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BlockHook&lt;/a&gt; 的原理，可以先阅读之前的文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://yulingtianxia.com/blog/2018/02/28/Hook-Objective-C-Block-with-Libffi/&quot;&gt;Hook Objective-C Block with Libffi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://yulingtianxia.com/blog/2019/04/27/BlockHook-with-Struct/&quot;&gt;BlockHook with Struct&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="Objective-C" scheme="http://yulingtianxia.com/tags/Objective-C/"/>
    
      <category term="BlockHook" scheme="http://yulingtianxia.com/tags/BlockHook/"/>
    
  </entry>
  
</feed>
