<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://shino.space/feed.xml" rel="self" type="application/atom+xml" /><link href="https://shino.space/" rel="alternate" type="text/html" /><updated>2025-12-21T15:17:35+00:00</updated><id>https://shino.space/feed.xml</id><title type="html">shino</title><subtitle>shino.space - shino 的个人网站</subtitle><author><name>shino</name><email>shino@shino.space</email></author><entry><title type="html">Xcode 中的 Diagnostic Tools</title><link href="https://shino.space/2019/Xcode%E4%B8%AD%E7%9A%84Diagnostic-Tools/" rel="alternate" type="text/html" title="Xcode 中的 Diagnostic Tools" /><published>2019-01-05T05:42:35+00:00</published><updated>2019-01-05T05:42:35+00:00</updated><id>https://shino.space/2019/Xcode%E4%B8%AD%E7%9A%84Diagnostic-Tools</id><content type="html" xml:base="https://shino.space/2019/Xcode%E4%B8%AD%E7%9A%84Diagnostic-Tools/"><![CDATA[<p>好久没更新了, 实习做的是 crash 处理和性能提升有关的东西, 底层和 debug 看得比较多, 以后周末有空更一些.</p>

<!-- more -->

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#section-1" id="markdown-toc-section-1">代码诊断</a>    <ul>
      <li><a href="#section-2" id="markdown-toc-section-2">地址诊断</a></li>
      <li><a href="#section-3" id="markdown-toc-section-3">线程诊断</a></li>
      <li><a href="#section-4" id="markdown-toc-section-4">未定义行为诊断</a></li>
      <li><a href="#section-5" id="markdown-toc-section-5">主线程检查</a></li>
    </ul>
  </li>
  <li><a href="#section-6" id="markdown-toc-section-6">内存管理</a>    <ul>
      <li><a href="#malloc-" id="markdown-toc-malloc-">malloc 填充</a></li>
      <li><a href="#malloc--1" id="markdown-toc-malloc--1">malloc 保护页</a></li>
      <li><a href="#malloc--2" id="markdown-toc-malloc--2">malloc 保护</a></li>
      <li><a href="#section-7" id="markdown-toc-section-7">僵尸对象</a></li>
    </ul>
  </li>
  <li><a href="#section-8" id="markdown-toc-section-8">日志</a>    <ul>
      <li><a href="#malloc--3" id="markdown-toc-malloc--3">malloc 调用栈</a></li>
      <li><a href="#api-" id="markdown-toc-api-">动态链接器 API 调用</a></li>
      <li><a href="#section-9" id="markdown-toc-section-9">动态链接器库加载</a></li>
    </ul>
  </li>
</ul>

<p>Xcode 中 scheme 设置里面有一栏 Diagnostics 选项, 方便进行调试, 可以分为代码诊断, 内存管理, 和日志三部分, 从这三个角度分析程序潜在的问题.</p>

<p><img src="/source/2019-01-05-Xcode中的Diagnostic-Tools-scheme.png" alt="" /></p>

<h2 id="section-1">代码诊断</h2>

<p>代码诊断分为地址诊断, 线程诊断和未定义行为诊断, 可以在[苹果官网][xocde]上查看详细说明.</p>

<h3 id="section-2">地址诊断</h3>

<p>用于检查内存使用问题, 可以检查以下类型的代码错误:</p>

<ul>
  <li>
    <p>使用被释放的内存</p>
  </li>
  <li>
    <p>重复释放内存</p>
  </li>
  <li>
    <p>释放未申请过的内存</p>
  </li>
  <li>
    <p>将栈内存做为函数返回值</p>
  </li>
  <li>
    <p>使用离开了作用域的栈内存</p>
  </li>
  <li>
    <p>内存越界</p>
  </li>
</ul>

<p>其实现原理为重写了 malloc 和 free 的实现, 内存在使用 malloc 申请之前被标记为禁止使用的, malloc 申请内存后, 申请部分的内存标记为可以使用; free 后内存被重新标记为禁止使用. 进行内存访问时, 会先检查内存是否可以使用.</p>

<h3 id="section-3">线程诊断</h3>

<p>会检查线程上不安全的行为:</p>

<ul>
  <li>
    <p>数据争用</p>
  </li>
  <li>
    <p>使用未初始化的锁</p>
  </li>
  <li>
    <p>线程泄漏</p>
  </li>
</ul>

<p>开启线程诊断后, 每次内存调用都会进行线程安全检查. 安全检查会对上述不安全行为进行检查, 比如每个线程都会保存内存访问时的时间戳, 如果对于同一块内存, 多个线程对其访问的时间戳有重叠且其中有写操作, 就判定为数据争用; 使用锁时会先判断其是否已经初始化; 还会检查不使用的线程是否被正确释放掉.</p>

<h3 id="section-4">未定义行为诊断</h3>
<p>对未定义(不好分类)的行为进行检查, 这些行为的名称不太直观, 所以展开来说明. 不过基本上很少会出现这些问题吧…</p>

<ul>
  <li>内存对齐</li>
</ul>

<p>直接对内存地址进行赋值可能会造成内存不对齐的情况</p>

<pre><code class="language-C">int_8 *buffer = malloc(64);
int_32 *pointer = (int_32 *)(buffer + 1);
*pointer = 123;
</code></pre>

<p>如果内存是32位对齐, 那么 pointer 是从第8位之后写入一个32位数据, 显然内存没有对齐. 推荐的方法是使用 memset 函数, 在编译过程中发现未对齐内存时会生成优化的汇编指令.</p>

<ul>
  <li>无效的 boolean 值</li>
</ul>

<p>使用 int 作为 boolean 进行判断时, 非零的值代表 true, 但编译器可能会对 boolean 进行优化, 只判断它的最后一位是否为0, 造成了逻辑错误.</p>

<ul>
  <li>数组越界</li>
</ul>

<p>语义上的数组越界检查, 和上面的广义的, 本质上的内存越界检查是有区别的.</p>

<ul>
  <li>无效的枚举值</li>
</ul>

<p>使用 int 来表示 enum 使, int 可能会取到 enum 没有定义的值.</p>

<ul>
  <li>跳转到控制流之外</li>
</ul>

<p>比如 switch 语句中的所有 case 都没有命中</p>

<ul>
  <li>动态类型错误</li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">Animal</span> <span class="p">{</span>
    <span class="k">virtual</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">speak</span><span class="p">()</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">};</span>
<span class="err">​</span>
<span class="k">struct</span> <span class="nc">Cat</span><span class="o">:</span> <span class="k">public</span> <span class="n">Animal</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">speak</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"nya"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>
<span class="err">​</span>
<span class="k">struct</span> <span class="nc">Dog</span><span class="o">:</span> <span class="k">public</span> <span class="n">Animal</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">speak</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"woof"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>
<span class="err">​</span>
<span class="k">auto</span> <span class="o">*</span><span class="n">dog</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">Dog</span> <span class="o">*&gt;</span><span class="p">(</span><span class="k">new</span> <span class="n">Cat</span><span class="p">);</span>
<span class="n">dog</span><span class="o">-&gt;</span><span class="n">speak</span><span class="p">();</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>reinterpret_cast 进行了无关类型之间的类型转换, 上面这种不正当地使用多态可能会引起难以预测的行为.</p>

<ul>
  <li>
    <p>浮点数溢出</p>
  </li>
  <li>
    <p>0作除数</p>
  </li>
  <li>
    <p>nonnull 相关的检查</p>
  </li>
  <li>
    <p>空指针引用和调用</p>
  </li>
  <li>
    <p>不正确的对象大小</p>
  </li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">Base</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">pad</span><span class="p">;</span>
<span class="p">};</span>
<span class="err">​</span>
<span class="k">struct</span> <span class="nc">Derived</span><span class="o">:</span> <span class="n">Base</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">pad2</span><span class="p">;</span>
<span class="p">};</span>
<span class="err">​</span>
<span class="n">Derived</span> <span class="o">*</span><span class="n">getDerived</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">Derived</span> <span class="o">*&gt;</span><span class="p">(</span><span class="k">new</span> <span class="n">Base</span><span class="p">);</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>还是多态使用的问题, getDerived 返回的指针指向的内存比 Derived 类型所占的内存要小, 当调用 getDerived()-&gt;pad2 时会发生错误.</p>

<ul>
  <li>
    <p>左移溢出</p>
  </li>
  <li>
    <p>整型溢出</p>
  </li>
</ul>

<p>上述问题并不都会发生错误, 开启了未定义行为诊断后使得代码检查变得更为严格.</p>

<p>地址诊断, 线程诊断, 未定义行为诊断都会在编译阶段增加了额外的代码和检查, 所以勾选后要重新编译.</p>

<h3 id="section-5">主线程检查</h3>

<p>会检查 AppKit, UIKit 和其他与 UI 有关的操作是否都在主线程进行, 检查是在 runtime 中进行的, 不需要重新编译, 而且性能损耗较少, 默认是开启的.</p>

<h2 id="section-6">内存管理</h2>

<h3 id="malloc-">malloc 填充</h3>

<p>已申请的内存填充为 0xAA, 未申请和释放掉的内存填充为 0x55</p>

<h3 id="malloc--1">malloc 保护页</h3>

<p>在申请的大块内存前后会加上保护页, 防止内存越界</p>

<h3 id="malloc--2">malloc 保护</h3>

<p>进行越界的内存读写时, 会直接触发 crash</p>

<h3 id="section-7">僵尸对象</h3>

<p>开启后释放掉的内存只会被标记为僵尸对象, 不会被系统回收, 程序调用被释放的内存时会发生 crash, 打印错误日志.</p>

<p>开启了 malloc 保护页和僵尸对象后, 内存占用会上升, 与实际程序占用内存不符, 所以这勾选这两个选项后, 调试界面的内存占用将不会显示内存占用情况.</p>

<h2 id="section-8">日志</h2>

<h3 id="malloc--3">malloc 调用栈</h3>

<p>会记录每一次 malloc 时的函数调用, Debug Memory Tool 使用.</p>

<p><img src="/source/2019-01-05-Xcode中的Diagnostic-Tools-malloc-history.png" alt="" /></p>

<p>启用后, 调试时点击 Debug Memory Tool 后, 右侧可以查看内存 malloc 时的调用栈信息. 除此之外, 使用模拟器调试时, 可以使用命令行工具(不是 lldb 中)malloc_history 查看对应 pid(可以在 xcode log 中找到), 来查看 malloc 调用栈日志.</p>

<h3 id="api-">动态链接器 API 调用</h3>

<p>开启后可以在 xocde 中输出动态链接器 API 的调用日志</p>

<h3 id="section-9">动态链接器库加载</h3>

<p>开启后可以在 xocde 中输出 dyld 加载动态链接库的日志</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><summary type="html"><![CDATA[好久没更新了, 实习做的是 crash 处理和性能提升有关的东西, 底层和 debug 看得比较多, 以后周末有空更一些.]]></summary></entry><entry><title type="html">美团 iOS 校招面经</title><link href="https://shino.space/2018/%E7%BE%8E%E5%9B%A2iOS%E6%A0%A1%E6%8B%9B%E9%9D%A2%E7%BB%8F/" rel="alternate" type="text/html" title="美团 iOS 校招面经" /><published>2018-10-10T05:55:23+00:00</published><updated>2018-10-10T05:55:23+00:00</updated><id>https://shino.space/2018/%E7%BE%8E%E5%9B%A2iOS%E6%A0%A1%E6%8B%9B%E9%9D%A2%E7%BB%8F</id><content type="html" xml:base="https://shino.space/2018/%E7%BE%8E%E5%9B%A2iOS%E6%A0%A1%E6%8B%9B%E9%9D%A2%E7%BB%8F/"><![CDATA[<p>今年投移动端人的特别少, 准备面试的时候都查不到什么资料, 而且自己这么菜都有人要emmm</p>

<!-- more -->

<hr />

<p>自己真正走完流程的也就这一家, 感觉秋招还是挺顺利的, 虽然 BAT 之类的面试机会都没有, 但最先面试的美团直接通过了, 也没什么可遗憾的. 比起面经更像是自己记录一下过程, 乱七八糟的可能会有点多, 有些自己当时不熟悉并且重要的知识点之后再详细补上吧, 再不写点多西自己都闲得长毛了.</p>

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#section-1" id="markdown-toc-section-1">预习</a></li>
  <li><a href="#section-2" id="markdown-toc-section-2">一面(电话面试)</a>    <ul>
      <li><a href="#section-3" id="markdown-toc-section-3">自我介绍</a></li>
      <li><a href="#section-4" id="markdown-toc-section-4">程序运行时内存的分布</a></li>
      <li><a href="#section-5" id="markdown-toc-section-5">进程和线程的区别</a></li>
      <li><a href="#section-6" id="markdown-toc-section-6">一个程序从编译到运行的全过程</a></li>
      <li><a href="#section-7" id="markdown-toc-section-7">网络分层</a></li>
      <li><a href="#https-" id="markdown-toc-https-">HTTPS 的原理</a></li>
      <li><a href="#section-8" id="markdown-toc-section-8">面向对象的特点</a></li>
      <li><a href="#swift-" id="markdown-toc-swift-">swift 中的值类型和引用类型</a></li>
      <li><a href="#k-" id="markdown-toc-k-">求数组中前 k 大的数字</a></li>
      <li><a href="#section-9" id="markdown-toc-section-9">额, 智力题</a></li>
      <li><a href="#ios-" id="markdown-toc-ios-">学习 iOS 的途径</a></li>
    </ul>
  </li>
  <li><a href="#section-10" id="markdown-toc-section-10">二面</a>    <ul>
      <li><a href="#section-11" id="markdown-toc-section-11">链表排序</a></li>
      <li><a href="#orz" id="markdown-toc-orz">还是一道小智力题orz</a></li>
      <li><a href="#swift--1" id="markdown-toc-swift--1">在 swift 协议中做用泛型</a></li>
      <li><a href="#section-12" id="markdown-toc-section-12">编译, 链接过程</a></li>
      <li><a href="#section-13" id="markdown-toc-section-13">学习途径</a></li>
      <li><a href="#section-14" id="markdown-toc-section-14">最近学习过程中有什么知识点值得一提的</a></li>
    </ul>
  </li>
  <li><a href="#section-15" id="markdown-toc-section-15">三面</a>    <ul>
      <li><a href="#section-16" id="markdown-toc-section-16">各种排序</a></li>
      <li><a href="#swift" id="markdown-toc-swift">各种 swift</a></li>
      <li><a href="#section-17" id="markdown-toc-section-17">项目</a></li>
      <li><a href="#wwdc" id="markdown-toc-wwdc">有没有看今年的 WWDC</a></li>
      <li><a href="#section-18" id="markdown-toc-section-18">最近在看什么书, 有什么可以值得分享的</a></li>
      <li><a href="#section-19" id="markdown-toc-section-19">介绍岗位</a></li>
    </ul>
  </li>
</ul>

<h2 id="section-1">预习</h2>

<p>学校讲的课emmm基本没用, 还是得自己另学. 书籍的话, 准备面试的时候网络看了《计算机网络自顶向下方法》, 计原, 或者说编译原理方向看了《程序员的自我修养链接装载与库》算法因为时间不够, 刷了两遍多《剑指offer》, 因为现在还是 OC 主流, 补了下 OC 基础, 看了《Objective-C编程全解》《Objective-C高级编程: iOS 与 OS X 多线程和内存管理》. 其他的书都是之前积累的了, 太多了不列举了.</p>

<h2 id="section-2">一面(电话面试)</h2>

<p>8月31号投的简历, 没想到进了提前批, 没过几天就电话面试了, 免了笔试, 因为当时还没怎么准备好(当然多长时间也准备不好的), 也没抱什么希望.</p>

<h3 id="section-3">自我介绍</h3>

<p>从来没有面过技术面, 就介绍了一下自己的专业(因为是五年制, 说明了一下), 兴趣爱好, 和简历上的项目.</p>

<p>被问到了学校都有哪些课程, 和电有关的专业都会学到的数学, 电路理论, 模电, 数电; 计算机基础的离散数学, C语言, C++; 比较重要的编译原理, 网络, 组成原理, 操作系统; 工程方向的专业选修, 以及数据库, 软件工程之类的. 哦对, 还有日语课, 专业本身是挺好的, 就是课程安排不合理, 我在准备秋招的时候, 从日本交流回来的同学才开始学 C++, 也是有意思.</p>

<h3 id="section-4">程序运行时内存的分布</h3>

<ul>
  <li>
    <p>BSS段用于存放未初始化的全局变量, 程序开始运行之后会被清空, 静态分配</p>
  </li>
  <li>
    <p>数据段用于存放初始化后的全局变量, 静态分配</p>
  </li>
  <li>
    <p>代码段用于存放代码</p>
  </li>
  <li>
    <p>栈区用于存放自动变量, 以及函数调用, 由系统管理内存分配</p>
  </li>
  <li>
    <p>堆区用于存放动态分配的内存, 要由程序员手动进行管理</p>
  </li>
</ul>

<p>又简单说了一下栈区和堆区的区别, 不多写</p>

<h3 id="section-5">进程和线程的区别</h3>

<p>进程是操作系统资源分配的最小单位, 线程是处理机分配的最小单位, 一个程序至少有一个进程, 一个进程至少有一个线程. 进程间内存虚拟地址是独立的, 而线程间内存是共享的.</p>

<p>被问到线程间共享的内存是上面内存分布是提到的哪些区域, 因为线程之间是可以并行的, 所以和函数调用相关的栈内存不可以区享, 而是每个线程都佳能自己独立的栈区, 堆区内存是可以共享的. 至于共享内存会出现的同步问题, 可以用线程锁或者信号量解决, 又简单说了下锁和信号量.</p>

<h3 id="section-6">一个程序从编译到运行的全过程</h3>

<ul>
  <li>
    <p>宏定义展开</p>
  </li>
  <li>
    <p>词法分析</p>
  </li>
  <li>
    <p>语法分析</p>
  </li>
  <li>
    <p>语义分析</p>
  </li>
  <li>
    <p>中间代码生成</p>
  </li>
  <li>
    <p>中间代码优化</p>
  </li>
  <li>
    <p>生成机器码</p>
  </li>
  <li>
    <p>链接</p>
  </li>
  <li>
    <p>装载到内存运行</p>
  </li>
</ul>

<p>其中链接详细问了一下, 就把静态链接和动态链接的区别, 优缺点解释了一下.</p>

<h3 id="section-7">网络分层</h3>

<p>OSI 的七层当时硬是没想出来, 就按 TCP/IP 四层结构详细地讲了一遍, 因为啃过自顶向下方法那本计算机网络书, 所以自我感觉说得可以, 面试官也没多问.</p>

<h3 id="https-">HTTPS 的原理</h3>

<p>因为我说过自己有信息安全的课程, 就问我课上都讲了什么, 其实课上也就是科普了一下加密及运用, 以及防火墙之类的东西, 自然而然地就问了下我 HTTPS 是怎么实现安全的传输的. 提了下使用非对称加密通过证书机构来验证服务器身份, 并交换对称加密的密钥, 验证身份后使用对称加密来传输数据以减少资源和时间浪费, 本来想详细说明一下过程的, 没让我讲完…</p>

<h3 id="section-8">面向对象的特点</h3>

<p>封装, 继承和多态, 然后举了例子说明了一个程序如果用面向过程的思想应该怎么写, 用面向对象的思想应该怎么写</p>

<h3 id="swift-">swift 中的值类型和引用类型</h3>

<p>讲面向对象的时候我用了 C++ 和 swift 进行了比较, 所以就问了下 swift 的问题. 值类型的变量可以保证变量本身不会被其他变量所修改, 而引用变量则可能会被其他引用(指针)对变量指向的内存进行修改. 值类型因为赋值或修改会进行复制, 会浪费性能, swift 中使用写时复制的机制来提高其性能.</p>

<p>举了很多值类型的例子, 所以问我如果只有值类型可不可以, 使用指针那样的机制也可以达到同样的效果. 懵了一下, 回答说那样就不能进行多态了, 然后马上意识到自己说得不对…使用指针操作值类型本身就是按照引用类型的语义来操作值类型, 还不如直接使用引用类型更容易理解一些. 和何况有些情况使用引用类型本身各贴近正常的思维, 比如一个人的多个身份相当于这个人的多个引用, 指向这个人本身.</p>

<h3 id="k-">求数组中前 k 大的数字</h3>

<p>《剑指 offer》上的原题, 就回忆了一下:</p>

<ul>
  <li>数组大小 n 比较小</li>
</ul>

<p>直接排序取前 k 个就好了, 本身也花不了多长时间, 使用快排的话时间复杂度就是 O(nlogn).</p>

<ul>
  <li>n 很大</li>
</ul>

<p>用于一个 k 个元素的小顶堆, 将数组中的元素入堆, 堆满之后, 下一个元素比堆顶大就弹出堆顶, 新元素入堆, 否则跳过. 相当于堆里始终存放着当前最大的 k 个元素. 小顶堆建立的过程中时间复杂度为 O(klogk), 之后每次堆的复杂度为 O(logk), 即若 k 远远小于 n 的话, 时间复杂度为 O(nlogk).</p>

<p>因为题目很简单, 所以问得特别细, 问了快排和堆排为什么时间复杂度是 O(nlogn). 快排每一轮都是O(n), 每轮相当于将数组分成一半, 所以排了 logn 轮(以2为底); 堆排每次调整最多要调整的次数就是堆的深度, 即 logn (以2为底).</p>

<h3 id="section-9">额, 智力题</h3>

<p>一个函数<code class="language-text highlighter-rouge">foo()</code>会返回<code class="language-text highlighter-rouge">0</code>或<code class="language-text highlighter-rouge">1</code>, 概率分别是 70% 和 30%, 设计一个函数<code class="language-text highlighter-rouge">bar()</code>, 使其返回<code class="language-text highlighter-rouge">0</code>和<code class="language-text highlighter-rouge">1</code>和概率各为 50%.</p>

<p>之前没做过这种题, 一脸懵逼, 然后想到调用两次<code class="language-text highlighter-rouge">foo()</code>都返回<code class="language-text highlighter-rouge">0</code>的概率是 70% * 70% = 49% , 和 50% 差不多了, 面试官应该没见过这样的操作, 也懵了hhh, 说这个方法不太好, 要是换成<code class="language-text highlighter-rouge">0</code>和<code class="language-text highlighter-rouge">1</code>的概率是 80% 和 20% 就行不通了, 让我再想.</p>

<p>后来提示我运行两次结果为<code class="language-text highlighter-rouge">01</code>和<code class="language-text highlighter-rouge">10</code>的概率是相同的. 当时我给出答案:</p>

<blockquote>
  <p>运行两次<code class="language-text highlighter-rouge">foo()</code>, 当结果为<code class="language-text highlighter-rouge">01</code>时, 返回<code class="language-text highlighter-rouge">0</code>, 结果为<code class="language-text highlighter-rouge">10</code>时, 返回<code class="language-text highlighter-rouge">1</code>, 其余情况不返回, 重新运行, 直接有返回为止.</p>
</blockquote>

<p>现在想<code class="language-text highlighter-rouge">00</code>, <code class="language-text highlighter-rouge">11</code>的概率也是相同的, 可以把<code class="language-text highlighter-rouge">00</code>, <code class="language-text highlighter-rouge">01</code>分为一组, <code class="language-text highlighter-rouge">11</code>, <code class="language-text highlighter-rouge">10</code>分为一组, 就不用轮空了.</p>

<p>接着又问我如果还是用这个函数<code class="language-text highlighter-rouge">foo()</code>, 设计一个随机数函数返回<code class="language-text highlighter-rouge">0</code>到<code class="language-text highlighter-rouge">9</code>之间的随机数应该怎么做. 根据上面的做法, 用二进制表示返回值, <code class="language-text highlighter-rouge">9</code>是4位二进制数, 那么运行<code class="language-text highlighter-rouge">bar()</code>函数4次, 如果结果大于<code class="language-text highlighter-rouge">9</code>就轮空, 否则返回.</p>

<h3 id="ios-">学习 iOS 的途径</h3>

<p>感觉学校里做 iOS 的也就我一个人了, 就谈了谈自己看过的官方文档, 书籍, 视频和博客, 以及查资料的方法. 之后闲聊几句就结束了.</p>

<h2 id="section-10">二面</h2>

<p>感觉一面很基础, 也挺简单, 但是后面智力题尬了好久, 有些担心会被刷, 结果第二天就通知我去北京总部面试. 反正不会有大公司来大连面试的, 没多想就买了小火车票, 面不上就当是见见世面了.</p>

<h3 id="section-11">链表排序</h3>

<p>一开始问我电话面试感觉怎么样, 傻傻地说了算法可能没回答好, 然后面试官就说那好我们先写个算法吧orz, 就让我写个给链表排序的算法. 算法写得少, 从来没给链表排过序…想了好久, 写了个抠脚的O(n^2)的插入排序, 还好没写错, 想着上来就写成这样应该是凉了.</p>

<p>接着问我复杂度是多少, 有没有更快的方法, 我就给理了一下归并排序的实现, 发现取链表中点不好取, 就使用一个指针沿链表走一步, 另一个走两步的方法. 之后立即发现了, 找中间的复杂度就已经是O(n)了, 再加上归并本身的O(nlogn), 更慢了……</p>

<p>又问我快排行不行, 想了一下, 快排的实现并没有信赖数组的线性储存特点, 可以进行O(nlogn)的排序, 写出来也很简单:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="n">ListNode</span><span class="o">*</span> <span class="nf">partition</span><span class="p">(</span><span class="n">ListNode</span><span class="o">*</span> <span class="n">start</span><span class="p">,</span> <span class="n">ListNode</span><span class="o">*</span> <span class="n">end</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">key</span> <span class="o">=</span> <span class="n">start</span><span class="o">-&gt;</span><span class="n">value</span><span class="p">;</span>
    <span class="n">ListNode</span><span class="o">*</span> <span class="n">p</span> <span class="o">=</span> <span class="n">start</span><span class="p">;</span>
    <span class="n">ListNode</span><span class="o">*</span> <span class="n">q</span> <span class="o">=</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">q</span> <span class="o">!=</span> <span class="n">end</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">value</span> <span class="o">&lt;</span> <span class="n">key</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">p</span> <span class="o">=</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
            <span class="n">swap</span><span class="p">(</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">value</span><span class="p">,</span> <span class="n">q</span><span class="o">-&gt;</span><span class="n">value</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">q</span> <span class="o">=</span> <span class="n">q</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">swap</span><span class="p">(</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">value</span><span class="p">,</span> <span class="n">start</span><span class="o">-&gt;</span><span class="n">value</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">p</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">qsort</span><span class="p">(</span><span class="n">ListNode</span><span class="o">*</span> <span class="n">start</span><span class="p">,</span> <span class="n">ListNode</span><span class="o">*</span> <span class="n">end</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">start</span> <span class="o">==</span> <span class="nb">nullptr</span> <span class="o">||</span> <span class="n">end</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">start</span> <span class="o">!=</span> <span class="n">end</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ListNode</span><span class="o">*</span> <span class="n">mid</span> <span class="o">=</span> <span class="n">partition</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">);</span>
        <span class="n">qsort</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">mid</span><span class="p">);</span>
        <span class="n">qsort</span><span class="p">(</span><span class="n">mid</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span> <span class="n">end</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="orz">还是一道小智力题orz</h3>

<p>真是怕什么来什么, 又问一道智力题, 6个候选人, 5个裁判, 设计一个公平的算法, 给候选人进行排序, 不允许并列.</p>

<p>出完题面试官就用我手机进我博客里看了(就是你现在看的这个), 我一直在想具体的实现应该怎么做, 结果面试官看完了, 我还是没有具体的方案. 面试结束后问了下这题应该怎么做, 得到的答案也很笼统, 大意就是设置好梯度…</p>

<h3 id="swift--1">在 swift 协议中做用泛型</h3>

<p>其实就是<code class="language-text highlighter-rouge">associatedtype</code>, swift 的协议中没有泛型, 因为协议是用来实现多继承, 或者模范一个类的实现, 我们可以让一个类实现一个协议, 但不会创建多个协议本身. 所以比起协议泛型, <code class="language-text highlighter-rouge">associatedtype</code>这种类型别名的方式更适合.</p>

<p>可惜的是, 平时泛型用得少, 当时我忘了这个关键字就什么了, 只记得实现类的时候要对<code class="language-text highlighter-rouge">associatedtype</code>的别名进行<code class="language-text highlighter-rouge">typealias</code>绑定到已有的类型上…</p>

<h3 id="section-12">编译, 链接过程</h3>

<p>和一面问得差不多, 但在动态链接上问得更加详细, 编译出的二进制文件中指令操作的地址是写死的, 但动态链接库在加载时人位置不确定, 如何保证程序正常运行. 因为编译的时候不知道动态链接库的位置, 所以编译后二进制文件中动态库的地址只是占位用, 在程序装载到内存准备运行时, 会把动态库的位置替换掉原来的占位符. 当时意思是答出来了, 不知道对方有没有理解.</p>

<h3 id="section-13">学习途径</h3>

<p>说我课程时并没有 iOS 相关的东西, 为什么学起了 iOS. 就把一面时那些东西又说了一遍</p>

<h3 id="section-14">最近学习过程中有什么知识点值得一提的</h3>

<p>想着这是最后发挥的机会了, 就把准备面试过程中 OC 和 swift 的 block 原理从头到尾讲了一遍(之前文章里有写), 感觉面试官很满意.</p>

<p>二面一开始, 手表就提示我心率过速, 中间又提示了好几次. 面完还问了下面试官是不是自己要凉, 对方告诉我没事, 可能自己身边没人交流, 有些知识点发散得不够, 其实学过的东西还是挺扎实的.</p>

<h2 id="section-15">三面</h2>

<p>本来都以为 HR 会让自己回去了, 结果三面的面试官过来了, 顿时觉得自己还有救. 面试官刚过来就让我看一下他的牌, 说自己是美团移动端的技术总监, 当时太紧张, 上面写的什么一个字也没看进去. 一开始和我谈学校还有家里住哪之类的还以为对方是 HR, 第二天 HR 电话通知我结果的时候才知道是安卓和 iOS 双修, 两头都管的真dalao…</p>

<h3 id="section-16">各种排序</h3>

<p>还是问了下上一面都问了什么, 这回我学聪明了点, 没提智力题的事, 就说链表排序之前没写好, 费了点时间, 果然让我讲一下常用的排序. 这个肯定要好好复习的, 详细地说了一遍. 之后问了有没有O(n)的排序方法, 就提了下使用 hash, 以及基数排序.</p>

<h3 id="swift">各种 swift</h3>

<p>我还纳闷怎么不问 iOS 相关的东西, 结果都攒到三面问了…因为我说过一直用的是 swift, 就问我 swift 与 OC 相比好在哪里, 把自己想到的都说了, 说了好久.</p>

<ul>
  <li>值类型&amp;引用类型</li>
</ul>

<p>除了一面时说的那些, 举例说明了一下值类型(struct 和 enum)和引用类型(class)的使用, 说<code class="language-text highlighter-rouge">mutation</code>函数的时候被吐槽日语学多了英语不会读.</p>

<ul>
  <li>泛型</li>
</ul>

<p>没怎么用过, 所以没说太多, 简单地和 OC 这种基本没泛型的语言比较了一下.</p>

<ul>
  <li>异常处理</li>
</ul>

<p><code class="language-text highlighter-rouge">try</code>, <code class="language-text highlighter-rouge">catch</code>这种语言上定义的异常处理自然要比<code class="language-text highlighter-rouge">NSError</code>这种没有语言约束的错误处理要强, 可以强制程序员在处理会抛出异常的函数时会对错误情况进行思考写出<code class="language-text highlighter-rouge">catch</code>条件, 而不是简单地传进一个<code class="language-text highlighter-rouge">nil</code>指针. 同时 swift 的类型匹配以及强大的枚举类型使得可以更方便得通过抛出的<code class="language-text highlighter-rouge">Error</code>得出异常信息, <code class="language-text highlighter-rouge">try</code>, <code class="language-text highlighter-rouge">catch</code>性能上的损失也远比 C++ 这要少.</p>

<ul>
  <li>枚举</li>
</ul>

<p>就 siwft 中的强类型的枚举和C语言中可以用<code class="language-text highlighter-rouge">int</code>替换的枚举进行对比, 又讨论了枚举的关联值和原始值, 还结合了一下上面提到的异常处理.</p>

<ul>
  <li>可选值</li>
</ul>

<p>提到枚举又想到了可选值, 批判了<code class="language-text highlighter-rouge">NULL</code>的问题, 讲了下可选值的解包, 可选链. 和面试官讨论了强制解包的坏处, 以及自己实践过的数组越界是不让其崩溃, 而是返回<code class="language-text highlighter-rouge">nil</code>时带来的问题.</p>

<h3 id="section-17">项目</h3>

<p>项目没问太细, 只是了解了一下是做什么的, 用了什么技术, 用这些技术的原因.</p>

<h3 id="wwdc">有没有看今年的 WWDC</h3>

<p>三面还是谈人生谈理想的东西多一些, 让我说一下对 iOS 发展的看法, 以及今年 WWDC 上有什么技术想和他说一说. 因为一直以来使用的都是越狱的 iOS, 就对苹果封闭生态的优缺点谈了谈, 认为就像逐步开放输入法, 通知中心, 分享菜单, Siri Kit 等, 越来越开放才是趋势. WWDC 这头谈了自己对<code class="language-text highlighter-rouge">ARKit</code>以及<code class="language-text highlighter-rouge">Create ML</code>的看法, 好在自己还真看了 WWDC, 虽然自己从来没用过, 吹吹牛逼还是可以的.</p>

<h3 id="section-18">最近在看什么书, 有什么可以值得分享的</h3>

<p>说了几本准备面试时看的 iOS 底层的书, 然后提了下之前研究过的 OpenWRT 路由和 IPv6 NAT(之前文章有写). 面试官之前做过网络, 就问了下 NAT 的实现, 怎么解决 IP 不足的. 详细地解释了一下.</p>

<h3 id="section-19">介绍岗位</h3>

<p>之后就没什么正经问题了, 给我介绍了下部门规模, 做什么业务, 最后问了下我对美团 app 的看法, 和对面试的评价, 就结束了.</p>

<p>本来二面觉得自己凉了, 三面之后完全没有这种感觉, 没让我想到的是面完后三面面试官开车送我到地铁站, 把我吓坏了…</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="工作" /><summary type="html"><![CDATA[今年投移动端人的特别少, 准备面试的时候都查不到什么资料, 而且自己这么菜都有人要emmm]]></summary></entry><entry><title type="html">LeetCode #91: 解码问题</title><link href="https://shino.space/2018/LeetCode-91-%E8%A7%A3%E7%A0%81%E9%97%AE%E9%A2%98/" rel="alternate" type="text/html" title="LeetCode #91: 解码问题" /><published>2018-09-04T08:19:59+00:00</published><updated>2018-09-04T08:19:59+00:00</updated><id>https://shino.space/2018/LeetCode-91:-%E8%A7%A3%E7%A0%81%E9%97%AE%E9%A2%98</id><content type="html" xml:base="https://shino.space/2018/LeetCode-91-%E8%A7%A3%E7%A0%81%E9%97%AE%E9%A2%98/"><![CDATA[<p>动态规划教做人</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#section-1" id="markdown-toc-section-1">题目</a></li>
  <li><a href="#section-2" id="markdown-toc-section-2">读题</a></li>
  <li><a href="#section-3" id="markdown-toc-section-3">求解</a></li>
  <li><a href="#c-" id="markdown-toc-c-">C++ 代码</a></li>
</ul>

<h2 id="section-1">题目</h2>

<blockquote>
  <p>有一种将字母编码成数字的方式: ‘a’ -&gt; 1, ‘b’ -&gt; 2, … , ‘z’ -&gt; 26.</p>
</blockquote>

<blockquote>
  <p>现在给一串数字，给出有多少种可能的译码结果。</p>
</blockquote>

<h2 id="section-2">读题</h2>

<p>一位数的时候, 只有一种解码; 两位数时, 如果数字在 1 到 26 之间, 就可以有两种解码. 要特别注意一下 0, 它本身是没有解码的, 只有 10 和 20 可以解码, 也就是说 100, 30 这种输入是没有解码结果的.</p>

<h2 id="section-3">求解</h2>

<p>首先是输入是否合法, 对于无输入或者 0, 直接返回 0.</p>

<p>之后从前向后扫描, 对于非 0 元素, 会有两种情况: 它与前面的数字在 1 到 26 之间, 或者不在. 如果不在, 那么它对于解码方法的增加没有贡献, 也是就和它前面的字符串的解码方式相等; 如果在 1 到 26 之间, 就会有把它当成1位数解码和当成2位数解码两种方式, 那么整个字符串的解码方式加 1.</p>

<p>如果扫描到 0, 那么不但它对解码方式增加没有贡献, 还会限制它前面的一个字符: 组合成 10 或 20 时, 0 前面的字符就不能和更前面的字符组合成两位数, 这时解码方式等于<strong>前n-2个</strong>字符的解码方式; 而如果不能组合成 10 或者 20, 就没办法进行解码, 解码方式为 0.</p>

<h2 id="c-">C++ 代码</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">solution</span><span class="p">(</span><span class="n">string</span> <span class="n">str</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 排除空输入或者 0 开头的数字</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">str</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">str</span><span class="p">.</span><span class="n">front</span><span class="p">()</span> <span class="o">!=</span> <span class="sc">'0'</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">// dp[i] 表示到第 i 个字符为止所有解码方式</span>
    <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">dp</span><span class="p">(</span><span class="n">str</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="mi">0</span><span class="p">);</span>
    <span class="c1">// 上面判断了第一个字符不是 0, 所以有一种解码</span>
    <span class="n">dp</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="c1">// 从前向后遍历</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">str</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 如果当前位不是 0, 那么至少和之前的解码数目相同</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">str</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="sc">'0'</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
        <span class="p">}</span>
        <span class="c1">// 如果可以和前面组合成可解码数字, 解码方式增加</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'1'</span> <span class="o">||</span> <span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'2'</span> <span class="o">&amp;&amp;</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="sc">'6'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">2</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// 正好是最前面的两个数字, 直接加 1</span>
                <span class="o">++</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="c1">// 否则再加上前 i - 2 位解码方式</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+=</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">2</span><span class="p">];</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// dp 中最后一个元素就是到到最后一个字符为止的解码方式</span>
    <span class="k">return</span> <span class="n">dp</span><span class="p">.</span><span class="n">back</span><span class="p">();</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>看到 dp 数组只用到了两个元素, 空间占用上可以再优化, 不过那样就更看不懂了, 不写了.</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="C++" /><summary type="html"><![CDATA[动态规划教做人]]></summary></entry><entry><title type="html">有符号整数除以2与右移1位的性能比较</title><link href="https://shino.space/2018/%E6%9C%89%E7%AC%A6%E5%8F%B7%E6%95%B4%E6%95%B0%E9%99%A4%E4%BB%A52%E4%B8%8E%E5%8F%B3%E7%A7%BB1%E4%BD%8D%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83/" rel="alternate" type="text/html" title="有符号整数除以2与右移1位的性能比较" /><published>2018-08-28T08:56:10+00:00</published><updated>2018-08-28T08:56:10+00:00</updated><id>https://shino.space/2018/%E6%9C%89%E7%AC%A6%E5%8F%B7%E6%95%B4%E6%95%B0%E9%99%A4%E4%BB%A52%E4%B8%8E%E5%8F%B3%E7%A7%BB1%E4%BD%8D%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83</id><content type="html" xml:base="https://shino.space/2018/%E6%9C%89%E7%AC%A6%E5%8F%B7%E6%95%B4%E6%95%B0%E9%99%A4%E4%BB%A52%E4%B8%8E%E5%8F%B3%E7%A7%BB1%E4%BD%8D%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83/"><![CDATA[<p>一直以来写东西都说可以用右移一位来代替除以2操作, 会提高性能, 但一直不知道为什么, 这次折腾一下, 看看是在哪里提高性能的.</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#section-1" id="markdown-toc-section-1">直接看汇编</a>    <ul>
      <li><a href="#section-2" id="markdown-toc-section-2">位运算</a></li>
      <li><a href="#section-3" id="markdown-toc-section-3">除法</a></li>
    </ul>
  </li>
  <li><a href="#section-4" id="markdown-toc-section-4">移位操作</a></li>
</ul>

<h2 id="section-1">直接看汇编</h2>

<p>对照C语言代码:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
</pre></td><td class="rouge-code"><pre><span class="c1">// 除法</span>
<span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>

<span class="c1">// 位运算</span>
<span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">&gt;&gt;</span> <span class="mi">2</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>一定要用中间变量储存结果, 否则编译器会优化(开了<code class="language-text highlighter-rouge">-O0</code>也没用), 不会生成对应的汇编代码. 使用<code class="language-text highlighter-rouge">clang -O0 -S c_file -o nasm_file</code>将C语言代码转换成汇编.</p>

<h3 id="section-2">位运算</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
</pre></td><td class="rouge-code"><pre>movl    $8, -4(%rbp)
movl    -4(%rbp), %ecx
sarl    $1, %ecx
movl    %ecx, -8(%rbp)
</pre></td></tr></tbody></table></code></pre></div></div>

<p>去掉无关的代码, 位运算代码转换成汇编就这几步:</p>

<ul>
  <li>将8入栈(因为是自动变量, 所谓的存入栈区)</li>
  <li>将栈中变量取出, 放入通用寄存器<code class="language-text highlighter-rouge">ecx</code></li>
  <li>将<code class="language-text highlighter-rouge">ecx</code>寄存器中数据右移一位, 结果存在<code class="language-text highlighter-rouge">ecx</code>中</li>
  <li>将<code class="language-text highlighter-rouge">ecx</code>数据入栈</li>
</ul>

<h3 id="section-3">除法</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
</pre></td><td class="rouge-code"><pre>movl    $2, %ecx
movl    $8, -4(%rbp)
movl    -4(%rbp), %edx
movl    %eax, -12(%rbp)
movl    %edx, %eax
cltd
idivl   %ecx
movl    %eax, -8(%rbp)
movl    -12(%rbp), %eax
</pre></td></tr></tbody></table></code></pre></div></div>

<p>因为没系统学过汇编, 所以看除法比上面位运算要麻烦得多.</p>

<ul>
  <li>向通用寄存器<code class="language-text highlighter-rouge">ecx</code>中存入2(因为不是自动变量, 不入栈)</li>
  <li>将8入栈</li>
  <li>将栈变量取出, 放入通用寄存器<code class="language-text highlighter-rouge">edx</code></li>
  <li>将累加寄存器<code class="language-text highlighter-rouge">eax</code>中数据暂存入栈中(一会解释)</li>
  <li>把<code class="language-text highlighter-rouge">edx</code>数据移入<code class="language-text highlighter-rouge">eax</code></li>
  <li>将<code class="language-text highlighter-rouge">eax</code>扩充为四字(8字节)</li>
  <li>将<code class="language-text highlighter-rouge">ecx</code>数据作为除数计算除法</li>
  <li>将<code class="language-text highlighter-rouge">eax</code>中的结果入栈(赋给变量a)</li>
  <li>将上面栈中暂存的<code class="language-text highlighter-rouge">eax</code>数据放回<code class="language-text highlighter-rouge">eax</code></li>
</ul>

<p>就看这多出来的步骤就比位移要麻烦. 首先解释一下除法, 计算机底层做除法时, 被除数位数是除数两倍长, 所以可以看到要使用<code class="language-text highlighter-rouge">edx</code>的空间, 把<code class="language-text highlighter-rouge">eax</code>扩充为四字, 然后用<code class="language-text highlighter-rouge">idivl</code>命令的操作数<code class="language-text highlighter-rouge">ecx</code>作为除数进行计算, 计算结果存放在<code class="language-text highlighter-rouge">eax</code>中, 余数存在<code class="language-text highlighter-rouge">edx</code>中, 但并没有看到对<code class="language-text highlighter-rouge">edx</code>数据做什么处理, 也符合整型除法不管余数的原则.</p>

<p>再说明一下把<code class="language-text highlighter-rouge">eax</code>数据入栈的操作, 因为累加寄存器中的数据还有用处, 但下一步除法要用它存放被除数, 此时四个寄存器<code class="language-text highlighter-rouge">eax</code>, <code class="language-text highlighter-rouge">ebx</code>, <code class="language-text highlighter-rouge">ecx</code>, <code class="language-text highlighter-rouge">edx</code>都用上了, 所以暂时从栈里借点地方, 之后再还回来.</p>

<h2 id="section-4">移位操作</h2>

<p>如果还是进行除法操作, 换成用无符号整型, 会发现生成的汇编和位运算一样:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
</pre></td><td class="rouge-code"><pre><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>这就要提一下有符号和无符号的移位操作了. 无符号的整型无所谓, 正常左移右移就可以, 而有符号整型有符号位, 在 macOS 上的 LLVN 9.1.0 中, 正数右移左面补0, 负数右移左面补1, 相当于符号位不变; 而左移会给出警告, 提示结果未定义, 忽略警告执行的话, 符号位不变, 正数右面补0, 负数补1. 总体来说, 和直觉相对.</p>

<p>虽然结果上是对的, 但有符号整型的左移毕竟是未定义行为, 所以在除法时会做出判断, 体现在汇编上就是使和<code class="language-text highlighter-rouge">idivl</code>指令, 而不是右移<code class="language-text highlighter-rouge">sarl</code>. 再加上除法会计算余数, 即使用不上, 所以在步数上和指令本身执行时间都不如移位.</p>

<p>看上去很简单的道理, 其实后面包含自底向上的组成原理, 编译原理, 汇编和C语言知识. 汇编真有趣, 我再也不看了…</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><summary type="html"><![CDATA[一直以来写东西都说可以用右移一位来代替除以2操作, 会提高性能, 但一直不知道为什么, 这次折腾一下, 看看是在哪里提高性能的.]]></summary></entry><entry><title type="html">Objective-C 中的 block</title><link href="https://shino.space/2018/Objective-C%E4%B8%AD%E7%9A%84block/" rel="alternate" type="text/html" title="Objective-C 中的 block" /><published>2018-08-21T14:08:58+00:00</published><updated>2018-08-21T14:08:58+00:00</updated><id>https://shino.space/2018/Objective-C%E4%B8%AD%E7%9A%84block</id><content type="html" xml:base="https://shino.space/2018/Objective-C%E4%B8%AD%E7%9A%84block/"><![CDATA[<p>看完了 swift 中的闭包, 自然又重新看了一下 Objective-C 中的 block, 之前因为没写过, 所以浏览一遍就过去了, 现在发现了解一下实现对于理解 swift 有很大的好处, 毕竟 swift 简化了太多底层的东西.</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#block-" id="markdown-toc-block-">block 的基本形式</a></li>
  <li><a href="#block--1" id="markdown-toc-block--1">block 的值捕获</a></li>
  <li><a href="#block--2" id="markdown-toc-block--2">block 也是对象</a></li>
  <li><a href="#block--3" id="markdown-toc-block--3">block 储存方式</a></li>
  <li><a href="#arc--block" id="markdown-toc-arc--block">ARC 下的 block</a></li>
  <li><a href="#block--4" id="markdown-toc-block--4">block 的实现</a>    <ul>
      <li><a href="#block--5" id="markdown-toc-block--5">block 本质是对象</a></li>
      <li><a href="#block--6" id="markdown-toc-block--6">block 捕获的变量</a></li>
      <li><a href="#block--7" id="markdown-toc-block--7">block 的复制</a></li>
      <li><a href="#block--8" id="markdown-toc-block--8">block 中对象的所有权</a></li>
    </ul>
  </li>
</ul>

<p>block 其实并不是 Objective-C 的语法, 而是苹果对于C语言的扩充, 所以完全可以在C文件中写 block, 然后使用 clang 来编译:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>clang block.c <span class="nt">-o</span> block
</pre></td></tr></tbody></table></code></pre></div></div>

<p>编译通过, 而且正常运行. 虽然 swift 不像 objc 是翻译成 C 再编译的, 但关于 block 与闭包的思想是相近的.</p>

<h2 id="block-">block 的基本形式</h2>

<p>只考虑函数特性的话, block 与函数指针的形式基本一致</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="c1">// 函数指针</span>
<span class="c1">// 声明</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">foo</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>
<span class="c1">// 使用</span>
<span class="n">foo</span><span class="p">();</span>
<span class="p">(</span><span class="o">*</span><span class="n">foo</span><span class="p">)();</span>
<span class="c1">// 数组</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">foo</span><span class="p">[</span><span class="mi">5</span><span class="p">])(</span><span class="kt">void</span><span class="p">);</span>
<span class="c1">// 作为函数参数, bar 为函数名, param 为作为参数的函数指针</span>
<span class="kt">void</span> <span class="nf">bar</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">param</span><span class="p">)(</span><span class="kt">void</span><span class="p">))</span> <span class="p">{</span> <span class="p">}</span>
<span class="c1">// 作为函数返回值, foo 为函数名</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">foo</span><span class="p">(</span><span class="kt">void</span><span class="p">))(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>

<span class="c1">// block</span>
<span class="c1">// 声明</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>
<span class="c1">// 使用</span>
<span class="n">block</span><span class="p">();</span>
<span class="c1">// 数组</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">[</span><span class="mi">5</span><span class="p">])(</span><span class="kt">void</span><span class="p">);</span>
<span class="c1">// 作为函数参数</span>
<span class="kt">void</span> <span class="nf">bar</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">param</span><span class="p">)(</span><span class="kt">void</span><span class="p">))</span> <span class="p">{</span> <span class="p">}</span>
<span class="c1">// 作为函数返回值</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">foo</span><span class="p">(</span><span class="kt">void</span><span class="p">))(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>因为 block 并不是指针, 所以调用 block 时不可以像函数指针一样使用<code class="language-text highlighter-rouge">*</code>, 其他形式与函数指针都是一致的. 当然, 为了便于读写, 一般会使用<code class="language-text highlighter-rouge">typedef</code>简化, 上面把 block 作为返回值的函数如果这样写会看起来会方便很多</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
</pre></td><td class="rouge-code"><pre><span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>
<span class="n">block</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>定义 block 时, 返回值可以跟据 block 内容推断出来, 所以可以省略, 而参数列表为空时, 也可能省略</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
</pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="o">^</span><span class="kt">void</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// something</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="o">^</span> <span class="p">{</span>
    <span class="c1">// something</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>上面两个 block 的定义是等价的.</p>

<h2 id="block--1">block 的值捕获</h2>

<p>和函数指针不同的是, block 可以捕获外部的值, 在C语言中, 变量按照储存方式分为全局变量, 全局静态变量, 局部静态变量, 自动变量, block 对它们的捕获方式如下:</p>

<ul>
  <li>对于全局变量和所有静态变量, block 以引用的形式进行捕获</li>
  <li>对于自动变量, block 进行值捕获, 即复制自动变量的副本</li>
</ul>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="n">global</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">static_global</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">static</span> <span class="kt">int</span> <span class="n">static_local</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">local</span> <span class="o">=</span> <span class="mi">1000</span>
    <span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">)</span> <span class="o">^</span> <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">global</span> <span class="o">&lt;&lt;</span> <span class="s">", "</span> <span class="o">&lt;&lt;</span> <span class="n">static_global</span> <span class="o">&lt;&lt;</span> <span class="s">", "</span> <span class="o">&lt;&lt;</span> <span class="n">static_local</span> <span class="o">&lt;&lt;</span> <span class="s">", "</span> <span class="o">&lt;&lt;</span> <span class="n">local</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">};</span>
    <span class="o">++</span><span class="n">global</span><span class="p">;</span>
    <span class="o">++</span><span class="n">static_global</span><span class="p">;</span>
    <span class="o">++</span><span class="n">static_local</span><span class="p">;</span>
    <span class="o">++</span><span class="n">local</span><span class="p">;</span>
    <span class="n">block</span><span class="p">();</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>运行<code class="language-text highlighter-rouge">foo()</code>后, 除了<code class="language-text highlighter-rouge">local</code>之外, 其他变量都为原来值加一, 因为只有自动变量没有进行值引用. 在 block 中, 自动变量是复制到 block 中进行储存的, 而其他种类的变量以指针的形式进行调用. 同时, 复制到 block 中的自动变量无法修改, 如果要用 block 修改外部的自动变量, 可以在声明自动变量时, 使用<code class="language-text highlighter-rouge">__block</code>标识符:</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
</pre></td><td class="rouge-code"><pre><span class="n">__block</span> <span class="kt">int</span> <span class="n">local</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>

<span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">)</span> <span class="o">^</span> <span class="p">{</span>
    <span class="o">++</span><span class="n">local</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>这样就可以修改外部的自动变量<code class="language-text highlighter-rouge">local</code>. 可以看出, swift 中的闭包默认的捕获行为与自动变量加了<code class="language-text highlighter-rouge">__block</code>的 block 相同.</p>

<h2 id="block--2">block 也是对象</h2>

<p>和上一篇 swift 中的闭包一样, 也就是说 block 也是引用类型, 同样可能会发生循环引用问题, 原理和处理方式同闭包类似, 不过多介绍了.</p>

<h2 id="block--3">block 储存方式</h2>

<p>在C语言中, 存储区分为字面区, 全局区, 栈区和堆区, 字面区储存的都是常量所以不能储存 block, 不过其他位置都可以, 也就是说 block 有三种储存形式, 全局block(<code class="language-text highlighter-rouge">_NSConcreteGlobalBlock</code>), 栈block(<code class="language-text highlighter-rouge">_NSConcreteStackBlock</code>)和堆block(<code class="language-text highlighter-rouge">_NSConcreteMallocBlock</code>). 分辨规则如下:</p>

<ul>
  <li>没有使用外部捕获的自动变量的 block 均为全局block</li>
  <li>其他情况定义的 block 类型与相应位置定义的变量类型一致</li>
</ul>

<p>国为栈block在离开作用域后会和栈变量一样释放内存, 所以在非 ARC 环境下涉及栈block的赋值要小心处理.</p>

<h2 id="arc--block">ARC 下的 block</h2>

<p>上面提了非 ARC 环境下栈block可能会出现空指针问题, 也就是说 ARC 会对block对象进行其他处理. 在 objc 中, 可以调用<code class="language-text highlighter-rouge">[block copy]</code>创建一个堆block, 对于栈block, 调用<code class="language-text highlighter-rouge">copy</code>方法会将它复制到堆上, 而对于堆block, 调用<code class="language-text highlighter-rouge">copy</code>方法不会再进行复制, 而只是将引用计数加一.</p>

<p>和自动插入<code class="language-text highlighter-rouge">retain</code>, <code class="language-text highlighter-rouge">release</code>一样, ARC 会适当地在代码中插入<code class="language-text highlighter-rouge">copy</code>, 当把栈block赋值给强引用对象(<code class="language-text highlighter-rouge">__strong</code>)或者作为函数返回值时, 会自动进行 cpoy, 在调用 gcd 之类的 cocoa 框架时, 也会复制 block. 考虑下面的例子:</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
</pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">[</span><span class="mi">5</span><span class="p">])(</span><span class="kt">void</span><span class="p">);</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="o">+</span><span class="k">+</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">block</span><span class="p">[</span><span class="nf">i</span><span class="p">]</span> <span class="o">=</span> <span class="o">^</span> <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">};</span>
<span class="p">}</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="o">+</span><span class="k">+</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">block</span><span class="p">[</span><span class="nf">i</span><span class="p">]();</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>在 MRC 下, 输出值是五排<code class="language-text highlighter-rouge">5</code>, 并且因为栈block实际已经释放了, 只是那块内存还没有被使用上, 所以没报错. 而在 ARC 下, 因为数组<code class="language-text highlighter-rouge">block</code>默认是强引用对象, 所以在第一个循环中会将栈block复制到堆上再赋给<code class="language-text highlighter-rouge">block</code>数组, 输出结果就是<code class="language-text highlighter-rouge">1 2 3 4 5</code>.</p>

<h2 id="block--4">block 的实现</h2>

<p>了解 block 的实现对理解 block 行为有很大帮助, 使用 clang 可以将含 block 的代码转换成 C++ 源码, 可以从中看出 block 的具体实现. 不过我这里就不看了, 定性了解就好orz</p>

<h3 id="block--5">block 本质是对象</h3>

<p>上面也说了, 这很好理解, block 对象中也包含了捕获的自动变量, 没有使用的变量不会出现在对象中. 因为实际上 objc, C++ 的类都可以翻译成C语言的结构体, block 也不例外, 所以 block 的调用实际上也是类似于函数指针一样的调用.</p>

<h3 id="block--6">block 捕获的变量</h3>

<p>block 使用值复制的方式捕获自动变量, 使用指针方式捕获静态变量和全局变量, 在自动变量声明前加入<code class="language-text highlighter-rouge">__block</code>后, 自动变量的声明也会变成结构体, 也就是原来的自动变量也变成了一个对象, block 中包含对这个对象的引用, 这样就可以在 block 中修改外部的自动变量.</p>

<h3 id="block--7">block 的复制</h3>

<p>调用<code class="language-text highlighter-rouge">copy</code>方法会复制 block, 那么在对栈block进行复制时, 会在栈和堆上出现两个一样的 block, 如何判断调用时使用哪个 block? 在 block 结构体中, 有一个<code class="language-text highlighter-rouge">__forwarding</code>变量, 它通常指向 block 自身, 在对栈block复制时, 栈block的<code class="language-text highlighter-rouge">__forwarding</code>会指向堆block, 这样就能保证任何时候都可以访问同一个 block.</p>

<h3 id="block--8">block 中对象的所有权</h3>

<p>在引用计数中, A 对 B 的所有权代表 A 是否会改变 B 的引用计数. 看一个在 ARC 环境下的例子:</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
</pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="n">id</span><span class="p">);</span>
<span class="p">{</span>
    <span class="n">id</span> <span class="n">array</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSMutableArray</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">];</span>
    <span class="n">block</span> <span class="o">=</span> <span class="o">^</span><span class="p">(</span><span class="n">id</span> <span class="n">object</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">[</span><span class="n">array</span> <span class="nf">addObject</span><span class="p">:</span> <span class="n">object</span><span class="p">];</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"array count: "</span> <span class="o">&lt;&lt;</span> <span class="p">[</span><span class="n">array</span> <span class="nf">count</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">};</span>
<span class="p">}</span>
<span class="n">block</span><span class="p">([[</span><span class="n">NSObject</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">]);</span>
<span class="n">block</span><span class="p">([[</span><span class="n">NSObject</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">]);</span>
<span class="n">block</span><span class="p">([[</span><span class="n">NSObject</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">]);</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
</pre></td><td class="rouge-code"><pre>array count: 1
array count: 2
array count: 3
</pre></td></tr></tbody></table></code></pre></div></div>

<p>正常情况<code class="language-text highlighter-rouge">array</code>在离开作用域后会立刻析构, 但上面的例子看起来是在<code class="language-text highlighter-rouge">block</code>未析构时, <code class="language-text highlighter-rouge">array</code>也未析构. 首先<code class="language-text highlighter-rouge">array</code>是自动变量, 可以看作是一个<code class="language-text highlighter-rouge">NSMutableArray</code>的指针, 在<code class="language-text highlighter-rouge">block</code>捕获它时, 是进行值复制的. <code class="language-text highlighter-rouge">array</code>是默认的强引用类型, 所以有复制的时候, 复制的值也是强引用, 也就是说在捕获的同时, <code class="language-text highlighter-rouge">block</code>也拥有了<code class="language-text highlighter-rouge">array</code>的所有权, 所以即使当<code class="language-text highlighter-rouge">array</code>离开作用域 release 之后, <code class="language-text highlighter-rouge">block</code>扔持有<code class="language-text highlighter-rouge">array</code>指向的<code class="language-text highlighter-rouge">NSMutableArray</code>. 如果在<code class="language-text highlighter-rouge">array</code>前加入<code class="language-text highlighter-rouge">__block</code>也一样, 只不过可以进一步修改<code class="language-text highlighter-rouge">array</code>的指向.</p>

<p>注意的是 block 对对象的持有是对于强引用的, 对于弱引用, 为了不破坏弱引用的意义, block 不会持有弱引用所指向的对象. 比如下面的输出就都是0:</p>

<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="n">id</span><span class="p">);</span>
<span class="p">{</span>
    <span class="n">id</span> <span class="n">array</span> <span class="o">=</span> <span class="p">[[</span><span class="nf">NSMutableArrayalloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">];</span>
    <span class="n">id</span> <span class="n">__weak</span> <span class="n">weakArray</span> <span class="o">=</span> <span class="n">array</span><span class="p">;</span>
    <span class="n">block</span> <span class="o">=</span> <span class="o">^</span><span class="p">(</span><span class="n">id</span> <span class="n">object</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">[</span><span class="n">weakArray</span> <span class="nf">addObject</span><span class="p">:</span> <span class="n">object</span><span class="p">];</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"array count: "</span> <span class="o">&lt;&lt;</span> <span class="p">[</span><span class="n">weakArray</span> <span class="nf">count</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">};</span>
<span class="p">}</span>
<span class="n">block</span><span class="p">([[</span><span class="n">NSObject</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">]);</span>
<span class="n">block</span><span class="p">([[</span><span class="n">NSObject</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">]);</span>
<span class="n">block</span><span class="p">([[</span><span class="n">NSObject</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">]);</span>
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="objc" /><summary type="html"><![CDATA[看完了 swift 中的闭包, 自然又重新看了一下 Objective-C 中的 block, 之前因为没写过, 所以浏览一遍就过去了, 现在发现了解一下实现对于理解 swift 有很大的好处, 毕竟 swift 简化了太多底层的东西.]]></summary></entry><entry><title type="html">swift 中的闭包捕获</title><link href="https://shino.space/2018/swift%E4%B8%AD%E7%9A%84%E9%97%AD%E5%8C%85%E6%8D%95%E8%8E%B7/" rel="alternate" type="text/html" title="swift 中的闭包捕获" /><published>2018-08-17T05:49:32+00:00</published><updated>2018-08-17T05:49:32+00:00</updated><id>https://shino.space/2018/swift%E4%B8%AD%E7%9A%84%E9%97%AD%E5%8C%85%E6%8D%95%E8%8E%B7</id><content type="html" xml:base="https://shino.space/2018/swift%E4%B8%AD%E7%9A%84%E9%97%AD%E5%8C%85%E6%8D%95%E8%8E%B7/"><![CDATA[<p>关于闭包捕获相关的问题在官方文档上并没有写在一起, 系统地整理一下, 复习了一遍.</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#section-1" id="markdown-toc-section-1">闭包表达式的基本形式</a></li>
  <li><a href="#section-2" id="markdown-toc-section-2">闭包中默认捕获的是变量的引用</a>    <ul>
      <li><a href="#section-3" id="markdown-toc-section-3">被捕获的是值类型</a></li>
      <li><a href="#section-4" id="markdown-toc-section-4">被捕获的是引用类型</a></li>
      <li><a href="#section-5" id="markdown-toc-section-5">引用是可以修改外部变量的</a></li>
    </ul>
  </li>
  <li><a href="#section-6" id="markdown-toc-section-6">闭包引起的循环引用</a>    <ul>
      <li><a href="#section-7" id="markdown-toc-section-7">闭包本身是引用类型</a></li>
      <li><a href="#section-8" id="markdown-toc-section-8">闭包对引用类型本身的引用是怎么释放的</a></li>
      <li><a href="#section-9" id="markdown-toc-section-9">闭包的引用循环</a></li>
    </ul>
  </li>
  <li><a href="#section-10" id="markdown-toc-section-10">总结</a></li>
</ul>

<h2 id="section-1">闭包表达式的基本形式</h2>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
</pre></td><td class="rouge-code"><pre><span class="p">{</span> <span class="p">[</span><span class="n">copyItem</span> <span class="o">=</span> <span class="n">item</span><span class="p">,</span> <span class="n">anotherItem</span><span class="p">,</span> <span class="n">unkonwn</span> <span class="k">self</span><span class="p">]</span> <span class="p">(</span><span class="nv">paraA</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="nv">paraB</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="k">in</span>
    <span class="c1">// some code</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>随便写了一个, 一个完整没有简写的闭包包括捕获列表, 参数类型和返回值, 参数和返回值与普通函数差不多, 就不提了. 捕获列表中可以为被捕获的值起一个别名, 也可以直接直接使用原来的名称(但实际上是闭包外变量的一个拷贝), 为了防止循环引用, 可以在被捕获的引用变量前加<code class="language-text highlighter-rouge">weak</code>, <code class="language-text highlighter-rouge">unknown</code>等修饰.</p>

<h2 id="section-2">闭包中默认捕获的是变量的引用</h2>

<p>为了下面方便演示, 首先定义下面这个延迟函数:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
</pre></td><td class="rouge-code"><pre><span class="kd">func</span> <span class="nf">delay</span><span class="p">(</span><span class="n">_</span> <span class="nv">time</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="kd">@escaping</span> <span class="nv">closure</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="p">())</span> <span class="p">{</span>
    <span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="nf">asyncAfter</span><span class="p">(</span><span class="nv">deadline</span><span class="p">:</span> <span class="o">.</span><span class="nf">now</span><span class="p">()</span> <span class="o">+</span> <span class="kt">DispatchTimeInterval</span><span class="o">.</span><span class="nf">seconds</span><span class="p">(</span><span class="n">time</span><span class="p">))</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"colsure start execute"</span><span class="p">)</span>
        <span class="nf">colsure</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>这个函数要把传入的闭包延迟两秒执行, 因为在函数执行后才执行闭包, 所以使用<code class="language-text highlighter-rouge">@escaping</code>来修饰闭包. emmmm没搞清 playground 里怎么弄延迟, 所以还是用iOS程序来运行吧.</p>

<h3 id="section-3">被捕获的是值类型</h3>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
</pre></td><td class="rouge-code"><pre><span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">delay</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"a = </span><span class="se">\(</span><span class="n">a</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">10</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
</pre></td><td class="rouge-code"><pre>closure start execute
a = 10
</pre></td></tr></tbody></table></code></pre></div></div>

<p>可以发现闭包并没有把<code class="language-text highlighter-rouge">a</code>的值复制下来, 而是直接捕获了它的引用, 在闭包执行前修改<code class="language-text highlighter-rouge">a</code>, 执行闭包的时候通过引用找到现在<code class="language-text highlighter-rouge">a</code>的值, 打印<code class="language-text highlighter-rouge">10</code>.</p>

<p>如果使用捕获列表, 那么就会在闭包内对捕获列表中的值进行复制, 闭包内外的变量此时不是同一个变量了.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
</pre></td><td class="rouge-code"><pre><span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">delay</span> <span class="p">{</span> <span class="p">[</span><span class="n">copyA</span> <span class="o">=</span> <span class="n">a</span><span class="p">]</span> <span class="k">in</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"a = </span><span class="se">\(</span><span class="n">copyA</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">10</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
</pre></td><td class="rouge-code"><pre>closure start execute
a = 0
</pre></td></tr></tbody></table></code></pre></div></div>

<p>等同于下面这么写</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
</pre></td><td class="rouge-code"><pre><span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">var</span> <span class="nv">copyA</span> <span class="o">=</span> <span class="n">a</span> <span class="c1">// 注意这个变量只可以在闭包内访问</span>
<span class="n">delay</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"a = </span><span class="se">\(</span><span class="n">copyA</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">10</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="section-4">被捕获的是引用类型</h3>

<p>因为闭包内捕获的是引用, 所以被捕获值本是是引用类型而言, 捕获的并不是引用的值, 而是引用类型本身的一个引用, 有点像C语言中的双重指针.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="kt">Person</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
    <span class="p">}</span>

    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="k">self</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s"> is dead"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">var</span> <span class="nv">b</span> <span class="o">=</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"B"</span><span class="p">)</span>
<span class="n">delay</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"the name of b is </span><span class="se">\(</span><span class="n">b</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">b</span> <span class="o">=</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"C"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
</pre></td><td class="rouge-code"><pre>B is dead
closure start execute
the name of b = C
C is dead
</pre></td></tr></tbody></table></code></pre></div></div>

<p>和上面值类型的例子差不多, 因为捕获的是引用类型本身的引用, 并没有持有类本身, 所以当给<code class="language-text highlighter-rouge">b</code>赋给其他值的时候, <code class="language-text highlighter-rouge">person B</code>引用计数为0, 进行了析构. 闭包调用的时候, 通过变量<code class="language-text highlighter-rouge">b</code>的引用找到了<code class="language-text highlighter-rouge">b</code>本身, 再跟据<code class="language-text highlighter-rouge">b</code>找到了<code class="language-text highlighter-rouge">person C</code>实例, 输出结果.</p>

<p>如果使用捕获列表, 情况就不一样了, 因为捕获列表是复制一份值到闭包内, 所以闭包内的复制变量会持有类实例的强引用.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
</pre></td><td class="rouge-code"><pre><span class="k">var</span> <span class="nv">b</span> <span class="o">=</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"B"</span><span class="p">)</span>
<span class="n">delay</span> <span class="p">{</span> <span class="p">[</span><span class="n">copyB</span> <span class="o">=</span> <span class="n">b</span><span class="p">]</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"then name of b is </span><span class="se">\(</span><span class="n">b</span><span class="o">.</span><span class="n">name</span><span class="s">")
}
b = Person(name: "</span><span class="kt">C</span><span class="s">")
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
</pre></td><td class="rouge-code"><pre>closure start execute
the name of b is B
B is dead
C is dead
</pre></td></tr></tbody></table></code></pre></div></div>

<p>因为闭包里的<code class="language-text highlighter-rouge">copyB</code>就是<code class="language-text highlighter-rouge">b</code>的复制, 而不是值引用, 所以同样可以对类实例保留强引用.</p>

<h3 id="section-5">引用是可以修改外部变量的</h3>

<p>最后, 因为捕获的是引用, 所以如果不是使用捕获列表复制变量的话, 闭包内部是可以修改外部被捕获的变量的</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
</pre></td><td class="rouge-code"><pre><span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">delay</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">=</span> <span class="mi">10</span>
<span class="p">}</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"a = </span><span class="se">\(</span><span class="n">a</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
</pre></td><td class="rouge-code"><pre>closure start execute
a = 10
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="section-6">闭包引起的循环引用</h2>

<h3 id="section-7">闭包本身是引用类型</h3>

<p>因为函数, 或者闭包在 swift 中是一等值, 所以闭包是可以以赋值的形式传来传去的, 在传递的过程中, 闭包本身没有进行复制, 传值传的只是闭包自身的引用</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="kd">func</span> <span class="nf">makeIncrease</span><span class="p">(</span><span class="n">_</span> <span class="nv">num</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">base</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">let</span> <span class="nv">closure</span> <span class="o">=</span>  <span class="p">{</span>
        <span class="n">base</span> <span class="o">+=</span> <span class="n">num</span>
        <span class="k">return</span> <span class="n">base</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">closure</span>
<span class="p">}</span>

<span class="k">let</span> <span class="nv">increase</span> <span class="o">=</span> <span class="nf">makeIncrease</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span class="nf">increase</span><span class="p">()</span>
<span class="c1">// 10</span>
<span class="nf">increase</span><span class="p">()</span>
<span class="c1">// 20</span>
<span class="k">let</span> <span class="nv">anotherIncrease</span> <span class="o">=</span> <span class="n">increase</span>
<span class="nf">anotherIncrease</span><span class="p">()</span>
<span class="c1">// 30</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>函数<code class="language-text highlighter-rouge">makeIncrease</code>返回一个闭包, 每执行一个这个闭包, 返回值增加<code class="language-text highlighter-rouge">num</code>. 可以看出当把闭包由<code class="language-text highlighter-rouge">increase</code>传给<code class="language-text highlighter-rouge">anotherIncrease</code>时, 还是同一个闭包, 说明传递的只是闭包的引用.</p>

<h3 id="section-8">闭包对引用类型本身的引用是怎么释放的</h3>

<p>说起来比较绕口, 还是用上面闭包捕获引用类型的例子来说明, 为了更清晰地看出引用类型什么时候脱离作用域进行析构, 我们把代码放在函数里:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="kt">Person</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>
    <span class="nf">init</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">demo</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">b</span> <span class="o">=</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"B"</span><span class="p">)</span>
    <span class="n">delay</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"the name of b is </span><span class="se">\(</span><span class="n">b</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">b</span> <span class="o">=</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"C"</span><span class="p">)</span>
<span class="p">}</span>

<span class="nf">demo</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>输出为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
</pre></td><td class="rouge-code"><pre>B is dead
closure start execute
the name of b is C
C is dead
</pre></td></tr></tbody></table></code></pre></div></div>

<p>开始的时候并没有说最下面的一行输出是怎么回事, 当离开<code class="language-text highlighter-rouge">demo</code>的作用域时(此时闭包还没有执行), <code class="language-text highlighter-rouge">person C</code>并没有析构, 因为引用类型变量<code class="language-text highlighter-rouge">b</code>还在被闭包引用着, 而<code class="language-text highlighter-rouge">b</code>保留着对<code class="language-text highlighter-rouge">person C</code>的引用, 所以直到闭包运行结束之后, 变量<code class="language-text highlighter-rouge">b</code>脱离作用域, <code class="language-text highlighter-rouge">person C</code>引用计数为0, 进行析构.</p>

<h3 id="section-9">闭包的引用循环</h3>

<p>因为闭包本身是引用类型, 可以被类或其他闭包引用; 而闭包本身可以引用类或者其他闭包, 这就可以会构成引用循环</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="kt">Person</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>

    <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">introduce</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">str</span> <span class="o">=</span> <span class="s">"my name is </span><span class="se">\(</span><span class="k">self</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s">"</span>
        <span class="k">return</span> <span class="n">str</span>
    <span class="p">}</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"A"</span><span class="p">)</span>
<span class="n">a</span><span class="o">.</span><span class="nf">introduce</span><span class="p">()</span>
<span class="c1">// my name is A</span>
<span class="n">a</span><span class="o">.</span><span class="n">introduce</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">str</span> <span class="o">=</span> <span class="s">"I am a person"</span>
<span class="p">}</span>
<span class="n">a</span><span class="o">.</span><span class="nf">introduce</span><span class="p">()</span>
<span class="c1">// I am a person</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-text highlighter-rouge">introduce</code>是类<code class="language-text highlighter-rouge">Person</code>的一个变量, 它是一个闭包, 将它声明为懒加载是因为它引用了<code class="language-text highlighter-rouge">self</code>, 而懒加载可以保证只有当<code class="language-text highlighter-rouge">self</code>初始化结束, 并且<code class="language-text highlighter-rouge">self</code>存在的时候才可以调用这个变量.</p>

<p>可以看出, 闭包中引用了<code class="language-text highlighter-rouge">self</code>, 也就是说闭包有对<code class="language-text highlighter-rouge">self</code>的强引用; 而闭包本身又是<code class="language-text highlighter-rouge">self</code>的一个变量, 即<code class="language-text highlighter-rouge">self</code>又强引用了闭包, 这就造成了循环引用, 可以使用<code class="language-text highlighter-rouge">unknown</code>, <code class="language-text highlighter-rouge">weak</code>来修饰这个闭包变量, 或者, 因为我们是在讲闭包, 所以可以在捕获列表中使用<code class="language-text highlighter-rouge">unknown</code>, <code class="language-text highlighter-rouge">weak</code>来避免循环引用:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
</pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="kt">Person</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>

    <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">introduce</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="o">=</span> <span class="p">{</span> <span class="p">[</span><span class="n">unknown</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
        <span class="k">let</span> <span class="nv">str</span> <span class="o">=</span> <span class="s">"my name is </span><span class="se">\(</span><span class="k">self</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s">"</span>
    <span class="p">}</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>因为类实例析构的时候闭包肯定也不在了, 而使用闭包的时候类一定还在, 所以可以保证在使用期间<code class="language-text highlighter-rouge">self</code>不会为<code class="language-text highlighter-rouge">nil</code>, 在捕获进闭包时使用<code class="language-text highlighter-rouge">unknown</code>修饰<code class="language-text highlighter-rouge">self</code>就可以了.</p>

<h2 id="section-10">总结</h2>

<p>看这些例子还是有点绕, 其实总结起来注意几点就好:</p>

<ul>
  <li>闭包默认捕获的是值的引用</li>
  <li>如果是引用类型的值, 闭包捕获的是这个引用的引用</li>
  <li>捕获列表里捕获的是复制进来的值, 而不是引用</li>
  <li>闭包本身也是引用类型</li>
</ul>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="swift" /><summary type="html"><![CDATA[关于闭包捕获相关的问题在官方文档上并没有写在一起, 系统地整理一下, 复习了一遍.]]></summary></entry><entry><title type="html">将 Core Data 数据文件备份成 sqlite 文件</title><link href="https://shino.space/2018/%E5%B0%86-Core-Data-%E6%95%B0%E6%8D%AE%E6%96%87%E4%BB%B6%E5%A4%87%E4%BB%BD%E6%88%90-sqlite-%E6%96%87%E4%BB%B6/" rel="alternate" type="text/html" title="将 Core Data 数据文件备份成 sqlite 文件" /><published>2018-06-08T08:23:31+00:00</published><updated>2018-06-08T08:23:31+00:00</updated><id>https://shino.space/2018/%E5%B0%86-Core-Data-%E6%95%B0%E6%8D%AE%E6%96%87%E4%BB%B6%E5%A4%87%E4%BB%BD%E6%88%90-sqlite-%E6%96%87%E4%BB%B6</id><content type="html" xml:base="https://shino.space/2018/%E5%B0%86-Core-Data-%E6%95%B0%E6%8D%AE%E6%96%87%E4%BB%B6%E5%A4%87%E4%BB%BD%E6%88%90-sqlite-%E6%96%87%E4%BB%B6/"><![CDATA[<p>额我知道 Core Data 默认就是以 sqlite 文件保存的数据…题目上说的意思是将这个数据文件备份出来给其他的 Core Data 栈使用, 比如我的应用场景是把手机上 Core Data 文件同步到手表 app 上(当然数据量很小才这么干).</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#sqlite-" id="markdown-toc-sqlite-">直接用原来的 sqlite 文件不行么</a></li>
  <li><a href="#core-data-api" id="markdown-toc-core-data-api">用得到的 Core Data API</a></li>
  <li><a href="#section-1" id="markdown-toc-section-1">问题解决</a></li>
</ul>

<h2 id="sqlite-">直接用原来的 sqlite 文件不行么</h2>

<p>不行, 亲测!</p>

<p>如果进到 Core Data 保存的目录下可以看到, 除了 sqlite 文件之外, 还会有预写式日志文件(.whl)和共享内存文件(.shm), 当前的数据可能还在读写, 或许有些还没有写入 .sqlite 文件, 所以仅仅复制 .sqlite 文件是不可取的, 并且 .whl 和 .shm 文件也不好处理. 最好是可以找到 Core Data 自身的相关 API.</p>

<h2 id="core-data-api">用得到的 Core Data API</h2>

<p>看向 Core Data 栈的底层, 可以发现<code class="language-text highlighter-rouge">NSPersistentCoordinator</code>类下有一个<code class="language-text highlighter-rouge">migratePersistentStore(_:to:options:withType:)</code>方法, 该方法会把传入的<code class="language-text highlighter-rouge">NSPersistentStore</code>文件复制到所传入的新 URL 的位置, 正是我们想要的 API.</p>

<p>不过这个 API 本来是用于转移数据位置的, 会导致原来的<code class="language-text highlighter-rouge">NSPersistentCoordinator</code>移掉这个<code class="language-text highlighter-rouge">NSPersistentStore</code>. 我们想要的功能仅仅是把数据复制一下, 而原来的 Core Data 继续生效, 所以还需要再创建一个临时的<code class="language-text highlighter-rouge">NSPersistentCoordinator</code>指向这个<code class="language-text highlighter-rouge">NSPersistentStore</code>.</p>

<p>也就是说, 我们要在一个<code class="language-text highlighter-rouge">NSPersistentStore</code>上建立两个<code class="language-text highlighter-rouge">NSPersistentCoordinator</code>, 为了不对原来的 Core Data 产生影响, 并且也没有干预原来数据的必要, 我们要设置这个临时的<code class="language-text highlighter-rouge">NSPersistentcoordinator</code>对<code class="language-text highlighter-rouge">NSPersistentStore</code>为只读. 同时像上面说的, .whl 和 .shm 文件不好处理, 所以对于转移后的新文件, 我们要关闭日志功能.</p>

<h2 id="section-1">问题解决</h2>

<p>上代码:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="kd">func</span> <span class="nf">backupFile</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">URL</span> <span class="p">{</span>
    <span class="c1">// 以只有一个文件为例</span>
    <span class="k">let</span> <span class="nv">sourceStore</span> <span class="o">=</span> <span class="n">persistentStores</span><span class="o">.</span><span class="n">first</span><span class="o">!</span>
    <span class="c1">// 使用原来的数据模型创建一个临时 NSPersistentCoordinator</span>
    <span class="k">let</span> <span class="nv">backupCoordinator</span> <span class="o">=</span> <span class="kt">NSPersistentStoreCoordinator</span><span class="p">(</span><span class="nv">managedObjectModel</span><span class="p">:</span> <span class="n">managedObjectModel</span><span class="p">)</span>
    <span class="c1">// 设置下面要添加 NSPersistentStore 的配置, 需要保存证为只读</span>
    <span class="k">let</span> <span class="nv">options</span> <span class="o">=</span> <span class="p">(</span><span class="n">sourceStore</span><span class="o">.</span><span class="n">options</span> <span class="p">??</span> <span class="p">[:])</span><span class="o">.</span><span class="nf">merging</span><span class="p">([</span><span class="kt">NSReadOnlyPersistentStoreOption</span><span class="p">:</span> <span class="kc">true</span><span class="p">])</span> <span class="p">{</span> <span class="nv">$1</span> <span class="p">}</span>
    <span class="c1">// 将原来的 NSPerstentStore 添加进来, 除了配置之外, 其他参数都使用原来的即可</span>
    <span class="k">let</span> <span class="nv">backupStore</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">backupCoordinator</span><span class="o">.</span><span class="nf">addPersistentStore</span><span class="p">(</span><span class="nv">ofType</span><span class="p">:</span> <span class="n">sourceStore</span><span class="o">.</span><span class="n">type</span><span class="p">,</span>
                                                                <span class="nv">configurationName</span><span class="p">:</span> <span class="n">sourceStore</span><span class="o">.</span><span class="n">configurationName</span><span class="p">,</span>
                                                                <span class="nv">at</span><span class="p">:</span> <span class="n">sourceStore</span><span class="o">.</span><span class="n">url</span><span class="p">,</span>
                                                                <span class="nv">options</span><span class="p">:</span> <span class="n">options</span><span class="p">)</span>
    <span class="c1">// 设置下面转移文件的配置, 还是设置为只读, 而且关闭日志, 在转移的时候整理原来的数据碎片</span>
    <span class="k">let</span> <span class="nv">backupStoreOptions</span><span class="p">:</span> <span class="p">[</span><span class="kt">AnyHashable</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="kt">NSReadOnlyPersistentStoreOption</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
                                                  <span class="kt">NSSQLitePragmasOption</span><span class="p">:</span> <span class="p">[</span><span class="s">"journal_mode"</span><span class="p">:</span> <span class="s">"DELETE"</span><span class="p">],</span>
                                                  <span class="kt">NSSQLiteManualVacuumOption</span><span class="p">:</span> <span class="kc">true</span><span class="p">]</span>
    <span class="c1">// 提供数据要转换到的 URL</span>
    <span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="nf">generateUniqueURL</span><span class="p">()</span>
    <span class="c1">// 转移数据, 并且指定一下保存为 sqlite 文件</span>
    <span class="k">try!</span> <span class="n">backupCoordinator</span><span class="o">.</span><span class="nf">migratePersistentStore</span><span class="p">(</span><span class="n">backupStore</span><span class="p">,</span>
                                                  <span class="nv">to</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span>
                                                  <span class="nv">options</span><span class="p">:</span> <span class="n">backupStoreOptions</span><span class="p">,</span>
                                                  <span class="nv">withType</span><span class="p">:</span> <span class="kt">NSSQLiteStoreType</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">url</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>最后别放了使用之后处理一下生成和备份文件.</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="swift" /><summary type="html"><![CDATA[额我知道 Core Data 默认就是以 sqlite 文件保存的数据…题目上说的意思是将这个数据文件备份出来给其他的 Core Data 栈使用, 比如我的应用场景是把手机上 Core Data 文件同步到手表 app 上(当然数据量很小才这么干).]]></summary></entry><entry><title type="html">在 Swift 中使用 async/await</title><link href="https://shino.space/2018/%E5%9C%A8Swift%E4%B8%AD%E4%BD%BF%E7%94%A8asyncawait/" rel="alternate" type="text/html" title="在 Swift 中使用 async/await" /><published>2018-05-22T05:06:23+00:00</published><updated>2018-05-22T05:06:23+00:00</updated><id>https://shino.space/2018/%E5%9C%A8Swift%E4%B8%AD%E4%BD%BF%E7%94%A8asyncawait</id><content type="html" xml:base="https://shino.space/2018/%E5%9C%A8Swift%E4%B8%AD%E4%BD%BF%E7%94%A8asyncawait/"><![CDATA[<p>用 nodejs 写了点东西才发现入理异步不仅仅是 Promise 这点东西, 在 ES7 标准中, async/await 的引入使得可以像写同步代码一样写异步. 虽然 Swift 目前没有在语言层面上支持 Promise 和 async/await, 不过有 <a href="https://github.com/mxcl/PromiseKit">PromiseKit</a> 和 <a href="https://github.com/yannickl/AwaitKit">AwaitKit</a> 这样的库来简化我们的异步代码.</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#section-1" id="markdown-toc-section-1">接下来一直用这个例子</a></li>
  <li><a href="#promise-" id="markdown-toc-promise-">使用 Promise 解开嵌套闭包</a></li>
  <li><a href="#asyncawait--promise-" id="markdown-toc-asyncawait--promise-">使用 async/await 把 Promise 变成同步代码</a></li>
</ul>

<h2 id="section-1">接下来一直用这个例子</h2>

<p>比如我们要登录网站取得用户的信息, 需要三个步骤:</p>

<ul>
  <li>
    <p>取得登录 token</p>
  </li>
  <li>
    <p>登录通过用户验证</p>
  </li>
  <li>
    <p>取得信息</p>
  </li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="s">"https://theurl.com"</span><span class="p">)</span><span class="o">!</span>
<span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">data</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">let</span> <span class="nv">token</span> <span class="o">=</span> <span class="nf">fetchToken</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">token</span><span class="p">)</span>
    <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
            <span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="p">}</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">data</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">let</span> <span class="nv">auth</span> <span class="o">=</span> <span class="nf">fetchAuth</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">auth</span><span class="p">)</span>
        <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
                <span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
                <span class="k">return</span>
            <span class="p">}</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">data</span> <span class="k">else</span> <span class="p">{</span>
                <span class="k">return</span><span class="p">;</span>
                <span class="k">let</span> <span class="nv">info</span> <span class="o">=</span> <span class="nf">fetchInfo</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
                <span class="nf">print</span><span class="p">(</span><span class="n">info</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
<span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>一共进行了三次异步请求, 因为每一步都需要上一步的数据, 所以只能把下一步的请求放在上一步请求的回调闭包中.</p>

<h2 id="promise-">使用 Promise 解开嵌套闭包</h2>

<p>在刚接触 Promise 的时候就写过一篇相关的<a href="https://shino.space/2017/使用函数式swift解决回调地狱">文章</a>, 主要是讲 Promise 内部是怎么把嵌套调用变成链式调用的, 这里主要讲一下 Promise 的思想.</p>

<p>在异步调用的回调函数中, 我们要处理两种情况: 回调成功和失败, 如果回调成功, 回调函数中应该会有一个我们希望得到的一个传入参数; 失败的话会传入一个<code class="language-text highlighter-rouge">Error</code>. 也就是说, 回调函数的传入参数可以是一个枚举:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
</pre></td><td class="rouge-code"><pre><span class="kd">enum</span> <span class="n">result</span> <span class="p">{</span>
    <span class="k">case</span> <span class="nf">success</span><span class="p">(</span><span class="kt">WishType</span><span class="p">)</span>
    <span class="k">case</span> <span class="nf">failed</span><span class="p">(</span><span class="kt">Error</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Promise 把这个过程封装了起来, 它允许传入两个函数, 对应着回调成功和失败, 两个函数的参数分别是<code class="language-text highlighter-rouge">WishType</code>和<code class="language-text highlighter-rouge">Error</code>. 这两个函数是 Promise 生成的, 在使用过程用我们跟据调用是否成功来选择调用哪一个. 使用<code class="language-text highlighter-rouge">then</code>函数可以向 Promise 添加新的异步调用, 对应着回调<code class="language-text highlighter-rouge">fullfill</code>函数, 并再返回一个 Promise. 使用<code class="language-text highlighter-rouge">catch</code>函数相当于传入了一个<code class="language-text highlighter-rouge">rejected</code>函数, 进行错误处理.</p>

<p><code class="language-text highlighter-rouge">then</code>与<code class="language-text highlighter-rouge">catch</code>的函数类型和<code class="language-text highlighter-rouge">fullfill</code>与<code class="language-text highlighter-rouge">rejected</code>的并不一样, Promise 内部实现了它们之前的转换. 需要注意的是, 并不是<code class="language-text highlighter-rouge">then</code>函数触发了异步调用的开始, Promise 实例化的时候, 异步调用就已经开始了, <code class="language-text highlighter-rouge">then</code>和<code class="language-text highlighter-rouge">catch</code>只是指写了当 Promise 调用成功或失败后, 下一步应该做什么.(这制杖的语法高亮还把catch标红了…)</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="kd">struct</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">WishType</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nf">init</span><span class="p">(</span><span class="nv">resolver</span><span class="p">:</span> <span class="p">(</span><span class="n">_</span> <span class="nv">fullfill</span><span class="p">:</span> <span class="kd">@escaping</span><span class="p">(</span><span class="kt">WishType</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">,</span> <span class="n">_</span> <span class="nv">rejected</span><span class="p">:</span> <span class="kd">@escaping</span><span class="p">(</span><span class="kt">Error</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">......</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="n">then</span><span class="o">&lt;</span><span class="kt">NextType</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">body</span><span class="p">:</span> <span class="p">(</span><span class="kt">WishType</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">NextType</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">NextType</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="o">......</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="p">`</span><span class="nv">catch</span><span class="p">`(</span><span class="n">_</span> <span class="nv">body</span><span class="p">:</span> <span class="p">(</span><span class="kt">Error</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">......</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>使用 Promise, 就可以把上面例子中的多层嵌套解开了:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="s">"https://theurl.com"</span><span class="p">)</span><span class="o">!</span>
<span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">Data</span><span class="o">&gt;</span> <span class="p">{</span> <span class="n">resolver</span> <span class="k">in</span>
    <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
            <span class="n">resolver</span><span class="o">.</span><span class="nf">rejected</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">data</span> <span class="p">{</span>
            <span class="n">resolver</span><span class="o">.</span><span class="nf">fullfill</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
<span class="p">}</span><span class="o">.</span><span class="n">then</span> <span class="p">{</span> <span class="n">data</span> <span class="k">in</span>
    <span class="k">let</span> <span class="nv">token</span> <span class="o">=</span> <span class="nf">fetchToken</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">token</span><span class="p">)</span>
    <span class="k">return</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">Data</span><span class="o">&gt;</span> <span class="p">{</span> <span class="n">resolver</span> <span class="k">in</span>
        <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
                <span class="n">resolver</span><span class="o">.</span><span class="nf">rejected</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">data</span> <span class="p">{</span>
                <span class="n">resolver</span><span class="o">.</span><span class="nf">fullfill</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span><span class="o">.</span><span class="n">then</span> <span class="p">{</span> <span class="n">data</span> <span class="k">in</span>
    <span class="k">let</span> <span class="nv">auth</span> <span class="o">=</span> <span class="nf">fetchAuth</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">fron</span><span class="p">:</span> <span class="n">auth</span><span class="p">)</span>
    <span class="k">return</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="n">data</span><span class="o">&gt;</span> <span class="p">{</span> <span class="n">resolver</span> <span class="k">in</span>
        <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
                <span class="n">resolver</span><span class="o">.</span><span class="nf">rejected</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">data</span> <span class="p">{</span>
                <span class="n">resolver</span><span class="o">.</span><span class="nf">fullfill</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span><span class="o">.</span><span class="n">then</span> <span class="p">{</span> <span class="n">data</span> <span class="k">in</span>
    <span class="k">let</span> <span class="nv">info</span> <span class="o">=</span> <span class="nf">fetchInfo</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
<span class="p">}</span><span class="o">.</span><span class="k">catch</span> <span class="p">{</span> <span class="n">error</span> <span class="k">in</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>

</pre></td></tr></tbody></table></code></pre></div></div>

<p>可以看到之前的多重嵌套回调已经变成了链式的函数调用. 额, 变得这么长是因为定义 Promise 的写了不少…如果使用 PromiseKit, <code class="language-text highlighter-rouge">URLsession</code>这处常用的异步函数都已经封装好了 Promise, 这里手动定义是为了更好地理解 Promise.</p>

<h2 id="asyncawait--promise-">使用 async/await 把 Promise 变成同步代码</h2>

<p>虽然 Promise 使得异步调用不必写多重嵌套回调, 不过这种链式函数调用的方式还是和一般的同步执行代码有一定的区别, 我们最终希望可以变成这样的同步形式的代码:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="s">"https://theurl.com"</span><span class="p">)</span><span class="o">!</span>
<span class="k">let</span> <span class="nv">tokenData</span> <span class="o">=</span> <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">token</span> <span class="o">=</span> <span class="nf">fetchToken</span><span class="p">(</span><span class="n">tokenData</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">authData</span> <span class="o">=</span> <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">tkoen</span><span class="p">))</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">auth</span> <span class="o">=</span> <span class="nf">fetchAuth</span><span class="p">(</span><span class="n">authData</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">infoData</span> <span class="o">=</span> <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="kt">URLRequest</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">auth</span><span class="p">))</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">info</span> <span class="o">=</span> <span class="nf">fetchInfo</span><span class="p">(</span><span class="n">infoData</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>借助 AwaitKit, 我们真的可以以这种形式来写异步代码. async/await 是基于 Promise 的, <code class="language-text highlighter-rouge">async</code>其实就是对 Promise 进行了封装, 可以更方便地定义 Promise:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
</pre></td><td class="rouge-code"><pre><span class="kd">func</span> <span class="nf">tokenPromise</span><span class="p">(</span><span class="n">_</span> <span class="nv">url</span><span class="p">:</span> <span class="kt">URL</span><span class="p">)</span><span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">Data</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">async</span> <span class="p">{</span>
        <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
                <span class="k">throw</span> <span class="n">error</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="n">data</span><span class="o">!</span>
        <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">authPromise</span><span class="p">(</span><span class="n">_</span> <span class="nv">request</span><span class="p">:</span> <span class="kt">URLRequest</span><span class="p">)</span><span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">Data</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">async</span> <span class="p">{</span>
        <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
                <span class="k">throw</span> <span class="n">error</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="n">data</span><span class="o">!</span>
        <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">infoPromise</span><span class="p">(</span><span class="n">_</span> <span class="nv">request</span><span class="p">:</span> <span class="kt">URLRequest</span><span class="p">)</span><span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">Data</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">async</span> <span class="p">{</span>
        <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">dataTask</span><span class="p">(</span><span class="n">with</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="p">{</span>
                <span class="k">throw</span> <span class="n">error</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="n">data</span><span class="o">!</span>
        <span class="p">}</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>再使用<code class="language-text highlighter-rouge">await</code>函数把异步函数变为同步函数, 写出同步形式的代码:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="s">"https://theurl.com"</span><span class="p">)</span><span class="o">!</span>
<span class="k">do</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">tokenData</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">await</span><span class="p">(</span><span class="nf">tokenPromise</span><span class="p">(</span><span class="n">url</span><span class="p">))</span>
    <span class="k">let</span> <span class="nv">token</span> <span class="o">=</span> <span class="nf">fetchToken</span><span class="p">(</span><span class="n">tokenData</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">authData</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">await</span><span class="p">(</span><span class="nf">authPromise</span><span class="p">(</span><span class="kt">URLRequest</span><span class="p">(</span><span class="nv">form</span><span class="p">:</span> <span class="n">token</span><span class="p">)))</span>
    <span class="k">let</span> <span class="nv">auth</span> <span class="o">=</span> <span class="nf">fetchAuth</span><span class="p">(</span><span class="n">authData</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">infoData</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">await</span><span class="p">(</span><span class="nf">infoPromise</span><span class="p">(</span><span class="kt">URLRequest</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">auth</span><span class="p">)))</span>
    <span class="k">let</span> <span class="nv">info</span> <span class="o">=</span> <span class="nf">fetchInfo</span><span class="p">(</span><span class="n">infoData</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
<span class="p">}</span><span class="o">.</span><span class="nf">catch</span><span class="p">(</span><span class="k">let</span> <span class="nv">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>可以看到调用异步函数, 处理异常都变成了同步的方式.</p>

<p><code class="language-text highlighter-rouge">await</code> 内部实现使用了信号量来阻塞线程, 使得异步调用结束之前当前线程不会向下运行:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="kd">func</span> <span class="k">await</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">promise</span><span class="p">:</span> <span class="kt">Promise</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">)</span><span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">T</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">queue</span> <span class="o">=</span> <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="s">"await.asyncqueue"</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="o">.</span><span class="n">concurrent</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">semaphore</span> <span class="o">=</span> <span class="kt">DispatchSemaphore</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">result</span><span class="p">:</span> <span class="kt">T</span><span class="p">?</span>
    <span class="k">let</span> <span class="nv">error</span><span class="p">:</span> <span class="kt">Error</span><span class="p">?</span>
    <span class="n">promise</span><span class="o">.</span><span class="nf">then</span><span class="p">(</span><span class="nv">on</span><span class="p">:</span> <span class="n">queue</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">result</span> <span class="o">=</span> <span class="nv">$0</span>
        <span class="n">semaphore</span><span class="o">.</span><span class="nf">signal</span><span class="p">()</span>
    <span class="p">}</span><span class="o">.</span><span class="nf">catch</span><span class="p">(</span><span class="nv">on</span><span class="p">:</span> <span class="n">queue</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">error</span> <span class="o">=</span> <span class="nv">$0</span>
        <span class="n">semaphore</span><span class="o">.</span><span class="nf">signal</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="n">_</span> <span class="o">=</span> <span class="n">semaphore</span><span class="o">.</span><span class="nf">wait</span><span class="p">(</span><span class="nv">timeout</span><span class="p">:</span> <span class="o">.</span><span class="n">distabtFuture</span><span class="p">)</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="n">result</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="n">error</span><span class="o">!</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">result</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>使用了数值为0的信号量阻塞当前线程, 创建了一个后台线程来运行异步调用, 异步回调运行结束的时候再解开信号量. 要注意的是这里并没有用 PromiseKit 中的<code class="language-text highlighter-rouge">ensure</code>函数保证<code class="language-text highlighter-rouge">semaphore.signal()</code>的运行, 是因为<code class="language-text highlighter-rouge">ensure</code>会在<code class="language-text highlighter-rouge">catch</code>之前运行, 这样会使得线程在<code class="language-text highlighter-rouge">catch</code>之前就解除阴塞, 无法正常抛出错误.</p>

<p>因为 Swift 对于解决回调地狱还没有语言层面上的支持, Promise 还好, async/await 这种还只能使用函数而不是关键字的方式来实现, 不过在未来的 Swift 5 中有望得到原生的支持.</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="swift" /><summary type="html"><![CDATA[用 nodejs 写了点东西才发现入理异步不仅仅是 Promise 这点东西, 在 ES7 标准中, async/await 的引入使得可以像写同步代码一样写异步. 虽然 Swift 目前没有在语言层面上支持 Promise 和 async/await, 不过有 PromiseKit 和 AwaitKit 这样的库来简化我们的异步代码.]]></summary></entry><entry><title type="html">Core Data 与 json 转换</title><link href="https://shino.space/2018/Core-Data-%E4%B8%8E-json-%E8%BD%AC%E6%8D%A2/" rel="alternate" type="text/html" title="Core Data 与 json 转换" /><published>2018-04-26T05:18:52+00:00</published><updated>2018-04-26T05:18:52+00:00</updated><id>https://shino.space/2018/Core-Data-%E4%B8%8E-json-%E8%BD%AC%E6%8D%A2</id><content type="html" xml:base="https://shino.space/2018/Core-Data-%E4%B8%8E-json-%E8%BD%AC%E6%8D%A2/"><![CDATA[<p>进行网络请求时还是 json 格式使用最多, swift 4 中新添加的<code class="language-text highlighter-rouge">Codable</code>协议使得自定义格式与 json 转换更加方便, 不过因为 Core Data 中的<code class="language-text highlighter-rouge">NSManagedObject</code>类的一些小问题, 不能直接遵守<code class="language-text highlighter-rouge">Codable</code>协议, 所以分享一下自己的方法.</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#nsmanagedobject" id="markdown-toc-nsmanagedobject">要使用的<code class="language-text highlighter-rouge">NSManagedObject</code>类</a></li>
  <li><a href="#json-" id="markdown-toc-json-">json 格式</a></li>
  <li><a href="#json--1" id="markdown-toc-json--1">自定义需要与 json 互相转换的键值</a></li>
  <li><a href="#section-1" id="markdown-toc-section-1">定义编码</a></li>
  <li><a href="#section-2" id="markdown-toc-section-2">定义解码</a></li>
  <li><a href="#core-data-" id="markdown-toc-core-data-">更像 Core Data 地调用</a></li>
</ul>

<h2 id="nsmanagedobject">要使用的<code class="language-text highlighter-rouge">NSManagedObject</code>类</h2>

<p>就拿我正在写的课程表的数据模型来举例吧, 定义了下面两个<code class="language-text highlighter-rouge">NSManagedObject</code>类:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="kd">final</span> <span class="kd">class</span> <span class="kt">CourseData</span><span class="p">:</span> <span class="kt">NSManagedObject</span> <span class="p">{</span>
    <span class="kd">@NSMananaged</span> <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">teacher</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">time</span><span class="p">:</span> <span class="kt">Set</span><span class="o">&lt;</span><span class="kt">TimeData</span><span class="o">&gt;</span><span class="p">?</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="kt">TimeData</span><span class="p">:</span> <span class="kt">NSManagedObject</span> <span class="p">{</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">place</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">startseciton</span><span class="p">:</span> <span class="kt">Int64</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">endsetion</span><span class="p">:</span> <span class="kt">Int64</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">week</span><span class="p">:</span> <span class="kt">Int64</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">teachweek</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int64</span><span class="p">]</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">course</span><span class="p">:</span> <span class="kt">CourseData</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>就是常见的课程表数据类型, 课程与上课时间建立了一对多关系. 分成两个类一方面是因为按照上课时间排课程表, 或者显示课程详情; 另一方面也方便通过关系找到对应上课时间的课程信息. 课程类中上课时间属性是可选值, 因为有的课程可能没有上课时间.</p>

<h2 id="json-">json 格式</h2>

<p>因为网络 api 也是我写的, 所以格式和上面基本差不多:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"操作系统"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"teacher"</span><span class="p">:</span><span class="w"> </span><span class="s2">"teacher"</span><span class="w">
    </span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"综一 156"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"startsection"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
        </span><span class="nl">"endsection"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
        </span><span class="nl">"week"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nl">"teachweek"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"> </span><span class="mi">14</span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"综一 156"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"startsection"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
        </span><span class="nl">"endsection"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
        </span><span class="nl">"week"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
        </span><span class="nl">"teachweek"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"> </span><span class="mi">14</span><span class="p">]</span><span class="w">
    </span><span class="p">}]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>课程名与上课时间写在了一起, 并且上课时间是一个数组, 不是上面类中定义的集合.</p>

<h2 id="json--1">自定义需要与 json 互相转换的键值</h2>

<p>可以看到<code class="language-text highlighter-rouge">CourseData</code>与<code class="language-text highlighter-rouge">TimeData</code>中都有代表关系的属性, 在与 json 互相转换的过程中不需要包含这两个属性. 而且因为<code class="language-text highlighter-rouge">@NSManaged</code>的关系, 直接遵守<code class="language-text highlighter-rouge">Codable</code>属性后也不会转换被它标记的属性, 所以要手动定义哪些属性需要被转换.</p>

<p><code class="language-text highlighter-rouge">Codable</code>协议中默认实现了<code class="language-text highlighter-rouge">CodingKey</code>协议的枚举, 为所有的储存属性添加了键值, 因为上面提到的原因, 现在我们要手动定义这个枚举:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c1">// CourseData</span>
<span class="kd">enum</span> <span class="kt">CodingKeys</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">CodingKey</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">name</span>
    <span class="k">case</span> <span class="n">teacher</span>
    <span class="k">case</span> <span class="n">time</span>
<span class="p">}</span>

<span class="c1">// TimeData</span>
<span class="kd">enum</span> <span class="kt">CodingKeys</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">CodingKey</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">place</span>
    <span class="k">case</span> <span class="n">startsection</span>
    <span class="k">case</span> <span class="n">endsection</span>
    <span class="k">case</span> <span class="n">week</span>
    <span class="k">case</span> <span class="n">teachweek</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>需要注意的是目前<code class="language-text highlighter-rouge">Codable</code>协议无法应用到类的 extension 中, 所以只能在类定义内添加协议要求的方法. 在文章的最后我会放上完整的类定义代码.</p>

<h2 id="section-1">定义编码</h2>

<p><code class="language-text highlighter-rouge">Codable</code>协议由<code class="language-text highlighter-rouge">Encodable</code>和<code class="language-text highlighter-rouge">Decodable</code>两个协议组成, 分别对应着编码和解码, 我们先来看编码. 编码要求我们实现<code class="language-text highlighter-rouge">encode(to encoder: Encoder) throws</code>方法. 因为<code class="language-text highlighter-rouge">CourseData</code>中包含<code class="language-text highlighter-rouge">TimeData</code>对象, 所以我们先实现<code class="language-text highlighter-rouge">TimeData</code>的编码:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
</pre></td><td class="rouge-code"><pre><span class="c1">// TimeData</span>
<span class="kd">func</span> <span class="nf">encode</span><span class="p">(</span><span class="n">to</span> <span class="nv">encoder</span><span class="p">:</span> <span class="kt">Encoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">container</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">place</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">place</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">startsection</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">startsection</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">endsection</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">endsection</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">week</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">week</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">teachweek</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teachweek</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>首先我们以刚才定义的键值从<code class="language-text highlighter-rouge">encoder</code>中取得一个<code class="language-text highlighter-rouge">container</code>, <code class="language-text highlighter-rouge">container</code>中应该包含键值和其对应的值, 接着我们调用<code class="language-text highlighter-rouge">container</code>的<code class="language-text highlighter-rouge">encode</code>方法为每个键赋值, 因为<code class="language-text highlighter-rouge">TimeData</code>中每个要编码的属性都是可编码的, 所以直接赋值即可. 为了举例方便, 我没有对可能出现的异常进行处理.</p>

<p>对于<code class="language-text highlighter-rouge">CourseData</code>因为刚定义好<code class="language-text highlighter-rouge">TimeData</code>的编码方法, 所以<code class="language-text highlighter-rouge">CourseData</code>中的属性也都可以直接进行编码了:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
</pre></td><td class="rouge-code"><pre><span class="c1">// CourseData</span>
<span class="kd">func</span> <span class="nf">encode</span><span class="p">(</span><span class="n">to</span> <span class="nv">encoder</span><span class="p">:</span> <span class="kt">Encoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">container</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">name</span><span class="p">)</span>
    <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">teacher</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teacher</span><span class="p">)</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nv">time</span> <span class="o">=</span> <span class="n">time</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">timeArray</span> <span class="o">=</span> <span class="kt">Array</span><span class="p">(</span><span class="n">time</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">timeArray</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">time</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>如果可选值为 nil, 根据 json 的语法, 对应的键也应该没有. 在 Core Data 中关系的储存方式为集合类型, 在转换成 json 的时候要记得转换为数组.</p>

<p>现在我们可以直接使将这两个类转换为 json:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="nv">encoder</span> <span class="o">=</span> <span class="kt">JSONEncoder</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">jsonData</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">courseData</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">json</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="n">jsonData</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="section-2">定义解码</h2>

<p>解码协议<code class="language-text highlighter-rouge">Decodable</code>要求实现指定初始化器<code class="language-text highlighter-rouge">init(from decoder: Decoder) throws</code>, 但是<code class="language-text highlighter-rouge">NSManagedObject</code>有两个指定初始化器, 要想再定义其他的初始化器必须调用这两个其中的一个, 所以需要使用一些技巧:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c1">// TimeData</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>

<span class="kd">required</span> <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="n">from</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">entity</span><span class="p">:</span> <span class="s">"TimeData"</span><span class="p">,</span> <span class="nv">insertInto</span><span class="p">:</span> <span class="kt">TimeData</span><span class="o">.</span><span class="n">context</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="n">place</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">place</span><span class="p">)</span>
    <span class="n">startsection</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Int64</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">startsection</span><span class="p">)</span>
    <span class="n">endsection</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Int64</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">endsection</span><span class="p">)</span>
    <span class="n">week</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Int64</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">week</span><span class="p">)</span>
    <span class="n">teachweek</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">([</span><span class="kt">Int64</span><span class="p">]</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teachweek</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>为了调用指定初始化器, 我们定义一一个便利初始化器, 为了传入寝室初始化器所需要的参数, 定义了一个类变量. 不过这样做会导致使用这个初始化器之前必须先修改类变量, 为了实现从 json 初始化, 也只能这么做了. 接着还是取出一个<code class="language-text highlighter-rouge">container</code>不过这次是从里面解码出数据.</p>

<p>对于<code class="language-text highlighter-rouge">CourseData</code>, 注意取出的<code class="language-text highlighter-rouge">TimeData</code>不能直接添加到集合中, 集合是为了表示 Core Data 中的一对多关系, 由 Core Data 维护, 直接修改会导致 Core Data 错误, 正确的做法是把<code class="language-text highlighter-rouge">CourseData</code>添加到每一个<code class="language-text highlighter-rouge">TimeData</code>的对单关系中:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="c1">// CourseData</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">Context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>

<span class="kd">required</span> <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="n">from</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">entity</span><span class="p">:</span> <span class="s">"CourseData"</span><span class="p">,</span> <span class="nv">insertInto</span><span class="p">:</span> <span class="kt">CourseData</span><span class="o">.</span><span class="n">context</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="n">name</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">name</span><span class="p">)</span>
    <span class="n">teacher</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teacher</span><span class="p">)</span>
    <span class="kt">TimeData</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="kt">CourseData</span><span class="o">.</span><span class="n">context</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nv">timeArray</span> <span class="o">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">([</span><span class="kt">TimeData</span><span class="p">]</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">time</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">timeArray</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">course</span> <span class="o">=</span> <span class="k">self</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="core-data-">更像 Core Data 地调用</h2>

<p>这样我们就完成对<code class="language-text highlighter-rouge">Codable</code>协议的实现, 不过还需要再封装一下解码方法, 因为在插入一个<code class="language-text highlighter-rouge">NSManageObject</code>的时候要指定<code class="language-text highlighter-rouge">NSManageObjectContext</code>, 直接调用<code class="language-text highlighter-rouge">init(from decoder: Decoder)</code>方法可能导致忘记先给<code class="language-text highlighter-rouge">context</code>赋值而发生程序错误. 所以我们再定义一个<code class="language-text highlighter-rouge">ManagedObject</code>协议, 规定一下和 json 转换的接口:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="kd">protocol</span> <span class="kt">ManagedObject</span> <span class="k">where</span> <span class="k">Self</span><span class="p">:</span> <span class="kt">NSManagedObject</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">entityName</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>

    <span class="kd">static</span> <span class="k">var</span> <span class="nv">viewContest</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="p">{</span> <span class="k">get</span> <span class="k">set</span> <span class="p">}</span>

    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">insertNewObject</span><span class="p">(</span><span class="n">from</span> <span class="nv">json</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="n">into</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">Self</span>
    
    <span class="kd">func</span> <span class="nf">exportJson</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">String</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">ManagedObject</span> <span class="k">where</span> <span class="k">Self</span><span class="p">:</span> <span class="kt">Codable</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">insertNewObject</span><span class="p">(</span><span class="n">from</span> <span class="nv">json</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="n">into</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">decoder</span> <span class="o">=</span> <span class="kt">JSONDecoder</span><span class="p">()</span>
        <span class="k">let</span> <span class="nv">jsonData</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span>
        <span class="k">Self</span><span class="o">.</span><span class="n">viewContext</span> <span class="o">=</span> <span class="n">context</span>
        <span class="k">let</span> <span class="nv">newObject</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="k">Self</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="n">jsonData</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">newObject</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">exportJson</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">JSON</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">encoder</span> <span class="o">=</span> <span class="kt">JSONEncoder</span><span class="p">()</span>
        <span class="k">let</span> <span class="nv">jsonData</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">json</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="n">jsonData</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span>
        <span class="k">return</span> <span class="n">json</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>定义一下<code class="language-text highlighter-rouge">entityName</code>是为了避免硬编码字符串, 不方便修改程序. 导出 json 的方法也没什么需要解释的. 从 json 新建一个<code class="language-text highlighter-rouge">NSManagedObject</code>需要调用<code class="language-text highlighter-rouge">insertNewObject(from json: String, into context: NSManagedObjectContext)</code>方法, 传入一个 json 的要添加到的context. 因为没什么需要在类中自定义的, 就使用协议扩展实现了默认实现.</p>

<p>提一下<code class="language-text highlighter-rouge">viewContext</code>属性, 因为<code class="language-text highlighter-rouge">Codable</code>只能在类定义中实现, 所以类定义中必须包含一个<code class="language-text highlighter-rouge">context</code>属性. 在协议中不能调用协议中未定义的属性, 所以只好额外定义一个<code class="language-text highlighter-rouge">viewContext</code>属性. 如果以后<code class="language-text highlighter-rouge">Codable</code>可以应用在 extension 中, 那就可以直接让<code class="language-text highlighter-rouge">ManagedObject</code>协议遵守<code class="language-text highlighter-rouge">Codable</code>, 也不用这么折腾了.</p>

<p>两个类对协议的实现如下:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="c1">// TimeData</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">entityName</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s">"TimeData"</span>
<span class="p">}</span>

<span class="kd">static</span> <span class="k">var</span> <span class="nv">viewContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="p">{</span>
    <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">context</span> <span class="p">}</span>
    <span class="k">set</span> <span class="p">{</span> <span class="n">context</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// CourseData</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">entityName</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s">"CourseData"</span>
<span class="p">}</span>

<span class="kd">static</span> <span class="k">var</span> <span class="nv">viewContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="p">{</span>
    <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">context</span> <span class="p">}</span>
    <span class="k">set</span> <span class="p">{</span> <span class="n">context</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>最后放一下两个类的完整定义:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
</pre></td><td class="rouge-code"><pre><span class="kd">final</span> <span class="kd">class</span> <span class="kt">CourseData</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="p">,</span> <span class="kt">Codable</span> <span class="p">{</span>
    <span class="kd">fileprivate</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>
    
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">teacher</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">time</span><span class="p">:</span> <span class="kt">Set</span><span class="o">&lt;</span><span class="kt">TimeData</span><span class="o">&gt;</span><span class="p">?</span>
    
    <span class="kd">enum</span> <span class="kt">CodingKeys</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">CodingKey</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">name</span>
        <span class="k">case</span> <span class="n">teacher</span>
        <span class="k">case</span> <span class="n">time</span>
    <span class="p">}</span>
    
    <span class="kd">func</span> <span class="nf">encode</span><span class="p">(</span><span class="n">to</span> <span class="nv">encoder</span><span class="p">:</span> <span class="kt">Encoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="k">var</span> <span class="nv">container</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">name</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">teacher</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teacher</span><span class="p">)</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">time</span> <span class="o">=</span> <span class="n">time</span> <span class="p">{</span>
            <span class="k">let</span> <span class="nv">timeArray</span> <span class="o">=</span> <span class="kt">Array</span><span class="p">(</span><span class="n">time</span><span class="p">)</span>
            <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">timeArray</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">time</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="kd">required</span> <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="n">from</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">entity</span><span class="p">:</span> <span class="kt">CourseData</span><span class="o">.</span><span class="nf">entity</span><span class="p">(),</span> <span class="nv">insertInto</span><span class="p">:</span> <span class="kt">CourseData</span><span class="o">.</span><span class="n">context</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
        <span class="n">name</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">name</span><span class="p">)</span>
        <span class="n">teacher</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teacher</span><span class="p">)</span>
        <span class="kt">TimeData</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="kt">CourseData</span><span class="o">.</span><span class="n">context</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">timeArray</span> <span class="o">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">([</span><span class="kt">TimeData</span><span class="p">]</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">time</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">_</span> <span class="o">=</span> <span class="n">timeArray</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">course</span> <span class="o">=</span> <span class="k">self</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">CourseData</span><span class="p">:</span> <span class="kt">ManagedObject</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">viewContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">context</span> <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span> <span class="n">context</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">entityName</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"CourseData"</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="kt">TimeData</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="p">,</span> <span class="kt">Codable</span> <span class="p">{</span>
    <span class="kd">fileprivate</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>
    
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">place</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">startsection</span><span class="p">:</span> <span class="kt">Int64</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">endsection</span><span class="p">:</span> <span class="kt">Int64</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">week</span><span class="p">:</span> <span class="kt">Int64</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">teachweek</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int64</span><span class="p">]</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">course</span><span class="p">:</span> <span class="kt">CourseData</span>
    
    <span class="kd">enum</span> <span class="kt">CodingKeys</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">CodingKey</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">place</span>
        <span class="k">case</span> <span class="n">startsection</span>
        <span class="k">case</span> <span class="n">endsection</span>
        <span class="k">case</span> <span class="n">week</span>
        <span class="k">case</span> <span class="n">teachweek</span>
    <span class="p">}</span>
    
    <span class="kd">func</span> <span class="nf">encode</span><span class="p">(</span><span class="n">to</span> <span class="nv">encoder</span><span class="p">:</span> <span class="kt">Encoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="k">var</span> <span class="nv">container</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">place</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">place</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">startsection</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">startsection</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">endsection</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">endsection</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">week</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">week</span><span class="p">)</span>
        <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">teachweek</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teachweek</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="kd">required</span> <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="n">from</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">entity</span><span class="p">:</span> <span class="kt">TimeData</span><span class="o">.</span><span class="nf">entity</span><span class="p">(),</span> <span class="nv">insertInto</span><span class="p">:</span> <span class="kt">TimeData</span><span class="o">.</span><span class="n">context</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
        <span class="n">place</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">place</span><span class="p">)</span>
        <span class="n">startsection</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Int64</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">startsection</span><span class="p">)</span>
        <span class="n">endsection</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Int64</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">endsection</span><span class="p">)</span>
        <span class="n">week</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Int64</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">week</span><span class="p">)</span>
        <span class="n">teachweek</span> <span class="o">=</span> <span class="k">try!</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">([</span><span class="kt">Int64</span><span class="p">]</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">teachweek</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">TimeData</span><span class="p">:</span> <span class="kt">ManagedObject</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">viewContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">context</span> <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span> <span class="n">context</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">entityName</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"TimeData"</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="swift" /><summary type="html"><![CDATA[进行网络请求时还是 json 格式使用最多, swift 4 中新添加的Codable协议使得自定义格式与 json 转换更加方便, 不过因为 Core Data 中的NSManagedObject类的一些小问题, 不能直接遵守Codable协议, 所以分享一下自己的方法.]]></summary></entry><entry><title type="html">把 Core Data 储存在 App Group 中</title><link href="https://shino.space/2018/%E6%8A%8ACore-Data%E5%82%A8%E5%AD%98%E5%9C%A8App_Group%E4%B8%AD/" rel="alternate" type="text/html" title="把 Core Data 储存在 App Group 中" /><published>2018-04-24T08:25:35+00:00</published><updated>2018-04-24T08:25:35+00:00</updated><id>https://shino.space/2018/%E6%8A%8ACore-Data%E5%82%A8%E5%AD%98%E5%9C%A8App_Group%E4%B8%AD</id><content type="html" xml:base="https://shino.space/2018/%E6%8A%8ACore-Data%E5%82%A8%E5%AD%98%E5%9C%A8App_Group%E4%B8%AD/"><![CDATA[<p>最近又看了一遍 Core Data , 顺便把 app 的数据改为了 Core Data 储存, 虽然数据量并不大. 对于在 App Group 中使用 Core Data, 官方文档好像只说了一句话:</p>

<blockquote>
  <p>Use Core Data, SQLite, or Posix locks to help coordinate data access in a shared container.</p>
</blockquote>

<p>不同于<code class="language-text highlighter-rouge">UserDefaults</code>, Core Data 中并没有直接使用 Group Identifier 初始化的方法, 网上资料也很少, 记录一下.</p>

<!-- more -->

<hr />

<h2 class="no_toc" id="section">目录</h2>

<ul id="markdown-toc">
  <li><a href="#core-data-" id="markdown-toc-core-data-">默认的 Core Data 初始化</a></li>
  <li><a href="#nspersistentcontainer" id="markdown-toc-nspersistentcontainer"><code class="language-text highlighter-rouge">NSPersistentContainer</code>隐藏了什么</a></li>
  <li><a href="#core-data--app-group-" id="markdown-toc-core-data--app-group-">把 Core Data 存到 App Group 中</a></li>
  <li><a href="#ex-core-data" id="markdown-toc-ex-core-data">EX: Core Data是怎么实际储存在设备上的</a></li>
</ul>

<h2 id="core-data-">默认的 Core Data 初始化</h2>

<p>如果在创建 target 的时候勾选了 Core Data 选项, xcode 会为我们的项目自动添加上 Core Data 相关的代码, 包括<code class="language-text highlighter-rouge">xcdatamodeld</code>数据模型文件和 Core Data Stack 初始化相关的代码. <code class="language-text highlighter-rouge">AppDelegate</code>中会多出一个<code class="language-text highlighter-rouge">persistentContainer</code>属性:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
 9
</pre></td><td class="rouge-code"><pre><span class="kd">lazy</span> <span class="k">var</span> <span class="nv">persistentContainer</span><span class="p">:</span> <span class="kt">NSPersistentContainer</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="kt">NSPersistentContainer</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"test"</span><span class="p">)</span>
    <span class="n">container</span><span class="o">.</span><span class="nf">loadPersistentStores</span><span class="p">(</span><span class="nv">completionHandler</span><span class="p">:</span> <span class="p">{</span> <span class="p">(</span><span class="n">storeDescription</span><span class="p">,</span> <span class="n">error</span><span class="p">)</span> <span class="k">in</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as</span> <span class="kt">NSError</span><span class="p">?</span> <span class="p">{</span>
            <span class="nf">fatalError</span><span class="p">(</span><span class="s">"Unresolved error </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">, </span><span class="se">\(</span><span class="n">error</span><span class="o">.</span><span class="n">userInfo</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">})</span>
    <span class="k">return</span> <span class="n">container</span>
<span class="p">}()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>使用了一个闭包来初始化这个 lazy 属性, 首先实例化了一个<code class="language-text highlighter-rouge">NSPersistentContainer</code>, 其中的 name 值是我们创建<code class="language-text highlighter-rouge">xcdatamodeld</code>的文件名, 系统会根据这个文件所定义的模型来创建数据模型, 然后以这个名字在 Main Bundle 中创建数据文件, 我们进行数据的增删改查时所使用的<code class="language-text highlighter-rouge">NSManagedObjectContext</code>类由这个<code class="language-text highlighter-rouge">persistentContainer</code>的<code class="language-text highlighter-rouge">viewContext</code>属性获得.</p>

<p>可以看出这样创建的 Core Data Stack 只能在 Main Bundle 中建立数据文件, 无法在 App Group 中共享.</p>

<h2 id="nspersistentcontainer"><code class="language-text highlighter-rouge">NSPersistentContainer</code>隐藏了什么</h2>

<p><code class="language-text highlighter-rouge">NSPersistentContainer</code>这个类其实是为了使 Core Data 的使用变得更加简单, 它隐藏了很多 Core Data 初始化的细节, 我们来看一下 Core Data Stack 中各个类之间的关系(图片来自于 <a href="https://objccn.io/products/core-data/">Core Data</a>):</p>

<p><img src="/source/2018-04-24-把Core-Data储存在App_Group中_core_data_stack" alt="" /></p>

<p>从底向上看, 数据的储存形式可以是 sqlite, xml 或者是内存, 我们不会手动创建它们, 而是使用<code class="language-text highlighter-rouge">NSPersistentStore</code>这个类来进行初始化, 也就是图中的持久化储存.</p>

<p>持久化储存上面一层是持久化储存协调器, 用<code class="language-text highlighter-rouge">NSPersistentCoordinator</code>类来代表, 它负责协调底层的持久化储存和上层的托管对象上下文, 从持久化储存中取出数据线给上下文, 或者将上下文中的改动储存至持久化储存.</p>

<p>再向上就是我们常用的托管对象上下文和托管对象了, 分别是<code class="language-text highlighter-rouge">NSManagedObjectContext</code>和<code class="language-text highlighter-rouge">NSManagedObject</code>, 对托管对象的增删改查都是对托管对象上下文进行操作.</p>

<p>除了这些类, 还有一个<code class="language-text highlighter-rouge">NSManagedObjectModel</code>, 用于管理托管对象的模型, 我们所创建的<code class="language-text highlighter-rouge">xcdatamodeld</code>文件就是用于初始化这个类的. 如果想在其他的 target 中也使用同一个模弄文件, 需要在 Target Membership 中勾选想要添加的 target:</p>

<p><img src="/source/2018-04-24-把Core-Data储存在App_Group中_target_membership" alt="" /></p>

<h2 id="core-data--app-group-">把 Core Data 存到 App Group 中</h2>

<p>这样看来, 涉及到的文件只有两个, 模型文件和数据文件. 模型文件我们可以直接添加到每个 target 中, 而数据文件是使用<code class="language-text highlighter-rouge">NSPersistentSrore</code>来创建的, 所以就不能使用<code class="language-text highlighter-rouge">NSPersistentContainer</code>来初始化 Core Data , 需要手动创建整个 Core Data Stack.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
 6
 7
 8
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="nv">bundle</span> <span class="o">=</span> <span class="kt">Bundle</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="kt">DataManager</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">model</span> <span class="o">=</span> <span class="kt">NSManagedObjectModel</span><span class="o">.</span><span class="nf">mergedModel</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="p">[</span><span class="n">bundle</span><span class="p">])</span><span class="o">!</span>
<span class="k">let</span> <span class="nv">coordinator</span> <span class="o">=</span> <span class="kt">NSPersistentStoreCoordinator</span><span class="p">(</span><span class="nv">managedObjectModel</span><span class="p">:</span> <span class="n">model</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">groupURL</span> <span class="o">=</span> <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">containerURL</span><span class="p">(</span><span class="nv">forSecurityApplicationGroupIdentifier</span><span class="p">:</span> <span class="s">"group.test.shino.space"</span><span class="p">)</span><span class="o">!</span>
<span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="n">groupURL</span><span class="o">.</span><span class="nf">appendingPathComponent</span><span class="p">(</span><span class="s">"data.data"</span><span class="p">)</span>
<span class="k">try!</span> <span class="n">coordinator</span><span class="o">.</span><span class="nf">addPersistentStore</span><span class="p">(</span><span class="nv">ofType</span><span class="p">:</span> <span class="kt">NSSQLiteStoreType</span><span class="p">,</span> <span class="nv">configurationName</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">at</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="nv">options</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">context</span> <span class="o">=</span> <span class="kt">NSManagedObjectContext</span><span class="p">(</span><span class="nv">concurrencyType</span><span class="p">:</span> <span class="o">.</span><span class="n">mainQueueConcurrencyType</span><span class="p">)</span>
<span class="n">context</span><span class="o">.</span><span class="n">persistentStoreCoordinator</span> <span class="o">=</span> <span class="n">coordinator</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>首先是初始化数据模型, 模型文件在项目的 mainbundle 或者是 extension 的 bundle 中, 所以使用<code class="language-text highlighter-rouge">Bundle(for: AnyClass)</code>来获取, 再使用这个模型来初始化待久化储存协调器. 对于持久化储存, 它的初始化需要提供一个 url, 我们使用 App Group Identifier 创建一个 url 给它, 数据文件的名字为<code class="language-text highlighter-rouge">data.data</code>. 把创建好的持久化储存赋给持久化储存协调器. 最后我们创建一个托管对象上下文, 把这个上下文赋给持久化储存协调器, Core Data Stack 就设置好了, 我们在这个上下文上进行操作的数据就会保存在 App Group 中</p>

<h2 id="ex-core-data">EX: Core Data是怎么实际储存在设备上的</h2>

<p>自己手机越狱装了 Filza, 所以顺便看了看文件具体的储存情况. 我在项目的主项目, today extension 和 watch extension 中都添加了 Core Data , 所以这三个 bundle 中都在模型文件, 路径分别为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
 2
 3
 4
 5
</pre></td><td class="rouge-code"><pre>/var/Containers/Bundle/Application/你的 App 文件夹

/var/Containers/Bundle/Application/你的 App 文件夹/Plugins/你的 App Extension 文件夹

/var/Containers/Bundle/Application/你的 App 文件夹/Watch/你的 Watch App 文件夹/Plugins/你的 Watch App Extension 文件夹
</pre></td></tr></tbody></table></code></pre></div></div>

<p>实际储存的模型文件名字不变, 后缀名变为<code class="language-text highlighter-rouge">.momd</code>.</p>

<p>而数据是存在 App Group 中的, 因为共享, 所以只有一个. 注意现在的 watch app 已经变为了独立的 app, 虽然可以和手机 app 使用一个 group identifier, 但是储存是分开的, 数据储存在手表上. 手机上的 Core Data 数据文件路径如下:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno"> 1
</pre></td><td class="rouge-code"><pre>/var/mobile/Containers/Shared/AppGroup/你的 APP 文件夹
</pre></td></tr></tbody></table></code></pre></div></div>

<p>数据文件的名称与定义的名称相同.</p>]]></content><author><name>shino</name><email>shino@shino.space</email></author><category term="计算机" /><category term="swift" /><summary type="html"><![CDATA[最近又看了一遍 Core Data , 顺便把 app 的数据改为了 Core Data 储存, 虽然数据量并不大. 对于在 App Group 中使用 Core Data, 官方文档好像只说了一句话: Use Core Data, SQLite, or Posix locks to help coordinate data access in a shared container. 不同于UserDefaults, Core Data 中并没有直接使用 Group Identifier 初始化的方法, 网上资料也很少, 记录一下.]]></summary></entry></feed>