<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>cubegao</title>
  
  <subtitle>热爱技术，热爱生活</subtitle>
  <link href="https://cubegao.com/atom.xml" rel="self"/>
  
  <link href="https://cubegao.com/"/>
  <updated>2026-06-19T05:52:21.725Z</updated>
  <id>https://cubegao.com/</id>
  
  <author>
    <name>cubegao</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Flutter中的三棵树是如何完成绘制的</title>
    <link href="https://cubegao.com/p/2021-09-29-flutter-three-tree/"/>
    <id>https://cubegao.com/p/2021-09-29-flutter-three-tree/</id>
    <published>2021-09-29T13:44:00.000Z</published>
    <updated>2026-06-19T05:52:21.725Z</updated>
    
    <content type="html"><![CDATA[<h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><p><code>Widget</code>:存放渲染内容、它只是一个配置数据结构，创建是非常轻量的，在页面刷新的过程中随时会重建。</p><blockquote><p>其中<code>Widget</code>根据功能可以分为三类：</p><ul><li>组合类<code>StatelessWidget/StatefulWidget</code>：比如我们常见的<code>Container</code>就是一个组合类的控件，它主要负责组合封装多个其他负责绘制的原子组件。</li><li>代理类<code>inheritedwidget</code>:它的父类是<code>ProxyWidget</code>，顾名思义。简单来说，<code>InheritedWidget</code> 的作用是向它的子 <code>Widget</code> 有效地传播和分享数据，当 <code>InheritedWidget</code> 作为一个<code>Parent Widget</code>时，它下面的<code>Widget tree</code>的所有<code>Widget</code>都可以去和 <code>InheritedWidget</code> 发生数据传递和交互。当数据发生改变时，一部分控件需要 <code>rebuild</code>，另外的控件不需要 <code>rebuild</code> 的时候，可以使用 <code>InheritedWidget</code>。</li><li>绘制类<code>RenderObjectWidget</code>：<code>RenderObjectWidget</code>会负责创建出负责实际渲染的<code>RenderObject</code>对象，<code>RenderObject</code>负责实际的<code>layout()</code>和<code>paint()</code>。后期有空，我会手动写一个<code>RenderObject</code>。</li></ul></blockquote><p><code>Element</code>： 是分离 <code>WidgetTree</code> 和真正的渲染对象的中间层，可以看成flutter的骨架。 <code>Widget</code> 用来描述对应的<code>Element</code> 属性，同时持有<code>Widget</code>和<code>RenderObject</code>，存放上下文信息，通过它来遍历视图树，支撑<code>UI</code>结构。</p><blockquote><p>其中<code>Element</code>可以根据功能分成两大类：</p><ul><li>组合类<code>ComponentElement</code>:主要包括如下 <code>StatelessElement / StatefulElement / ProxyElement </code>子类；其中各<code>Element</code>都是与 <code>Widget </code>对应的。</li><li>绘制类<code>RenderObjectElement</code>：<code>RenderObjectElement</code>对应的是<code>RenderObjectWidget</code>。</li></ul></blockquote><p><code>RenderObject</code>用于应用界面的布局和绘制，负责真正的渲染，保存了元素的大小，布局等信息。</p><blockquote><p>当应用启动时 <code>Flutter</code> 会遍历并创建所有的 <code>Widget</code> 形成 <code>Widget</code>树，通过调用 <code>Widget</code> 上的 <code>createElement()</code> 方法创建每个 <code>Element</code> 对象，形成 <code>Element</code> 树。最后调用 <code>Element</code> 的 <code>createRenderObject()</code> 方法创建每个渲染对象，形成一个 <code>RenderObject</code> 树。</p></blockquote><h3 id="Widget-Element"><a href="#Widget-Element" class="headerlink" title="Widget &#x3D;&gt; Element"></a>Widget &#x3D;&gt; Element</h3><p>如何通过Widget更新Element：</p><ul><li><code>Element/ComponentElement</code>：<br>单个<code>Element</code>时候通过<code>updateChild</code>方法更新；</li></ul><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) &#123;</span><br><span class="line">  //如果newWidget是null，并且old child非null，直接deactivateChild</span><br><span class="line">  if (newWidget == null) &#123;</span><br><span class="line">    if (child != null)</span><br><span class="line">      deactivateChild(child);</span><br><span class="line">    return null;</span><br><span class="line">  &#125;</span><br><span class="line">  final Element newChild;</span><br><span class="line">  if (child != null) &#123;</span><br><span class="line">    //新旧widget相同的情况</span><br><span class="line">    if (child.widget == newWidget) &#123;</span><br><span class="line">      if (child.slot != newSlot)</span><br><span class="line">        updateSlotForChild(child, newSlot);</span><br><span class="line">      newChild = child;</span><br><span class="line">    &#125; else if (Widget.canUpdate(child.widget, newWidget)) &#123;</span><br><span class="line">      //可以update的情况，也就是runtimetype和key相同</span><br><span class="line">      if (child.slot != newSlot)</span><br><span class="line">        updateSlotForChild(child, newSlot);</span><br><span class="line">      child.update(newWidget);</span><br><span class="line">      newChild = child;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      //其他情况，移除旧的，重新inflateWidget新的widget，会创建element</span><br><span class="line">      deactivateChild(child);</span><br><span class="line">      newChild = inflateWidget(newWidget, newSlot);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    //old child是null，这里直接inflate新的widget，会创建element</span><br><span class="line">    newChild = inflateWidget(newWidget, newSlot);</span><br><span class="line">  &#125;</span><br><span class="line">  return newChild;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>大致归纳一下，如下表：</p><table><thead><tr><th>标题</th><th>newWidget &#x3D;&#x3D; null</th><th>newWidget !&#x3D; null</th></tr></thead><tbody><tr><td>child &#x3D;&#x3D; null</td><td>Returns null.</td><td>Returns new [Element].</td></tr><tr><td>child !&#x3D; null</td><td>Old child is removed, returns null.</td><td>Old child updated if possible, returns child or new [Element].</td></tr></tbody></table><ul><li><code>RenderObjectElement</code>:<br>多个<code>Element</code>时候通过<code>updateChildren</code>方法更新；</li></ul><figure class="highlight plaintext"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line">List&lt;Element&gt; updateChildren(List&lt;Element&gt; oldChildren, List&lt;Widget&gt; newWidgets, &#123; Set&lt;Element&gt;? forgottenChildren &#125;) &#123;</span><br><span class="line">//如果forgottenChildren包含当前child，表示它已经在oldChild中，返回null</span><br><span class="line">    Element? replaceWithNullIfForgotten(Element child) &#123;</span><br><span class="line">      return forgottenChildren != null &amp;&amp; forgottenChildren.contains(child) ? null : child;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    int newChildrenTop = 0;</span><br><span class="line">    int oldChildrenTop = 0;</span><br><span class="line">    int newChildrenBottom = newWidgets.length - 1;</span><br><span class="line">    int oldChildrenBottom = oldChildren.length - 1;</span><br><span class="line"></span><br><span class="line">    final List&lt;Element&gt; newChildren = oldChildren.length == newWidgets.length ?</span><br><span class="line">        oldChildren : List&lt;Element&gt;.filled(newWidgets.length, _NullElement.instance, growable: false);</span><br><span class="line"></span><br><span class="line">    Element? previousChild;</span><br><span class="line"></span><br><span class="line">    // 从列表的顶部向下更新Element</span><br><span class="line">    while ((oldChildrenTop &lt;= oldChildrenBottom) &amp;&amp; (newChildrenTop &lt;= newChildrenBottom)) &#123;</span><br><span class="line">      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);</span><br><span class="line">      final Widget newWidget = newWidgets[newChildrenTop];</span><br><span class="line">      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))</span><br><span class="line">        break;</span><br><span class="line">      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot&lt;Element?&gt;(newChildrenTop, previousChild))!;</span><br><span class="line">      newChildren[newChildrenTop] = newChild;</span><br><span class="line">      previousChild = newChild;</span><br><span class="line">      newChildrenTop += 1;</span><br><span class="line">      oldChildrenTop += 1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 从列表底部开始扫描Element，但不更新</span><br><span class="line">    while ((oldChildrenTop &lt;= oldChildrenBottom) &amp;&amp; (newChildrenTop &lt;= newChildrenBottom)) &#123;</span><br><span class="line">      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);</span><br><span class="line">      final Widget newWidget = newWidgets[newChildrenBottom];</span><br><span class="line">      assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active);</span><br><span class="line">      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))</span><br><span class="line">        break;</span><br><span class="line">      oldChildrenBottom -= 1;</span><br><span class="line">      newChildrenBottom -= 1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">// 扫描旧的子Element列表里面中间的子Element，保存Widget有Key的Element到oldKeyChildren，其他的失效</span><br><span class="line">    final bool haveOldChildren = oldChildrenTop &lt;= oldChildrenBottom;</span><br><span class="line">    Map&lt;Key, Element&gt;? oldKeyedChildren;</span><br><span class="line">    if (haveOldChildren) &#123;</span><br><span class="line">      oldKeyedChildren = &lt;Key, Element&gt;&#123;&#125;;</span><br><span class="line">      while (oldChildrenTop &lt;= oldChildrenBottom) &#123;</span><br><span class="line">        final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);</span><br><span class="line">        if (oldChild != null) &#123;</span><br><span class="line">          if (oldChild.widget.key != null)</span><br><span class="line">            oldKeyedChildren[oldChild.widget.key!] = oldChild;</span><br><span class="line">          else</span><br><span class="line">            deactivateChild(oldChild);</span><br><span class="line">        &#125;</span><br><span class="line">        oldChildrenTop += 1;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">// 根据Widget的Key更新oldKeyChildren中的Element。</span><br><span class="line">    while (newChildrenTop &lt;= newChildrenBottom) &#123;</span><br><span class="line">      Element? oldChild;</span><br><span class="line">      final Widget newWidget = newWidgets[newChildrenTop];</span><br><span class="line">      if (haveOldChildren) &#123;</span><br><span class="line">        final Key? key = newWidget.key;</span><br><span class="line">        if (key != null) &#123;</span><br><span class="line">          oldChild = oldKeyedChildren![key];</span><br><span class="line">          if (oldChild != null) &#123;</span><br><span class="line">            if (Widget.canUpdate(oldChild.widget, newWidget)) &#123;</span><br><span class="line">              // we found a match!</span><br><span class="line">              // remove it from oldKeyedChildren so we don&#x27;t unsync it later</span><br><span class="line">              oldKeyedChildren.remove(key);</span><br><span class="line">            &#125; else &#123;</span><br><span class="line">              // Not a match, let&#x27;s pretend we didn&#x27;t see it for now.</span><br><span class="line">              oldChild = null;</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot&lt;Element?&gt;(newChildrenTop, previousChild))!;</span><br><span class="line">      newChildren[newChildrenTop] = newChild;</span><br><span class="line">      previousChild = newChild;</span><br><span class="line">      newChildrenTop += 1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 整个列表扫描结束</span><br><span class="line">    newChildrenBottom = newWidgets.length - 1;</span><br><span class="line">    oldChildrenBottom = oldChildren.length - 1;</span><br><span class="line"></span><br><span class="line">    // 更新底部的Element</span><br><span class="line">    while ((oldChildrenTop &lt;= oldChildrenBottom) &amp;&amp; (newChildrenTop &lt;= newChildrenBottom)) &#123;</span><br><span class="line">      final Element oldChild = oldChildren[oldChildrenTop];</span><br><span class="line">      final Widget newWidget = newWidgets[newChildrenTop];</span><br><span class="line">      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot&lt;Element?&gt;(newChildrenTop, previousChild))!;</span><br><span class="line">      newChildren[newChildrenTop] = newChild;</span><br><span class="line">      previousChild = newChild;</span><br><span class="line">      newChildrenTop += 1;</span><br><span class="line">      oldChildrenTop += 1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 清除旧子Element列表中其他所有剩余Element</span><br><span class="line">    if (haveOldChildren &amp;&amp; oldKeyedChildren!.isNotEmpty) &#123;</span><br><span class="line">      for (final Element oldChild in oldKeyedChildren.values) &#123;</span><br><span class="line">        if (forgottenChildren == null || !forgottenChildren.contains(oldChild))</span><br><span class="line">          deactivateChild(oldChild);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return newChildren;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>该函数的主要职责如下：</p><ul><li>复用能复用的子节点，并调用updateChild对子节点进行更新。</li><li>对不能更新的子节点，调用deactivateChild对该子节点进行失效。</li></ul><p>我们注意到<code>updateChild</code>如果<code>old child</code>是空或者无法<code>update</code>就需要<code>inflateWidget</code>;<br>我们从源码看看创建方法inflateWidget():</p><figure class="highlight plaintext"><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">Element inflateWidget(Widget newWidget, Object? newSlot) &#123;</span><br><span class="line">  final Key? key = newWidget.key;</span><br><span class="line">  if (key is GlobalKey) &#123;</span><br><span class="line">    final Element? newChild = _retakeInactiveElement(key, newWidget);</span><br><span class="line">    if (newChild != null) &#123;</span><br><span class="line">      newChild._activateWithParent(this, newSlot);</span><br><span class="line">      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);</span><br><span class="line">      return updatedChild!;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  //创建子Element</span><br><span class="line">  final Element newChild = newWidget.createElement();</span><br><span class="line">  //将child挂载到当前element</span><br><span class="line">  newChild.mount(this, newSlot);</span><br><span class="line">  //返回child element</span><br><span class="line">  return newChild;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>逻辑非常简单，创建一个<code>element</code>，然后<code>mount</code>到当前<code>element</code>。</p><h3 id="RenderObject"><a href="#RenderObject" class="headerlink" title="RenderObject"></a>RenderObject</h3><p><code>RenderObjectWidget</code>会调用<code>createElement</code>，创建出<code>RenderObjectElement</code>，<br>然后<code>RenderObjectWidget</code>会调用<code>createRenderObject</code>，创建出<code>RenderObject</code>，<br>然后通过<code>[RenderObjectElement.mount]</code>方法，将<code>RenderObject</code>挂载为<code>RenderObjectElement._renderObject</code>属性。从此可以看出，<code>Element</code>是连接<code>Widget</code>和<code>RenderObject</code>的桥梁。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;定义&quot;&gt;&lt;a href=&quot;#定义&quot; class=&quot;headerlink&quot; title=&quot;定义&quot;&gt;&lt;/a&gt;定义&lt;/h3&gt;&lt;p&gt;&lt;code&gt;Widget&lt;/code&gt;:存放渲染内容、它只是一个配置数据结构，创建是非常轻量的，在页面刷新的过程中随时会重建。&lt;/p&gt;
&lt;b</summary>
      
    
    
    
    <category term="Flutter开发" scheme="https://cubegao.com/categories/Flutter%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Flutter" scheme="https://cubegao.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>如何在ListView中优雅的嵌套ListView</title>
    <link href="https://cubegao.com/p/2021-07-06-listview-nested-listview/"/>
    <id>https://cubegao.com/p/2021-07-06-listview-nested-listview/</id>
    <published>2021-07-06T14:20:00.000Z</published>
    <updated>2026-06-19T05:52:21.724Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>日常的开发工作中，经常会遇到列表中嵌套<code>ListView</code>的需求。具体情况也分为两种。</p><h3 id="1-竖向ListView嵌套横向ListView"><a href="#1-竖向ListView嵌套横向ListView" class="headerlink" title="1.竖向ListView嵌套横向ListView"></a>1.竖向ListView嵌套横向ListView</h3><p>常见的需求：竖向<code>ListView</code>行数不限制，横向<code>ListView</code>的列数不限制。具体情况如下图。</p><img src="/images/2022/08/2198626706.jpg" width="50%" height="50%"><p>代码如下：</p><figure class="highlight plaintext"><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">ListView(</span><br><span class="line">         children: [</span><br><span class="line">           Text(&#x27;ListView&#x27;),</span><br><span class="line">           Stack(</span><br><span class="line">             children: [</span><br><span class="line">               IgnorePointer(</span><br><span class="line">                 child: Opacity(</span><br><span class="line">                   opacity: 0.0,</span><br><span class="line">                   child: Item(),</span><br><span class="line">                 ),</span><br><span class="line">               ),</span><br><span class="line">               SizedBox(width: double.infinity),</span><br><span class="line">               Positioned.fill(</span><br><span class="line">                 child: ListView.builder(</span><br><span class="line">                   scrollDirection: Axis.horizontal,</span><br><span class="line">                   itemBuilder: (_, index) =&gt; Item(),</span><br><span class="line">                 ),</span><br><span class="line">               ),</span><br><span class="line">             ],</span><br><span class="line">           ),</span><br><span class="line">         ],</span><br><span class="line">       )</span><br></pre></td></tr></table></figure><p><code>Stack</code>存在无<code>Positioned</code>包裹的子组件，决定<code>Stack</code>尺寸的是无位置的组件中的最大的尺寸，<code>Positioned</code>不会影响<code>Stack</code>的尺寸。（当然如果子类全部是<code>Positioned</code>包裹的子组件，<code>Stack</code>会将自身尺寸设置为父级布局约束所允许的最大尺寸，为对齐子组件创造条件。）。<br>此处用<code>Positioned.fill</code>,让<code>ListView</code>尽量占满整个<code>Stack</code>。<br>接下来设置宽度，利用<code>SizedBox</code>的向下传递约束，向上传递尺寸的特性。用<code>SizedBox(width: double.infinity)</code>把宽度撑开到整个屏幕的宽度。<br>接下来算出<code>Item</code>的高度，用<code>Item</code>的高度撑开<code>Stack</code>的高度。<br>此处用<code>Item</code>的初衷只是为了定制高度，不能让<code>Item</code>显示在屏幕上，所以用<code>Opacity</code>隐藏<code>Item</code>，为了忽略掉事件响应，最后用上<code>IgnorePointer</code>。</p><h3 id="2-竖向ListView嵌套竖向ListView"><a href="#2-竖向ListView嵌套竖向ListView" class="headerlink" title="2.竖向ListView嵌套竖向ListView"></a>2.竖向ListView嵌套竖向ListView</h3><p>这类需求是由于父列表和子列表的行数都不确定。处理也比较简单。</p><figure class="highlight plaintext"><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">ListView(</span><br><span class="line">          children: [</span><br><span class="line">            Text(&#x27;ListView&#x27;),</span><br><span class="line">            ListView.builder(</span><br><span class="line">                    shrinkWrap: true,                           </span><br><span class="line">    physics: NeverScrollableScrollPhysics(),</span><br><span class="line">                    itemBuilder: (_, index) =&gt; Item(),</span><br><span class="line">                  ),</span><br><span class="line">          ],</span><br><span class="line">        )</span><br></pre></td></tr></table></figure><p>通过设置<code>shrinkWrap</code>来固定<code>ListView</code>的高度，禁用滑动事件，让外面的<code>ListView</code>响应滑动事件就行了。<br>需要注意的是使用<code>shrinkWrap</code>，会一次性加载出所有的<code>Item</code>。如果数量太多，会出现内存问题。这时候可以通过父<code>ListView</code>和子<code>ListView</code>共用一个<code>ScrollController</code>来解决。代码如下：</p><figure class="highlight plaintext"><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">ScrollController _scrollController;</span><br><span class="line">  ListView(</span><br><span class="line">    controller: _scrollController,</span><br><span class="line">      children: [</span><br><span class="line">          ListView(</span><br><span class="line">                controller:_scrollController,</span><br><span class="line">          ),</span><br><span class="line">    ],</span><br><span class="line">   )</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;日常的开发工作中，经常会遇到列表中嵌套&lt;code&gt;ListView&lt;/code&gt;的需求。具体情况也分为两种。&lt;/p&gt;
&lt;h3 id=&quot;1-竖</summary>
      
    
    
    
    <category term="Flutter开发" scheme="https://cubegao.com/categories/Flutter%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Flutter" scheme="https://cubegao.com/tags/Flutter/"/>
    
    <category term="ListView" scheme="https://cubegao.com/tags/ListView/"/>
    
  </entry>
  
  <entry>
    <title>如何解决Flutter中的依赖冲突</title>
    <link href="https://cubegao.com/p/2021-04-22-flutter-dependencies/"/>
    <id>https://cubegao.com/p/2021-04-22-flutter-dependencies/</id>
    <published>2021-04-22T01:08:00.000Z</published>
    <updated>2026-06-19T05:52:21.723Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>今天更新第三方依赖库，执行<code>flutter pub get</code>后，报错了</p><blockquote><p>Because project depends on path_provider 1.6.8 which doesn’t match any versions, version solving failed. pub get failed (1; Because project depends on path_provider 1.6.8 which doesn’t match any versions, version solving failed.) Process finished with exit code 1</p></blockquote><h3 id="分析为什么会出现冲突"><a href="#分析为什么会出现冲突" class="headerlink" title="分析为什么会出现冲突"></a>分析为什么会出现冲突</h3><p>查看文件<code>pubspec.yaml</code>是这个样子的</p><figure class="highlight plaintext"><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">dependencies:</span><br><span class="line">  path_provider: ^1.6.8</span><br></pre></td></tr></table></figure><p>我们在开发<code>Flutter</code>项目中，经常会依赖第三方库。如果两个第三方库，同时依赖了某个库。比如库<code>A</code>要求<code>path_provider</code>最低版本为<code>1.5.0</code>，库<code>B</code>要求<code>path_provider</code>最低版本为<code>1.6.8</code>，当<code>path_provider</code>的版本更新为<code>1.6.8</code>时，虽然符合库<code>B</code>的要求，但是不符合库<code>A</code>的要求，这个时候就冲突报错了。</p><h3 id="如何解决"><a href="#如何解决" class="headerlink" title="如何解决"></a>如何解决</h3><p>修改版本为any。</p><figure class="highlight plaintext"><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">dependencies:</span><br><span class="line">  path_provider: any</span><br></pre></td></tr></table></figure><p>运行<code>flutter pub upgrade</code>。<br>设置<code>any</code>后，<code>pub</code>的版本分析器会自动分析当前的依赖版本，寻找合适的能够避免冲突的依赖版本并下载。</p><h3 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h3><p>但是我们不能每次<code>flutter clean</code>后再去执行，都让<code>pub</code>的版本分析器去分析，这会有一定的耗时。而且这相当于写了一个不确定的版本号在项目中。如果交付人员打包的时候，恰好碰到了第三方库版本更新，有可能会打出会<code>crash</code>的包，所以我们需要把<code>any</code>替换为实际的版本号。</p><p>使用<code>pubspec.lock</code>找到正确的版本</p><figure class="highlight plaintext"><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">path_provider:</span><br><span class="line">    dependency: &quot;direct main&quot;</span><br><span class="line">    description:</span><br><span class="line">      name: path_provider</span><br><span class="line">      url: &quot;https://pub.flutter-io.cn&quot;</span><br><span class="line">    source: hosted</span><br><span class="line">    version: &quot;1.5.0&quot;</span><br></pre></td></tr></table></figure><p>可以看到版本分析器为我们找到的无冲突的依赖版本号为<code>1.5.0</code>。</p><p>这时我们可以回到文件<code>pubspec.yaml</code>中，修改为</p><figure class="highlight plaintext"><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">dependencies:</span><br><span class="line">  path_provider: 1.5.0</span><br></pre></td></tr></table></figure><p>大功告成~</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;今天更新第三方依赖库，执行&lt;code&gt;flutter pub get&lt;/code&gt;后，报错了&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bec</summary>
      
    
    
    
    <category term="Flutter开发" scheme="https://cubegao.com/categories/Flutter%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Flutter" scheme="https://cubegao.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>Flutter中的BuildContext到底是什么</title>
    <link href="https://cubegao.com/p/2021-03-11-buildcontext/"/>
    <id>https://cubegao.com/p/2021-03-11-buildcontext/</id>
    <published>2021-03-11T08:55:00.000Z</published>
    <updated>2026-06-19T05:52:21.723Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>首先我们看看官方的定义：</p><blockquote><p>[BuildContext] objects are actually [Element] objects. The [BuildContext]<br> interface is used to discourage direct manipulation of [Element] objects.<br> [BuildContext] 对象实际上是 [Element] 对象。 [BuildContext] 接口用于阻止直接操作 [Element] 对象。</p></blockquote><p>根据官方这段注释，可以了解到<code>BuildContext</code> 实际上就是 <code>Element</code> 对象，产生的意义主要是为了防止开发者直接操作 <code>Element</code> 对象。</p><h3 id="如何使用BuildContext"><a href="#如何使用BuildContext" class="headerlink" title="如何使用BuildContext"></a>如何使用BuildContext</h3><p>写一段大家很熟悉的代码</p><figure class="highlight plaintext"><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">class _MyHomePageState extends State&lt;MyHomePage&gt; &#123;</span><br><span class="line">  int _counter = 0;</span><br><span class="line"></span><br><span class="line">  void _incrementCounter() &#123;</span><br><span class="line">    setState(() &#123;</span><br><span class="line">      _counter++;</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><blockquote><p>这里有同学好奇，<code>_counter++;</code>不写在<code>setState</code>的回调方法里可以生效吗？答案是可以的。但是我更推荐把和状态更新相关的操作，放在回调里面。这样更加的工程化管理。因为一个项目经过长时期的更新，你可能无法分清，这个方法里面的<code>setState</code>,是不是实际上起作用了，会造成代码冗余。</p></blockquote><p>上面的代码也可以写成这样</p><figure class="highlight plaintext"><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">class _MyHomePageState extends State&lt;MyHomePage&gt; &#123;</span><br><span class="line">  int _counter = 0;</span><br><span class="line"></span><br><span class="line">  void _incrementCounter() &#123;</span><br><span class="line">      _counter++;</span><br><span class="line">  (context as Element).markNeedsBuild();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为什么会有同样的作用呢？那我们看看<code>setState</code>的源码吧</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">void setState(VoidCallback fn) &#123;</span><br><span class="line">  assert(fn != null);</span><br><span class="line">  assert(() &#123;</span><br><span class="line">    if (_debugLifecycleState == _StateLifecycle.defunct) &#123;</span><br><span class="line">     ///......</span><br><span class="line">    &#125;</span><br><span class="line">    if (_debugLifecycleState == _StateLifecycle.created &amp;&amp; !mounted) &#123;</span><br><span class="line">     ///......</span><br><span class="line">    &#125;</span><br><span class="line">    return true;</span><br><span class="line">  &#125;());</span><br><span class="line">  final dynamic result = fn() as dynamic;</span><br><span class="line">  assert(() &#123;</span><br><span class="line">    if (result is Future) &#123;</span><br><span class="line">      ///......</span><br><span class="line">      ]);</span><br><span class="line">    &#125;</span><br><span class="line">    return true;</span><br><span class="line">  &#125;());</span><br><span class="line">  _element!.markNeedsBuild();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那么我们就清楚了，<code>BuildContext</code>实际上就是<code>Widget</code>树中特定位置所对应的实例<code>Element</code>。</p><h3 id="使用BuildContext时，应该注意作用域"><a href="#使用BuildContext时，应该注意作用域" class="headerlink" title="使用BuildContext时，应该注意作用域"></a>使用BuildContext时，应该注意作用域</h3><p>举个例子</p><figure class="highlight plaintext"><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">import &#x27;package:flutter/material.dart&#x27;;</span><br><span class="line"></span><br><span class="line">void main() =&gt; runApp(MyApp());</span><br><span class="line"></span><br><span class="line">class MyApp extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context)&#123;</span><br><span class="line">    return MaterialApp(</span><br><span class="line">      title: &#x27;Test Flutter&#x27;,</span><br><span class="line">      home: Scaffold(</span><br><span class="line">        body: Center(</span><br><span class="line">          child: FlatButton(</span><br><span class="line">              onPressed: () &#123;</span><br><span class="line">                Navigator.of(context).push(</span><br><span class="line">                    MaterialPageRoute(builder: (context) =&gt; NewWidget()));</span><br><span class="line">              &#125;,</span><br><span class="line">              child: Text(&#x27;跳转到新页面&#x27;)),</span><br><span class="line">        ),</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">class NewWidget extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build (BuildContext context)&#123;</span><br><span class="line">    return Scaffold(</span><br><span class="line">      appBar: AppBar(</span><br><span class="line">        title: Text(&quot;this is title&quot;),</span><br><span class="line">      ),</span><br><span class="line">      body: Center(</span><br><span class="line">        child: Text(&quot;this is body&quot;),</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行一下，发现会报错：提示当前的<code>context</code>找不到<code>Navigator</code>。<br>我们知道<code>Navigator</code>是<code>MaterialApp</code>为我们提供的，但是当前的调用的<code>context</code>，实际上是<code>MyApp</code>拥有的实例，并不是<code>MaterialApp</code>对应的<code>context</code>。<br>那我们可以改造一下：</p><figure class="highlight plaintext"><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">class MyApp extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context)&#123;</span><br><span class="line">    return MaterialApp(</span><br><span class="line">      title: &#x27;Test Flutter&#x27;,</span><br><span class="line">      home: Scaffold(</span><br><span class="line">        body: Center(</span><br><span class="line">          child: Builder(</span><br><span class="line">          builder: (context) &#123;</span><br><span class="line">            return FlatButton(</span><br><span class="line">  onPressed: () &#123;</span><br><span class="line">Navigator.of(context).push(</span><br><span class="line">MaterialPageRoute(builder: (context) =&gt; NewWidget()));</span><br><span class="line">  &#125;,</span><br><span class="line">  child: Text(&#x27;跳转到新页面&#x27;)),</span><br><span class="line">),</span><br><span class="line">  );</span><br><span class="line">          &#125;,</span><br><span class="line">        ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;首先我们看看官方的定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[BuildContext] objects are actually [</summary>
      
    
    
    
    <category term="Flutter开发" scheme="https://cubegao.com/categories/Flutter%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Flutter" scheme="https://cubegao.com/tags/Flutter/"/>
    
    <category term="BuildContext" scheme="https://cubegao.com/tags/BuildContext/"/>
    
  </entry>
  
  <entry>
    <title>给AsyncDisplayKit（Texture）替换网络图片下载缓存框架</title>
    <link href="https://cubegao.com/p/2020-08-03-asdk-sdwebimage-kingfisher/"/>
    <id>https://cubegao.com/p/2020-08-03-asdk-sdwebimage-kingfisher/</id>
    <published>2020-08-03T04:10:00.000Z</published>
    <updated>2026-06-19T05:52:21.722Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><blockquote><p>为了方便管理图片缓存，统一缓存文件夹和缓存方法，将AsyncDisplayKit自带的PINRemoteImage插件，<br>替换为项目在用的Kingfisher、SDWebImage。</p></blockquote><figure class="highlight javascript"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Kingfisher</span></span><br><span class="line"></span><br><span class="line">extension <span class="title class_">ASNetworkImageNode</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> func <span class="title function_">imageNode</span>() -&gt; <span class="title class_">ASNetworkImageNode</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">ASNetworkImageNode</span>(<span class="attr">cache</span>: <span class="title class_">ASImageManager</span>.<span class="property">shared</span>, <span class="attr">downloader</span>: <span class="title class_">ASImageManager</span>.<span class="property">shared</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ASImageManager</span>: <span class="title class_">NSObject</span>, <span class="title class_">ASImageDownloaderProtocol</span>, <span class="title class_">ASImageCacheProtocol</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">let</span> shared = <span class="title class_">ASImageManager</span>()</span><br><span class="line">    private override <span class="title function_">init</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    func <span class="title function_">downloadImage</span>(<span class="keyword">with</span> <span class="attr">url</span>: <span class="variable constant_">URL</span>, <span class="attr">callbackQueue</span>: <span class="title class_">DispatchQueue</span>, <span class="attr">downloadProgress</span>: <span class="title class_">ASImageDownloaderProgress</span>?, <span class="attr">completion</span>: @escaping <span class="title class_">ASImageDownloaderCompletion</span>) -&gt; <span class="title class_">Any</span>? &#123;</span><br><span class="line"></span><br><span class="line">        <span class="title class_">ImageDownloader</span>.<span class="property">default</span>.<span class="property">downloadTimeout</span> = <span class="number">30.0</span></span><br><span class="line">        <span class="keyword">var</span> <span class="attr">operation</span>: <span class="title class_">DownloadTask</span>?</span><br><span class="line">        operation = <span class="title class_">ImageDownloader</span>.<span class="property">default</span>.<span class="title function_">downloadImage</span>(<span class="attr">with</span>: url, <span class="attr">options</span>: nil, <span class="attr">progressBlock</span>: &#123; (received, expected) <span class="keyword">in</span></span><br><span class="line">            <span class="keyword">if</span> downloadProgress != nil &#123;</span><br><span class="line">                callbackQueue.<span class="title function_">async</span>(<span class="attr">execute</span>: &#123;</span><br><span class="line">                    <span class="keyword">let</span> progress = expected == <span class="number">0</span> ? <span class="number">0</span> : received / expected</span><br><span class="line">                    downloadProgress?(<span class="title class_">CGFloat</span>(progress))</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;) &#123; (result) <span class="keyword">in</span></span><br><span class="line">            <span class="keyword">switch</span> result &#123;</span><br><span class="line">            <span class="keyword">case</span> .<span class="title function_">success</span>(<span class="keyword">let</span> value):</span><br><span class="line">                callbackQueue.<span class="title function_">async</span>(<span class="attr">execute</span>: &#123; <span class="title function_">completion</span>(value.<span class="property">image</span>, nil, nil, nil) &#125;)</span><br><span class="line">                <span class="title class_">ImageCache</span>.<span class="property">default</span>.<span class="title function_">store</span>(value.<span class="property">image</span>, <span class="attr">original</span>: value.<span class="property">originalData</span>, <span class="attr">forKey</span>: url.<span class="property">cacheKey</span>, <span class="attr">toDisk</span>: <span class="literal">true</span>)</span><br><span class="line">            <span class="keyword">case</span> .<span class="title function_">failure</span>(<span class="keyword">let</span> error):</span><br><span class="line">                callbackQueue.<span class="title function_">async</span>(<span class="attr">execute</span>: &#123; <span class="title function_">completion</span>(nil, error, operation, nil) &#125;)</span><br><span class="line">                <span class="title function_">dPrint</span>(<span class="string">&quot;Job failed: \(error.localizedDescription)&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> operation</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    func <span class="title function_">cancelImageDownload</span>(<span class="params">forIdentifier downloadIdentifier: Any</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> task = downloadIdentifier <span class="keyword">as</span>? <span class="title class_">DownloadTask</span> &#123;</span><br><span class="line">            task.<span class="title function_">cancel</span>()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    func <span class="title function_">cachedImage</span>(<span class="params"><span class="keyword">with</span> url: URL, callbackQueue: DispatchQueue, completion: @escaping ASImageCacherCompletion</span>) &#123;</span><br><span class="line">        <span class="title class_">ImageCache</span>.<span class="property">default</span>.<span class="title function_">retrieveImage</span>(<span class="params">forKey: url.cacheKey</span>) &#123; (result) <span class="keyword">in</span></span><br><span class="line">            <span class="keyword">switch</span> result &#123;</span><br><span class="line">            <span class="keyword">case</span> .<span class="title function_">success</span>(<span class="keyword">let</span> value):</span><br><span class="line">                callbackQueue.<span class="property">async</span> &#123; <span class="title function_">completion</span>(value.<span class="property">image</span>) &#125;</span><br><span class="line">            <span class="keyword">case</span> .<span class="title function_">failure</span>(<span class="keyword">let</span> error):</span><br><span class="line">                <span class="title function_">dPrint</span>(<span class="string">&quot;Job failed: \(error.localizedDescription)&quot;</span>)</span><br><span class="line">            &#125;</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><figure class="highlight swift"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> SDWebImage</span><br><span class="line"></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ASNetworkImageNode</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">func</span> <span class="title function_">imageNode</span>() -&gt; <span class="type">ASNetworkImageNode</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="type">ASNetworkImageNode</span>(cache: <span class="type">ASNetImageManage</span>.shared, downloader: <span class="type">ASNetImageManage</span>.shared)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ASNetImageManage</span>: <span class="title class_ inherited__">NSObject</span>, <span class="title class_ inherited__">ASImageDownloaderProtocol</span>, <span class="title class_ inherited__">ASImageCacheProtocol</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">let</span> shared <span class="operator">=</span> <span class="type">ASNetImageManage</span>()</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">func</span> <span class="title function_">downloadImage</span>(<span class="params">with</span> <span class="params">URL</span>: <span class="type">URL</span>, <span class="params">callbackQueue</span>: <span class="type">DispatchQueue</span>, <span class="params">downloadProgress</span>: <span class="type">ASImageDownloaderProgress</span>?, <span class="params">completion</span>: <span class="keyword">@escaping</span> <span class="type">ASImageDownloaderCompletion</span>) -&gt; <span class="keyword">Any</span><span class="operator">?</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">weak</span> <span class="keyword">var</span> weakOperation: <span class="type">SDWebImageOperation</span>?</span><br><span class="line">        <span class="keyword">let</span> operation <span class="operator">=</span> <span class="type">SDWebImageManager</span>.shared.loadImage(with: <span class="type">URL</span>, options: .retryFailed, progress: &#123; (received, expected, url) <span class="keyword">in</span></span><br><span class="line">            <span class="keyword">if</span> downloadProgress <span class="operator">!=</span> <span class="literal">nil</span> &#123;</span><br><span class="line">                callbackQueue.async(execute: &#123;</span><br><span class="line">                    <span class="keyword">let</span> progress <span class="operator">=</span> expected <span class="operator">==</span> <span class="number">0</span> <span class="operator">?</span> <span class="number">0</span> : received <span class="operator">/</span> expected</span><br><span class="line">                    downloadProgress<span class="operator">?</span>(<span class="type">CGFloat</span>(progress))</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;) &#123; (cachedImage, data, error, type, unknow, url) <span class="keyword">in</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">let</span> image <span class="operator">=</span> cachedImage &#123;</span><br><span class="line">                callbackQueue.async(execute: &#123; completion(image, <span class="literal">nil</span>, <span class="literal">nil</span>, <span class="literal">nil</span>) &#125;)</span><br><span class="line">                <span class="keyword">return</span></span><br><span class="line">            &#125;</span><br><span class="line">            callbackQueue.async(execute: &#123; completion(<span class="literal">nil</span>, error, <span class="literal">nil</span>, <span class="literal">nil</span>) &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">        weakOperation <span class="operator">=</span> operation</span><br><span class="line">        <span class="keyword">return</span> weakOperation</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">func</span> <span class="title function_">cancelImageDownload</span>(<span class="params">forIdentifier</span> <span class="params">downloadIdentifier</span>: <span class="keyword">Any</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> downloadIdentifier <span class="operator">=</span> downloadIdentifier <span class="keyword">as?</span> <span class="type">SDWebImageOperation</span> &#123;</span><br><span class="line">            downloadIdentifier.cancel()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">func</span> <span class="title function_">cachedImage</span>(<span class="params">with</span> <span class="params">URL</span>: <span class="type">URL</span>, <span class="params">callbackQueue</span>: <span class="type">DispatchQueue</span>, <span class="params">completion</span>: <span class="keyword">@escaping</span> <span class="type">ASImageCacherCompletion</span>) &#123;</span><br><span class="line">                </span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> key <span class="operator">=</span> <span class="type">SDWebImageManager</span>.shared.cacheKey(for: <span class="type">URL</span>) &#123;</span><br><span class="line">            <span class="type">SDWebImageManager</span>.shared.imageCache.queryImage(forKey: key, options: .allowInvalidSSLCertificates, context: <span class="literal">nil</span>) &#123; (cachedImage, data, type) <span class="keyword">in</span></span><br><span class="line">                <span class="keyword">if</span> <span class="keyword">let</span> image <span class="operator">=</span> cachedImage &#123;</span><br><span class="line">                    callbackQueue.async(execute: &#123; completion(image) &#125;)</span><br><span class="line">                    <span class="keyword">return</span></span><br><span class="line">                &#125;</span><br><span class="line">                callbackQueue.async(execute: &#123; completion(<span class="literal">nil</span>) &#125;)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            callbackQueue.async &#123;</span><br><span class="line">                completion(<span class="literal">nil</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;为了方便管理图片缓存，统一缓存文件夹和缓存方法，将AsyncDisplayKit自带的PINRemoteImage</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
  </entry>
  
  <entry>
    <title>Flutter 嵌套过深的解决方案</title>
    <link href="https://cubegao.com/p/2020-01-03-flutter-nested/"/>
    <id>https://cubegao.com/p/2020-01-03-flutter-nested/</id>
    <published>2020-01-03T10:33:00.000Z</published>
    <updated>2026-06-19T05:52:21.721Z</updated>
    
    <content type="html"><![CDATA[<p>写代码容易，读代码难。功能是都实现了，但是对维护人员来说，简直就是灾难。</p><p>##背景<br><code>Flutter</code> 注重组合而非继承，要想搭建出 <code>UI</code>，需要组合不同功能的 <code>Widget</code>，如布局 <code>Widget</code>、响应 <code>Widget</code>、控件 <code>Widget</code> 等才能搭建出一个功能完善的<code>UI</code>界面，这便导致了嵌套地狱: 在顶级<code>Widget</code>的构造器中内嵌众多<code> Widget</code>。</p><p>像下面这个例子，其实都不是最多层的，只要你卖力，可以超乎想象的<code>)))))))))))))))))).....</code></p><figure class="highlight plaintext"><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">class FrostedGlass extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return new Scaffold(</span><br><span class="line">      body: new Stack(</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">          new ConstrainedBox(</span><br><span class="line">              constraints: const BoxConstraints.expand(),</span><br><span class="line">              child: new FlutterLogo()),</span><br><span class="line">          new Center(</span><br><span class="line">            child: new ClipRect(</span><br><span class="line">              child: new BackdropFilter(</span><br><span class="line">                filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),</span><br><span class="line">                child: Opacity(</span><br><span class="line">                  opacity: 0.5,</span><br><span class="line">                  child: new Container(</span><br><span class="line">                    width: 200.0,</span><br><span class="line">                    height: 200.0,</span><br><span class="line">                    decoration: new BoxDecoration(</span><br><span class="line">                      color: Colors.grey.shade200,</span><br><span class="line">                    ),</span><br><span class="line">                    child: new Center(</span><br><span class="line">                      child: new Text(&#x27;Frosted&#x27;,</span><br><span class="line">                          style: Theme.of(context).textTheme.headline2),</span><br><span class="line">                    ),</span><br><span class="line">                  ),</span><br><span class="line">                ),</span><br><span class="line">              ),</span><br><span class="line">            ),</span><br><span class="line">          ),</span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>IDE</code>上面看到是这样的。<br><img src="/images/2020/05/466304477.jpg" alt="23FE5157A35842B9E575CC472D30A4E7.jpg"></p><p>##方法一<br>使用变量、方法与自定义 <code>Widget </code>缓解嵌套地狱</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">// 把嵌套严重的 Widget 的一部分提出一个新的 Widget</span><br><span class="line">class A xxxx &#123; </span><br><span class="line">        // 使用方法抽离部分 Widget</span><br><span class="line">        Widget constructD() &#123;</span><br><span class="line">          return new D(</span><br><span class="line">     child:new E(</span><br><span class="line">        child:new F(</span><br><span class="line">                   child:new G(</span><br><span class="line">...</span><br><span class="line">                    ),</span><br><span class="line">                 ),</span><br><span class="line">              ),</span><br><span class="line">            ),</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">@override</span><br><span class="line">Widget build(BuildContext context) &#123;</span><br><span class="line">return new B(</span><br><span class="line">child:new C(</span><br><span class="line">child: constructD()</span><br><span class="line">),</span><br><span class="line">);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>##方法二<br>在<code>widget</code>外面再包一层。请求自己的<code>class</code>，返回封装好的<code>widget</code>。使用时就是链式调用。</p><figure class="highlight plaintext"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br></pre></td><td class="code"><pre><span class="line">import &#x27;package:flutter/material.dart&#x27;;</span><br><span class="line"></span><br><span class="line">///widget装饰器</span><br><span class="line">class WidgetDecoration &#123;</span><br><span class="line">  Widget _widget;</span><br><span class="line"></span><br><span class="line">  WidgetDecoration(Widget widget) &#123;</span><br><span class="line">    this._widget = widget;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Function _onTapFunc;</span><br><span class="line">  Function _onDoubleTapFunc;</span><br><span class="line">  Function _onLongPressFunc;</span><br><span class="line"></span><br><span class="line">  ///add padding属性</span><br><span class="line">  WidgetDecoration padding(</span><br><span class="line">      &#123;Key key, double left = 0.0, double top = 0.0, double right = 0.0, double bottom = 0.0&#125;) &#123;</span><br><span class="line">    var padding = EdgeInsets.only(left: left, top: top, right: right, bottom: bottom);</span><br><span class="line">    _widget = new Padding(key: key, padding: padding, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///增加padingall</span><br><span class="line">  WidgetDecoration paddAll(&#123;Key key, double all = 0.0&#125;) &#123;</span><br><span class="line">    var padding = EdgeInsets.all(all);</span><br><span class="line">    _widget = new Padding(key: key, padding: padding, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///增加align 当前布局相对位置</span><br><span class="line">  ///FractionalOffset.centerRight</span><br><span class="line">  WidgetDecoration align(&#123;Key key, AlignmentGeometry alignment = Alignment.center&#125;) &#123;</span><br><span class="line">    _widget = new Align(key: key, alignment: alignment, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///位置</span><br><span class="line">  WidgetDecoration positioned(</span><br><span class="line">      &#123;Key key,</span><br><span class="line">      double left,</span><br><span class="line">      double top,</span><br><span class="line">      double right,</span><br><span class="line">      double bottom,</span><br><span class="line">      double width,</span><br><span class="line">      double height&#125;) &#123;</span><br><span class="line">    _widget = new Positioned(</span><br><span class="line">        key: key,</span><br><span class="line">        left: left,</span><br><span class="line">        top: top,</span><br><span class="line">        right: right,</span><br><span class="line">        bottom: bottom,</span><br><span class="line">        width: width,</span><br><span class="line">        height: height,</span><br><span class="line">        child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///stack 相当于frameLayout布局</span><br><span class="line"></span><br><span class="line">  ///填充布局</span><br><span class="line">  WidgetDecoration expanded(&#123;Key key, int flex = 1&#125;) &#123;</span><br><span class="line">    _widget = new Expanded(key: key, flex: flex, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///是否显示布局 true为不显示 false为显示</span><br><span class="line">  WidgetDecoration offstage(&#123;Key key, bool offstage = true&#125;) &#123;</span><br><span class="line">    _widget = new Offstage(key: key, offstage: offstage, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///透明度 0 是完全透明 1 完全不透明</span><br><span class="line">  WidgetDecoration opacity(&#123;Key key, @required double opacity, alwaysIncludeSemantics = false&#125;) &#123;</span><br><span class="line">    _widget = new Opacity(</span><br><span class="line">        key: key, opacity: opacity, alwaysIncludeSemantics: alwaysIncludeSemantics, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///基准线布局</span><br><span class="line">  WidgetDecoration baseline(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required double baseline,</span><br><span class="line">    @required TextBaseline baselineType,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget =</span><br><span class="line">        new Baseline(key: key, baseline: baseline, baselineType: baselineType, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///设置宽高比</span><br><span class="line">  WidgetDecoration aspectRatio(&#123;Key key, @required double aspectRatio&#125;) &#123;</span><br><span class="line">    _widget = new AspectRatio(key: key, aspectRatio: aspectRatio, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///矩阵转换</span><br><span class="line">  WidgetDecoration transform(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required Matrix4 transform,</span><br><span class="line">    origin,</span><br><span class="line">    alignment,</span><br><span class="line">    transformHitTests = true,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget = new Transform(</span><br><span class="line">        key: key,</span><br><span class="line">        transform: transform,</span><br><span class="line">        origin: origin,</span><br><span class="line">        alignment: alignment,</span><br><span class="line">        transformHitTests: transformHitTests,</span><br><span class="line">        child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///居中 todo: center</span><br><span class="line">  WidgetDecoration center(&#123;Key key, double widthFactor, double heightFactor&#125;) &#123;</span><br><span class="line">    _widget =</span><br><span class="line">        new Center(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///布局容器</span><br><span class="line">  WidgetDecoration container(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    alignment,</span><br><span class="line">    padding,</span><br><span class="line">    Color color,</span><br><span class="line">    Decoration decoration,</span><br><span class="line">    foregroundDecoration,</span><br><span class="line">    double width,</span><br><span class="line">    double height,</span><br><span class="line">    BoxConstraints constraints,</span><br><span class="line">    margin,</span><br><span class="line">    transform,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget = new Container(</span><br><span class="line">        key: key,</span><br><span class="line">        alignment: alignment,</span><br><span class="line">        padding: padding,</span><br><span class="line">        color: color,</span><br><span class="line">        decoration: decoration,</span><br><span class="line">        foregroundDecoration: foregroundDecoration,</span><br><span class="line">        width: width,</span><br><span class="line">        height: height,</span><br><span class="line">        constraints: constraints,</span><br><span class="line">        margin: margin,</span><br><span class="line">        transform: transform,</span><br><span class="line">        child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///设置具体尺寸</span><br><span class="line">  WidgetDecoration sizedBox(&#123;Key key, double width, double height&#125;) &#123;</span><br><span class="line">    _widget = new SizedBox(key: key, width: width, height: height, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///设置最大最小宽高布局</span><br><span class="line">  WidgetDecoration constrainedBox(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    minWidth = 0.0,</span><br><span class="line">    maxWidth = double.infinity,</span><br><span class="line">    minHeight = 0.0,</span><br><span class="line">    maxHeight = double.infinity,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    BoxConstraints constraints = new BoxConstraints(</span><br><span class="line">        minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight);</span><br><span class="line">    _widget = new ConstrainedBox(key: key, constraints: constraints, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///限定最大宽高布局</span><br><span class="line">  WidgetDecoration limitedBox(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    maxWidth = double.infinity,</span><br><span class="line">    maxHeight = double.infinity,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget = new LimitedBox(key: key, maxWidth: maxWidth, maxHeight: maxHeight, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///百分比布局</span><br><span class="line">  WidgetDecoration fractionallySizedBox(</span><br><span class="line">      &#123;Key key, alignment = Alignment.center, double widthFactor, double heightFactor&#125;) &#123;</span><br><span class="line">    _widget = new FractionallySizedBox(</span><br><span class="line">        key: key,</span><br><span class="line">        alignment: alignment,</span><br><span class="line">        widthFactor: widthFactor,</span><br><span class="line">        heightFactor: heightFactor,</span><br><span class="line">        child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///缩放布局</span><br><span class="line">  WidgetDecoration fittedBox(&#123;Key key, fit = BoxFit.contain, alignment = Alignment.center&#125;) &#123;</span><br><span class="line">    _widget = new FittedBox(key: key, fit: fit, alignment: alignment, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///旋转盒子 1次是90度</span><br><span class="line">  WidgetDecoration rotatedBox(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required int quarterTurns,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget = new RotatedBox(key: key, quarterTurns: quarterTurns, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///装饰盒子 细节往外抛 decoration 编写放在外面</span><br><span class="line">  WidgetDecoration decoratedBox(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required Decoration decoration,</span><br><span class="line">    position = DecorationPosition.background,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget =</span><br><span class="line">        new DecoratedBox(key: key, decoration: decoration, position: position, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///圆形剪裁</span><br><span class="line">  WidgetDecoration clipOval(</span><br><span class="line">      &#123;Key key, CustomClipper&lt;Rect&gt; clipper, Clip clipBehavior = Clip.antiAlias&#125;) &#123;</span><br><span class="line">    _widget = new ClipOval(key: key, clipper: clipper, clipBehavior: clipBehavior, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///圆角矩形剪裁</span><br><span class="line">  WidgetDecoration clipRRect(</span><br><span class="line">      &#123;Key key,</span><br><span class="line">      @required BorderRadius borderRadius,</span><br><span class="line">      CustomClipper&lt;RRect&gt; clipper,</span><br><span class="line">      Clip clipBehavior = Clip.antiAlias&#125;) &#123;</span><br><span class="line">    _widget = new ClipRRect(</span><br><span class="line">        key: key,</span><br><span class="line">        borderRadius: borderRadius,</span><br><span class="line">        clipper: clipper,</span><br><span class="line">        clipBehavior: clipBehavior,</span><br><span class="line">        child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///矩形剪裁  todo: 需要自定义clipper 否则无效果</span><br><span class="line">  WidgetDecoration clipRect(</span><br><span class="line">      &#123;Key key, @required CustomClipper&lt;Rect&gt; clipper, Clip clipBehavior = Clip.hardEdge&#125;) &#123;</span><br><span class="line">    _widget = new ClipRect(key: key, clipper: clipper, clipBehavior: clipBehavior, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///路径剪裁  todo: 需要自定义clipper 否则无效果</span><br><span class="line">  WidgetDecoration clipPath(</span><br><span class="line">      &#123;Key key, @required CustomClipper&lt;Path&gt; clipper, Clip clipBehavior = Clip.antiAlias&#125;) &#123;</span><br><span class="line">    _widget = new ClipPath(key: key, clipper: clipper, clipBehavior: clipBehavior, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///animatedOpacity 淡入淡出</span><br><span class="line">  WidgetDecoration animatedOpacity(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required double opacity,</span><br><span class="line">    Curve curve = Curves.linear,</span><br><span class="line">    @required Duration duration,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">    _widget = new AnimatedOpacity(</span><br><span class="line">        key: key, opacity: opacity, curve: curve, duration: duration, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///页面简单切换效果</span><br><span class="line">  WidgetDecoration hero(&#123;Key key, @required Object tag&#125;) &#123;</span><br><span class="line">    _widget = new Hero(key: key, tag: tag, child: _widget);</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///点击事件</span><br><span class="line">  WidgetDecoration onClick(&#123;Key key, onTap, onDoubleTap, onLongPress&#125;) &#123;</span><br><span class="line">    _widget = new GestureDetector(</span><br><span class="line">      key: key,</span><br><span class="line">      child: _widget,</span><br><span class="line">      onTap: onTap ?? _onTapFunc,</span><br><span class="line">      onDoubleTap: onDoubleTap ?? _onDoubleTapFunc,</span><br><span class="line">      onLongPress: onLongPress ?? _onLongPressFunc,</span><br><span class="line">    );</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///添加点击事件</span><br><span class="line">  WidgetDecoration onTap(Function func, &#123;Key key&#125;) &#123;</span><br><span class="line">    _onTapFunc = func;</span><br><span class="line">    _widget = new GestureDetector(</span><br><span class="line">      key: key,</span><br><span class="line">      child: _widget,</span><br><span class="line">      onTap: _onTapFunc,</span><br><span class="line">      onDoubleTap: _onDoubleTapFunc,</span><br><span class="line">      onLongPress: _onLongPressFunc,</span><br><span class="line">    );</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///双击</span><br><span class="line">  WidgetDecoration onDoubleTap(Function func, &#123;Key key&#125;) &#123;</span><br><span class="line">    _onDoubleTapFunc = func;</span><br><span class="line">    _widget = new GestureDetector(</span><br><span class="line">      key: key,</span><br><span class="line">      child: _widget,</span><br><span class="line">      onTap: _onTapFunc,</span><br><span class="line">      onDoubleTap: _onDoubleTapFunc,</span><br><span class="line">      onLongPress: _onLongPressFunc,</span><br><span class="line">    );</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ///长按</span><br><span class="line">  WidgetDecoration onLongPress(Function func, &#123;Key key&#125;) &#123;</span><br><span class="line">    _onLongPressFunc = func;</span><br><span class="line">    _widget = new GestureDetector(</span><br><span class="line">      key: key,</span><br><span class="line">      child: _widget,</span><br><span class="line">      onTap: _onTapFunc,</span><br><span class="line">      onDoubleTap: _onDoubleTapFunc,</span><br><span class="line">      onLongPress: _onLongPressFunc,</span><br><span class="line">    );</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Widget build() &#123;</span><br><span class="line">    return _widget;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>##方法三<br>用扩展函数来给系统<code>widget</code>添加一个<code>child</code>属性。<br>使用时也支持链式调用。</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">extension WidgetExt on Widget &#123;</span><br><span class="line"></span><br><span class="line">  Container intoContainer(&#123;</span><br><span class="line">      //复制Container构造函数的所有参数（除了child字段）</span><br><span class="line">    Key key,</span><br><span class="line">    AlignmentGeometry alignment,</span><br><span class="line">    EdgeInsetsGeometry padding,</span><br><span class="line">    Color color,</span><br><span class="line">    Decoration decoration,</span><br><span class="line">    Decoration foregroundDecoration,</span><br><span class="line">    double width,</span><br><span class="line">    double height,</span><br><span class="line">    BoxConstraints constraints,</span><br><span class="line">    EdgeInsetsGeometry margin,</span><br><span class="line">    Matrix4 transform,</span><br><span class="line">  &#125;) &#123;</span><br><span class="line">      //调用Container的构造函数，并将当前widget对象作为child参数</span><br><span class="line">    return Container(</span><br><span class="line">      key: key,</span><br><span class="line">      alignment: alignment,</span><br><span class="line">      padding: padding,</span><br><span class="line">      color: color,</span><br><span class="line">      decoration: decoration,</span><br><span class="line">      foregroundDecoration: foregroundDecoration,</span><br><span class="line">      width: width,</span><br><span class="line">      height: height,</span><br><span class="line">      constraints: constraints,</span><br><span class="line">      margin: margin,</span><br><span class="line">      transform: transform,</span><br><span class="line">      child: this,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;写代码容易，读代码难。功能是都实现了，但是对维护人员来说，简直就是灾难。&lt;/p&gt;
&lt;p&gt;##背景&lt;br&gt;&lt;code&gt;Flutter&lt;/code&gt; 注重组合而非继承，要想搭建出 &lt;code&gt;UI&lt;/code&gt;，需要组合不同功能的 &lt;code&gt;Widget&lt;/code&gt;，如布局</summary>
      
    
    
    
    <category term="Flutter开发" scheme="https://cubegao.com/categories/Flutter%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Flutter" scheme="https://cubegao.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>给斐讯 K3 安装 Merlin 系统</title>
    <link href="https://cubegao.com/p/2019-12-03-k3-merlin/"/>
    <id>https://cubegao.com/p/2019-12-03-k3-merlin/</id>
    <published>2019-12-03T10:45:00.000Z</published>
    <updated>2026-06-19T05:52:21.719Z</updated>
    
    <content type="html"><![CDATA[<p>给K3改造好散热之后，打算把系统从官改系统升级成梅林系统。<br>主要是因为在改造散热的过程，我发现这台<code>K3</code>的闪存是<code>mxic</code>闪存，而并非是常见的三星闪存。<br>因为三星闪存对梅林的兼容性不好，容易导致坏块的产生。</p><p>##开始刷机<br>如果固件版本很老，像我这台的固件版本 <code>V21.6.12.66</code>,那就可以直接刷了。<br>如果固件版本很新，并且不能使用漏洞降级，那你可能要拆机，然后用TTL刷机了。</p><p>首先是激活<code>Telnet</code>，使用这个<a href="https://www.right.com.cn/forum/thread-261028-1-1.html">工具</a>。<br>原理就是利用<code>web</code>注入的方式。</p><p>重点来了，第一步备份路由器固件，防止刷机失败了，可以恢复原本的固件。</p><figure class="highlight plaintext"><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">telnet 192.168.2.1</span><br><span class="line"></span><br><span class="line">mount  # 查看当前挂载的 U 盘</span><br><span class="line">cd /tmp/share/sda1/</span><br><span class="line">mkdir backup</span><br><span class="line">cd backup</span><br><span class="line">cat /dev/mtdblock0 &gt; mtdblock0.bin     # 备份 CFE</span><br><span class="line">cat /dev/mtdblock5 &gt; mtdblock5.bin     # 备份设备信息</span><br><span class="line">cat /dev/mtdblock6 &gt; mtdblock6.bin     # 备份固件，备份固件时间可能有些长 1min 左右</span><br></pre></td></tr></table></figure><p><code>K3</code>固件分区如下：</p><figure class="highlight plaintext"><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">mtd0 boot</span><br><span class="line">cfe 引导分区，建议备份，还包含了部分 nvram 参数和 MAC 等信息，不要刷入别人的，也不要随意刷写这个分区。</span><br><span class="line">mtd1 nvram</span><br><span class="line">设置分区，不用备份，保存了路由的所有设置信息，恢复出厂时会格式化然后从 cfe 和当前固件复制默认参数。</span><br><span class="line">mtd2 nvram_back</span><br><span class="line">从名字看像是 nvram 的备份，实际固件没有使用，全部空白，不用备份。</span><br><span class="line">mtd3 res_info</span><br><span class="line">没有使用，全部空白，不用备份。</span><br><span class="line">mtd4 pro_info</span><br><span class="line">只有 6 字节数据，00904C17F234，所有机器都一样，可选备份，就算没备份把这 6 字节复制过去就恢复了。</span><br><span class="line">mtd5 dev_info</span><br><span class="line">176 字节数据，好像是加密的固件的版本一些信息，刷写官方固件版本号发生变化时候这里的数据就变化，可选备份。</span><br><span class="line">mtd6 linux</span><br><span class="line">整个固件分区，平常刷写官方固件或者 LEDE 都是刷到这里，官方提供固件下载，只要 cfe 正常随便刷，不用备份。</span><br><span class="line">mtd7 rootfs</span><br><span class="line">动态的，包含在 linux 分区，mtd6 减去当前固件内核部分，是固件的后半部分，而固件是一个整体，当然不用备份。</span><br><span class="line">mtd8 brcmnand</span><br><span class="line">存储分区，日志文件、自己安装的软件保存再这里，固件版本信息变化时系统会格式化，不用备份，也不要恢复。</span><br></pre></td></tr></table></figure><p><code>Telnet</code>激活成功后，首先刷入官Root固件。</p><p><code>Telnet</code>登陆路由后 —&gt; <code>PuTTY</code>执行： </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -Lksf tbvv.net/k3/one|sh</span><br></pre></td></tr></table></figure><p>##在线刷入merlin固件<br><code>SSH</code>工具箱–&gt;系统&amp;其它–&gt;其他–&gt;<code>MAC</code>修改<br>输入粘贴当前路由<code>LAN</code>口的<code>MAC</code>地址(官方固件管理页面右下方有显示)，完成后梅林固件下的无线<code>MAC</code>和当前固件一致</p><p><code>SSH</code>工具箱–&gt;刷机&amp;还原—&gt;<code>Asuswrt-Merlin</code> 固件—&gt;<code>Merlin</code>固件<code> by tb</code>，确认重启后完成</p><p>默认管理地址:<code>http://router.asus.com/</code>或者 <code>http://192.168.50.1/</code></p><p>(<code>CFE</code>降级和<code>MAC</code>修改仅需执行一次, 固件<code>nvram</code>和其它固件冲突, 不要导入保留其它固件的设置)</p><p>下面就装好了，看一下全景。<br><img src="/images/2020/05/72459122.png" alt="fdsfsd5665465.png"></p><p>软件中心安装好了的常用插件。<br><img src="/images/2020/05/647955476.png" alt="781e71aa-e73a-4c61-b14d-7ef70ccf059f.png"></p><p>软件中心剩余没安装的所有插件。<br><img src="/images/2020/05/2900156547.png" alt="fb13fc12-67b7-44e1-b62f-f5045b6f1918.png"></p><p>以后也可以通过<code>ssh</code>来管理了。</p><figure class="highlight plaintext"><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">$ ssh admin@192.168.50.1</span><br><span class="line">admin@192.168.50.1&#x27;s password:****</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">ASUSWRT-Merlin RT-AC3100 380.70-0-X7.9.1 Tue Sep 25 14:08:09 UTC 2018</span><br><span class="line">admin@RT-AC3100-6D96:/tmp/home/root# $</span><br></pre></td></tr></table></figure><p>下面是工具箱：</p><figure class="highlight plaintext"><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><br><span class="line">                     主菜单</span><br><span class="line"> -----------------------------------------------</span><br><span class="line"> [1] 安装插件</span><br><span class="line"> [2] 升级&amp;还原</span><br><span class="line"> [3] 其它</span><br><span class="line"></span><br><span class="line"> [4] 关于</span><br><span class="line"></span><br><span class="line">输入你的选择 [0-4]:</span><br></pre></td></tr></table></figure><p>下面是安装插件：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line"> -----------------------------------------------</span><br><span class="line">                    安装插件</span><br><span class="line"> -----------------------------------------------</span><br><span class="line"> [1] Koolproxy [2] ADMflt [3] 签到dog [4] 虚拟内存</span><br><span class="line"> [5] Shadowsocks [6] SS-Server [7] SoftEtherVPN [8] KMS</span><br><span class="line"> [9] ShadowVPN [10] Webshell [11] Shellinabox [12] Aria2</span><br><span class="line"> [13] Frpc穿透 [14] Frps [15] P2P穿透 [16] 穿透DDNS</span><br><span class="line"> [17] DDNSTO [18] EasyExplorer [19] 讯雷快鸟 [20] 中文SSID</span><br><span class="line"> [21] ServerChan [22] 花生壳内网版 [23] 策略路由 [24] Let&#x27;s Encrypt</span><br><span class="line"> [25] Aliddns [26] DDnspod [27] GodaddyDDNS [28] CloudXNSDDNS</span><br><span class="line"> [29] 讯雷远程下载 [30] Adbyby [31] BonusCloud [32] X</span><br><span class="line"></span><br><span class="line"> [A] Softcenter</span><br><span class="line"></span><br><span class="line"> [0] 返回</span><br><span class="line"></span><br><span class="line"> 已使用: 23.1MB   可用: 56.9MB</span><br><span class="line"></span><br><span class="line">输入插件序号 :</span><br></pre></td></tr></table></figure><p>下面是升级系统：</p><figure class="highlight plaintext"><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><br><span class="line">                     升级&amp;还原</span><br><span class="line">  -----------------------------------------------</span><br><span class="line">  [1] update to the latest version</span><br><span class="line">  [2] 下载还原root固件: V21.5.37.246</span><br><span class="line">  [3] 下载还原root固件: V21.6.8.46</span><br><span class="line"></span><br><span class="line">  [0] 返回</span><br><span class="line"></span><br><span class="line"> 输入你的选择 [0-3]:</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;给K3改造好散热之后，打算把系统从官改系统升级成梅林系统。&lt;br&gt;主要是因为在改造散热的过程，我发现这台&lt;code&gt;K3&lt;/code&gt;的闪存是&lt;code&gt;mxic&lt;/code&gt;闪存，而并非是常见的三星闪存。&lt;br&gt;因为三星闪存对梅林的兼容性不好，容易导致坏块的产生。&lt;/p&gt;</summary>
      
    
    
    
    <category term="数码改造" scheme="https://cubegao.com/categories/%E6%95%B0%E7%A0%81%E6%94%B9%E9%80%A0/"/>
    
    
    <category term="改造" scheme="https://cubegao.com/tags/%E6%94%B9%E9%80%A0/"/>
    
  </entry>
  
  <entry>
    <title>容易被人忽略的 NSCache</title>
    <link href="https://cubegao.com/p/2019-11-11-nscache/"/>
    <id>https://cubegao.com/p/2019-11-11-nscache/</id>
    <published>2019-11-11T12:13:00.000Z</published>
    <updated>2026-06-19T05:52:21.719Z</updated>
    
    <content type="html"><![CDATA[<p>第一次见到<code>NSCache</code>，是在<code>SDWebImage</code>中。<code>SDWebImage</code>的内存缓存机制就是通过<code>NSCache</code>完成的。所以可能你不太了解这个类，但是其实一直在使用它。</p><h3 id="为什么要使用NSCache？"><a href="#为什么要使用NSCache？" class="headerlink" title="为什么要使用NSCache？"></a>为什么要使用NSCache？</h3><p>我们通常用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能，因为它们的值不需要重新计算。另外一方面，这些对象对于程序来说不是紧要的，在内存紧张时会被丢弃。如果对象被丢弃了，则下次使用时需要重新计算。</p><p><code>NSCache</code>是一个可变的集合，主要用来存储<code>key-value</code>对。它有着和<code>NSMutableDictionary</code>类似的API。实际上，<code>NSCache</code>就像是一个会自动移除对象来释放内存的<code>NSMutableDictionary</code>。</p><figure class="highlight plaintext"><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">- (id)objectForKey:(id)key</span><br><span class="line">- (void)setObject:(id)obj forKey:(id)key</span><br><span class="line">- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)num</span><br><span class="line">- (void)removeObjectForKey:(id)key</span><br><span class="line">- (void)removeAllObjects</span><br></pre></td></tr></table></figure><p><code>NSCahce</code>与可变集合不同之处：</p><blockquote><p>1.<code>NSCache</code>类有自己的自动删除策略，以确保缓存不用使用太多的系统内存。如果其他应用需要内存，则自动删除策略会从缓存中删除一些项目，从而最大限度的减少内存占用。<br> 2.<code>NSCache</code>是线程安全的，我们可以从不同的线程中添加、删除和查询缓存中的对象，而不需要锁定缓存区域。其中线程安全是<code>pthread_mutex</code>完成的。<br>3.明显区别于<code>NSMutableDictionary</code>的是，键对象不会被<code>retain</code>。（键不需要实现<code>NSCopying</code>协议）</p></blockquote><h3 id="需要注意的点"><a href="#需要注意的点" class="headerlink" title="需要注意的点"></a>需要注意的点</h3><p><code>NSCache</code>提供了一个<code>delegate</code>方法：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (void)cache:(NSCache *)cache willEvictObject:(id)obj</span><br></pre></td></tr></table></figure><p>需要注意的是在这个代理方法中不能修改<code>NSCache</code>对象，也就是只能测试使用。</p><blockquote><p><code>NSCache</code>提供了一系列的属性来限制缓存的大小。比如属性<code>countLimit</code>限定了缓存最多维护的对象的个数，属性<code>totalCostLimit</code>限定了缓存能维持的最大内存。<br>在存取方法中，有一个<code>setObject:forKey:cost: </code>方法，它和<code>setObject:forKey: </code>方法类似，但是带着<code>cost</code>参数。<code>cost</code>表示每次的消耗，如对象占用的字节数。当内存不足或者所有缓存对象的总消耗超过了最大值时，缓存会移除其中的一些对象。</p></blockquote><p>上面这些内容，看起来很美好，但是问题来了。从缓存中移除对象并不能保证顺序，也就是有可能刚写入的对象，就被丢弃了。如果你要使用<code>cost</code>来完成一些特殊的需求，那你可能要失望了。所以计算<code>cost</code>值，可能反而会增加使用缓存的代价。</p><blockquote><p>另外<code>countLimit</code>和<code>totalCostLimit</code>属性都是不精确或者不严格的，也就是如果缓存的数量超过了<code>countLimit</code>，或者缓存维持的最大内存超过了<code>totalCostLimit</code>，缓存中的对象可能会被丢弃，也可能不会，具体的策略只有苹果自己知道。<br>虽然苹果提供了<code>NSDiscardableContent</code>来控制对象是否会被自动移除的机制，但是这可能会让你碰到更多的问题。</p></blockquote><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>虽然<code>NSCache</code>有一些缺点，但是也应该去积极使用。如果我们需要构建内存缓存机制，就应该选用<code>NSCache</code>而非<code>NSDictionary</code>，这样可以减少我们应用对内存的占用，从而达到优化内存的目标。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;第一次见到&lt;code&gt;NSCache&lt;/code&gt;，是在&lt;code&gt;SDWebImage&lt;/code&gt;中。&lt;code&gt;SDWebImage&lt;/code&gt;的内存缓存机制就是通过&lt;code&gt;NSCache&lt;/code&gt;完成的。所以可能你不太了解这个类，但是其实一直在使用它。&lt;/</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="iOS开发" scheme="https://cubegao.com/tags/iOS%E5%BC%80%E5%8F%91/"/>
    
    <category term="NSCache" scheme="https://cubegao.com/tags/NSCache/"/>
    
  </entry>
  
  <entry>
    <title>通过 SSH 连接 iOS 设备的几种方法</title>
    <link href="https://cubegao.com/p/2019-10-20-iOS-ssh/"/>
    <id>https://cubegao.com/p/2019-10-20-iOS-ssh/</id>
    <published>2019-10-20T11:20:00.000Z</published>
    <updated>2026-06-19T05:52:21.718Z</updated>
    
    <content type="html"><![CDATA[<h1 id="必备工具"><a href="#必备工具" class="headerlink" title="必备工具"></a>必备工具</h1><ul><li>一台已经越狱的iPhone</li><li>一台PC</li></ul><h1 id="方案一"><a href="#方案一" class="headerlink" title="方案一"></a>方案一</h1><ol><li>打开Cydia,搜索OpenSSH，安装。</li></ol><p><img src="/images/2019/12/492416257.png" alt="da95dc82edb749c09f8da787f1630990.png"></p><ol start="2"><li>保证PC和iPhone在同一局域网下，然后连接SSH。默认的账户是root，密码是alpine。地址是设备的局域网地址。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh root@192.169.50.99</span><br></pre></td></tr></table></figure><h1 id="方案二"><a href="#方案二" class="headerlink" title="方案二"></a>方案二</h1><ol><li><p>打开Cydia，卸载掉OpenSSH（如果安装了），然后添加源：<code>http://cydia.ichitaso.com/test</code>。（如果iPhone重启了，打开Cydia闪退，记得先Re-Jailbreak）</p></li><li><p>在Cydia中搜索dropbear，安装。</p></li></ol><p><img src="/images/2019/12/3976466228.png" alt="d51c54fa7d0b45e28ee73ac6e7124417.png"></p><ol start="3"><li>然后重复方案一的2.,直接SSH连接即可。</li></ol><h1 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h1><p>如果wifi连接iPhone设备一直失败怎么办？</p><p>我们可以借助<code>usbmuxd</code>这个工具通过USB连接。</p><p><code>usbmuxd</code> 是苹果的一个服务，这个服务主要用于在USB协议上实现多路TCP连接，将USB通信抽象为TCP通信。苹果的iTunes、Xcode，都直接或间接地用到了这个服务。它提供了一个USB - TCP的转换服务。</p><ol><li>安装usbmuxd。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew install usbmuxd</span><br></pre></td></tr></table></figure><ol start="2"><li>建立映射关系。</li></ol><figure class="highlight plaintext"><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">$ iproxy 1234 22</span><br><span class="line">$ waiting for connection</span><br></pre></td></tr></table></figure><p>这样当前连接设备的22端口(SSH端口)映射到电脑的1234端口，因此想和设备通信，直接和本地端口1234通信就可以了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh -p 1234 root@127.0.0.1</span><br></pre></td></tr></table></figure><p>如果你还是连不上设备，那就打开i4助手-工具箱-打开SSH通道。当然前提是设备已经安装了OpenSSH。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;必备工具&quot;&gt;&lt;a href=&quot;#必备工具&quot; class=&quot;headerlink&quot; title=&quot;必备工具&quot;&gt;&lt;/a&gt;必备工具&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;一台已经越狱的iPhone&lt;/li&gt;
&lt;li&gt;一台PC&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;方案一&quot;&gt;&lt;a h</summary>
      
    
    
    
    <category term="逆向学习" scheme="https://cubegao.com/categories/%E9%80%86%E5%90%91%E5%AD%A6%E4%B9%A0/"/>
    
    
    <category term="iOS" scheme="https://cubegao.com/tags/iOS/"/>
    
    <category term="SSH" scheme="https://cubegao.com/tags/SSH/"/>
    
    <category term="OpenSSH" scheme="https://cubegao.com/tags/OpenSSH/"/>
    
  </entry>
  
  <entry>
    <title>给斐讯 K3 改造散热</title>
    <link href="https://cubegao.com/p/2019-10-15-k3-remodel/"/>
    <id>https://cubegao.com/p/2019-10-15-k3-remodel/</id>
    <published>2019-10-15T06:59:00.000Z</published>
    <updated>2026-06-19T05:52:21.717Z</updated>
    
    <content type="html"><![CDATA[<p>天气越来越热，路由器温度也越来越高，由于<code>K3</code>的劣质硅脂垫在高温下容易出油，加上在硬件设计中，无线模块放在主板下方，硅脂垫流出的油会滴到无线模块上，然后导致无线模块丢失，最常见的表现就是<code>2.4G</code>信号或者<code>5G</code>信号丢失。</p><p>##购买配件<br>本身K3硬件设计就是没有风扇的被动散热，所以我预想的改动也就是增强被动散热。如果加风扇，散热效果会更加明显，但是风扇耗电，可能会导致主机电源不稳定，从而造成网速波动。<br><img src="/images/2020/05/859530795.jpg" alt="IMG_4288.jpg"></p><p>这套配件主要包含铜片，硅脂垫，硅脂。</p><p>###拆机<br>由于是卡扣式的安装，拆机只能用巧劲来拆，实际上花了半个多小时才拆开，然后卡扣还断了一根，算及格吧。<br><img src="/images/2020/05/3124766220.jpg" alt="IMG_4286.JPG"></p><p>我这台K3是刚从某转上淘来的。是全新的，但是由于气温高，可以看一下，无线板子上都已经油迹斑斑了。<br><img src="/images/2020/05/3307840977.jpg" alt="IMG_4287.JPG"></p><p>然后开始给CPU和无线板开始涂硅脂，涂硅脂有个小技巧就是不要涂太厚，然后中间留个小坨，压下去后，就会更均匀了。<br><img src="/images/2020/05/2863396663.jpg" alt="IMG_4285.JPG"></p><p>硅脂涂好后，开始安装铜片。<br>大铜片是安装到无线板和散热片中间的，主要是增加接触面积来达到散热功能。<br><img src="/images/2020/05/547206411.jpg" alt="IMG_4283.JPG"></p><p>最后开始组装了。<br><img src="/images/2020/05/2152686093.jpg" alt="IMG_4284.JPG"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;天气越来越热，路由器温度也越来越高，由于&lt;code&gt;K3&lt;/code&gt;的劣质硅脂垫在高温下容易出油，加上在硬件设计中，无线模块放在主板下方，硅脂垫流出的油会滴到无线模块上，然后导致无线模块丢失，最常见的表现就是&lt;code&gt;2.4G&lt;/code&gt;信号或者&lt;code&gt;5G&lt;/c</summary>
      
    
    
    
    <category term="数码改造" scheme="https://cubegao.com/categories/%E6%95%B0%E7%A0%81%E6%94%B9%E9%80%A0/"/>
    
    
    <category term="改造" scheme="https://cubegao.com/tags/%E6%94%B9%E9%80%A0/"/>
    
  </entry>
  
  <entry>
    <title>安装 frida 遇到的一些问题</title>
    <link href="https://cubegao.com/p/2019-10-09-install-frida/"/>
    <id>https://cubegao.com/p/2019-10-09-install-frida/</id>
    <published>2019-10-09T11:20:00.000Z</published>
    <updated>2026-06-19T05:52:21.717Z</updated>
    
    <content type="html"><![CDATA[<h1 id="开始安装frida"><a href="#开始安装frida" class="headerlink" title="开始安装frida"></a>开始安装frida</h1><p>首先需要安装python，Mac已经自带python2.7，frida作者是推荐安装python3.x。python2.x同样也能安装成功。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo pip install frida</span><br></pre></td></tr></table></figure><p>如果报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DEPRECATION: Uninstalling a distutils installed project (six) has been deprecated and will be removed in a future version. This is due to the fact that uninstalling a distutils project will only partially uninstall the project.</span><br></pre></td></tr></table></figure><p>安装命令修改成</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo pip install frida --ignore-installed six</span><br></pre></td></tr></table></figure><p>如果报错：</p><figure class="highlight plaintext"><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">Exception:</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File &quot;/Library/Python/2.7/site-packages/pip-9.0.1-py2.7.egg/pip/basecommand.py&quot;, line 215, in main</span><br><span class="line">    status = self.run(options, args)</span><br></pre></td></tr></table></figure><p>安装命令修改成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo -H pip install frida --ignore-installed six</span><br></pre></td></tr></table></figure><p>然后一个小时过去了，回来一看卡住了，一直停留在下面这个地方。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Running setup.py install for frida</span><br></pre></td></tr></table></figure><p>如果有科学上网工具，可以操作一下。如果没有也没关系，我们可以先把egg文件下到本地用setuptools进行安装。</p><h3 id="先装setuptools"><a href="#先装setuptools" class="headerlink" title="先装setuptools"></a>先装setuptools</h3><p><a href="https://pypi.org/project/setuptools/#files">https://pypi.org/project/setuptools/#files</a></p><p>下载最新版的.whl文件，命令行安装：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pip install (本地.whl文件路径).whl</span><br></pre></td></tr></table></figure><h3 id="通过setuptools安装frida"><a href="#通过setuptools安装frida" class="headerlink" title="通过setuptools安装frida"></a>通过setuptools安装frida</h3><p><a href="https://pypi.org/project/frida/#files">https://pypi.org/project/frida/#files</a></p><p>下载你需要的版本.egg文件，命令行安装：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ easy_install (本地.egg文件路径).egg</span><br></pre></td></tr></table></figure><h1 id="安装frida-tools"><a href="#安装frida-tools" class="headerlink" title="安装frida-tools"></a>安装frida-tools</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pip install frida-tools</span><br></pre></td></tr></table></figure><p>如果安装卡住，也可以先下载文件到本地。</p><p><a href="https://pypi.org/project/frida-tools/#files">https://pypi.org/project/frida-tools/#files</a></p><p>解压后运行setup.py安装。</p><h1 id="安装完成"><a href="#安装完成" class="headerlink" title="安装完成"></a>安装完成</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ frida-ps</span><br></pre></td></tr></table></figure><p>如果能显示当前系统进程则证明安装成功。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;开始安装frida&quot;&gt;&lt;a href=&quot;#开始安装frida&quot; class=&quot;headerlink&quot; title=&quot;开始安装frida&quot;&gt;&lt;/a&gt;开始安装frida&lt;/h1&gt;&lt;p&gt;首先需要安装python，Mac已经自带python2.7，frida作者是推荐安装</summary>
      
    
    
    
    <category term="逆向学习" scheme="https://cubegao.com/categories/%E9%80%86%E5%90%91%E5%AD%A6%E4%B9%A0/"/>
    
    
    <category term="iOS开发" scheme="https://cubegao.com/tags/iOS%E5%BC%80%E5%8F%91/"/>
    
    <category term="iOS" scheme="https://cubegao.com/tags/iOS/"/>
    
    <category term="frida" scheme="https://cubegao.com/tags/frida/"/>
    
    <category term="逆向" scheme="https://cubegao.com/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title>Flutter 从入门到放弃</title>
    <link href="https://cubegao.com/p/2019-06-10-flutter-start/"/>
    <id>https://cubegao.com/p/2019-06-10-flutter-start/</id>
    <published>2019-06-10T04:24:00.000Z</published>
    <updated>2026-06-19T05:52:21.716Z</updated>
    
    <content type="html"><![CDATA[<p>##看看历史<br>你想想一套代码，移动端、<code>web</code>端、<code>pc</code>端都搞定！这生产力！非常值得学习了。</p><p>2015年5月 <code>Dart</code> 开发者峰会上，亮相了基于<code>Dart </code>语言的移动应用程序开发框架<code>Sky </code>，后更名为 <code>Flutter</code>。<code>Dart</code>语言2011年诞生，起初的竞对目标是<code>Js</code>，2016年谷歌的<code>AdWords</code>、<code>AdSense</code>和<code>Fiber</code>项目团队开始把<code>Dart</code>融入他们的前端应用开发。一项当时的内部报告表明，<code>Dart</code>可以帮助他们提升<code>25%</code>到<code>100%</code>的前端开发效率。谷歌内部的<code>Dart</code>代码量比去年增长了<code>3.5</code>倍。</p><p>5月7日 ， <code>Google i/O</code>大会 官方宣布，<code>Flutter 1.5</code> 预览版来了，已支持移动、<code>Web</code>、桌面和嵌入式设备，也意味着它正式成为了支持多平台的轻量级 UI 框架，对于开发者而言越来越友好！</p><p>##看看架构<br>Flutter for Mobile<br><img src="/images/2020/05/4031449499.jpg" alt="01850A5A55468298AB6693E8585B4CBC.jpg"></p><p>Flutter for web<br><img src="/images/2020/05/466304477.jpg" alt="C827C2BDF29EBA36E7DD6A10C26A4861.jpg"></p><p>##动手鼓捣<br>一个最简单的Flutter应用程序，只需一个widget即可!</p><figure class="highlight plaintext"><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">import &#x27;package:flutter/material.dart&#x27;;</span><br><span class="line"></span><br><span class="line">void main() &#123;</span><br><span class="line">  runApp(</span><br><span class="line">    new Center(</span><br><span class="line">      child: new Text(</span><br><span class="line">        &#x27;Hello, world!&#x27;,</span><br><span class="line">        textDirection: TextDirection.ltr,</span><br><span class="line">      ),</span><br><span class="line">    ),</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>UIView</code> 相当于 <code>Flutter</code> 中的什么？<br>在 <code>iOS </code>中，构建<code> UI</code> 的过程中将大量使用<code>view</code>对象。这些对象都是<code> UIView</code> 的实例。它们可以用作容器来承载其他的 <code>UIView</code>，最终构成你的界面布局。</p><p>在<code>Flutter</code>中，你可以粗略地认为<code>Widget</code>相当于 <code>UIView </code>。<code>Widget</code> 和 <code>iOS</code> 中的控件并不完全等价，但当你试图去理解<code>Flutter</code>是如何工作的时候，你可以认为它们是“声明和构建 <code>UI</code> 的方法”。</p><p>然而，<code>Widget</code> 和<code>UIView</code>还是有些区别的。首先，<code>Widgets </code>拥有不同的生存时间：它们一直存在且保持不变，直到当它们需要被改变。当<code> Widgets</code> 和它们的状态被改变时，<code>Flutter</code> 会构建一颗新的 <code>Widgets </code>树。作为对比，<code>iOS </code>中的 <code>views</code> 在改变时并不会被重新创建。但是与其说<code>views</code>是可变的实例，不如说它们被绘制了一次，并且直到使用 <code>setNeedsDisplay()</code> 之后才会被重新绘制。</p><p>此外，不像 <code>UIView</code>，由于不可变性，<code>Flutter</code> 的 <code>widgets</code> 非常轻量。这是因为它们本身并不是什么控件，也不会被直接绘制出什么，而只是 <code>UI</code> 的描述。</p><p><code>Flutter </code>包含了<code> Material</code> 组件库。这些<code> widgets</code> 遵循了<code> Material</code> 设计规范。<code>MD</code> 是一个灵活的设计系统，并且为包括 <code>iOS</code> 在内的所有系统进行了优化。</p><p><code>Flutter</code>有一套丰富、强大的基础<code>widget</code>，其中以下是很常用的：</p><blockquote><p><code>StatelessWidget</code> 是无状态的 <code>Widget</code> ，当我们要展示的 <code>Widget</code> 不需要改变显示内容的时候，使用<code>StatelessWidget</code>即可；</p></blockquote><blockquote><p><code>StatefulWidget</code> 是有状态的 <code>Widget</code> ，当我们要展示的 <code>Widget</code> 需要改变显示内容的时候，需要使用 <code>StatefulWidget</code> ；<code>StatefulWidget</code> 的子类相当于存放了<code>State</code>的配置信息。<code>StatefulWidget</code>的界面显示效果由 <code>State</code> 来控制展示。 当 <code>StatefulWidget</code> 对应的界面数据变化后，调用 <code>setState() </code>方法，然后系统会运行 <code>buildContext()</code> 就可以做到更新界面的效果。</p></blockquote><blockquote><p><code>Row、 Column</code>： 这些具有弹性空间的布局类<code>Widget</code>可让您在水平<code>（Row）</code>和垂直<code>（Column）</code>方向上创建灵活的布局。其设计是基于<code>web</code>开发中的<code>Flexbox</code>布局模型。</p></blockquote><blockquote><p><code>Container</code> 是一个容器<code>Widget</code>；</p></blockquote><blockquote><p><code>Text</code> 用于展示文字，相当于 <code>iOS</code> 中的<code> UILabel</code>；</p></blockquote><blockquote><p><code>Image</code> 用于展示图片；</p></blockquote><blockquote><p><code>FlatButton</code> 相当于 <code>iOS </code>中的 <code>UIButton</code> ，用于处理交互事件，同时，<code>Flutter</code>中可以使用<code>GestureDetector</code> 对<code>Widget</code> 进行包裹，也可以达到具备交互的效果的目的；</p></blockquote><blockquote><p><code>ListView</code> 相当于 <code>iOS</code>中的 <code>UITableView</code> ，用于展示列表内容。</p></blockquote><blockquote><p><code>ListTile</code> 相当于<code>iOS</code> 中的 <code>UITableViewCell</code>。</p></blockquote><figure class="highlight plaintext"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line">import &#x27;package:flutter/material.dart&#x27;;</span><br><span class="line"></span><br><span class="line">class MyAppBar extends StatelessWidget &#123;</span><br><span class="line">  MyAppBar(&#123;this.title&#125;);</span><br><span class="line"></span><br><span class="line">  // Widget子类中的字段往往都会定义为&quot;final&quot;</span><br><span class="line"></span><br><span class="line">  final Widget title;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return new Container(</span><br><span class="line">      height: 56.0, // 单位是逻辑上的像素（并非真实的像素，类似于浏览器中的像素）</span><br><span class="line">      padding: const EdgeInsets.symmetric(horizontal: 8.0),</span><br><span class="line">      decoration: new BoxDecoration(color: Colors.blue[500]),</span><br><span class="line">      // Row 是水平方向的线性布局（linear layout）</span><br><span class="line">      child: new Row(</span><br><span class="line">        //列表项的类型是 &lt;Widget&gt;</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">          new IconButton(</span><br><span class="line">            icon: new Icon(Icons.menu),</span><br><span class="line">            tooltip: &#x27;Navigation menu&#x27;,</span><br><span class="line">            onPressed: null, // null 会禁用 button</span><br><span class="line">          ),</span><br><span class="line">          // Expanded expands its child to fill the available space.</span><br><span class="line">          new Expanded(</span><br><span class="line">            child: title,</span><br><span class="line">          ),</span><br><span class="line">          new IconButton(</span><br><span class="line">            icon: new Icon(Icons.search),</span><br><span class="line">            tooltip: &#x27;Search&#x27;,</span><br><span class="line">            onPressed: null,</span><br><span class="line">          ),</span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class MyScaffold extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    // Material 是UI呈现的“一张纸”</span><br><span class="line">    return new Material(</span><br><span class="line">      // Column is 垂直方向的线性布局.</span><br><span class="line">      child: new Column(</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">          new MyAppBar(</span><br><span class="line">            title: new Text(</span><br><span class="line">              &#x27;Example title&#x27;,</span><br><span class="line">              style: Theme.of(context).primaryTextTheme.title,</span><br><span class="line">            ),</span><br><span class="line">          ),</span><br><span class="line">          new Expanded(</span><br><span class="line">            child: new Center(</span><br><span class="line">              child: new Text(&#x27;Hello, world!&#x27;),</span><br><span class="line">            ),</span><br><span class="line">          ),</span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void main() &#123;</span><br><span class="line">  runApp(new MaterialApp(</span><br><span class="line">    title: &#x27;My app&#x27;, // used by the OS task switcher</span><br><span class="line">    home: new MyScaffold(),</span><br><span class="line">  ));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;##看看历史&lt;br&gt;你想想一套代码，移动端、&lt;code&gt;web&lt;/code&gt;端、&lt;code&gt;pc&lt;/code&gt;端都搞定！这生产力！非常值得学习了。&lt;/p&gt;
&lt;p&gt;2015年5月 &lt;code&gt;Dart&lt;/code&gt; 开发者峰会上，亮相了基于&lt;code&gt;Dart &lt;/code&gt;</summary>
      
    
    
    
    <category term="Flutter开发" scheme="https://cubegao.com/categories/Flutter%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Flutter" scheme="https://cubegao.com/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>HTTP 协议中 GET 和 POST 的区别</title>
    <link href="https://cubegao.com/p/2019-02-02-get-post/"/>
    <id>https://cubegao.com/p/2019-02-02-get-post/</id>
    <published>2019-02-02T05:14:00.000Z</published>
    <updated>2026-06-19T05:52:21.716Z</updated>
    
    <content type="html"><![CDATA[<h3 id="关于HTTP"><a href="#关于HTTP" class="headerlink" title="关于HTTP"></a>关于HTTP</h3><p><code>HTTP</code>（超文本传输协议）是一种请求&#x2F;响应型的协议。</p><p>HTTP的<strong>语法</strong>大致如下：<br>客户端给服务端发送的请求，由三部分组成：<strong>请求行，消息报头，消息正文。</strong><br>请求行：<code>Method Request-URI HTTP-Version CRLF</code><br>例如：    <code>GET /form.html HTTP/1.1 /r/n</code><br>服务端给客户端发送的响应，也是三部分组成：<strong>状态行，消息报头，响应正文。</strong><br>状态行：<code>HTTP-Version Status-Code Reason-Phrase </code><br>例如:      <code>HTTP/1.1 200 OK /r/n</code></p><p>正写到这里，小A走过来说：<code>Request-URL</code>，你打错了一个字母？<br>其实没错！<br>1.<code>URI</code>的全称是<code>uniform resource identifier</code>，统一资源标识符，用来唯一的标识一个资源。<br>2.<code>URL</code>的全称是<code>uniform resource locator</code>，统一资源定位符。用来标识一个资源，还指明了如何定位这个资源。如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://www.qq.com/index.html</span><br></pre></td></tr></table></figure><p>3.<code>URN</code>的全称是<code>Uniform Resource Name</code>，统一资源名称。通过名称来标识资源。如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mailto:xxx@qq.com</span><br></pre></td></tr></table></figure><p>其中，<code>URL</code>,<code>URN</code>是<code>URI</code>的子集。</p><p>HTTP的请求方法有<code>OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT</code>等。其中<code>GET、POST、PUT、DELETE</code>对应着对当前资源的查、改、增、删4个操作。</p><h3 id="HTTP请求中的GET方法"><a href="#HTTP请求中的GET方法" class="headerlink" title="HTTP请求中的GET方法"></a>HTTP请求中的GET方法</h3><p><code>GET</code>方法的语义是获取资源，而且是安全，幂等和可缓存的。<br>1.<strong>安全</strong>，这里的安全不是通常理解的安全。所谓安全意味着该操作用于获取信息而非修改信息。换句话说，<code>GET</code>请求一般不应产生副作用。就是说，它仅仅是获取资源信息，就像数据库查询一样，不会修改，增加数据，不会影响资源的状态。<code>HTTP</code>请求方法中，<code>GET, HEAD, OPTIONS</code> 和 <code>TRACE </code>这几个方法是安全的。<br>2.<strong>幂等</strong>，指同一个请求方法执行多次和仅执行一次的效果完全相同。比如，刷微博的时候，内容是不断更新的。虽然第二次请求会返回不同的一批内容，该操作仍然被认为是安全的和幂等的，因为它总是返回当前的内容。从根本上说，如果目标是当用户打开一个链接时，他可以确信从自身的角度来看没有改变资源即可。<br>3.<strong>可缓存</strong>，指的是方法是可以被缓存的。<code>HTTP</code>请求方法中，<code>GET，HEAD</code>是可缓存的。（其实部分<code>POST</code>也是可缓存的，如果对当前资源来说是幂等的，那就可以缓存。一种实现是将<code>POST</code>的内容（附带一部分头信息），做一个摘要，将摘要附在<code>URL</code>后面，使用这个来作为缓存的<code>key</code>。换句话说，缓存主键被修改为包括<code>URL</code>以及一些请求体，后续的拥有相同的请求体的请求将会命中缓存。）</p><h3 id="HTTP请求中的POST方法"><a href="#HTTP请求中的POST方法" class="headerlink" title="HTTP请求中的POST方法"></a>HTTP请求中的POST方法</h3><p><code>POST</code>方法的语义是处理资源，或者说修改资源。还是微博为例，发微博的操作应该通过<code>POST</code>实现，因为在微博提交后站点的资源已经不同了，或者说资源被修改了。</p><h3 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h3><p>主要从两方面分析，语法和语义。<br>1.语法<br><code>POST</code>方法和<code>GET</code>方法的语法是一样的。因为它们都是<a href="#%E5%85%B3%E4%BA%8EHTTP">HTTP请求</a>。<br>2.语义<br><code>GET</code>的语义就是获取资源，<code>POST</code>的语义是处理资源，或修改资源。</p><p><strong>语义定义了这一类型的请求具有什么样的性质。那么在具体实现这两个方法时，就必须考虑其语义，做出符合其语义的行为。当然在符合语法的前提下实现违背语义的行为也是可以做到的，比如使用<code>GET</code>方法修改用户信息，<code>POST</code>获取资源列表，这样就只能说这个请求是合法的，但不是符合语义的。</strong></p><p><strong><code>GET</code>的语义是请求获取<code>Request-URI</code>指定的资源。<code>GET</code>方法是安全、幂等、可缓存的。<code>GET</code>方法的报文主体没有任何语义，所以一般服务器会直接忽略。<code>POST</code>的语义是根据请求负荷（报文主体）对指定的资源做出处理，具体的处理方式视资源类型而不同。<code>POST</code>不安全，不幂等，（大部分实现）不可缓存。</strong></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;关于HTTP&quot;&gt;&lt;a href=&quot;#关于HTTP&quot; class=&quot;headerlink&quot; title=&quot;关于HTTP&quot;&gt;&lt;/a&gt;关于HTTP&lt;/h3&gt;&lt;p&gt;&lt;code&gt;HTTP&lt;/code&gt;（超文本传输协议）是一种请求&amp;#x2F;响应型的协议。&lt;/p&gt;
&lt;p&gt;HT</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="GET" scheme="https://cubegao.com/tags/GET/"/>
    
    <category term="POST" scheme="https://cubegao.com/tags/POST/"/>
    
    <category term="HTTP" scheme="https://cubegao.com/tags/HTTP/"/>
    
  </entry>
  
  <entry>
    <title>聊聊 iOS 中的内存管理</title>
    <link href="https://cubegao.com/p/2018-08-23-ios-memory-management/"/>
    <id>https://cubegao.com/p/2018-08-23-ios-memory-management/</id>
    <published>2018-08-23T13:03:00.000Z</published>
    <updated>2026-06-19T05:52:21.715Z</updated>
    
    <content type="html"><![CDATA[<p>对于开发者来说，内存管理几乎是一个永恒的话题。</p><p>内存管理对于编写出高效率的<code>iOS APP</code>是非常重要的，这是因为<code>iOS</code>是多任务系统，在同一时刻可能有多个应用程序共享内存，有时为了使某个任务更好地执行，<code>iOS</code>系统可能会对其它任务分配的内存进行移动，甚至删除。</p><p>现在被广泛使用的内存管理机制主要有<code>GC</code>和<code>RC</code>两种。<br><code>GC</code>：垃圾回收机制，定期查找不再使用的对象，释放对象占用的内存。<br><code>RC</code>：引用计数机制，采用引用计数来管理对象的内存，当需要持有一个对象时，使它的引用计数+1，当不需要再持有一个对象的时候，使它的引用计数-1；当一个对象的引用计数为0时，该对象就会被销毁。</p><p><code>Objective-c</code>支持三种内存管理机制:<code>GC,MRC,ARC</code>。以前<code>MacOS</code>开发中是支持<code>GC</code>的，后面引入<code>ARC</code>后就弃用了<code>GC</code>机制。<code>iOS</code>开发中使用的是<code>RC</code>机制，从<code>MRC</code>到现在的<code>ARC</code>。</p><p>###引用计数<br>可以看我之前的一篇关于<a href="https://cubegao.com/archives/isa_rc.html">引用计数</a>。</p><p>###MRC时代<br>基本内存管理规则：<code>MRC</code>下是要严格遵守引用计数内存管理规则。<br>内存管理模型是基于对象的所有权。任何对象都可以拥有一个或者多个所有者，但是一个对象最少要拥有一个所有者，它才会继续存在。如果对象没有所有者的，它将会被销毁。</p><p>####四条内存管理策略：<br>1.自己生成的对象，自己持有<br><code>alloc/new/copy/mutableCopy</code>等方法创建的对象，我们自己直接持有，它的<code>RC</code>初始值为1，我们可以直接使用，不需要使用的时候调用<code>release</code>释放。</p><p>2.非自己生成的对象，自己也能持有。<br>不通过上面方法创建的对象，它的<code>RC</code>初始值也为1，但是我们不能直接使用，要先调用<code>retain</code>，否则可能会<code>crash</code>。原因是因为这些方法内部是给对象调用了<code>autorelease</code>方法，所以这些对象是被添加到了自动释放池中了。</p><p>两种情况，程序没有手动指定<code>@autoreleasepool</code>，当<code>runloop</code>迭代结束时，会自动给释放池中的对象调用<code>release</code>方法。如果我们使用前不进行<code>retain</code>，刚好<code>runloop</code>迭代结束，对象的RC从1变成0,然后就被销毁了.我们去访问一个已经被销毁的对象,程序就会<code>crash</code>。<br>程序手动指定了<code>@autoreleasepool</code>，当时出了其作用域，对象会调用<code>release</code>方法，然后销毁，这个时候使用对象也会<code>crash</code>。</p><p>3.不再需要自己持有的对象时，释放。<br>不再需要持有或者使用对象的时候，就调用<code>release</code>或者<code>autorelease</code>方法进行释放。<code>rc</code>变成0时，就会调用<code>dealloc</code>方法销毁。<br><code>release</code>立马<code>rc-1</code>，<code>autorelease</code>加入自动释放池，在合适的时候<code>rc-1</code>，实际上就是延迟释放。</p><p>4.非自己持有的对象，无需释放。<br>自己不是持有者，调用<code>release</code>的话，会<code>crash</code>。</p><p>####实用的内存管理原则<br>使用访问器方法可以省略大量的<code>retain</code>和<code>release</code><br>推荐使用访问器方法设置属性，一个是可以避免忘记写<code>retain</code>和<code>release</code>，第二个是触发<code>kvo</code><br>不要在初始化和dealloc中使用访问器方法，原因是在初始化中使用访问器方法，<code>self=[super init]</code>，首先会调用父类的访问器方法，然后如果子类重写了访问器方法，会去调用子类的访问器方法，而这个时候，子类实际上是没有初始化的，所以会出错。<code>dealloc</code>实际上是个反向的。就是子类先释放，再去调用父类。就是调用父类的<code>dealloc</code>时，会调用父类的访问器方法，如果子类重写了该访问器方法，就会去调用子类的访问器方法，但是子类已经释放掉了，也会出错。还有一个原因就是会触发<code>KVO</code>，在准备释放前，触发了<code>kvo</code>，即将释放的对象被持有了。就是出现莫名其妙的错误。<br>推荐使用<code>@autoreleasepool</code>，虽然<code>mrc</code>下，可以使用<code>NSautoreleasepool</code>，但是速度慢很多。<br>1.写的程序是非<code>UI</code>框架，比如命令行工具<br>2.循环创建大量的临时变量。<br>3.多线程中。比如创建辅助线程。</p><p>###ARC时代<br><code>ARC</code>时代，内存管理基本都交给编译器来处理了，对于开发者来说是十分友好了。<br><code>ARC</code>的编译器有两部分，前端编译器和优化器。前端编译器会对每一个对象插入相应的<code>release</code>语句。类拥有的对象会在<code>dealloc</code>中释放。方法内创建的对象，前端编译器会在方法末尾自动插入<code>release</code>语句销毁它。优化器负责移除多余的<code>retain</code>和<code>release</code>操作语句。</p><p>但是还是有些地方需要注意的。</p><p>新增的几个关键字：<br><code>__strong</code> 强引用,用来保证对象不会被释放。<br><code>__weak</code> 弱引用 释放时会置为nil<br><code>__unsafe_unretained</code> 弱引用  可能不安全，因为释放时不置为nil。悬垂对象。<br><code>__autoreleasing</code> 对象被注册到<code>autorelease pool</code>中方法在返回时自动释放。</p><p>####block使用中出现循环引用</p><figure class="highlight plaintext"><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">_person1 = [[Person alloc] init];</span><br><span class="line">_person2 = [[Person alloc] init];</span><br><span class="line">_person2.name = @&quot;张三&quot;;</span><br><span class="line">__weak __typeof(self) weakSelf = self;</span><br><span class="line">_person1.block = ^&#123;</span><br><span class="line">    __typeof(&amp;*weakSelf) strongSelf = weakSelf;</span><br><span class="line">    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^&#123;</span><br><span class="line">        NSLog(@&quot;%@&quot;,strongSelf.person2.name);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>使用weakSelf结合strongSelf的情况下，能够避免循环引用，也不会造成提前释放导致block内部代码无效。<br>在MRC下，应该使用__block。</p><p>####NSTimer循环引用<br>使用category处理。</p><figure class="highlight plaintext"><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">@implementation NSTimer (BlcokTimer)</span><br><span class="line"></span><br><span class="line">+ (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats &#123;</span><br><span class="line">    </span><br><span class="line">    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (void)bl_blockSelector:(NSTimer *)timer &#123;</span><br><span class="line">    </span><br><span class="line">    void(^block)(void) = timer.userInfo;</span><br><span class="line">    if (block) &#123;</span><br><span class="line">        block();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">@end</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用中间件，利用runtime，self依赖timer，time依赖中间件，中间件弱引用self</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">@interface WeakObject()</span><br><span class="line"></span><br><span class="line">@property (weak, nonatomic) id weakObject;</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation WeakObject</span><br><span class="line"></span><br><span class="line">- (instancetype)initWithWeakObject:(id)obj &#123;</span><br><span class="line">    _weakObject = obj;</span><br><span class="line">    return self;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (instancetype)proxyWithWeakObject:(id)obj &#123;</span><br><span class="line">    return [[WeakObject alloc] initWithWeakObject:obj];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 消息转发，让_weakObject响应事件</span><br><span class="line"> */</span><br><span class="line">- (id)forwardingTargetForSelector:(SEL)aSelector &#123;</span><br><span class="line">    return _weakObject;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (void)forwardInvocation:(NSInvocation *)invocation &#123;</span><br><span class="line">    void *null = NULL;</span><br><span class="line">    [invocation setReturnValue:&amp;null];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (BOOL)respondsToSelector:(SEL)aSelector &#123;</span><br><span class="line">    return [_weakObject respondsToSelector:aSelector];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用method swizzing，利用Method Swizzling，通过替换原有的接口，将循环引用的对象改为弱引用。</p><figure class="highlight plaintext"><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><br><span class="line">#define ClassMethodSwizzling(func0, func1) \</span><br><span class="line">&#123;\</span><br><span class="line">Method imp = class_getInstanceMethod(object_getClass([self class]), @selector(func0));\</span><br><span class="line">Method myImp = class_getInstanceMethod(object_getClass([self class]), @selector(func1));\</span><br><span class="line">method_exchangeImplementations(imp, myImp);\</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (void)load</span><br><span class="line">&#123;</span><br><span class="line">    //替换方法</span><br><span class="line">   ClassMethodSwizzling(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:, fc_scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo</span><br><span class="line">&#123;</span><br><span class="line">    typeof(aTarget) __weak weakTarget = aTarget;</span><br><span class="line">    </span><br><span class="line">    //创建类方法的引用，并指向中间函数</span><br><span class="line">    NSTimer *timer = [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(fc_blockInvoke:) userInfo:userInfo repeats:yesOrNo];</span><br><span class="line">    typeof(NSTimer *) __weak weakTime = timer;</span><br><span class="line">    //生成block</span><br><span class="line">    timer.fcTimerBlock = ^(void)&#123;</span><br><span class="line">        if (!weakTarget)</span><br><span class="line">        &#123;</span><br><span class="line">            return;</span><br><span class="line">        &#125;</span><br><span class="line">        IMP imp = [weakTarget methodForSelector:aSelector];</span><br><span class="line">        void (*func)(id, SEL, id) = (void *)imp;</span><br><span class="line">        func(weakTarget, aSelector,weakTime);</span><br><span class="line">    &#125;;</span><br><span class="line">    weakTime = timer;</span><br><span class="line">    return timer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;对于开发者来说，内存管理几乎是一个永恒的话题。&lt;/p&gt;
&lt;p&gt;内存管理对于编写出高效率的&lt;code&gt;iOS APP&lt;/code&gt;是非常重要的，这是因为&lt;code&gt;iOS&lt;/code&gt;是多任务系统，在同一时刻可能有多个应用程序共享内存，有时为了使某个任务更好地执行，&lt;code</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="iOS" scheme="https://cubegao.com/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>聊聊 isa 和引用计数</title>
    <link href="https://cubegao.com/p/2018-05-03-isa-rc/"/>
    <id>https://cubegao.com/p/2018-05-03-isa-rc/</id>
    <published>2018-05-03T04:43:00.000Z</published>
    <updated>2026-06-19T05:52:21.714Z</updated>
    
    <content type="html"><![CDATA[<p>大家都知道内存管理是通过引用计数管理对象的生命周期。</p><p>那<code>isa</code>和引用计数又有什么关系呢？先留着疑问。</p><p>###isa指针</p><figure class="highlight plaintext"><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">/// An opaque type that represents an Objective-C class.</span><br><span class="line">typedef struct objc_class *Class;</span><br><span class="line"></span><br><span class="line">/// Represents an instance of a class.</span><br><span class="line">struct objc_object &#123;</span><br><span class="line">    Class isa  OBJC_ISA_AVAILABILITY;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">/// A pointer to an instance of a class.</span><br><span class="line">typedef struct objc_object *id;</span><br></pre></td></tr></table></figure><p>通过注释和代码不难发现，我们创建的一个对象或实例其实就是一个<code>struct objc_object</code>结构体。</p><p>这个结构体只有一个成员变量，这是一个<code>Class</code>类型的变量<code>isa</code>，也是一个结构体指针，那这个指针又指向什么呢？</p><p>面向对象中每一个对象都必须依赖一个类来创建，因此对象的<code>isa</code>指针就指向对象所属的类根据这个类模板能够创建出实例变量、实例方法等。</p><p><img src="/images/2020/05/3674809831.jpg" alt="1975281-cc44a16eb3f252d3.jpg"><br>结合这张图片，就很好理解了。类方法的实现又是如何查找并且调用的呢？这时，就需要引入元类来保证无论是类还是对象都能通过相同的机制查找方法的实现。整个过程被设计成了一个闭环。</p><p>总结一下就是，<code>isa</code>指针是用来维护对象和类之间的关系，并确保对象和类能够通过<code>isa</code>指针找到对应的方法，实例变量，属性，协议等。在<code>arm64</code>架构之前，<code>isa</code>就是一个普通的指针，直接指向<code>objc_class</code>，存储着<code>Class、Meta-Class</code>对象的内存地址。<code>instance</code>对象的<code>isa</code>指向<code>class</code>对象，<code>class</code>对象的<code>isa</code>指向<code>meta-class</code>对象；</p><p>###引用计数<br>####arm64之前<br>在<code>arm64</code>架构之前，引用计数都是存储在<code>sidetable</code>这个数据结构中。</p><figure class="highlight plaintext"><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">SideTables(哈希表，key对象地址，value 是 SideTable)</span><br><span class="line">    |</span><br><span class="line">    |---SideTable</span><br><span class="line">    |       |</span><br><span class="line">    |       |---slock(自旋锁)</span><br><span class="line">    |       |</span><br><span class="line">    |       |---refcnts(引用计数哈希表, key 是对象地址，value是引用计数)</span><br><span class="line">    |       |</span><br><span class="line">    |       |---weak_table(弱引用哈希表，key是对象地址，value 是 entry)</span><br><span class="line">    |               |</span><br><span class="line">    |               |---entry(也可以理解为是哈希表，存储一个对象的所有弱引用，key 是弱引用地址(id*), value 也是弱引用地址)</span><br><span class="line">    |               |</span><br><span class="line">    |               |</span><br><span class="line">    |               |---entry</span><br><span class="line">    |               |      .</span><br><span class="line">    |               |      .</span><br><span class="line">    |               |      .</span><br><span class="line">    |</span><br><span class="line">    |</span><br><span class="line">    |---SideTable</span><br><span class="line">    |       .</span><br><span class="line">    |       .</span><br><span class="line">    |       .</span><br></pre></td></tr></table></figure><p><code>SideTables</code>可以理解为一个全局的<code>hash</code>数组，里面存储了<code>SideTable</code>类型的数据，其长度为64。</p><p><code>SideTable</code>的<code>RefCountMap</code>里面就存储着引用计数。</p><p>其中<code>weak_table</code>就是<code>weak</code>的具体实现。<code>weak</code>表其实是一个<code>hash</code>（哈希）表，<code>Key</code>是对象的内存地址，<code>Value</code>是指向该对象的所有弱引用的指针地址数组。</p><p>为什么<code>SideTables</code>被设计成多个<code>SideTable</code>组成的结构？<br>如果只有一个SideTable，那我们在内存中分配的所有对象的引用计数或者弱引用都放在这个SideTable中，那我们对对象的引用计数进行操作时，为了多线程安全就要加锁，就存在效率问题。<br>系统为了解决这个问题，就引入 “分离锁” 技术方案，提高访问效率。把对象的引用计数表分拆多个部分，对每个部分分别加锁，那么当所属不同部分的对象进行引用操作的时候，在多线程下就可以并发操作。所以，使用多个SideTable组成SideTables()结构。</p><p>####arm64之后<br>苹果为了进一步提高性能，优化了<code>isa</code>结构体指针。</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">struct objc_object &#123;</span><br><span class="line">private:</span><br><span class="line">    isa_t isa; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">union isa_t </span><br><span class="line">&#123;</span><br><span class="line">    isa_t() &#123; &#125;</span><br><span class="line">    isa_t(uintptr_t value) : bits(value) &#123; &#125;</span><br><span class="line"></span><br><span class="line">    Class cls;</span><br><span class="line">    uintptr_t bits;</span><br><span class="line"></span><br><span class="line">#if SUPPORT_PACKED_ISA</span><br><span class="line"></span><br><span class="line">    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)</span><br><span class="line">    // nonpointer must be the LSB (fixme or get rid of it)</span><br><span class="line">    // shiftcls must occupy the same bits that a real class pointer would</span><br><span class="line">    // bits + RC_ONE is equivalent to extra_rc + 1</span><br><span class="line">    // RC_HALF is the high bit of extra_rc (i.e. half of its range)</span><br><span class="line"></span><br><span class="line">    // future expansion:</span><br><span class="line">    // uintptr_t fast_rr : 1;     // no r/r overrides</span><br><span class="line">    // uintptr_t lock : 2;        // lock for atomic property, @synch</span><br><span class="line">    // uintptr_t extraBytes : 1;  // allocated with extra bytes</span><br><span class="line"></span><br><span class="line"># if __arm64__  // 在 __arm64__ 架构下</span><br><span class="line">#   define ISA_MASK        0x0000000ffffffff8ULL  // 用来取出 Class、Meta-Class 对象的内存地址</span><br><span class="line">#   define ISA_MAGIC_MASK  0x000003f000000001ULL</span><br><span class="line">#   define ISA_MAGIC_VALUE 0x000001a000000001ULL</span><br><span class="line">    struct &#123;</span><br><span class="line">        uintptr_t nonpointer        : 1;  // 0：代表普通的指针，存储着 Class、Meta-Class 对象的内存地址</span><br><span class="line">                                          // 1：代表优化过，使用位域存储更多的信息</span><br><span class="line">        uintptr_t has_assoc         : 1;  // 是否有设置过关联对象，如果没有，释放时会更快</span><br><span class="line">        uintptr_t has_cxx_dtor      : 1;  // 是否有C++的析构函数（.cxx_destruct），如果没有，释放时会更快</span><br><span class="line">        uintptr_t shiftcls          : 33; // 存储着 Class、Meta-Class 对象的内存地址信息</span><br><span class="line">        uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化</span><br><span class="line">        uintptr_t weakly_referenced : 1;  // 是否有被弱引用指向过，如果没有，释放时会更快</span><br><span class="line">        uintptr_t deallocating      : 1;  // 对象是否正在释放</span><br><span class="line">        uintptr_t has_sidetable_rc  : 1;  // 如果为1，代表引用计数过大无法存储在 isa 中，那么超出的引用计数会存储在一个叫 SideTable 结构体的 RefCountMap（引用计数表）散列表中</span><br><span class="line">        uintptr_t extra_rc          : 19; // 里面存储的值是对象本身之外的引用计数的数量，retainCount - 1</span><br><span class="line">#       define RC_ONE   (1ULL&lt;&lt;45)</span><br><span class="line">#       define RC_HALF  (1ULL&lt;&lt;18)</span><br><span class="line">    &#125;;</span><br><span class="line">......  // 在 __x86_64__ 架构下</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果<code>isa</code>非<code>nonpointer</code>，即 <code>arm64</code> 架构之前的<code>isa</code>指针。由于它只是一个普通的指针，存储着<code>Class、Meta-Class</code>对象的内存地址，所以它本身不能存储引用计数，所以以前对象的引用计数都存储在一个叫<code>SideTable</code>结构体的<code>RefCountMap</code>（引用计数表）散列表中。<br>如果<code>isa</code>是<code>nonpointer</code>，则它本身可以存储一些引用计数。从以上<code>union isa_t</code>的定义中我们可以得知，<code>isa_t</code>中存储了两个引用计数相关的东西：<code>extra_rc</code>和<code>has_sidetable_rc</code>。</p><p><code>extra_rc</code>：里面存储的值是对象本身之外的引用计数的数量，这 19 位如果不够存储，<code>has_sidetable_rc</code>的值就会变为 1；<br><code>has_sidetable_rc</code>：如果为 1，代表引用计数过大无法存储在<code>isa</code>中，那么超出的引用计数会存储<code>SideTable</code>的<code>RefCountMap</code>中。</p><p><code>retain</code>:如果<code>isa</code>是<code>nonpointer</code>，就将<code>isa</code>中的<code>extra_rc</code>存储的引用计数进行 +1，如果溢出，就将<code>extra_rc</code>中<code>RC_HALF</code>（<code>extra_rc</code>满值的一半）个引用计数转移到<code>sidetable</code>中存储。</p><p><code>release</code>:如果<code>isa</code>是<code>nonpointer</code>，就将<code>isa</code>中的<code>extra_rc</code>存储的引用计数进行 -1。如果下溢，即<code>extra_rc</code>中的引用计数已经为 0，判断<code>has_sidetable_rc</code>是否为<code>true</code>即是否有使用<code>Sidetable</code>存储。如果有的话就申请从<code>Sidetable</code>中申请<code>RC_HALF</code>个引用计数转移到<code>extra_rc</code>中存储，如果不足<code>RC_HALF</code>就有多少申请多少，然后将<code>Sidetable</code>中的引用计数值减去<code>RC_HALF</code>（或是小于<code>RC_HALF</code>的实际值），将实际申请到的引用计数值 -1 后存储到<code>extra_rc</code>中。如果<code>extra_rc</code>中引用计数为 0 且<code>has_sidetable_rc</code>为<code>false</code>或者<code>Sidetable</code>中的引用计数也为 0 了，那就<code>dealloc</code>对象。</p><p>所以，如果<code>isa</code>是<code>nonpointer</code>，则对象的引用计数存储在它的<code>isa_t</code>的<code>extra_rc</code>中以及<code>SideTable</code>的<code>RefCountMap</code>中。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;大家都知道内存管理是通过引用计数管理对象的生命周期。&lt;/p&gt;
&lt;p&gt;那&lt;code&gt;isa&lt;/code&gt;和引用计数又有什么关系呢？先留着疑问。&lt;/p&gt;
&lt;p&gt;###isa指针&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="isa" scheme="https://cubegao.com/tags/isa/"/>
    
  </entry>
  
  <entry>
    <title>在 Centos7.2 上安装编译 Swift</title>
    <link href="https://cubegao.com/p/2018-03-30-installing-and-compiling-swift-on-centos7-2/"/>
    <id>https://cubegao.com/p/2018-03-30-installing-and-compiling-swift-on-centos7-2/</id>
    <published>2018-03-30T08:10:00.000Z</published>
    <updated>2026-06-19T05:52:21.714Z</updated>
    
    <content type="html"><![CDATA[<p>由于想用Swift写服务玩玩，准备在Centos7.2上部署一个Swift环境，能跑一下Perfect。</p><p>由于Docker的种种好处，当然是选择它来部署Swift环境。</p><h3 id="安装Docker"><a href="#安装Docker" class="headerlink" title="安装Docker"></a>安装Docker</h3><h4 id="更新yum包到最新。如果是生产机器务必慎重更新内核，避免出现不必要的问题。"><a href="#更新yum包到最新。如果是生产机器务必慎重更新内核，避免出现不必要的问题。" class="headerlink" title="更新yum包到最新。如果是生产机器务必慎重更新内核，避免出现不必要的问题。"></a>更新yum包到最新。<code>如果是生产机器务必慎重更新内核，避免出现不必要的问题。</code></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[root@fadaixiaohai ~]# yum update</span><br></pre></td></tr></table></figure><h4 id="执行-Docker-安装脚本。"><a href="#执行-Docker-安装脚本。" class="headerlink" title="执行 Docker 安装脚本。"></a>执行 Docker 安装脚本。</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[root@fadaixiaohai ~]# curl -fsSL https://get.docker.com/ | sh</span><br></pre></td></tr></table></figure><h4 id="启动-Docker-进程。"><a href="#启动-Docker-进程。" class="headerlink" title="启动 Docker 进程。"></a>启动 Docker 进程。</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[root@fadaixiaohai ~]# service docker start</span><br></pre></td></tr></table></figure><h4 id="验证-docker-是否安装成功并在容器中执行一个测试的镜像。"><a href="#验证-docker-是否安装成功并在容器中执行一个测试的镜像。" class="headerlink" title="验证 docker 是否安装成功并在容器中执行一个测试的镜像。"></a>验证 docker 是否安装成功并在容器中执行一个测试的镜像。</h4><figure class="highlight bash"><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">[root@fadaixiaohai ~]# docker run hello-world</span><br><span class="line">Hello from Docker!</span><br></pre></td></tr></table></figure><p>大概两三分钟就安装好了，国内的服务器可能要用镜像加速会好点。</p><h3 id="安装Swift"><a href="#安装Swift" class="headerlink" title="安装Swift"></a>安装Swift</h3><h4 id="拉取Swift的镜像到本地。"><a href="#拉取Swift的镜像到本地。" class="headerlink" title="拉取Swift的镜像到本地。"></a>拉取Swift的镜像到本地。</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[root@fadaixiaohai ~]# docker pull swift</span><br></pre></td></tr></table></figure><p>镜像拉取成功后就可以使用docker images列出当前我们拉取到本地的所有镜像：</p><figure class="highlight bash"><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">[root@fadaixiaohai ~]# docker images</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">swift               latest              934835f58041        3 weeks ago         1.3GB</span><br><span class="line">hello-world         latest              f2a91732366c        4 months ago        1.85kB</span><br></pre></td></tr></table></figure><p>我们都知道，操作系统分为内核和用户空间。对于 Linux 而言，内核启动后，会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 root 文件系统。</p><p>镜像（Image）和容器（Container）的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p><p>容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间，甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里，使用起来，就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。</p><h4 id="在镜像里面创建一个容器并且连接它"><a href="#在镜像里面创建一个容器并且连接它" class="headerlink" title="在镜像里面创建一个容器并且连接它"></a>在镜像里面创建一个容器并且连接它</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[root@fadaixiaohai ~]# docker run  -it --name swiftfun swift /bin/bash</span><br></pre></td></tr></table></figure><p>其中<code>-it</code>意思是以交互式(interactive)终端(tty)的方式运行，<code>--name swiftfun</code>指定容器的名称，<code>/bin/bash</code>是容器启动后执行的命令，也就是进入bash控制台，运行成功后会进入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">root@8408a26c123b:/#</span><br></pre></td></tr></table></figure><p>默认是root用户，这里我们使用诸如<code>apt-get</code>命令时就不需要加sudo了</p><p>现在，你可以在容器中运行<code>swift --version</code>来确认是否配置成功：</p><figure class="highlight bash"><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">root@8408a26c123b:/# swift --version</span><br><span class="line">Swift version 4.0.3 (swift-4.0.3-RELEASE)</span><br><span class="line">Target: x86_64-unknown-linux-gnu</span><br></pre></td></tr></table></figure><p>然后用<code>swift</code>命令看是否能进入swift REPL：</p><figure class="highlight bash"><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">root@8408a26c123b:/# swift</span><br><span class="line">error: failed to launch REPL process: process launch failed: <span class="string">&#x27;A&#x27;</span> packet returned an error: 8</span><br></pre></td></tr></table></figure><p>发现报错了，找到Docker官方给的解决方案是：</p><figure class="highlight bash"><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">[root@fadaixiaohai ~]# docker run --cap-add sys_ptrace -it --<span class="built_in">rm</span> swift swift</span><br><span class="line">error: failed to launch REPL process: process launch failed: <span class="string">&#x27;A&#x27;</span> packet returned an error: 8</span><br></pre></td></tr></table></figure><p>运行时添加了<code>--cap-add sys_ptrace</code>选项，应该是添加一个系统的权限。加上<code>--rm</code>选项是创建新容器时，把已经存在的旧容器删掉。但是依然是相同的报错。</p><p>最后在github上面好到了解决方案。添加一条<code>--privileged</code>。它的含义是：<br>–privileged&#x3D;false Give extended privileges to this container 赋予容器扩展权限<br>运行结果如下：</p><figure class="highlight bash"><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">[root@fadaixiaohai ~]# docker run --cap-add sys_ptrace --privileged -it --<span class="built_in">rm</span> swift swift</span><br><span class="line">Welcome to Swift version 4.0.3 (swift-4.0.3-RELEASE). Type :<span class="built_in">help</span> <span class="keyword">for</span> assistance.</span><br><span class="line">1&gt; <span class="built_in">print</span>(<span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">hello world</span><br><span class="line">2&gt;</span><br></pre></td></tr></table></figure><p>以后想要连接swiftfun这个容器，必须先启动，后连接。方法如下：</p><figure class="highlight bash"><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">[root@fadaixiaohai ~]# docker start swiftfun</span><br><span class="line">swiftfun</span><br><span class="line">[root@fadaixiaohai ~]# docker attach swiftfun</span><br><span class="line">root@8408a26c123b:/#</span><br></pre></td></tr></table></figure><p>查看所有容器状态:</p><figure class="highlight bash"><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">[root@fadaixiaohai ~]# docker ps -a</span><br><span class="line">CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                          PORTS               NAMES</span><br><span class="line">8408a26c123b        swift               <span class="string">&quot;/bin/bash&quot;</span>         27 hours ago        Exited (0) About a minute ago                       swiftfun</span><br><span class="line">cc12559d586c        hello-world         <span class="string">&quot;/hello&quot;</span>            27 hours ago        Exited (0) 27 hours ago                             boring_yonath</span><br></pre></td></tr></table></figure><h3 id="设置Docker开机启动。"><a href="#设置Docker开机启动。" class="headerlink" title="设置Docker开机启动。"></a>设置Docker开机启动。</h3><figure class="highlight bash"><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">[root@fadaixiaohai ~]# systemctl <span class="built_in">enable</span> docker</span><br><span class="line">Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.</span><br></pre></td></tr></table></figure><p>以上就是本文的所有内容了，祝你玩得愉快！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;由于想用Swift写服务玩玩，准备在Centos7.2上部署一个Swift环境，能跑一下Perfect。&lt;/p&gt;
&lt;p&gt;由于Docker的种种好处，当然是选择它来部署Swift环境。&lt;/p&gt;
&lt;h3 id=&quot;安装Docker&quot;&gt;&lt;a href=&quot;#安装Docker&quot; cla</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="iOS开发" scheme="https://cubegao.com/tags/iOS%E5%BC%80%E5%8F%91/"/>
    
    <category term="Siwft" scheme="https://cubegao.com/tags/Siwft/"/>
    
    <category term="iOS" scheme="https://cubegao.com/tags/iOS/"/>
    
    <category term="Centos" scheme="https://cubegao.com/tags/Centos/"/>
    
    <category term="Docker" scheme="https://cubegao.com/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>利用 Travis CI 自动构建 Github Pages</title>
    <link href="https://cubegao.com/p/2018-02-06-Travis-CI/"/>
    <id>https://cubegao.com/p/2018-02-06-Travis-CI/</id>
    <published>2018-02-06T11:54:00.000Z</published>
    <updated>2026-06-19T05:52:21.713Z</updated>
    
    <content type="html"><![CDATA[<p>###背景</p><p>每次写完<code>blog</code>,都要在本地<code>hexo</code>环境编译，等编译完成后，再<code>push</code>到<code>github</code>，重复的动作，浪费的时间。</p><p>懒惰是互联网进步的第一动力！我能不能每次写完<code>md</code>，直接<code>push</code>到<code>github</code>，然后它自动编译，编译完成后，自己<code>push</code>到仓库主分支呢？肯定是可以的，答案就是使用<code>Travis CI</code>自动构建工具。</p><p>###关联Github和Travis CI<br><code>Github</code> 有提供一个 <code>Personal access tokens</code>，这个 <code>Token</code> 与 账号密码 以及 <code>SSH Keys</code> 同样具有 <code>Github </code>写入能力。</p><p>前往 <code>Github</code> 帐号 <code>Settings</code> 页面，在左侧选择 <code>Personal Access Token</code>，然后在右侧面板点击 <code>“Generate new token”</code> 来新建一个 <code>Token</code>。需要注意的是，创建完的 <code>Token</code> 只有第一次可见，之后再访问就无法看见（只能看见他的名称），因此要保存好这个值。<br><img src="/images/2020/05/4152391612.jpg" alt="333123.jpg"></p><p>###登录Travis CI关联对应项目<br>首先打开官方网站 <code>travis-ci.org</code>，然后使用<code> Github</code> 账号登入 <code>Travis CI</code>，然后 <code>Travis</code> 中会列出你<code> Github</code> 上面所有的仓库，以及你所属于的组织。</p><p>然后，勾选你需要 <code>Travis</code> 帮你自动构建的仓库，打开仓库旁边的开关，打开以后，<code>Travis </code>就会监听这个仓库的所有变化了。<br>![E1BF98EB2CB8D8E4E6E1F6E8E3BDC5EA.jpg][2]</p><p>###travis.yml配置<br>原理就是监控对应的文件,有<code>push</code>就开始构建。</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">language: node_js # 声明环境为node</span><br><span class="line">node_js: stable</span><br><span class="line"></span><br><span class="line"># Travis-CI Caching</span><br><span class="line">cache:</span><br><span class="line">directories:</span><br><span class="line">- node_modules # 缓存node_modules文件夹</span><br><span class="line"></span><br><span class="line"># S: Build Lifecycle</span><br><span class="line">before_install:</span><br><span class="line">- export TZ=&#x27;Asia/Shanghai&#x27; # 更改时区</span><br><span class="line">- npm install hexo-cli -g</span><br><span class="line"></span><br><span class="line">install:</span><br><span class="line">- npm install # 下载依赖</span><br><span class="line"></span><br><span class="line">script:</span><br><span class="line">- hexo clean</span><br><span class="line">- hexo g</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">after_script: # 推送到github的部分</span><br><span class="line">- git clone https://$&#123;GH_REF&#125; .deploy_git</span><br><span class="line">- cd .deploy_git</span><br><span class="line">- git checkout master</span><br><span class="line">- cd ../</span><br><span class="line">- mv .deploy_git/.git/ ./public/</span><br><span class="line">- cd ./public</span><br><span class="line">- git config user.name &quot;xxxxxxx&quot;</span><br><span class="line">- git config user.email &quot;xxxxxxxx@gmail.com&quot;</span><br><span class="line">- git add .</span><br><span class="line">- git commit -m &quot;Travis CI Auto Builder at `date +&quot;%Y-%m-%d %H:%M&quot;`&quot;</span><br><span class="line">- git push --force --quiet &quot;https://$&#123;GH_TOKEN&#125;@$&#123;GH_REF&#125;&quot; master:master</span><br><span class="line"></span><br><span class="line"># E: Build LifeCycle</span><br><span class="line">branches:</span><br><span class="line">only:</span><br><span class="line">- hexo # 只对hexo分支构建</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>###Build Config</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;only&quot;: [</span><br><span class="line">    &quot;hexo&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;script&quot;: [</span><br><span class="line">    &quot;hexo clean&quot;,</span><br><span class="line">    &quot;hexo g&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;install&quot;: [</span><br><span class="line">    &quot;npm install&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;node_js&quot;: &quot;stable&quot;,</span><br><span class="line">  &quot;language&quot;: &quot;node_js&quot;,</span><br><span class="line">  &quot;directories&quot;: [</span><br><span class="line">    &quot;node_modules&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;after_script&quot;: [</span><br><span class="line">    &quot;git clone https://$&#123;GH_REF&#125; .deploy_git&quot;,</span><br><span class="line">    &quot;cd .deploy_git&quot;,</span><br><span class="line">    &quot;git checkout master&quot;,</span><br><span class="line">    &quot;cd ../&quot;,</span><br><span class="line">    &quot;mv .deploy_git/.git/ ./public/&quot;,</span><br><span class="line">    &quot;cd ./public&quot;,</span><br><span class="line">    &quot;git config user.name \&quot;xxxxxxxx\&quot;&quot;,</span><br><span class="line">    &quot;git config user.email \&quot;xxxxxxx@gmail.com\&quot;&quot;,</span><br><span class="line">    &quot;git add .&quot;,</span><br><span class="line">    &quot;git commit -m \&quot;Travis CI Auto Builder at `date +\&quot;%Y-%m-%d %H:%M\&quot;`\&quot;&quot;,</span><br><span class="line">    &quot;git push --force --quiet \&quot;https://$&#123;GH_TOKEN&#125;@$&#123;GH_REF&#125;\&quot; master:master&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;before_install&quot;: [</span><br><span class="line">    &quot;export TZ=&#x27;Asia/Shanghai&#x27;&quot;,</span><br><span class="line">    &quot;npm install hexo-cli -g&quot;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;###背景&lt;/p&gt;
&lt;p&gt;每次写完&lt;code&gt;blog&lt;/code&gt;,都要在本地&lt;code&gt;hexo&lt;/code&gt;环境编译，等编译完成后，再&lt;code&gt;push&lt;/code&gt;到&lt;code&gt;github&lt;/code&gt;，重复的动作，浪费的时间。&lt;/p&gt;
&lt;p&gt;懒惰是互联网进步的</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="github" scheme="https://cubegao.com/tags/github/"/>
    
    <category term="自动化" scheme="https://cubegao.com/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>读源码之 SDWebImage</title>
    <link href="https://cubegao.com/p/2017-11-02-sdwebimage/"/>
    <id>https://cubegao.com/p/2017-11-02-sdwebimage/</id>
    <published>2017-11-02T10:51:00.000Z</published>
    <updated>2026-06-19T05:52:21.712Z</updated>
    
    <content type="html"><![CDATA[<p>###背景<br><code>SDWebImage</code>是<code>iOS</code>开发过程中最常使用的一个网络图片库，包括图片的下载和缓存。</p><p>主要提供的功能如下：</p><p>1.提供<code>UIImageView</code>, <code>UIButton</code>,<code> MKAnnotationView</code> 的分类，用来加载网络图片，并进行缓存管理;<br>2.异步方式来下载网络图片<br>3.异步方式: <code>memory</code> (内存)＋ <code>disk</code> (磁盘) 来缓存网络图片，<code>LRU</code>自动管理缓存;<br>4.后台图片解码,转换及压缩;<br>5.同一个 <code>URL</code> 不会重复下载;<br>6.失效的 <code>URL</code> 不会被无限重试;<br>7.支持 <code>GIF</code>动画 及<code> WebP</code> 格式;<br>8.使用 <code>GCD </code>和 <code>ARC</code>;<br>9.开启子线程进行耗时操作，不阻塞主线程;</p><p>###入口部分<br>在使用<code>SDWebImage</code>的时候，入口如下：</p><figure class="highlight plaintext"><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">[self.imageView sd_setImageWithURL:[NSURL URLWithString:@&quot;url&quot;] </span><br><span class="line">                placeholderImage:[UIImage imageNamed:@&quot;placeholder.png&quot;]];</span><br></pre></td></tr></table></figure><p>点进去，可以看到下面这些方法：</p><figure class="highlight plaintext"><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">- (void)sd_setImageWithURL:(NSURL *)url;</span><br><span class="line">- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;</span><br><span class="line">- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;</span><br><span class="line">...</span><br><span class="line">- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;</span><br></pre></td></tr></table></figure><p>作为入口函数，在 <code>sd_setImageWithURL</code> 方法中采用了多种参数灵活搭配的同名方法。而内部实质，都在向最后一个 <code>sd_setImageWithURL</code> 传入参数最多的方法进行调用处理。可以清晰的梳理函数构造逻辑，减轻代码编写量。</p><p>###下载部分<br>整个下载管理器对于下载请求的管理都是放在<code>downloadImageWithURL:options:progress:completed:</code>方法里面来处理的，该方法调用了上面所提到的<code>addProgressCallback:andCompletedBlock:forURL:createCallback:</code>方法来将请求的信息存入管理器中，同时在创建回调的<code>block</code>中创建新的操作，配置之后将其放入<code>downloadQueue</code>操作队列中，最后方法返回新创建的操作。每个下载操作的超时时间可以通过downloadTimeout属性来设置，默认值为15秒。</p><p>其具体实现如下：</p><figure class="highlight plaintext"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">- (id &lt;SDWebImageOperation&gt;)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock &#123;</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^&#123;</span><br><span class="line">        ...</span><br><span class="line">        </span><br><span class="line">        // 1. 创建请求对象，并根据options参数设置其属性</span><br><span class="line">        // 为了避免潜在的重复缓存(NSURLCache + SDImageCache)，如果没有明确告知需要缓存，则禁用图片请求的缓存操作</span><br><span class="line">        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options &amp; SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];</span><br><span class="line">        ...</span><br><span class="line">        </span><br><span class="line">        // 2. 创建SDWebImageDownloaderOperation操作对象，并进行配置</span><br><span class="line">        // 配置信息包括是否需要认证、优先级</span><br><span class="line">        operation = [[wself.operationClass alloc] initWithRequest:request</span><br><span class="line">                                                          options:options</span><br><span class="line">                                   progress:^(NSInteger receivedSize, NSInteger expectedSize) &#123;</span><br><span class="line">                      // 3. 从管理器的callbacksForURL中找出该URL所有的进度处理回调并调用</span><br><span class="line">                       ...</span><br><span class="line">                    for (NSDictionary *callbacks in callbacksForURL) &#123;</span><br><span class="line">                  SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];</span><br><span class="line">                   if (callback) callback(receivedSize, expectedSize);</span><br><span class="line">                                                             &#125;</span><br><span class="line">                                                         &#125;</span><br><span class="line">               completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) &#123;</span><br><span class="line">               // 4. 从管理器的callbacksForURL中找出该URL所有的完成处理回调并调用，</span><br><span class="line">               // 如果finished为YES，则将该url对应的回调信息从URLCallbacks中删除</span><br><span class="line">                 ...</span><br><span class="line">                 if (finished) &#123;</span><br><span class="line">                 [sself removeCallbacksForURL:url];</span><br><span class="line">                 &#125;</span><br><span class="line">                  for (NSDictionary *callbacks in callbacksForURL) &#123;</span><br><span class="line">                  SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];</span><br><span class="line">                  if (callback) callback(image, data, error, finished);</span><br><span class="line">                                                            &#125;</span><br><span class="line">                                                        &#125;</span><br><span class="line">                                                        cancelled:^&#123;</span><br><span class="line">                                                        // 5. 取消操作将该url对应的回调信息从URLCallbacks中删除</span><br><span class="line">                                                            SDWebImageDownloader *sself = wself;</span><br><span class="line">                                                            if (!sself) return;</span><br><span class="line">                                                            [sself removeCallbacksForURL:url];</span><br><span class="line">                                                        &#125;];</span><br><span class="line">        </span><br><span class="line">        ...</span><br><span class="line">        </span><br><span class="line">        // 6. 将操作加入到操作队列downloadQueue中</span><br><span class="line">// 如果是LIFO顺序，则将新的操作作为原队列中最后一个操作的依赖，然后将新操作设置为最后一个操作</span><br><span class="line">        [wself.downloadQueue addOperation:operation];</span><br><span class="line">        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) &#123;</span><br><span class="line">            [wself.lastAddedOperation addDependency:operation];</span><br><span class="line">            wself.lastAddedOperation = operation;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;];</span><br><span class="line">    </span><br><span class="line">    return operation;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>###缓存部分<br>为了减少网络流量的消耗，我们都希望下载下来的图片缓存到本地，下次再去获取同一张图片时，可以直接从本地获取，而不再从远程服务器获取。这样做的另一个好处是提升了用户体验，用户第二次查看同一幅图片时，能快速从本地获取图片直接呈现给用户。</p><p>SDWebImage提供了对图片缓存的支持，而该功能是由SDImageCache类来完成的。该类负责处理内存缓存及一个可选的磁盘缓存。其中磁盘缓存的写操作是异步的，这样就不会对UI操作造成影响。</p><figure class="highlight plaintext"><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">- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk &#123;</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    // 1. 内存缓存，将其存入NSCache中，同时传入图片的消耗值</span><br><span class="line">    [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale * image.scale];</span><br><span class="line">    </span><br><span class="line">    if (toDisk) &#123;</span><br><span class="line">        // 2. 如果确定需要磁盘缓存，则将缓存操作作为一个任务放入ioQueue中</span><br><span class="line">        dispatch_async(self.ioQueue, ^&#123;</span><br><span class="line">            NSData *data = imageData;</span><br><span class="line">            </span><br><span class="line">            if (image &amp;&amp; (recalculate || !data)) &#123;</span><br><span class="line">#if TARGET_OS_IPHONE</span><br><span class="line">                </span><br><span class="line">                // 3. 需要确定图片是PNG还是JPEG。PNG图片容易检测，因为有一个唯一签名。PNG图像的前8个字节总是包含以下值：137 80 78 71 13 10 26 10</span><br><span class="line">                // 在imageData为nil的情况下假定图像为PNG。我们将其当作PNG以避免丢失透明度。而当有图片数据时，我们检测其前缀，确定图片的类型</span><br><span class="line">                BOOL imageIsPng = YES;</span><br><span class="line">                </span><br><span class="line">                if ([imageData length] &gt;= [kPNGSignatureData length]) &#123;</span><br><span class="line">                    imageIsPng = ImageDataHasPNGPreffix(imageData);</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                if (imageIsPng) &#123;</span><br><span class="line">                    data = UIImagePNGRepresentation(image);</span><br><span class="line">                &#125;</span><br><span class="line">                else &#123;</span><br><span class="line">                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);</span><br><span class="line">                &#125;</span><br><span class="line">#else</span><br><span class="line">                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];</span><br><span class="line">#endif</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            // 4. 创建缓存文件并存储图片</span><br><span class="line">            if (data) &#123;</span><br><span class="line">                if (![_fileManager fileExistsAtPath:_diskCachePath]) &#123;</span><br><span class="line">                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                [_fileManager createFileAtPath:[self defaultCachePathForKey:key] contents:data attributes:nil];</span><br><span class="line">            &#125;</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>缓存到了本地，怎么查找缓存呢？代码如下：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock &#123;</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    // 1. 首先查看内存缓存，如果查找到，则直接回调doneBlock并返回</span><br><span class="line">    UIImage *image = [self imageFromDiskCacheForKey:key];</span><br><span class="line">    if (image) &#123;</span><br><span class="line">        doneBlock(image, SDImageCacheTypeMemory);</span><br><span class="line">        return nil;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    // 2. 如果内存中没有，则在磁盘中查找。如果找到，则将其放到内存缓存，并调用doneBlock回调</span><br><span class="line">    NSOperation *operation = [NSOperation new];</span><br><span class="line">    dispatch_async(self.ioQueue, ^&#123;</span><br><span class="line">        if (operation.isCancelled) &#123;</span><br><span class="line">            return;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        @autoreleasepool &#123;</span><br><span class="line">            UIImage *diskImage = [self diskImageForKey:key];</span><br><span class="line">            if (diskImage) &#123;</span><br><span class="line">                CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale * diskImage.scale;</span><br><span class="line">                [self.memCache setObject:diskImage forKey:key cost:cost];</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            dispatch_async(dispatch_get_main_queue(), ^&#123;</span><br><span class="line">                doneBlock(diskImage, SDImageCacheTypeDisk);</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    return operation;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而部分清理则是根据我们设定的一些参数值来移除一些文件，这里主要有两个指标：文件的缓存有效期及最大缓存空间大小。文件的缓存有效期可以通过maxCacheAge属性来设置，默认是1周的时间。如果文件的缓存时间超过这个时间值，则将其移除。而最大缓存空间大小是通过maxCacheSize属性来设置的，如果所有缓存文件的总大小超过这一大小，则会按照文件最后修改时间的逆序，以每次一半的递归来移除那些过早的文件，直到缓存的实际大小小于我们设置的最大使用空间。清理的操作在<code>-cleanDiskWithCompletionBlock:</code>方法中，其实现如下:</p><figure class="highlight plaintext"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock &#123;</span><br><span class="line">    dispatch_async(self.ioQueue, ^&#123;</span><br><span class="line">        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];</span><br><span class="line">        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];</span><br><span class="line">        </span><br><span class="line">        // 1. 该枚举器预先获取缓存文件的有用的属性</span><br><span class="line">        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL</span><br><span class="line">                                                   includingPropertiesForKeys:resourceKeys</span><br><span class="line">                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles</span><br><span class="line">                                                                 errorHandler:NULL];</span><br><span class="line">        </span><br><span class="line">        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];</span><br><span class="line">        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];</span><br><span class="line">        NSUInteger currentCacheSize = 0;</span><br><span class="line">        </span><br><span class="line">        // 2. 枚举缓存文件夹中所有文件，该迭代有两个目的：移除比过期日期更老的文件；存储文件属性以备后面执行基于缓存大小的清理操作</span><br><span class="line">        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];</span><br><span class="line">        for (NSURL *fileURL in fileEnumerator) &#123;</span><br><span class="line">            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];</span><br><span class="line">            </span><br><span class="line">            // 3. 跳过文件夹</span><br><span class="line">            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) &#123;</span><br><span class="line">                continue;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            // 4. 移除早于有效期的老文件</span><br><span class="line">            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];</span><br><span class="line">            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) &#123;</span><br><span class="line">                [urlsToDelete addObject:fileURL];</span><br><span class="line">                continue;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            // 5. 存储文件的引用并计算所有文件的总大小，以备后用</span><br><span class="line">            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];</span><br><span class="line">            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];</span><br><span class="line">            [cacheFiles setObject:resourceValues forKey:fileURL];</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        for (NSURL *fileURL in urlsToDelete) &#123;</span><br><span class="line">            [_fileManager removeItemAtURL:fileURL error:nil];</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        // 6.如果磁盘缓存的大小大于我们配置的最大大小，则执行基于文件大小的清理，我们首先删除最老的文件</span><br><span class="line">        if (self.maxCacheSize &gt; 0 &amp;&amp; currentCacheSize &gt; self.maxCacheSize) &#123;</span><br><span class="line">            // 7. 以设置的最大缓存大小的一半作为清理目标</span><br><span class="line">            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;</span><br><span class="line">            </span><br><span class="line">            // 8. 按照最后修改时间来排序剩下的缓存文件</span><br><span class="line">            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent</span><br><span class="line">                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) &#123;</span><br><span class="line">                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];</span><br><span class="line">                                                            &#125;];</span><br><span class="line">            </span><br><span class="line">            // 9. 删除文件，直到缓存总大小降到我们期望的大小</span><br><span class="line">            for (NSURL *fileURL in sortedFiles) &#123;</span><br><span class="line">                if ([_fileManager removeItemAtURL:fileURL error:nil]) &#123;</span><br><span class="line">                    NSDictionary *resourceValues = cacheFiles[fileURL];</span><br><span class="line">                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];</span><br><span class="line">                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];</span><br><span class="line">                    </span><br><span class="line">                    if (currentCacheSize &lt; desiredCacheSize) &#123;</span><br><span class="line">                        break;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">                        if (completionBlock) &#123;</span><br><span class="line">            dispatch_async(dispatch_get_main_queue(), ^&#123;</span><br><span class="line">                completionBlock();</span><br><span class="line">            &#125;);</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>###总结<br>1.把图片的解码工作放到子线程（<code>SDWebImageDecoder</code>）。<br>2.在主线程中完成重采样的工作。重采样算法一般有： <code>Nearest Neighbour Resampling </code>（最邻近重采样）、 <code>Bilinear Resampling</code>（双线性&#x2F;两次线性重采样）、<code>Bicubic Resampling</code> （双立方&#x2F;两次立方重采样）等。<br>3.利用空间换时间的做法，在子线程中解码图片并缓存位图结果，避免图片的重复解码，提升图片展示性能。如果列表中需要展示很多网络图片，<code>SDWebImage</code>这种做法，有利于提高列表的流畅度。<br>4.<code>SDWebImage</code>中下载的图片，即使解码缩放(<code>decodedAndScaledDownImageWithImage:</code>)后，图片大小未必和<code>imageView</code>的大小相同，这会引发重采样，我们可以在图片显示前，将图片裁剪成和imageView的大小相同，提升性能（一个小的优化点）。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;###背景&lt;br&gt;&lt;code&gt;SDWebImage&lt;/code&gt;是&lt;code&gt;iOS&lt;/code&gt;开发过程中最常使用的一个网络图片库，包括图片的下载和缓存。&lt;/p&gt;
&lt;p&gt;主要提供的功能如下：&lt;/p&gt;
&lt;p&gt;1.提供&lt;code&gt;UIImageView&lt;/code&gt;, &lt;co</summary>
      
    
    
    
    <category term="iOS开发" scheme="https://cubegao.com/categories/iOS%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="源码" scheme="https://cubegao.com/tags/%E6%BA%90%E7%A0%81/"/>
    
  </entry>
  
  <entry>
    <title>Swift.数字在排序数组中出现的次数</title>
    <link href="https://cubegao.com/p/2017-10-06-find-nums-of-k/"/>
    <id>https://cubegao.com/p/2017-10-06-find-nums-of-k/</id>
    <published>2017-10-06T03:03:00.000Z</published>
    <updated>2026-06-19T05:52:21.711Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>题目描述：统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3，由于3在这个数组中出现了4次，因此输出4。</p></blockquote><figure class="highlight plaintext"><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><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line">import Foundation</span><br><span class="line"></span><br><span class="line">class For38Solution &#123;</span><br><span class="line">    func findNumsOfK(_ nums: [Int],_ k: Int) -&gt; Int &#123;</span><br><span class="line">        </span><br><span class="line">        var n = nums</span><br><span class="line">        let fisrt = getFirstK(&amp;n, k, 0, n.count-1)</span><br><span class="line">        let last = getLastK(&amp;n, k, 0, n.count-1)</span><br><span class="line">        </span><br><span class="line">        if fisrt == -1 || last == -1 &#123;</span><br><span class="line">            //没找到</span><br><span class="line">            return -1</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        return last - fisrt + 1</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    func getFirstK(_ nums: inout [Int],_ k: Int,_ start1: Int,_ end1: Int) -&gt; Int &#123;</span><br><span class="line">        </span><br><span class="line">        var start = start1</span><br><span class="line">        var end = end1</span><br><span class="line"></span><br><span class="line">        if start &gt; end &#123;</span><br><span class="line">            return -1</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        let mid = (start + end) / 2</span><br><span class="line">        let midNum = nums[mid]</span><br><span class="line">        </span><br><span class="line">        if midNum == k &#123;</span><br><span class="line">            </span><br><span class="line">            if mid == 0 || (mid &gt; 0 &amp;&amp; nums[mid - 1] != k) &#123;</span><br><span class="line">                return mid</span><br><span class="line">            &#125;else &#123;</span><br><span class="line">                end = mid - 1</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;else if midNum &gt; k &#123;</span><br><span class="line">            end = mid - 1</span><br><span class="line">        &#125;else &#123;</span><br><span class="line">            start = mid + 1</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        return getFirstK(&amp;nums, k, start, end)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    func getLastK(_ nums: inout [Int],_ k: Int,_ start1: Int,_ end1: Int) -&gt; Int &#123;</span><br><span class="line">     </span><br><span class="line">        var start = start1</span><br><span class="line">        var end = end1</span><br><span class="line">        </span><br><span class="line">        if start &gt; end &#123;</span><br><span class="line">            return -1</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        let mid = (start + end) / 2</span><br><span class="line">        let midNum = nums[mid]</span><br><span class="line">        </span><br><span class="line">        if midNum == k &#123;</span><br><span class="line">            if mid == end || (mid &lt; end &amp;&amp; nums[mid+1] != k) &#123;</span><br><span class="line">                return mid</span><br><span class="line">            &#125;else &#123;</span><br><span class="line">                start = mid + 1</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;else if midNum &lt; k &#123;</span><br><span class="line">            start = mid + 1</span><br><span class="line">        &#125;else &#123;</span><br><span class="line">            end = mid - 1</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        return getLastK(&amp;nums, k, start, end)</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法思想：如果不考虑数字会重复，那就错了。实际上就是topK的变种题，而且这个K要自己来求，实际解法是FirstK+LastK。</p><p>github地址：<a href="https://github.com/cubegao/LeetCode/tree/swift/LeetCode/ForOffer">https://github.com/cubegao/LeetCode</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;题目描述：统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3，由于3在这个数组中出现了4次，因此输出4。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight plaint</summary>
      
    
    
    
    <category term="LeetCode" scheme="https://cubegao.com/categories/LeetCode/"/>
    
    
    <category term="算法" scheme="https://cubegao.com/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>Swift.数组中只出现一次的数字</title>
    <link href="https://cubegao.com/p/2017-05-02-appear-once/"/>
    <id>https://cubegao.com/p/2017-05-02-appear-once/</id>
    <published>2017-05-02T04:43:00.000Z</published>
    <updated>2026-06-19T05:52:21.711Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>题目描述：求数组中只出现一次的数字</p></blockquote><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import Foundation</span><br><span class="line"></span><br><span class="line">class For40Solution &#123;</span><br><span class="line">    func appearOnce(_ nums: [Int]) -&gt; Int &#123;</span><br><span class="line">        </span><br><span class="line">        var n = nums[0]</span><br><span class="line">        for i in 1..&lt;nums.count &#123;</span><br><span class="line">            n ^= nums[i]</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        return n</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法思想：这是假设数组中只出现一次的数字只有一个，那就直接异或，最后的这个数就是结果。<br>如果只出现一次的数字有两个呢？那就先异或，得出两个数的异或结果，然后从低位向高位找第一个为1的位。然后根据这一位为1和0把数组分成两组，然后两组数分别异或，就得出最后的结果了。因为异或，不同才为1。所以找不为1的那位，也就证明两个数在那一位肯定不同，所以能把两个数分到不同的数组。</p><p>github地址：<a href="https://github.com/cubegao/LeetCode/tree/swift/LeetCode/ForOffer">https://github.com/cubegao/LeetCode</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;题目描述：求数组中只出现一次的数字&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line</summary>
      
    
    
    
    <category term="LeetCode" scheme="https://cubegao.com/categories/LeetCode/"/>
    
    
    <category term="算法" scheme="https://cubegao.com/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
</feed>
