<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>优三帝同学</title>
    <description>嗨，我是优三帝 (@onevcat)，一名 Unity 开发者。</description>
    <link>https://u3dc.github.io//</link>
    <atom:link href="https://u3dc.github.io//feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 30 Nov 2025 06:22:51 +0000</pubDate>
    <lastBuildDate>Sun, 30 Nov 2025 06:22:51 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>SwiftUI 的一些初步探索 (二)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/2019/swift-ui.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我已经计划写一本关于 SwiftUI 和 Combine 编程的书籍，希望能通过一些实践案例帮助您快速上手 SwiftUI 及 Combine 响应式编程框架，掌握下一代客户端 UI 开发技术。现在这本书已经开始预售，预计能在 10 月左右完成。如果您对此有兴趣，可以查看 &lt;a href=&quot;https://objccn.io/products/&quot;&gt;ObjC 中国的产品页面&lt;/a&gt;了解详情及购买。十分感谢！&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接&lt;a href=&quot;https://onevcat.com/2019/06/swift-ui-firstlook/&quot;&gt;上一篇&lt;/a&gt;继续对 SwiftUI 的教程进行一些解读。&lt;/p&gt;

&lt;h3 id=&quot;教程-2---building-lists-and-navigation&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation&quot;&gt;教程 2 - Building Lists and Navigation&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;section-4---step-2-静态-list&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation#create-the-list-of-landmarks&quot;&gt;Section 4 - Step 2: 静态 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;LandmarkRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;landmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;landmarkData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;LandmarkRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;landmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;landmarkData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HStack&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 之类的容器很相似，接受一个 view builder 并采用 View DSL 的方式列举了两个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LandmarkRow&lt;/code&gt;。这种方式构建了对应着 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; 的静态 cell 的组织方式。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以运行 app，并使用 Xcode 的 View Hierarchy 工具来观察 UI，结果可能会让你觉得很眼熟：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://images.xiaozhuanlan.com/photo/2019/a900c8d2687dab13ba438602da826552.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;实际上在屏幕上绘制的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UpdateCoalesingTableView&lt;/code&gt; 是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; 的子类，而两个 cell &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ListCoreCellHost&lt;/code&gt; 也是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableViewCell&lt;/code&gt; 的子类。对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 来说，SwiftUI 底层直接使用了成熟的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; 的一套实现逻辑，而并非重新进行绘制。相比起来，像是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image&lt;/code&gt; 这样的单一 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIKit&lt;/code&gt; 层则全部统一由 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DisplayList.ViewUpdater.Platform.CGDrawingView&lt;/code&gt; 这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIView&lt;/code&gt; 的子类进行绘制。&lt;/p&gt;

&lt;p&gt;不过在使用 SwiftUI 时，我们首先需要做的就是跳出 UIKit 的思维方式，不应该去关心背后的绘制和实现。使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; 来表达 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 也许只是权宜之计，也许在未来也会被另外更高效的绘制方式取代。由于 SwiftUI 层只是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 描述的数据抽象，因此和 React 的 Virtual DOM 以及 Flutter 的 Widget 一样，背后的具体绘制方式是完全解耦合，并且可以进行替换的。这为今后 SwiftUI 更进一步留出了足够的可能性。&lt;/p&gt;

&lt;h4 id=&quot;section-5---step-2-动态-list-和-identifiable&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation#make-the-list-dynamic&quot;&gt;Section 5 - Step 2: 动态 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Identifiable&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;landmarkData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;identified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;landmark&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;LandmarkRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;landmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;landmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;除了静态方式以外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 当然也可以接受动态方式的输入，这时使用的初始化方法和上面静态的情况不一样：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Selection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SelectionManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RowContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;IdentifiedValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;rowContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;IdentifiedValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RowContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ForEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;HStack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RowContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RandomAccessCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;RowContent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Identifiable&lt;/span&gt;
        
    &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个初始化方法的约束比较多，我们一行行来看：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content == ForEach&amp;lt;Data, Button&amp;lt;HStack&amp;lt;RowContent&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; 因为这个函数签名中并没有出现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content&lt;/code&gt; 仅只 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&amp;lt;Selection, Content&amp;gt;&lt;/code&gt; 的类型声明中有定义，所以在这与其说是一个约束，不如说是一个用来反向确定 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 实际类型的描述。现在让我们先将注意力放在更重要的地方，稍后会再多讲一些这个。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data : RandomAccessCollection&lt;/code&gt; 这基本上等同于要求第一个输入参数是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RowContent : View&lt;/code&gt; 对于构建每一行的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rowContent&lt;/code&gt; 来说，需要返回是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 是很正常的事情。注意 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rowContent&lt;/code&gt; 其实也是被 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ViewBuilder&lt;/code&gt; 标记的，因此你也可以把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LandmarkRow&lt;/code&gt; 的内容展开写进去。不过一般我们会更希望尽可能拆小 UI 部件，而不是把东西堆在一起。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data.Element : Identifiable&lt;/code&gt; 要求 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data.Element&lt;/code&gt; (也就是数组元素的类型) 上存在一个可以辨别出某个实例的&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/identifiable/3285392-id&quot;&gt;满足 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; 的 id&lt;/a&gt;。这个要求将在数据变更时快速定位到变化的数据所对应的 cell，并进行 UI 刷新。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 以及其他一些常见的基础 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;，有一个比较有趣的事实。在下面的代码中，我们期望 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 的初始化方法生成的是某个类型的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是你看遍 &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/list&quot;&gt;List 的文档&lt;/a&gt;，甚至是 Cmd + Click 到 SwiftUI 的 interface 中查找 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 相关的内容，都找不到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List : View&lt;/code&gt; 之类的声明。&lt;/p&gt;

&lt;p&gt;难道是因为 SwiftUI 做了什么手脚，让本来没有满足 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的类型都可以“充当”一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 吗？当然不是这样…如果你在运行时暂定 app 并用 lldb 打印一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 的类型信息，可以看到下面的下面的信息：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(lldb) type lookup List
...
struct List&amp;lt;Selection, Content&amp;gt; : SwiftUI._UnaryView where ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;进一步，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UnaryView&lt;/code&gt; 的声明是：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_UnaryView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_UnaryView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SwiftUI 内部的一元视图 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UnaryView&lt;/code&gt; 协议虽然是满足 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的，但它被隐藏起来了，而满足它的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; 虽然是 public 的，但是却可以把这个协议链的信息也作为内部信息隐藏起来。这是 Swift 内部框架的特权，第三方的开发者无法这样在在两个 public 的声明之间插入一个私有声明。&lt;/p&gt;

&lt;p&gt;最后，SwiftUI 中当前 (Xcode 11 beta 1) 只有对应 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt;，而没有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt; 对应的像是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grid&lt;/code&gt; 这样的类型。现在想要实现类似效果的话，只能嵌套使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HStack&lt;/code&gt;。这是比较奇怪的，因为技术层面上应该和 table view 没有太多区别，大概是因为工期不太够？相信今后应该会补充上 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grid&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;教程-3---handling-user-input&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/handling-user-input&quot;&gt;教程 3 - Handling User Input&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;section-3---step-2-state-和-binding&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/handling-user-input#add-a-control-to-toggle-the-state&quot;&gt;Section 3 - Step 2: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Binding&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;showFavoritesOnly&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;NavigationView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;isOn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showFavoritesOnly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Favorites only&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showFavoritesOnly&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;landmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isFavorite&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里出现了两个以前在 Swift 里没有的特性：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$showFavoritesOnly&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;如果你 Cmd + Click 点到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; 的定义里面，可以看到它其实是一个特殊的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@propertyWrapper&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DynamicViewProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BindingConvertible&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Initialize with the provided initial value.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initialValue&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// The current state value.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;nonmutating&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Returns a binding referencing the state value.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;binding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Binding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Produces the binding referencing this state value&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;delegateValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Binding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt; 标注和&lt;a href=&quot;https://xiaozhuanlan.com/topic/7652341890#sectionsection3step5viewbuilderhttpsdeveloperapplecomtutorialsswiftuicreatingandcombiningviewscombineviewsusingstacks&quot;&gt;上一篇中提到&lt;/a&gt;的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@_functionBuilder&lt;/code&gt; 类似，它修饰的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; 可以变成一个新的修饰符并作用在其他代码上，来改变这些代码默认的行为。这里 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt; 修饰的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; 被用做了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; 修饰符，并用来修饰 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showFavoritesOnly&lt;/code&gt; 变量。&lt;/p&gt;

&lt;p&gt;和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@_functionBuilder&lt;/code&gt; 负责按照规矩“重新构造”函数的作用不同，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt; 的修饰符最终会作用在属性上，将属性“包裹”起来，以达到控制某个属性的读写行为的目的。如果将这部分代码“展开”，它实际上是这个样子的：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// @State var showFavoritesOnly = true&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;showFavoritesOnly&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;initialValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;NavigationView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//          Toggle(isOn: $showFavoritesOnly) {&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;isOn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;showFavoritesOnly&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Favorites only&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//          if !self.showFavoritesOnly || landmark.isFavorite {&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showFavoritesOnly&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;landmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isFavorite&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我把变化之前的部分注释了一下，并且在后面一行写上了展开后的结果。可以看到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; 只是声明 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; struct 的一种简写方式而已。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; 里对具体要如何读写属性的规则进行了定义。对于读取，非常简单，使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showFavoritesOnly.value&lt;/code&gt; 就能拿到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; 中存储的实际值。而原代码中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$showFavoritesOnly&lt;/code&gt; 的写法也只不过是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showFavoritesOnly.binding&lt;/code&gt; 的简化。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binding&lt;/code&gt; 将创建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showFavoritesOnly&lt;/code&gt; 的引用，并将它传递给 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Toggle&lt;/code&gt;。再次强调，这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binding&lt;/code&gt; 是一个&lt;strong&gt;引用&lt;/strong&gt;类型，所以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Toggle&lt;/code&gt; 中对它的修改，会直接反应到当前 View 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showFavoritesOnly&lt;/code&gt; 去设置它的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;。而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; 的 value didSet 将触发 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; 的刷新，从而完成 State -&amp;gt; View 的绑定。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;在 Xcode 11 beta 1 中，Swift 中使用的修饰符名字是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyDelegate&lt;/code&gt;，不过在 WWDC 上 Apple 提到这个特性时把它叫做了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt;。根据&lt;a href=&quot;https://twitter.com/josefdolezal/status/1137619597002248192?s=21&quot;&gt;可靠消息&lt;/a&gt;，在未来正式版中应该也会叫做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt;，所以大家在看各种资料的时候最好也建议一个简单的映射关系。&lt;/p&gt;

  &lt;p&gt;如果你想要了解更多关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt; 的细节，可以看看&lt;a href=&quot;https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-delegates.md&quot;&gt;相关的提案&lt;/a&gt;和&lt;a href=&quot;https://forums.swift.org/t/se-0258-property-delegates/23139&quot;&gt;论坛讨论&lt;/a&gt;。比较有意思的细节是 Apple 在将相应的 PR merge 进了 master 以后又把这个提案的打回了“修改”的状态，而非直接接受。除了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@propertyWrapper&lt;/code&gt; 的名称修正以外，应该还会有一些其他的细节修改，但是已经公开的行为模式上应该不会太大变化了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SwiftUI 中还有几个常见的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@&lt;/code&gt; 开头的修饰，比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Binding&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Environment&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@EnvironmentObject&lt;/code&gt; 等，原理上和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; 都一样，只不过它们所对应的 struct 中定义读写方式有区别。它们共同构成了 SwiftUI 数据流的最基本的单元。对于 SwiftUI 的数据流，如果展开的话足够一整篇文章了。在这里还是十分建议看一看 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/226/&quot;&gt;Session 226 - Data Flow Through SwiftUI&lt;/a&gt; 的相关内容。&lt;/p&gt;

&lt;h3 id=&quot;教程-5---animating-views-and-transitions&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions&quot;&gt;教程 5 - Animating Views and Transitions&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;section-2---step-4-两种动画的方式&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions#customize-view-transitions&quot;&gt;Section 2 - Step 4: 两种动画的方式&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;在 SwiftUI 中，做动画变的十分简单。Apple 的教程里提供了两种动画的方式：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;直接在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 上使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.animation&lt;/code&gt; modifier&lt;/li&gt;
  &lt;li&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withAnimation {  }&lt;/code&gt; 来控制某个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt;，进而触发动画。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于只需要对单个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 做动画的时候，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation(_:)&lt;/code&gt; 要更方便一些，它和其他各类 modifier 并没有太大不同，返回的是一个包装了对象 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 和对应的动画类型的新的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation(_:)&lt;/code&gt; 接受的参数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Animation&lt;/code&gt; 并不是直接定义 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 上的动画的数值内容的，它是描述的是动画所使用的时间曲线，动画的延迟等这些和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 无关的东西。具体和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 有关的，想要进行动画的数值方面的变更，由其他的诸如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotationEffect&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scaleEffect&lt;/code&gt; 这样的 modifier 来描述。&lt;/p&gt;

&lt;p&gt;在上面的 &lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions#add-animations-to-individual-views&quot;&gt;教程 5 - Section 1 - Step 5&lt;/a&gt; 里有这样一段代码：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showDetail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;chevron.right.circle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imageScale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;large&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rotationEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;degrees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showDetail&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scaleEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showDetail&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;要注意，SwiftUI 的 modifier 是有顺序的。在我们调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation(_:)&lt;/code&gt; 时，SwiftUI 做的事情等效于是把之前的所有 modifier 检查一遍，然后找出所有满足 &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/animatable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Animatable&lt;/code&gt;&lt;/a&gt; 协议的 view 上的数值变化，比如角度、位置、尺寸等，然后将这些变化打个包，创建一个事物 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt;) 并提交给底层渲染去做动画。在上面的代码中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rotationEffect&lt;/code&gt; 后的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.animation(nil)&lt;/code&gt; 将 rotation 的动画提交，因为指定了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; 所以这里没有实际的动画。在最后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rotationEffect&lt;/code&gt; 已经被处理了，所以末行的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.animation(.spring())&lt;/code&gt; 提交的只有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.scaleEffect&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withAnimation {  }&lt;/code&gt; 是一个顶层函数，在闭包内部，我们一般会触发某个 State 的变化，并让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View.body&lt;/code&gt; 进行重新计算：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;withAnimation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showDetail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果需要，你也可以为它指定一个具体的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Animation&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;withAnimation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;basic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showDetail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个方法相当于把一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation&lt;/code&gt; 设置到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 数值变化的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt; 上，并提交给底层渲染去做动画。从原理上来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withAnimation&lt;/code&gt; 是统一控制单个的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt;，而针对不同 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation(_:)&lt;/code&gt; 调用则可能对应多个不同的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;教程-7---working-with-ui-controls&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/working-with-ui-controls&quot;&gt;教程 7 - Working with UI Controls&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;section-4---step-2-关于-view-的生命周期&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/working-with-ui-controls#delay-edit-propagation&quot;&gt;Section 4 - Step 2: 关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的生命周期&lt;/a&gt;&lt;/h4&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;ProfileEditor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;profile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draftProfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onDisappear&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draftProfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;profile&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 UIKit 开发时，我们经常会接触一些像是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewDidLoad&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewWillAppear&lt;/code&gt; 这样的生命周期的方法，并在里面进行一些配置。SwiftUI 里也有一部分这类生命周期的方法，比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.onAppear&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.onDisappear&lt;/code&gt;，它们也被“统一”在了 modifier 这面大旗下。&lt;/p&gt;

&lt;p&gt;但是相对于 UIKit 来说，SwiftUI 中能 hook 的生命周期方法比较少，而且相对要通用一些。本身在生命周期中做操作这种方式就和声明式的编程理念有些相悖，看上去就像是加上了一些命令式的 hack。我个人比较期待 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Combine&lt;/code&gt; 能再深度结合一些，把像是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.draftProfile = self.profile&lt;/code&gt; 这类依赖生命周期的操作也用绑定的方式搞定。&lt;/p&gt;

&lt;p&gt;相比于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.onAppear&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.onDisappear&lt;/code&gt;，更通用的事件响应 hook 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.onReceive(_:perform:)&lt;/code&gt;，它定义了一个可以响应目标 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Publisher&lt;/code&gt; 的任意的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;，一旦订阅的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Publisher&lt;/code&gt; 发出新的事件时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onReceive&lt;/code&gt; 就将被调用。因为我们可以自行定义这些 publisher，所以它是完备的，这在把现有的 UIKit View 转换到 SwiftUI View 时会十分有用。&lt;/p&gt;

</description>
        <pubDate>Tue, 11 Jun 2019 03:32:00 +0000</pubDate>
        <link>https://u3dc.github.io//2019/06/swift-ui-firstlook-2/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2019/06/swift-ui-firstlook-2/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>SwiftUI 的一些初步探索 (一)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/2019/swift-ui.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我已经计划写一本关于 SwiftUI 和 Combine 编程的书籍，希望能通过一些实践案例帮助您快速上手 SwiftUI 及 Combine 响应式编程框架，掌握下一代客户端 UI 开发技术。现在这本书已经开始预售，预计能在 10 月左右完成。如果您对此有兴趣，可以查看 &lt;a href=&quot;https://objccn.io/products/&quot;&gt;ObjC 中国的产品页面&lt;/a&gt;了解详情及购买。十分感谢！&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;总览&quot;&gt;总览&lt;/h2&gt;

&lt;p&gt;如果你想要入门 SwiftUI 的使用，那 Apple 这次给出的&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui&quot;&gt;官方教程&lt;/a&gt;绝对给力。这个教程提供了非常详尽的步骤和说明，网页的交互也是一流，是觉得值得看和动手学习的参考。&lt;/p&gt;

&lt;p&gt;不过，SwiftUI 中有一些值得注意的细节在教程里并没有太详细提及，也可能造成一些困惑。这篇文章以我的个人观点对教程的某些部分进行了补充说明，希望能在大家跟随教程学习 SwiftUI 的时候有点帮助。这篇文章的推荐阅读方式是，一边参照 SwiftUI 教程实际动手进行实现，一边在到达对应步骤时参照本文加深理解。在下面每段内容前我标注了对应的教程章节和链接，以供参考。&lt;/p&gt;

&lt;p&gt;在开始学习 SwiftUI 之前，我们需要大致了解一个问题：为什么我们会需要一个新的 UI 框架。&lt;/p&gt;

&lt;h2 id=&quot;为什么需要-swiftui&quot;&gt;为什么需要 SwiftUI&lt;/h2&gt;

&lt;h3 id=&quot;uikit-面临的挑战&quot;&gt;UIKit 面临的挑战&lt;/h3&gt;

&lt;p&gt;对于 Swift 开发者来说，昨天的 WWDC 19 首日 Keynote 和 Platforms State of the Union 上最引人注目的内容自然是 SwiftUI 的公布了。从 iOS SDK 2.0 开始，UIKit 已经伴随广大 iOS 开发者经历了接近十年的风风雨雨。UIKit 的思想继承了成熟的 AppKit 和 MVC，在初出时，为 iOS 开发者提供了良好的学习曲线。&lt;/p&gt;

&lt;p&gt;UIKit 提供的是一套符合直觉的，基于控制流的命令式的编程方式。最主要的思想是在确保 View 或者 View Controller 生命周期以及用户交互时，相应的方法 (比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewDidLoad&lt;/code&gt; 或者某个 target-action 等) 能够被正确调用，从而构建用户界面和逻辑。不过，不管是从使用的便利性还是稳定性来说，UIKit 都面临着巨大的挑战。我个人勉强也能算是 iOS 开发的“老司机”了，但是「掉到 UIKit 的坑里」这件事，也几乎还是我每天的日常。UIKit 的基本思想要求 View Controller 承担绝大部分职责，它需要协调 model，view 以及用户交互。这带来了巨大的 side effect 以及大量的状态，如果没有妥善安置，它们将在 View Controller 中混杂在一起，同时作用于 view 或者逻辑，从而使状态管理愈发复杂，最后甚至不可维护而导致项目失败。不仅是作为开发者我们自己写的代码，UIKit 本身内部其实也经常受困于可变状态，各种奇怪的 bug 也频频出现。&lt;/p&gt;

&lt;h3 id=&quot;声明式的界面开发方式&quot;&gt;声明式的界面开发方式&lt;/h3&gt;

&lt;p&gt;近年来，随着编程技术和思想的进步，使用声明式或者函数式的方式来进行界面开发，已经越来越被接受并逐渐成为主流。最早的思想大概是来源于 &lt;a href=&quot;https://elm-lang.org&quot;&gt;Elm&lt;/a&gt;，之后这套方式被 &lt;a href=&quot;https://reactjs.org&quot;&gt;React&lt;/a&gt; 和 &lt;a href=&quot;https://flutter.dev&quot;&gt;Flutter&lt;/a&gt; 采用，这一点上 SwiftUI 也几乎与它们一致。总结起来，这些 UI 框架都遵循以下步骤和原则：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;使用各自的 DSL 来描述「UI 应该是什么样子」，而不是用一句句的代码来指导「要怎样构建 UI」。&lt;/p&gt;

    &lt;p&gt;比如传统的 UIKit，我们会使用这样的代码来添加一个 “Hello World” 的标签，它负责“创建 label”，“设置文字”，“将其添加到 view 上”：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UILabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addSubview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;// 省略了布局的代码&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;而相对起来，使用 SwiftUI 我们只需要告诉 SDK 我们需要一个文字标签：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;接下来，框架内部读取这些 view 的声明，负责将它们以合适的方式绘制渲染。&lt;/p&gt;

    &lt;p&gt;注意，这些 view 的声明只是纯数据结构的描述，而不是实际显示出来的视图，因此这些结构的创建和差分对比并不会带来太多性能损耗。相对来说，将描述性的语言进行渲染绘制的部分是最慢的，这部分工作将交由框架以黑盒的方式为我们完成。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 需要根据某个状态 (state) 进行改变，那我们将这个状态存储在变量中，并在声明 view 时使用它：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Tom&quot;&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;blockquote&gt;
      &lt;p&gt;关于代码细节可以先忽略，我们稍后会更多地解释这方面的内容。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;状态发生改变时，框架重新调用声明部分的代码，计算出新的 view 声明，并和原来的 view 进行差分，之后框架负责对变更的部分进行高效的重新绘制。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SwiftUI 的思想也完全一样，而且实际处理也不外乎这几个步骤。使用描述方式开发，大幅减少了在 app 开发者层面上出现问题的机率。&lt;/p&gt;

&lt;h2 id=&quot;一些细节解读&quot;&gt;一些细节解读&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui&quot;&gt;官方教程&lt;/a&gt;中对声明式 UI 的编程思想有深刻的体现。另外，SwiftUI 中也采用了非常多 Swift 5.1 的新特性，会让习惯了 Swift 4 或者 5 的开发者“耳目一新”。接下来，我会分几个话题，对官方教程的一些地方进行解释和探索。&lt;/p&gt;

&lt;h3 id=&quot;教程-1---creating-and-combining-views&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views&quot;&gt;教程 1 - Creating and Combining Views&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;section-1---step-3-swiftui-app-的启动&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#create-a-new-project-and-explore-the-canvas&quot;&gt;Section 1 - Step 3: SwiftUI app 的启动&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;创建 app 之后第一件好奇的事情是，SwiftUI app 是怎么启动的。&lt;/p&gt;

&lt;p&gt;教程示例 app 在 AppDelegate 中通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application(_:configurationForConnecting:options)&lt;/code&gt; 返回了一个名为 “Default Configuration” 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UISceneConfiguration&lt;/code&gt; 实例：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;configurationForConnecting&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;connectingSceneSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ConnectionOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneConfiguration&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Default Configuration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sessionRole&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connectingSceneSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个名字的 Configuration 在 Info.plist 的 “UIApplicationSceneManifest -&amp;gt; UISceneConfigurations” 中进行了定义，指定了 Scene Session Delegate 类为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(PRODUCT_MODULE_NAME).SceneDelegate&lt;/code&gt;。这部分内容是 iOS 13 中新加入的通过 Scene 管理 app 生命周期的方式，以及多窗口支持部分所需要的代码。这部分不是我们今天的话题。在 app 完成启动后，控制权被交接给 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SceneDelegate&lt;/code&gt;，它的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scene(_:willConnectTo:options:)&lt;/code&gt; 将会被调用，进行 UI 的配置：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;willConnectTo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;connectionOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ConnectionOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScreen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bounds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rootViewController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIHostingController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rootView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;makeKeyAndVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这部分内容就是标准的 iOS app 启动流程了。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIHostingController&lt;/code&gt; 是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIViewController&lt;/code&gt; 子类，它将负责接受一个 SwiftUI 的 View 描述并将其用 UIKit 进行渲染 (在 iOS 下的情况)。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIHostingController&lt;/code&gt; 就是一个普通的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIViewController&lt;/code&gt;，因此完全可以做到将 SwiftUI 创建的界面一点点集成到已有的 UIKit app 中，而并不需要从头开始就是基于 SwiftUI 的构建。&lt;/p&gt;

&lt;p&gt;由于 Swift ABI 已经稳定，SwiftUI 是一个搭载在用户 iOS 系统上的 Swift 框架。因此它的最低支持的版本是 iOS 13，可能想要在实际项目中使用，还需要等待一两年时间。&lt;/p&gt;

&lt;h4 id=&quot;section-1---step-4-关于-some-view&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#create-a-new-project-and-explore-the-canvas&quot;&gt;Section 1 - Step 4: 关于 some View&lt;/a&gt;&lt;/h4&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一眼看上去可能会对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some&lt;/code&gt; 比较陌生，为了讲明白这件事，我们先从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 说起。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 是 SwiftUI 的一个最核心的协议，代表了一个屏幕上元素的描述。这个协议中含有一个 associatedtype：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;associatedtype&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种带有 associatedtype 的协议不能作为&lt;strong&gt;类型&lt;/strong&gt;来使用，而只能作为&lt;strong&gt;类型约束&lt;/strong&gt;使用：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Error&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// OK&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样一来，其实我们是不能写类似这种代码的：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Error，含有 associatedtype 的 protocol View 只能作为类型约束使用&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;想要 Swift 帮助自动推断出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View.Body&lt;/code&gt; 的类型的话，我们需要明确地指出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; 的真正的类型。在这里，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; 的实际类型是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然我们可以明确指定出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; 的类型，但是这带来一些麻烦：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;每次修改 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; 的返回时我们都需要手动去更改相应的类型。&lt;/li&gt;
  &lt;li&gt;新建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的时候，我们都需要去考虑会是什么类型。&lt;/li&gt;
  &lt;li&gt;其实我们只关心返回的是不是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;，而对实际上它是什么类型并不感兴趣。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some View&lt;/code&gt; 这种写法使用了 Swift 5.1 的 &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/master/proposals/0244-opaque-result-types.md&quot;&gt;Opaque return types 特性&lt;/a&gt;。它向编译器作出保证，每次 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; 得到的一定是某一个确定的，遵守 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 协议的类型，但是请编译器“网开一面”，不要再细究具体的类型。返回类型&lt;strong&gt;确定单一&lt;/strong&gt;这个条件十分重要，比如，下面的代码也是无法通过的：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;someCondition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Error: Function declares an opaque return type, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// but the return statements in its body do not have &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// matching underlying types.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;someCondition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 这个分支返回 Text&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 这个分支返回 Button，和 if 分支的类型不统一&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tap me&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是一个编译期间的特性，在保证 associatedtype protocol 的功能的前提下，使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some&lt;/code&gt; 可以抹消具体的类型。这个特性用在 SwiftUI 上简化了书写难度，让不同 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 声明的语法上更加统一。&lt;/p&gt;

&lt;h4 id=&quot;section-2---step-1-预览-swiftui&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#customize-the-text-view&quot;&gt;Section 2 - Step 1: 预览 SwiftUI&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;SwiftUI 的 Preview 是 Apple 用来对标 RN 或者 Flutter 的 Hot Reloading 的开发工具。由于 IBDesignable 的性能上的惨痛教训，而且得益于 SwiftUI 经由 UIKit 的跨 Apple 平台的特性，Apple 这次选择了直接在 macOS 上进行渲染。因此，你需要使用搭载有 SwiftUI.framework 的 macOS 10.15 才能够看到 Xcode Previews 界面。&lt;/p&gt;

&lt;p&gt;Xcode 将对代码进行静态分析 (得益于 &lt;a href=&quot;https://github.com/apple/swift-syntax&quot;&gt;SwiftSyntax 框架&lt;/a&gt;)，找到所有遵守 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PreviewProvider&lt;/code&gt; 协议的类型进行预览渲染。另外，你可以为这些预览提供合适的数据，这甚至可以让整个界面开发流程不需要实际运行 app 就能进行。&lt;/p&gt;

&lt;p&gt;笔者自己尝试下来，这套开发方式带来的效率提升相比 Hot Reloading 要更大。Hot Reloading 需要你有一个大致界面和准备相应数据，然后运行 app，停在要开发的界面，再进行调整。如果数据状态发生变化，你还需要 restart app 才能反应。SwiftUI 的 Preview 相比起来，不需要运行 app 并且可以提供任何的 dummy 数据，在开发效率上更胜一筹。&lt;/p&gt;

&lt;p&gt;经过短短一天的使用，Option + Command + P 这个刷新 preview 的快捷键已经深入到我的肌肉记忆中了。&lt;/p&gt;

&lt;h4 id=&quot;section-3---step-5-关于-viewbuilder&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#combine-views-using-stacks&quot;&gt;Section 3 - Step 5: 关于 ViewBuilder&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;创建 Stack 的语法很有趣：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;leading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Turtle Rock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Joshua Tree National Park&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subheadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一开始看起来好像我们给出了两个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt;，似乎是构成的是一个类似数组形式的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[View]&lt;/code&gt;，但实际上并不是这么一回事。这里调用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 类型的初始化方法：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HorizontalAlignment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;nv&quot;&gt;spacing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;nv&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;前面的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alignment&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spacing&lt;/code&gt; 没啥好说，最后一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt; 比较有意思。看签名的话，它是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;() -&amp;gt; Content&lt;/code&gt; 类型，但是我们在创建这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 时所提供的代码只是简单列举了两个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt;，而并没有实际返回一个可用的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这里使用了 Swift 5.1 的另一个新特性：&lt;a href=&quot;https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md&quot;&gt;Funtion builders&lt;/a&gt;。如果你实际观察 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 的&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/vstack/3278367-init&quot;&gt;这个初始化方法的签名&lt;/a&gt;，会发现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt; 前面其实有一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ViewBuilder&lt;/code&gt; 标记：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HorizontalAlignment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;nv&quot;&gt;spacing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;kd&quot;&gt;@ViewBuilder&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt; 则是一个由 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@_functionBuilder&lt;/code&gt; 进行标记的 struct：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@_functionBuilder&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ViewBuilder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@_functionBuilder&lt;/code&gt; 进行标记的类型 (这里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt;)，可以被用来对其他内容进行标记 (这里用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ViewBuilder&lt;/code&gt; 对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt; 进行标记)。被用 function builder 标记过的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt; 标记以后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt; 这个输入的 function 在被使用前，会按照 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt; 中合适的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildBlock&lt;/code&gt; &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md#function-building-methods&quot;&gt;进行 build&lt;/a&gt; 后再使用。如果你阅读 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt; 的&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/viewbuilder&quot;&gt;文档&lt;/a&gt;，会发现有很多接受不同个数参数的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildBlock&lt;/code&gt; 方法，它们将负责把闭包中一一列举的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt; 和其他可能的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 转换为一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TupleView&lt;/code&gt;，并返回。由此，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt; 的签名 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;() -&amp;gt; Content&lt;/code&gt; 可以得到满足。&lt;/p&gt;

&lt;p&gt;实际上构建这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 的代码会被转换为类似下面这样：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 等效伪代码，不能实际编译。&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;leading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewBuilder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;text1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Turtle Rock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;text2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Joshua Tree National Park&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subheadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;buildBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然这种基于 funtion builder 的方式是有一定限制的。比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt; 就只实现了最多&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/viewbuilder/3278693-buildblock&quot;&gt;十个参数&lt;/a&gt;的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildBlock&lt;/code&gt;，因此如果你在一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 中放超过十个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的话，编译器就会不太高兴。不过对于正常的 UI 构建，十个参数应该足够了。如果还不行的话，你也可以考虑直接使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TupleView&lt;/code&gt; 来用多元组的方式合并 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;TupleView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;除了按顺序接受和构建 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildBlock&lt;/code&gt; 以外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewBuilder&lt;/code&gt; 还实现了两个特殊的方法：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildEither&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildIf&lt;/code&gt;。它们分别对应 block 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if...else&lt;/code&gt; 的语法和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; 的语法。也就是说，你可以在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 里写这样的代码：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;someCondition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;leading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Turtle Rock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Joshua Tree National Park&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subheadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;someCondition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Condition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Not Condition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其他的命令式的代码在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VStack&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt; 闭包里是不被接受的，下面这样也不行：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;leading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// let 语句无法通过 function builder 创建合适的输出&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;someCondition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;someCondition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Condition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Not Condition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;到目前为止，只有以下三种写法能被接受 (有可能随着 SwiftUI 的发展出现别的可接受写法)：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;结果为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的语句&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; 语句&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if...else...&lt;/code&gt; 语句&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;section-4---step-7-链式调用修改-view-的属性&quot;&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#create-a-custom-image-view&quot;&gt;Section 4 - Step 7: 链式调用修改 View 的属性&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;教程到这一步的话，相信大家已经对 SwiftUI 的超强表达能力有所感悟了。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;turtlerock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clipShape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Circle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;overlay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Circle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lineWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以试想一下，在 UIKit 中要动手撸一个这个效果的困难程度。我大概可以保证，99% 的开发者很难在不借助文档或者 copy paste 的前提下完成这些事情，但是在 SwiftUI 中简直信手拈来。在创建 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 之后，用链式调用的方式，可以将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 转换为一个含有变更后内容的对象。这么说比较抽象，我们可以来看一个具体的例子。比如简化一下上面的代码：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;turtlerock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;modified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_ModifiedContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_ShadowEffect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image&lt;/code&gt; 通过一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.shadow&lt;/code&gt; 的 modifier，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modified&lt;/code&gt; 变量的类型将转变为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ModifiedContent&amp;lt;Image, _ShadowEffect&amp;gt;&lt;/code&gt;。如果你查看 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 上的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shadow&lt;/code&gt; 的定义，它是这样的：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sRGBLinear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; 
        &lt;span class=&quot;nv&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Modified&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_ShadowEffect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modified&lt;/code&gt; 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 上的一个 typealias，在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct Image: View&lt;/code&gt; 的实现里，我们有：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Modified&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_ModifiedContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ModifiedContent&lt;/code&gt; 是一个 SwiftUI 的私有类型，它存储了待变更的内容，以及用来实施变更的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modifier&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_ModifiedContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Modifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;modifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Modifier&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content&lt;/code&gt; 遵守 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modifier&lt;/code&gt; 遵守 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewModifier&lt;/code&gt; 的情况下，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ModifiedContent&lt;/code&gt; 也将遵守 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt;，这是我们能够通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; 的各个 modifier extension 进行链式调用的基础：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_ModifiedContent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_View&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Modifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ViewModifier&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shadow&lt;/code&gt; 的例子中，SwiftUI 内部会使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ShadowEffect&lt;/code&gt; 这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewModifier&lt;/code&gt;，并把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image&lt;/code&gt; 自身和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ShadowEffect&lt;/code&gt; 实例存放到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ModifiedContent&lt;/code&gt; 里。不论是 image 还是 modifier，都只是对未来实际视图的描述，而不是直接对渲染进行的操作。在最终渲染前，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewModifier&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body(content: Self.Content) -&amp;gt; Self.Body&lt;/code&gt; 将被调用，以给出最终渲染层所需要的各个属性。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;更具体来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ShadowEffect&lt;/code&gt; 是一个满足 &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/environmentalmodifier&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnvironmentalModifier&lt;/code&gt; 协议&lt;/a&gt;的类型，这个协议要求在使用前根据使用环境将自身解析为具体的 modifier。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;其他的几个修改 View 属性的链式调用与 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shadow&lt;/code&gt; 的原理几乎一致。&lt;/p&gt;

&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;

&lt;p&gt;上面是对 SwiftUI 教程的第一部分进行的一些说明，在之后的一篇文章里，我会对剩余的几个教程中有意思的部分再做些解释。&lt;/p&gt;

&lt;p&gt;虽然公开还只有一天，但是 SwiftUI 已经经常被用来和 Flutter 等框架进行比较。试用下来，在 view 的描述表现力上和与 app 的结合方面，SwiftUI 要胜过 Flutter 和 Dart 的组合很多。Swift 虽然开源了，但是 Apple 对它的掌控并没有减弱。Swift 5.1 的很多特性几乎可以说都是为了 SwiftUI 量身定制的，我们已经在本文中看到了一些例子，比如 Opaque return types 和 Function builder 等。在接下来对后面几个教程的解读中，我们还会看到更多这方面的内容。&lt;/p&gt;

&lt;p&gt;另外，Apple 在背后使用 Combine.framework 这个响应式编程框架来对 SwiftUI.framework 进行驱动和数据绑定，相比于现有的 RxSwift/RxCocoa 或者是 ReactiveSwift 的方案来说，得到了语言和编译器层级的大力支持。如果有机会，我想我也会对这方面的内容进行一些探索和介绍。&lt;/p&gt;
</description>
        <pubDate>Tue, 04 Jun 2019 06:32:00 +0000</pubDate>
        <link>https://u3dc.github.io//2019/06/swift-ui-firstlook/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2019/06/swift-ui-firstlook/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>Swift ABI 稳定对我们到底意味着什么</title>
        <description>&lt;p&gt;Swift 社区最近最重大的新闻应该就是 ABI 稳定了。这个话题虽然已经讨论了有一阵子了，但随着 Xcode 10.2 beta 的迭代和 Swift 5 的 release 被提上日程，最终 Swift ABI 稳定能做到什么程度，我们开发者能做些什么，需要做些什么，就变成了一个重要的话题。Apple 在这个月接连发布了 &lt;a href=&quot;https://swift.org/blog/abi-stability-and-more/&quot;&gt;ABI Stability and More&lt;/a&gt; 和 &lt;a href=&quot;https://swift.org/blog/abi-stability-and-apple/&quot;&gt;Evolving Swift On Apple Platforms After ABI Stability&lt;/a&gt; 两篇文章来阐述 Swift 5 发布以后 ABI 相关的内容所带来的改变。虽然原文不是很长，但是有些地方上下文没有说太清楚，可能不太容易理解。本文希望对这个话题以问答的形式进行一些总结，让大家能更明白将要发生的事情。&lt;/p&gt;

&lt;h3 id=&quot;我是一个-app-开发者swift-5-发布以后会怎么样&quot;&gt;我是一个 app 开发者，Swift 5 发布以后会怎么样？&lt;/h3&gt;

&lt;p&gt;简单说，安装 Xcode 10.2，然后&lt;strong&gt;正常迁移&lt;/strong&gt;就可以了，和以往 Swift 3 到 Swift 4 需要做的事情差不多。单论 Swift 5 这个版本，不会对你的开发造成什么影响，直到下一个版本 (比如 Swift 5.1) 之前，你几乎不需要关心 ABI 稳定这件事。关于下个 Swift 版本，我们稍后会提到这件事情。&lt;/p&gt;

&lt;h3 id=&quot;我还是想知道什么是-abi-稳定&quot;&gt;我还是想知道什么是 ABI 稳定？&lt;/h3&gt;

&lt;p&gt;就是 binary 接口稳定，也就是在运行的时候只要是用 Swift 5 (或以上) 的编译器编译出来的 binary，就可以跑在任意的 Swift 5 (或以上) 的 runtime 上。这样，我们就不需要像以往那样在 app 里放一个 Swift runtime 了，Apple 会把它弄到 iOS 和 macOS 系统里。&lt;/p&gt;

&lt;h3 id=&quot;所以说-app-尺寸会变小&quot;&gt;所以说 app 尺寸会变小？&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;是的&lt;/strong&gt;，但是这是 Apple 通过 App Thinning 帮我们完成的，不需要你操心。在提交 app 时，Apple 将会按照 iOS 系统创建不同的下载包。对于 iOS 12.2 的系统，因为它们预装了 Swift 5 的 runtime，所以不再需要 Swift 的库，它们会被从 app bundle 中删掉。对于 iOS 12.2 以下的系统，外甥打灯笼，照旧。&lt;/p&gt;

&lt;p&gt;一个新创建的空 app，针对 &lt;strong&gt;iOS 12.2 打包出来压缩后的下载大小是 26KB&lt;/strong&gt;，&lt;strong&gt;而对 iOS 12.0 则是 2.4MB&lt;/strong&gt;。如果你使用了很多标准库里的东西，那这个差距会更大 (因为没有用到的标准库的符号会被 strip 掉)，对于一个比较有规模的 app 来说，一般可以减小 10M 左右的体积。&lt;/p&gt;

&lt;h3 id=&quot;还有什么其他好处么&quot;&gt;还有什么其他好处么？&lt;/h3&gt;

&lt;p&gt;因为系统集成了 Swift，所以大家都用同一个 Swift 了，app 启动的时候也就不需要额外加载 Swift，所以在新系统上会更快更省内存。当然啦，只是针对新系统。&lt;/p&gt;

&lt;p&gt;另外，对于 Apple 的工程师来说，他们终于能在系统的框架里使用 Swift 了。这样一来，很多东西就不必通过 Objective-C wrap 一遍，这会让代码运行效率提高很多。虽然在 iOS 12.2 中应该还没有 Swift 编写的框架，但是我们也许能在不久的将来看到 Swift 被 Apple 自己所使用。等今年 WWDC 的消息吧。&lt;/p&gt;

&lt;h3 id=&quot;我还想用一段时间的-xcode-101不太想这么快升级&quot;&gt;我还想用一段时间的 Xcode 10.1，不太想这么快升级&lt;/h3&gt;

&lt;p&gt;Xcode 10.1 里的是 Swift 4.2 的编译器，出来的 binary 不是 ABI 稳定的，而且必定打包了 Swift runtime。新的系统发现 app 包中有 Swift runtime 后，就会选择不去使用系统本身的 Swift runtime。这种情况下一切保持和现在不变。旧版本的 Xcode 只有旧版本的 iOS SDK，所以自然你也没有办法用到新系统的 Swift 写的框架，系统肯定不需要在同一个进程中跑两个 Swift runtime。&lt;/p&gt;

&lt;p&gt;简单说，你还可以一直使用 Xcode 10.1 直到 Apple 不再接受它打包的 app。不过这样的话，你不能使用新版本 Swift 的任何特性，也不能从 ABI 稳定中获得任何好处。&lt;/p&gt;

&lt;h3 id=&quot;我升级了-xcode-102但是还想用-swift-4-的兼容模式会怎么样&quot;&gt;我升级了 Xcode 10.2，但是还想用 Swift 4 的兼容模式，会怎么样？&lt;/h3&gt;

&lt;p&gt;首先你需要弄清楚 Swift 的&lt;strong&gt;编译器版本&lt;/strong&gt;和&lt;strong&gt;语言兼容版本&lt;/strong&gt;的区别：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;编译器版本&lt;/th&gt;
      &lt;th&gt;语言兼容版本&lt;/th&gt;
      &lt;th&gt;对应的 Xcode 版本&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Swift 5.0&lt;/td&gt;
      &lt;td&gt;Swift 5.0, 4.2, 4.0&lt;/td&gt;
      &lt;td&gt;Xcode 10.2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Swift 4.2&lt;/td&gt;
      &lt;td&gt;Swift 4.2, 4.0, 3.0&lt;/td&gt;
      &lt;td&gt;Xcode 10.0, Xcode 10.1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;更多历史版本 …&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;同一个 Xcode 版本默认使用的编译器版本只有一个 (在你不更换 toolchain 的前提下)，当我们在说到“使用 Xcode10.2 的 Swift 4 兼容模式”时，我们其实指的是，使用 Xcode 10.2 搭载的 Swift 5.0 版本的编译器，它提供了 4.2 的语法兼容，可以让我们不加修改地编译 Swift 4.2 的代码。即使你在 Xcode 10.2 中选择语言为 Swift 4，你所得到的二进制依然是 ABI 稳定的。ABI 和你的语言是 Swift 4 还是 Swift 5 无关，只和你的编译器版本，或者说 Xcode 版本有关。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;多提一句，即使你选择了 Swift 4 的语言兼容，只要编译器版本 (当然，以及对应的标准库版本) 是 5.0 以上，你依然可以使用 Swift 5 的语法特性 (比如新增加的类型等)。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;看起来-abi-稳定很美好那么代价呢&quot;&gt;看起来 ABI 稳定很美好，那么代价呢？&lt;/h3&gt;

&lt;p&gt;Good question! 我们在第一个问题里就提到过，一切都会很美好，直到下一个版本。因为 Swift runtime 现在被放到 iOS 系统里了，所以想要升级就没那么容易了。&lt;/p&gt;

&lt;p&gt;在 ABI 稳定之前，Swift runtime 是作为开发工具的一部分，被作为库打包到 app 中的。这样一来，在开发时，我们可以随意使用新版本 Swift 的类型或特性，因为它们的版本是开发者自己决定的。不过，当 ABI 稳定后，Swift runtime 变为了用户系统的一部分，它从开发工具，变为了运行的环境，不再由我们开发者唯一决定。比如说，对应 iOS 13 的 Swift 6 的标准库中添加了某个类型 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;，但是在 iOS 12.2 这个只搭载了 Swift 5 的系统中，并没有这个类型。这意味着我们需要在使用 Swift 的时候考虑设备兼容的问题：如果你需要兼容那些搭载了旧版本 Swift 的系统，那你将无法在代码里使用新版本的 Swift runtime 特性。&lt;/p&gt;

&lt;p&gt;这和我们一直以来适配新系统的 API 时候的情况差不多，在 Swift 5 以后，我们需要等到 deploy target 升级到对应的版本，才能开始使用对应的 Swift 特性。这意味着，我们可能会需要写一些这样的兼容代码：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 假如 Swift 6.0 是 iOS 13.0 的 Swift 版本&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;#available(iOS 13.0, *)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Swift 6.0 标准库中存在 A&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 不存在 A 时的处理&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于“新添加的某个类型”这种程度的兼容，我们可以用上面的方式处理。但是对于更靠近语言层面的一些东西 (比如现在已有的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Codable&lt;/code&gt; 这样的特性)，恐怕适配起来就没有那么简单了。在未来，Deployment target 可能会和 Swift 语言版本挂钩，新的语言特性出现后，我们可能需要等待一段时间才能实际用上。而除了那些纯编译期间的内容外，任何与 Swift runtime 有关的特性，都会要遵守这个规则。&lt;/p&gt;

&lt;h3 id=&quot;可以像现在一样打包新版本的-swift-runtime-到-app-里然后指定用打包的-swift-版本么&quot;&gt;可以像现在一样打包新版本的 Swift runtime 到 app 里，然后指定用打包的 Swift 版本么&lt;/h3&gt;

&lt;p&gt;不能，对于包含有 Swift runtime 的系统，如果运行的 binary 是 ABI 稳定的，那么就必须使用系统提供的 Swift。这里的主要原因是，Apple 想要保留使用 Swift 来实现系统框架的可能性：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果允许两个 Swift runtime (系统自带，以及 app 打包的)，那么这两个运行时将无法互相访问，app 也无法与系统的 Swift 框架或者第三方的 ABI 稳定的框架进行交互。&lt;/li&gt;
  &lt;li&gt;如果允许完全替换 Swift runtime，系统的 Swift 框架将执行用户提供的 Swift 标准库中的代码，这将造成重大的安全隐患。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;有任何可能性让我能无视系统版本去使用-swift-的新特性么&quot;&gt;有任何可能性让我能无视系统版本，去使用 Swift 的新特性么&lt;/h3&gt;

&lt;p&gt;有，但是相对麻烦，很大程度上也依赖 Apple 是否愿意支持。如果你还记得 iOS 5.0 引入 ARC 时，Apple 为了让 iOS 4.3 和之前的系统也能使用 ARC 的代码，在 deployment target 选到 iOS 4.3 或之前时，会用 static link 的方式打包一个叫做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libarclite&lt;/code&gt; 的库，其中包含了 ARC 所需要的一些 runtime 方法。对于 ABI 稳定后的 Swift，也许可以采用类似做法，来提供兼容。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这种做法在感觉上和 Android 的 &lt;a href=&quot;https://developer.android.com/topic/libraries/support-library/packages&quot;&gt;Support Library Packages&lt;/a&gt; 的方式类似，但是 Apple 似乎不是很倾向于提供这样的官方支持。所以之后要看有没有机会依靠社区力量来提供 Swift 的兼容支持了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;不能第一时间用上新的语言特性，必然会打击大家进行适配和使用新特性的积极性，也势必会影响到语言的发展和快速迭代，可以说这一限制是相当不利的。&lt;/p&gt;

&lt;p&gt;所以，对于一般的 app 开发者来说，ABI 稳定其实就是一场博弈：你现在有更小的 app 尺寸，但是却被限制了无法使用最新的语言特性，除非你提升 app 的 depolyment target。&lt;/p&gt;

&lt;h3 id=&quot;我是框架开发者abi-稳定后我可以用-binary-形式来发布了么&quot;&gt;我是框架开发者，ABI 稳定后我可以用 binary 形式来发布了么？&lt;/h3&gt;

&lt;p&gt;还不能。ABI 稳定是使用 binary 发布框架的必要非充分条件。框架的 binary 在不同的 runtime 是兼容了，但是作为框架，现在是依靠一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftmodule&lt;/code&gt; 的二进制文件来描述 API Interface 的，这个二进制文件中包含了序列化后的 AST (更准确说，是 interface 的 SIL)，以及编译这个 module 时的平台环境 (Swift 编译器版本等)。&lt;/p&gt;

&lt;p&gt;ABI 稳定并不意味着编译工具链的稳定，对于框架来说，想要用 binary 的方式提供框架，除了 binary 本身稳定以外，还需要描述 binary 的方式 (也就是现在的 swiftmodule) 也稳定，而这正在开发中。将来，Swift 将为 module 提供文本形式的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftinterface&lt;/code&gt; 作为框架 API 描述，然后让未来的编译器根据这个描述去“编译”出对应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftmodule&lt;/code&gt; 作为缓存并使用。&lt;/p&gt;

&lt;p&gt;这一目标被称为 module stability，当达到 module stability 后，你就可以使用 binary 来发布框架了 (当然，这种 binary 框架只支持带有 ABI 稳定的 Swift runtime 的平台，也就是 iOS 12.2 及以上)。&lt;/p&gt;

&lt;h3 id=&quot;能总结一下-abi-稳定或者展望一下未来么&quot;&gt;能总结一下 ABI 稳定，或者展望一下未来么？&lt;/h3&gt;

&lt;p&gt;ABI 稳定最大的受益者应该是 Apple，这让 Apple 在自己的生态系统中，特别是系统框架中，可以使用 Swift 来进行实现。在我看来，Swift ABI 稳定为 Apple 开发平台的一场革命奠定了基础。在接下来的几年里，如果你还想要关注 Apple 平台，可能下面几件事情会特别重要：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Apple 什么时候发布第一个 Swift 写的系统框架&lt;/li&gt;
  &lt;li&gt;Apple 什么时候开始提供第一个 Swift only 的 API&lt;/li&gt;
  &lt;li&gt;Apple 什么时候开始“锁定” Objective-C 的 SDK，不再为它增加新的 API&lt;/li&gt;
  &lt;li&gt;Apple 什么时候开始用 Swift 特性更新现有的 Objective-C SDK&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这些事情也许会在未来几年陆续发生。面对微软从 Win32 API 向 .Net 一路迁移，到今天的 UWP (Universal Windows Platform)，Google 来势汹汹的 Fuchsia 和 Dart，Swift 是 Apple 唯一能与它们抗衡的答案。相比于微软提供的泛型和并行编程模型，Google 的 Flutter 的跨平台的先天优势，Apple 平台基于 Objective-C 的 API 的易用性已然被抛开很远。虽然 Apple 在 2014 年承诺过依然维护 Objective-C，但是经过 Swift 这五年的发展，随着 Swift ABI 的稳定，什么时候如果 Objective-C 成为了继续发展的阻碍，相信 Apple 已经有足够的理由将它抛弃。&lt;/p&gt;

&lt;p&gt;作为 Apple 平台的从业者，我们也许正处在另一个时代变革的开端。&lt;/p&gt;
</description>
        <pubDate>Thu, 21 Feb 2019 01:28:00 +0000</pubDate>
        <link>https://u3dc.github.io//2019/02/swift-abi/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2019/02/swift-abi/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>与 JOSE 战斗的日子 - 写给 iOS 开发者的密码学入门手册 (实践)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/2018/matrix.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;概述&quot;&gt;概述&lt;/h2&gt;

&lt;p&gt;这是关于 JOSE 和密码学的三篇系列文章中的最后一篇，你可以在下面的链接中找到其他部分：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/12/jose-1/&quot;&gt;基础 - 什么是 JWT 以及 JOSE&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/12/jose-2/&quot;&gt;理论 - JOSE 中的签名和验证流程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;实践 - 如何使用 Security.framework 处理 JOSE 中的验证 (本文)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这一篇中，我们会在 JOSE 基础篇和理论篇的知识架构上，使用 iOS (或者说 Cocoa) 的相关框架来完成对 JWT 的解析，并利用 JWK 对它的签名进行验证。在最后，我会给出一些我自己在实现和学习这些内容时的思考，并把一些相关工具和标准列举一下。&lt;/p&gt;

&lt;h2 id=&quot;解码-jwt&quot;&gt;解码 JWT&lt;/h2&gt;

&lt;p&gt;JWT，或者更精确一点，JWS 中的 Header 和 Payload 都是 Base64Url 编码的。为了获取原文内容，先需要对 Header 和 Payload 解码。&lt;/p&gt;

&lt;h3 id=&quot;base64url&quot;&gt;Base64Url&lt;/h3&gt;

&lt;p&gt;Base64 相信大家都已经很熟悉了，随着网络普及，这套编码有一个很大的“缺点”，就是使用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt;。这些字符在 URL 里是很不友好的，在作为传输时需要额外做 escaping。Base64Url 就是针对这个问题的改进，具体来说就是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; 替换为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; 替换为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;将末尾的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt; 干掉。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;相关代码的话非常简单，为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; 分别添加 extension 来相互转换就好：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Encode `self` with URL escaping considered.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base64URLEncoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base64Encoded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;base64EncodedString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64Encoded&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replacingOccurrences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replacingOccurrences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replacingOccurrences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Returns the data of `self` (which is a base64 string), with URL related characters decoded.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base64URLDecoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;paddingLength&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Filling = for %4 padding.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paddingLength&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;repeating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paddingLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base64EncodedString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replacingOccurrences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replacingOccurrences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;base64Encoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64EncodedString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;结合使用-jsondecoder-和-base64url-来处理-jwt&quot;&gt;结合使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSONDecoder&lt;/code&gt; 和 Base64Url 来处理 JWT&lt;/h3&gt;

&lt;p&gt;因为 JWT 的 Header 和 Payload 部分实际上是有效的 JSON，为了简单，我们可以利用 Swift 的 Codable 来解析 JWT。为了简化处理，可以封装一个针对以 Base64Url 表示的 JSON 的 decoder：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;Base64URLJSONDecoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JSONDecoder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Decodable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ascii&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 错误处理&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Decodable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;decodedData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64URLDecoded&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 错误处理&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodedData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Base64URLJSONDecoder&lt;/code&gt; 将 Base64Url 的转换封装到解码过程中，这样一来，我们只需要获取 JWT，将它用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; 分割开，然后使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Base64URLJSONDecoder&lt;/code&gt; 就能把 Header 和 Payload 轻易转换了，比如：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;algorithm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;tokenType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;keyID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CodingKeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CodingKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;algorithm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;alg&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;typ&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keyID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kid&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jwtRaw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eyJhbGciOiJSUzI1NiI...&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// JWT 字符串，后面部分省略了&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rawComponents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;separatedBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;decoder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Base64JSONDecoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rawComponents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;keyID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keyID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* 验证失败 */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 Header 中，我们应该可以找到指定了验证签名所需要使用的公钥的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keyID&lt;/code&gt;。如果没有的话，验证失败，登录过程终止。&lt;/p&gt;

&lt;p&gt;对于签名，我们将解码后的原始的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt; 保存下来，稍后使用。同样地，我们最好也保存一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{Header}.{Payload}&lt;/code&gt; 的部分，它在验证中也会被使用到：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;signature&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rawComponents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64URLDecoded&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;plainText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawComponents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawComponents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;这里的代码基本都没有考虑错误处理，大部分是直接让程序崩溃。实际的产品中验证签名过程中的错误应该被恰当处理，而不是粗暴挂掉。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;在-securityframework-中处理签名&quot;&gt;在 Security.framework 中处理签名&lt;/h2&gt;

&lt;p&gt;我们已经准备好签名的数据和原文了，万事俱备，只欠密钥。&lt;/p&gt;

&lt;h3 id=&quot;处理密钥&quot;&gt;处理密钥&lt;/h3&gt;

&lt;p&gt;通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keyID&lt;/code&gt;，在预先设定的 JWT Host 中我们应该可以找到以 JWK 形式表示的密钥。我们计划使用 Security.framework 来处理密钥和签名验证，首先要做的就是遵守框架和 JWA 的规范，通过 JWK 的密钥生成 Security 框架喜欢的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKey&lt;/code&gt; 值。&lt;/p&gt;

&lt;p&gt;在其他大部分情况下，我们可能会从一个证书 (certificate，不管是从网络下载的 PEM 还是存储在本地的证书文件) 里获取公钥。像是处理 HTTPS challenge 或者 SSL Pinning 的时候，大部分情况下我们拿到的是完整的证书数据，通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecCertificateCreateWithData&lt;/code&gt; 使用 DER 编码的数据创建证书并获取公钥：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecCertificateCreateWithData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 错误处理&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;policy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecPolicyCreateBasicX509&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;trust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecTrust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;SecTrustCreateWithCertificates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;policy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecTrustCopyPublicKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 错误处理&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，在 JWK 的场合，我们是没有 X.509 证书的。JWK 直接将密钥类型和参数编码在 JSON 中，我们当然可以按照 DER 编码规则将这些信息编码回一个符合 X.509 要求的证书，然后使用上面的方法再从中获取证书。不过这显然是画蛇添足，我们完全可以直接通过这些参数，使用特定格式的数据来直接生成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKey&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;有可能有同学会迷惑于“公钥”和“证书”这两个概念。一个证书，除了包含有公钥以外，还包含有像是证书发行者，证书目的，以及其他一些元数据的信息。因此，我们可以从一个证书中，提取它所存储的公钥。&lt;/p&gt;

  &lt;p&gt;另外，证书本身一般会由另外一个私钥进行签名，并由颁发机构或者受信任的机构进行验证保证其真实性。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用 &lt;a href=&quot;https://developer.apple.com/documentation/security/1643701-seckeycreatewithdata&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyCreateWithData&lt;/code&gt;&lt;/a&gt; 就可以直接通过公钥参数来生成了：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyCreateWithData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;keyData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                          &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                          &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UnsafeMutablePointer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Unmanaged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;第二个参数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attributes&lt;/code&gt; 需要的是密钥种类 (RSA 还是 EC)，密钥类型 (公钥还是私钥)，密钥尺寸 (数据 bit 数) 等信息，比较简单。&lt;/p&gt;

&lt;p&gt;关于所需要的数据格式，根据密钥种类不同，而有所区别。在&lt;a href=&quot;https://developer.apple.com/documentation/security/1643698-seckeycopyexternalrepresentation&quot;&gt;这个风马牛不相及的页面&lt;/a&gt; 以及 &lt;a href=&quot;https://opensource.apple.com/source/Security/Security-58286.41.2/keychain/SecKey.h&quot;&gt;SecKey 源码&lt;/a&gt; 的注释中有所提及：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The method returns data in the PKCS #1 format for an RSA key. For an elliptic curve public key, 
the format follows the ANSI X9.63 standard using a byte string of 04 || X || Y.  …  All of 
these representations use constant size integers, including leading zeros as needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;The requested data format depend on the type of key (kSecAttrKeyType) being created:&lt;/p&gt;

  &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kSecAttrKeyTypeRSA               PKCS#1 format, public key can be also in x509 public key format
kSecAttrKeyTypeECSECPrimeRandom  ANSI X9.63 format (04 || X || Y [ || K])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;jwa---rsa&quot;&gt;JWA - RSA&lt;/h4&gt;

&lt;p&gt;简单说，RSA 的公钥需要遵守  PKCS#1，使用 X.509 编码即可。所以对于 RSA 的 JWK 里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt;，我们用 DER 按照 X.509 编码成序列后，就可以扔给 Security 框架了：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JWK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RSA&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;modulus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jwk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JWK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RSA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modulus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64URLDecoded&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exponent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64URLDecoded&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;modulusBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;            
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;firstByte&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modulusBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;firstByte&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x80&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;modulusBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exponentBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;modulusEncoded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modulusBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exponentEncoded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exponentBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sequenceEncoded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modulusEncoded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exponentEncoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sequenceEncoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 DER 编码部分的代码，可以在&lt;a href=&quot;https://github.com/line/line-sdk-ios-swift/blob/8c2476d9d00225cf4b33c0e245e9bd580c59f4d8/LineSDK/LineSDK/Crypto/JWK/JWA.swift#L185-L240&quot;&gt;这里&lt;/a&gt;找到。对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modulusBytes&lt;/code&gt;，首位大于等于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x80&lt;/code&gt; 时需要追加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 的原因，也已经在&lt;a href=&quot;/2018/jose-1/&quot;&gt;第一篇&lt;/a&gt;中提及。如果你不知道我在说什么，建议回头仔细再看一下前两篇的内容。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用上面的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; 就可以获取 RSA 的公钥了：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sizeInBits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MemoryLayout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;kSecAttrKeyType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSecAttrKeyTypeRSA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;kSecAttrKeyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSecAttrKeyClassPublic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;kSecAttrKeySizeInBits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sizeInBits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unmanaged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyCreateWithData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attributes&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 错误处理&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 一切正常的话，打印类似这样：&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// &amp;lt;SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// block size: 1024 bits, exponent: {hex: 10001, decimal: 65537}, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// modulus: DD95AB518D18E8828DD6A238061C51D82EE81D516018F624..., &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// addr: 0x6000027ffb00&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;jwa---ecsda&quot;&gt;JWA - ECSDA&lt;/h4&gt;

&lt;p&gt;按照说明，对于 EC 公钥，期望的数据是符合 X9.63 中未压缩的椭圆曲线点座标：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;04 || X || Y&lt;/code&gt;。不过，虽然在文档说明里提及：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;All of these representations use constant size integers, including leading zeros as needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但事实是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyCreateWithData&lt;/code&gt; 并不喜欢在首位追加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 的做法。这里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; &lt;strong&gt;必须&lt;/strong&gt;是满足椭圆曲线对应要求的密钥位数的整数值，如果在首位大于等于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x80&lt;/code&gt; 的值前面追加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt;，反而会导致无法创建 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKey&lt;/code&gt;。所以，在组织数据时，不仅不需要添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt;，我们反而最好检查一下获取的 JWK，如果首位有不必要的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 的话，应该将其去除：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JWK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RSA&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jwk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JWK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RSA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;decodedXData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64URLDecoded&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;decodedYData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base64URLDecoded&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;xBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodedXData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;curve&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordinateOctetLength&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodedXData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodedXData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dropFirst&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;yBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodedYData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;curve&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordinateOctetLength&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;yBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodedYData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;yBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodedYData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dropFirst&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;uncompressedIndicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x04&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uncompressedIndicator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;创建公钥时和 RSA 类似：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sizeInBits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MemoryLayout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;kSecAttrKeyType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSecAttrKeyTypeECSECPrimeRandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;kSecAttrKeyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kSecAttrKeyClassPublic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;kSecAttrKeySizeInBits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sizeInBits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unmanaged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyCreateWithData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attributes&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 错误处理&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 一切正常的话，打印类似这样：&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// &amp;lt;SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// key type: ECPublicKey, version: 4, block size: 256 bits, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// y: 3D4F8B27B29E5C77FCF877367245F3D75C2FBA806C54A0A0C05807E1B536E68A, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// x: FFB00CF903B79BB0F6C049208A59C448049BE0A2A1AF4692C486085CBD9057EF, &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// addr: 0x7fcafd80ced0&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;验证签名&quot;&gt;验证签名&lt;/h3&gt;

&lt;p&gt;Security 框架中为使用公钥进行签名验证准备了一个方法：&lt;a href=&quot;https://developer.apple.com/documentation/security/1643715-seckeyverifysignature&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyVerifySignature&lt;/code&gt;&lt;/a&gt;：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyVerifySignature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                         &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;algorithm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyAlgorithm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                         &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;signedData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                         &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                         &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UnsafeMutablePointer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Unmanaged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key&lt;/code&gt; 我们已经拿到了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signedData&lt;/code&gt; 就是之前我们准备的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{Header}.{Payload}&lt;/code&gt; 的字符串的数据表示 (也就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plainText.data(using: .ascii)&lt;/code&gt;。注意，这里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plainText&lt;/code&gt; 不是一个 Base64Url 字符串，JWS 签名所针对的就是这个拼凑后的字符串的散列值)。我们需要为不同的签名算法指定合适的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyAlgorithm&lt;/code&gt;，通过访问 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyAlgorithm&lt;/code&gt; 的静态成员，就可以获取 Security 框架预先定义的算法了。比如常用的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let ecdsa256 = SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
let rsa256 = SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你可以在 Apple 的&lt;a href=&quot;https://developer.apple.com/documentation/security/seckeyalgorithm&quot;&gt;文档里&lt;/a&gt;找到所有支持的算法的定义，但是不幸的是，这些算法都只有名字，没有具体说明，也没有使用范例。想要具体知道某个算法的用法，可能需要在&lt;a href=&quot;https://opensource.apple.com/source/Security/Security-57740.51.3/keychain/SecKey.h&quot;&gt;源码级别&lt;/a&gt;去参考注释。为了方便，对于签名验证相关的一些常用算法，我列了一个表说明对应关系：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;算法&lt;/th&gt;
      &lt;th&gt;输入数据 (signedData)&lt;/th&gt;
      &lt;th&gt;签名 (signature)&lt;/th&gt;
      &lt;th&gt;对应 JWT 算法&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;rsaSignatureDigestPKCS1v15SHA{x}&lt;/td&gt;
      &lt;td&gt;原数据的 SHA-x 摘要&lt;/td&gt;
      &lt;td&gt;PKCS#1 v1.5 padding 的签名&lt;/td&gt;
      &lt;td&gt;RS{x}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rsaSignatureMessagePKCS1v15SHA{x}&lt;/td&gt;
      &lt;td&gt;原数据本身，框架负责计算 SHA-x 摘要&lt;/td&gt;
      &lt;td&gt;PKCS#1 v1.5 padding 的签名&lt;/td&gt;
      &lt;td&gt;RS{x}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rsaSignatureDigestPSSSHA{x}&lt;/td&gt;
      &lt;td&gt;原数据的 SHA-x 摘要&lt;/td&gt;
      &lt;td&gt;使用 PSS 的 PKCS#1 v2.1 签名&lt;/td&gt;
      &lt;td&gt;PS{x}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rsaSignatureMessagePSSSHA{x}&lt;/td&gt;
      &lt;td&gt;原数据本身，框架负责计算 SHA-x 摘要&lt;/td&gt;
      &lt;td&gt;使用 PSS 的 PKCS#1 v2.1 签名&lt;/td&gt;
      &lt;td&gt;PS{x}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ecdsaSignatureDigestX962SHA{x}&lt;/td&gt;
      &lt;td&gt;原数据的 SHA-x 摘要&lt;/td&gt;
      &lt;td&gt;DER x9.62 编码的 r 和 s&lt;/td&gt;
      &lt;td&gt;ES{x}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ecdsaSignatureMessageX962SHA{x}&lt;/td&gt;
      &lt;td&gt;原数据本身，框架负责计算 SHA-x 摘要&lt;/td&gt;
      &lt;td&gt;DER x9.62 编码的 r 和 s&lt;/td&gt;
      &lt;td&gt;ES{x}&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;不难看出，这些签名算法基本就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{算法类型} + {数据处理方式} + {签名格式}&lt;/code&gt; 的组合。另外还有一些更为泛用的签名算法，像是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ecdsaSignatureRFC4754&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rsaSignatureRaw&lt;/code&gt;，你需要按照源码注释给入合适的输入，不过一般来说还是直接使用预设的散列的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__Message__SHA___&lt;/code&gt; 这类算法最为方便。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyAlgorithm&lt;/code&gt; 中除了签名算法，也包括了使用 RSA 和 EC 进行加密的相关算法。整体上和签名算法的命名方式类似，有兴趣和需要相关内容的同学可以自行研究。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;对于 JWT 来说，RS 算法的签名已经是 PKCS#1 v1.5 padding 的了，所以直接将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signedData&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signature&lt;/code&gt; 配合使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsaSignatureMessagePKCS1v15SHA{x}&lt;/code&gt; 就可以完成验证。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unmanaged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyVerifySignature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rsaSignatureMessagePKCS1v15SHA256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;signedData&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于 ES 的 JWT 来说，事情要麻烦一些。我们收到的 JWT 里的签名只是 {r, s} 的简单连接，所以需要预先进行处理。按照 X9.62 中对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signature&lt;/code&gt; 的编码定义：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ECDSA-Sig-Value ::= SEQUENCE {
    r INTEGER,
    s INTEGER }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因此，在调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyVerifySignature&lt;/code&gt; 之前，先处理签名：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 错误，签名应该是两个等长的整数&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 处理首位，我们已经做过很多次了。&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 完成签名的 DER 编码&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;processedSignature&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unmanaged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CFError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SecKeyVerifySignature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ecdsaSignatureMessageX962SHA256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;signedData&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;processedSignature&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CFData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;上面 RSA 和 ECDSA 的验证，都假设了使用 SHA-256 作为散列算法。如果你采用的是其他的散列算法，记得替换。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;验证-payload-内容&quot;&gt;验证 Payload 内容&lt;/h3&gt;

&lt;p&gt;签名正确完成验证之后，我们就可以对 JWT Payload 里的内容进行验证了：包括但不限于 “iss”，”sub”，”exp”，”iat” 这些保留值是否正确。当签名和内容都验证无误后，就可以安心使用这个 JWT 了。&lt;/p&gt;

&lt;h2 id=&quot;一些问题&quot;&gt;一些问题&lt;/h2&gt;

&lt;p&gt;至此，我们从最初的 JWT 定义开始，引伸出 JWA，JWK 等一系列 JOSE 概念。然后我们研究了互联网安全领域的通用编码方式和几种最常见的密钥的构成。最后，我们使用这些知识在 Security 框架的帮助下，完成了 JWT 的签名验证的整个流程。&lt;/p&gt;

&lt;p&gt;事后看上去没有太大难度，但是由于涉及到的名词概念很多，相关标准错综复杂，因此初上手想要把全盘都弄明白，还是会有一定困难。希望这系列文章能够帮助你在起步阶段就建立相对清晰的知识体系，这样在阅读其他的相关信息时，可以对新的知识进行更好的分类整理。&lt;/p&gt;

&lt;p&gt;最后，是一些我自己在学习和实践中的考虑。在此一并列出，以供参考。如果您有什么指正和补充，也欢迎留言评论。&lt;/p&gt;

&lt;h4 id=&quot;为什么不用已有的相关开源框架&quot;&gt;为什么不用已有的相关开源框架&lt;/h4&gt;

&lt;p&gt;现存的和这个主题相关的 iOS 或者 Swift 框架有一些，比如 &lt;a href=&quot;https://github.com/airsidemobile/JOSESwift&quot;&gt;JOSESwift&lt;/a&gt;，&lt;a href=&quot;https://github.com/kylef/JSONWebToken.swift&quot;&gt;JSONWebToken.swift&lt;/a&gt;，&lt;a href=&quot;https://github.com/IBM-Swift/Swift-JWT&quot;&gt;Swift-JWT&lt;/a&gt;，&lt;a href=&quot;https://github.com/vapor/jwt&quot;&gt;vaper/jwt&lt;/a&gt; 等等。来回比较考察，它们现在 (2018 年 12 月) 或多或少存在下面的不足：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;没有一个从 JWK 开始到 JWT 的完整方案。JWT 相关的框架基本都是从本地证书获取公钥进行验证，而我需要从 JWK 获取证书&lt;/li&gt;
  &lt;li&gt;支持 JWK 的框架只实现了部分算法，比如只有 RSA，没有 ECDSA 支持。&lt;/li&gt;
  &lt;li&gt;一些框架依赖关系太复杂，而且大部分实现是面向 Swift Server Side，而非 iOS 的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 &lt;a href=&quot;https://github.com/line/line-sdk-ios-swift&quot;&gt;LINE SDK&lt;/a&gt; 中，我们需要，且只需要在 iOS 上利用 Security 框架完成验证。同时 Server 可能会变更配置，所以我们需要同时支持 RSA 和 ECDSA (当前默认使用 ECDSA)。另外，本身作为一个提供给第三方开发者的 SDK，我们不允许引入不可靠的复杂依赖关系 (最理想的情况是零依赖，也就是 LINE SDK 的现状)。基于这些原因，我没有使用现有的开源代码，而是自己从头进行实现。&lt;/p&gt;

&lt;h4 id=&quot;为什么不把你做的相关内容整理开源&quot;&gt;为什么不把你做的相关内容整理开源&lt;/h4&gt;

&lt;p&gt;在 LINE SDK 中的方案是不完备的，它是 JOSE 中满足我们的 JWT 解析和验证需求的最小子集，因此没有很高的泛用性，不适合作为单独项目开源。不过因为 LINE SDK 整个项目是开源的，JOSE 部分的代码其实也都是公开且相对独立的。如果你感兴趣，可以在 LINE SDK 的 &lt;a href=&quot;https://github.com/line/line-sdk-ios-swift/tree/master/LineSDK/LineSDK/Crypto&quot;&gt;Crypto 文件夹&lt;/a&gt;下找到所有相关代码。&lt;/p&gt;

&lt;h4 id=&quot;为什么要用非对称算法各算法之间有什么优劣&quot;&gt;为什么要用非对称算法，各算法之间有什么优劣&lt;/h4&gt;

&lt;p&gt;不少 JWT 使用 HS 的算法 (HMAC)。和 RSA 或 ECDSA 不同，HMAC 是对称加密算法。对称算法加密和解密比较简单，因为密钥相同，所以比较适合用在 Server to Server 这种双方可信的场合。如果在客户端上使用对称算法，那就需要将这个密钥存放在客户端上，这显然是不可接受的。对于 Client - Server 的通讯，非对称算法应该是毋庸置疑的选择。&lt;/p&gt;

&lt;p&gt;相比与 RSA，ECDSA 可以使用更短的密钥实现和数倍长于自己的 RSA 相同的安全性能。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;For example, at a security level of 80 bits (meaning an attacker requires a maximum of about 
2^80 operations to find the private key) the size of an ECDSA public key would be 160 bits, whereas the size of a DSA public key is at least 1024 bits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;由于 ECDSA 是专用的 DSA 算法，只能用于签名，而不能用作加密和密钥交换，所以它比 RSA 要快很多。另外，更小的密钥也带来了更小的计算量。这些特性对于减少 Server 负担非常重要。关于 ECDSA 的优势和它相对于 RSA 的对比，可以参考 Cloudflare 的&lt;a href=&quot;https://blog.cloudflare.com/ecdsa-the-digital-signature-algorithm-of-a-better-internet/&quot;&gt;这篇文章&lt;/a&gt;。&lt;/p&gt;

&lt;h4 id=&quot;签名的安全性&quot;&gt;签名的安全性&lt;/h4&gt;

&lt;p&gt;JWT 签名的伪造一直是一个困扰人的问题。因为 JWT 的 Header 和 Payload 内容一旦确定的话，它的签名也就确定了 (虽然 ECDSA 会产生随机数使签名每次都不同，但是这些签名都可以通过验证)。这带来一个问题，攻击者可以通过截取以前的有效的 JWT，然后把它作为新的响应发给用户。这类 JWT 依然可以正确通过签名验证。&lt;/p&gt;

&lt;p&gt;因此，我们必须每次生成不同的 JWT，来防止这种替换攻击。最简单的方式就是在内存中存储随机值，发送 JWT 请求时附带这个随机值，然后 Server 将这个随机值嵌入在返回的 JWT 的 Payload 中。Client 收到后，再与内存中保存的值进行比对。这样保证了每次返回的 JWT 都不相同，让签名验证更加安全。&lt;/p&gt;

&lt;h4 id=&quot;openssl-版本的问题&quot;&gt;OpenSSL 版本的问题&lt;/h4&gt;

&lt;p&gt;macOS 上自带的 OpenSSL 版本一般比较旧，而大部分 Linux 系统的 OpenSSL 更新一些。不同版本的 OpenSSL (或者其他的常用安全框架) 实现细节上会有差异，比如有些版本会在负数首位补 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 等。在测试时，最好让 Server 的小伙伴确认一下使用的 OpenSSL 版本，这样能在验证和使用密钥上避免一些不必要的麻烦。(请不要问我细节！都是泪)&lt;/p&gt;

&lt;h4 id=&quot;jwt-可以用来做什么应该用来做什么&quot;&gt;JWT 可以用来做什么，应该用来做什么&lt;/h4&gt;

&lt;p&gt;JWT 最常见的使用场景有两个：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;授权&lt;/strong&gt;：用户登录后，在后续的请求中带上一个有效的 JWT，其中包含该用户可以访问的路径或权限等。服务器验证 JWT 有效性后对访问进行授权。相比于传统像是 OAuth 的 token 来说，服务器并不需要存储这些 token，可以实现无状态的授权，因此它的开销较小，也更容易实现和理解。另外，由于 JWT 不需要依赖 Cookie 的特性，跨站或者跨服务依然可能使用，这让单点登录非常简单。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;信息交换&lt;/strong&gt;：LINE SDK 中对用户信息进行签名和验证，就属于信息交换的范畴。依赖 JWT 的签名特性，接收方可以确保 JWT 中的内容没有被篡改，是一种安全的信息交换方式。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最近有非常多的关于反对使用 JWT 进行授权的声音，比如&lt;a href=&quot;http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/&quot;&gt;这篇文章&lt;/a&gt;和&lt;a href=&quot;https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid&quot;&gt;这篇文章&lt;/a&gt;。JWT 作为授权 token 来使用，最大的问题在于无法过期或者作废，另外，一些严格遵守标准的实现，反而可能&lt;a href=&quot;https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/&quot;&gt;引入严重的安全问题&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;不过对于第二种用法，也就是信息交换来说，JWT 所提供的便捷和安全性是无人质疑的。&lt;/p&gt;

&lt;h4 id=&quot;我也想读读看相关标准&quot;&gt;我也想读读看相关标准&lt;/h4&gt;

&lt;p&gt;如你所愿，我整理了一下涉及到的标准。祝武运昌隆！&lt;/p&gt;

&lt;h5 id=&quot;关于编码和算法&quot;&gt;关于编码和算法&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.itu.int/ITU-T/studygroups/com17/languages/X.680-0207.pdf&quot;&gt;X.680 - ASN.1 的标准和基本标注方式&lt;/a&gt;：ASN.1 是这套方法的名字，而对应的标准号是 X.680。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf&quot;&gt;X.690 - DER 编码规则&lt;/a&gt;：也包括了其他的，比如 BER 和 CER 的编码规则。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc3279&quot;&gt;RFC 3279 - 关于 X.509 如何编码密钥和签名&lt;/a&gt;：在 X.509 应用层面上密钥以及签名的构成。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.secg.org/sec2-v2.pdf&quot;&gt;SEC 2 - 关于椭圆曲线算法参数&lt;/a&gt;：ECDSA 的各种 OIDs 定义和椭圆曲线 G 值的表示方式。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&amp;amp;rep=rep1&amp;amp;type=pdf&quot;&gt;X9.62 - 椭圆曲线的应用和相关编码方式&lt;/a&gt;：描述了 ECDSA 算法和密钥的表示方式。它在 SEC 2 的基础上添加了关于曲线点 (也就是实际的密钥本身) 的定义。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc5480&quot;&gt;RFC 5480 - 椭圆曲线公钥的信息&lt;/a&gt;：EC 公钥的定义，表示方式，使用曲线和对应的密钥位数及散列算法的关系。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc8017&quot;&gt;RFC 8017 - RSA 算法相关的标准&lt;/a&gt;：包括像是 RSA key 的 ASN.1 定义，所注册的 OIDs 。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;关于-jose&quot;&gt;关于 JOSE&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7515&quot;&gt;RFC 7515 - JSON Web Signature (JWS)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7516&quot;&gt;RFC 7516 - JSON Web Encryption (JWE)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7517&quot;&gt;RFC 7517 - JSON Web Key (JWK)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7518&quot;&gt;RFC 7518 - JSON Web Algorithms (JWA)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7519&quot;&gt;RFC 7519 - JSON Web Token (JWT)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7165&quot;&gt;RFC 7165 - JOSE 的使用例子和要求&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;杂项&quot;&gt;杂项&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc4648&quot;&gt;RFC 4648 - 关于 Base64Url 的编码规则&lt;/a&gt;：JOSE 中的数据都是使用 Base64Url 进行编码的。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openid.net/specs/openid-connect-discovery-1_0.html&quot;&gt;OpenID Connect Discovery&lt;/a&gt;：OpenID 相关的 profile 取得方式，以及其中键值对的定义。关于 Discovery Document 的更好的说明，可以参考 &lt;a href=&quot;https://developers.google.com/identity/protocols/OpenIDConnect#discovery&quot;&gt;Google 的这个指南&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;验证和速查工具汇总&quot;&gt;验证和速查工具汇总&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://holtstrom.com/michael/tools/asn1decoder.php&quot;&gt;ASN.1 解码器&lt;/a&gt;：将一段 DER 数据解码为可读的 ASN.1 表示。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cryptii.com/pipes/base64-to-hex&quot;&gt;数据格式转换&lt;/a&gt;：将数据在 Base64、文本和字节表示之间进行任意转换。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.alvestrand.no/objectid/top.html&quot;&gt;ASN.1 中的 OIDs 转换&lt;/a&gt;：帮助解码和编码 OBJECT IDENTIFIER 值。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://8gwifi.org/jwkconvertfunctions.jsp&quot;&gt;JWK 和 PEM 相互转换&lt;/a&gt;：将 JWK 或者 PEM 的密钥相互转换的工具。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;你的这篇文章或者代码好像有问题&quot;&gt;你的这篇文章或者代码好像有问题！&lt;/h4&gt;

&lt;p&gt;我是初学者，文章中的纰漏请不吝赐教指出！&lt;/p&gt;

&lt;p&gt;关于代码方面的不足，&lt;a href=&quot;https://github.com/line/line-sdk-ios-swift&quot;&gt;LINE SDK&lt;/a&gt; 欢迎各种 PR。但是如果您发现的问题涉及安全漏洞，或者会导致比较严重后果的话，还请&lt;strong&gt;先不要公开公布&lt;/strong&gt;。如果能按照&lt;a href=&quot;https://github.com/line/line-sdk-ios-swift/blob/master/.github/ISSUE_TEMPLATE.md&quot;&gt;这里的说明&lt;/a&gt;给我们发送邮件联系的话，实在感激不尽。&lt;/p&gt;

</description>
        <pubDate>Fri, 07 Dec 2018 01:28:00 +0000</pubDate>
        <link>https://u3dc.github.io//2018/12/jose-3/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/12/jose-3/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>与 JOSE 战斗的日子 - 写给 iOS 开发者的密码学入门手册 (理论)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/2018/matrix.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;概述&quot;&gt;概述&lt;/h2&gt;

&lt;p&gt;这是关于 JOSE 和密码学的三篇系列文章中的第二篇，你可以在下面的链接中找到其他部分：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/12/jose-1/&quot;&gt;基础 - 什么是 JWT 以及 JOSE&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;理论 - JOSE 中的签名和验证流程 (本文)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/12/jose-3/&quot;&gt;实践 - 如何使用 Security.framework 处理 JOSE 中的验证&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这一篇中，主要介绍网络传输的密钥的编码和处理方法，以及进行数字签名和验证的基本流程。我们在之后实践一篇里，会使用到这些知识。&lt;/p&gt;

&lt;h2 id=&quot;密钥的表现形式&quot;&gt;密钥的表现形式&lt;/h2&gt;

&lt;p&gt;显然 JWK 是一种密钥的表现形式，它使用 JSON 的方式，遵守 JWA 的参数，来定义密钥。不过这种表现形式在日常里使用得并不是那么普遍，我们在平时看到得更多的也许是这样的密钥：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAryQICCl6NZ5gDKrnSztO
3Hy8PEUcuyvg/ikC+VcIo2SFFSf18a3IMYldIugqqqZCs4/4uVW3sbdLs/6PfgdX
7O9D22ZiFWHPYA2k2N744MNiCD1UE+tJyllUhSblK48bn+v1oZHCM0nYQ2NqUkvS
j+hwUU3RiWl7x3D2s9wSdNt7XUtW05a/FXehsPSiJfKvHJJnGOX0BgTvkLnkAOTd
OrUZ/wK69Dzu4IvrN4vs9Nes8vbwPa/ddZEzGR0cQMt0JBkhk9kU/qwqUseP1QRJ
5I1jR4g8aYPL/ke9K35PxZWuDp3U0UPAZ3PjFAh+5T+fc7gzCs9dPzSHloruU+gl
FQIDAQAB
-----END PUBLIC KEY-----
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是一个 RSA 公钥的 PEM (Privacy-Enhanced Mail) 表示方式。类似地，对于 ECDSA 密钥，也可以类似表示：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在处理 JOSE 相关的验证时，我们其实是不会涉及这种格式的密钥的。但是我们会用到里面的相关的一些编码方式来处理 JWK 密钥和 Security 框架中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKey&lt;/code&gt; 的转换。所以这里把它作为一节单独介绍。&lt;/p&gt;

&lt;h3 id=&quot;pemasn1x509-和-der-编码&quot;&gt;PEM，ASN.1，X.509 和 DER 编码&lt;/h3&gt;

&lt;p&gt;上面的 PEM 格式的密钥可以用任意的文本编辑器打开，它就是一个简单的纯 ASCII 字符的文件。由于容易读写复制，所以在交换密钥时这种格式非常流行。每个 PEM 密钥都由 “—–BEGIN #{label})—–” 标签开头，以 “—–END #{label}—–” 标签结尾。注意，PEM 并非专门为了传递 Key 而生，BEGIN 和 END 之后的 label 并不一定就是例子中的 “PUBLIC KEY”，它只是一个让人能读懂的描述，来表示通过这个 PEM 传递的数据到底是什么。&lt;/p&gt;

&lt;p&gt;在两个标签之间，就是密钥本身。PEM 中的换行字符需要被忽略掉，可以很清楚地看到，这其实就是一个 &lt;strong&gt;Base64 编码&lt;/strong&gt;的字符串。用上面的 ECDSA 密钥为例，将这个 Base64 还原为字节数据的话，结果是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 59 30 13 06 07 2a 86 48 ce 3d 02 01 06 08 2a 86 48 ce 3d 03 01 07 
03 42 00 04 11 5b 3f a3 9f ae 41 b4 e3 2f 77 21 ca 72 f8 c1 78 14 83 
64 7d ab d5 14 f0 8e 66 12 8b d4 7f ce 90 67 b9 0e 04 88 c9 c2 a9 f3 
0f 5a 26 6a 07 84 1d 6c 07 74 13 ba 07 e7 45 69 b9 9d 4f d3 ce c6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很多同学到这里就退缩了，觉得这种二进制没有实际意义。但其实这一串字节是通过 &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One&quot;&gt;ASN.1 (Abstract Syntax Notation One) 定义&lt;/a&gt;的数据。ASN.1 定义了一些表示信息的标准句法，用来对字符串，整数等等进行无歧义和精确地传输。ASN.1 里有很多具体的编码规则，来具体将一些数据按照 ASN.1 的方式进行编码，进行具体表达。在网络传输和密码学中，最简单和最常见的编码方式是 &lt;a href=&quot;https://en.wikipedia.org/wiki/X.690#DER_encoding&quot;&gt;DER (Distinguished Encoding Rules)&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;ASN.1 格式对应的标准是 X.680，DER 被定义在 X.690 中。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;有了编码方式以后，为了能表达密钥，我们还需要定义一些元信息，比如一个密钥应该需要声明自己的身份 (在 ASN.1 中称为 “OBJECT IDENTIFIER”)，是一个什么种类的密钥，采用的是什么样的曲线或者 padding，&lt;strong&gt;X.509 标准&lt;/strong&gt;就是做这件事的。最后，对于 ECDSA 来说，还有一个 &lt;strong&gt;X9.62&lt;/strong&gt; 的标准。它是 X.509 中规定的用来编码 ECDSA 密钥的方式。&lt;/p&gt;

&lt;p&gt;光这样说会很抽象，具体来讲，可以简单对这几个概念和各自的作用进行总结：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ASN.1 - 一种数据或者信息表达时使用的句法，比如 “接下来是一串连续内容 (SEQUENCE)，长度是…”；“现在开始一个整数”；“从这里开始是位串 (BITSTRING)” 等这样句法信息。&lt;/li&gt;
  &lt;li&gt;DER - 是 ASN.1 的一种具体编码方式，比如使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x30&lt;/code&gt; 表示 SEQUENCE 的开始，然后下一个/若干个字节表示这段内容的长度；使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x02&lt;/code&gt; 表示现在开始是一个整数；使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x03&lt;/code&gt; 表示 BIT STRING 开始等。&lt;/li&gt;
  &lt;li&gt;X.509 - 在网络证书和公钥传输时，所应该遵守的 ASN.1 形式。它定义了一个特定证书或者公钥应该由哪些部分构成，比如“一开始应该有一个 SEQUENCE，然后紧接着是两个整数来代表密钥值”等。这些构成的部分由 ASN.1 格式表达，一般由 DER 编码。&lt;/li&gt;
  &lt;li&gt;X9.62 - 针对 ECDSA 相关算法的定义。X.509 是一个一般性的密钥编码规定，在 X.509 中指定了 ECDSA 的密钥和签名需要遵守 X9.62。(类似相应地，它也规定了 RSA 的密钥和签名要遵守 PKCS (Public Key Cryptography Standards))。&lt;/li&gt;
  &lt;li&gt;PEM - 将证书或者密钥用 DER 编码后，可以得到一组字节数据。把这些数据转换为 Base64 编码的字符串，然后在前后加上 BEGIN 和 END 标签，就得到 PEM 的表现形式。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;标准有点多？没错，这个世界上有很多标准化制定的组织，在这篇文章中，标准来源也不尽相同。从名字基本可以简单分类：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;RFC (Request For Comments) 是 IETF 这个专门推动互联网标准的组织所发布的&lt;/li&gt;
    &lt;li&gt;“X.” 开头的是 ITU-T 相关的标准，比如 ASN.1 (X.680) 是 ISO 和 ITU-T 的联合标准，X.509 是基于 ASN.1 的补充和扩展。&lt;/li&gt;
    &lt;li&gt;“X9.” 开头的，比如 X9.62，是 ANSI (美国国家标准学会) 的产品&lt;/li&gt;
    &lt;li&gt;PKCS 是 RSA Security 所制定的标准&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;我们会看到，在一些 RFC 标准中，会引用和规定需要使用 ANSI 的标准；而本来属于 RSA Security 的一些标准，也出现在了 RFC 中。另外，IETF 也会收录某些其他标准化组织的内容，比如  RFC 3280 其实就是 X.509。和专利市场的相互授权类似，各个标准组织之间也有竞争合作。不过这是另外一个关于爱恨情仇的故事了，其中八卦，我们有机会以后再说。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;der-编码规则&quot;&gt;DER 编码规则&lt;/h3&gt;

&lt;p&gt;DER 编码的通用规则是，在一个代表类型的字节后面，一般都会接上这个类型的数据所占用的字节长度，然后是实际的数据。&lt;/p&gt;

&lt;h4 id=&quot;整数&quot;&gt;整数&lt;/h4&gt;

&lt;p&gt;举例说明，在 DER 中，使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x02&lt;/code&gt; 代表整数，所以如果我们想要编码十进制的 100 这个整数时，会得到：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00000010 00000001 01100100
  0x02     0x01     0x64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x02&lt;/code&gt; 代表之后是一个整数，这个整数占用的字节长度为 1 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x01&lt;/code&gt;)，值为 100 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x64&lt;/code&gt;)。&lt;/p&gt;

&lt;p&gt;我们在&lt;a href=&quot;/2018/12/jose-1/&quot;&gt;系列的上一篇文章&lt;/a&gt;中提到过，如果数值的首个字节超过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x80&lt;/code&gt; 的话，就说明第一个 bit 是 1，在有符号域上这代表一个负数。这时候如果我们想要编码的是一个正数的话，就需要在前面添加一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 的字节。比如我们如果想要编码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xCE 29 10&lt;/code&gt; 这个整数的话，就需要添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt;，因为首位的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xCE&lt;/code&gt; 在二进制下为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0b_1100_1110&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0x02 0x04 0x00 0xCE 0x29 0x10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;序列&quot;&gt;序列&lt;/h4&gt;

&lt;p&gt;将两个整数前后排列，就可以形成一个序列 (SEQUENCE)，序列的类型编码为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x30&lt;/code&gt;，类似地，在类型编码后面也是字节长度值。比如两个整数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x64&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xCE2910&lt;/code&gt; 编码成一个序列，得到的结果是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 09 02 01 64 02 04 00 CE 29 10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为了看上去舒适一些，可以整理一下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 09 -&amp;gt; SEQUENCE 9 bytes
   02 01 -&amp;gt; Int 1 byte
      64          -&amp;gt; Value 0x64
   02 04 -&amp;gt; Int 4 byte
      00 CE 29 10 -&amp;gt; Value 0xCE2910
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;其他类型及实例&quot;&gt;其他类型及实例&lt;/h4&gt;

&lt;p&gt;列举几个我们在本文中用到的 DER 的类型编码：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;编码&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;INTEGER&lt;/td&gt;
      &lt;td&gt;0x02&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SEQUENCE&lt;/td&gt;
      &lt;td&gt;0x30&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BIT STRING&lt;/td&gt;
      &lt;td&gt;0x03&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OBJECT IDENTIFIER&lt;/td&gt;
      &lt;td&gt;0x06&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果你想对 DER 有更深入了解，最好的办法应该是看微软的的&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/distinguished-encoding-rules&quot;&gt;这个文档&lt;/a&gt;，相比于冰冷的标准定义，这里面用人类能懂的语言详细描述了 DER 编码方法。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;有了这些基础知识，我们可以来看看本文一开始例子中的 ECDSA 公钥的二进制里都是些什么内容了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 59 30 13 06 07 2a 86 48 ce 3d 02 01 06 08 2a 86 48 ce 3d 03 01 07 
03 42 00 04 11 5b 3f a3 9f ae 41 b4 e3 2f 77 21 ca 72 f8 c1 78 14 83 
64 7d ab d5 14 f0 8e 66 12 8b d4 7f ce 90 67 b9 0e 04 88 c9 c2 a9 f3 
0f 5a 26 6a 07 84 1d 6c 07 74 13 ba 07 e7 45 69 b9 9d 4f d3 ce c6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;整理一下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/ECDSA-pub-der.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;需要简单说明的有两点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30 13&lt;/code&gt; 这个 SEQUENCE 里，我们能找到两个 OBJECT IDENTIFIER 的定义。关于 OBJECT IDENTIFIER 的编解码规则，可以参考&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-object-identifier&quot;&gt;这里的说明&lt;/a&gt;。这部分不是重点，所以就简单只说结论然后跳过了。这两个值分别代表：&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.840.10045.2.1&lt;/code&gt; - (ecPublicKey)&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.840.10045.3.1.7&lt;/code&gt; - (P-256)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可以看到，它定义了这个公钥的类型，以及使用的曲线。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于解码后的 OBJECT IDENTIFIER 所代表的涵义，可以在&lt;a href=&quot;https://www.alvestrand.no/objectid/top.html&quot;&gt;这里&lt;/a&gt;进行查询。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
  &lt;li&gt;BIT STRING 定义的是一个一串 BIT 数据 (注意这里的 STRING 并不是字符串的意思)。在一个 bit 为单位的数据里，可能存在想要传输的数据 bit 数不是 8 的倍数的情况。但是在 DER 编码长度时，我们指定的是 byte 数。比如在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;03 42&lt;/code&gt; 这 BIT STRING 中，我们的 STRING 长度是 66 个字节 (528 bit)。但是如果我们想要传输的 bit 数只有 523 bit 时，最后一个 byte 中的后 5 bit 数据其实并不是我们想要的。这时候我们需要一种方式来指定应该“丢弃”掉最后若干 bit。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;03 42&lt;/code&gt; 之后的字节 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 负责指定数据末尾有多少 bit 不应该使用。当然，这里我们想要传输的数据 bit 数恰好是 8 的倍数，所以设为 0，表示所有 bit 我们都要使用。如果我们想要舍弃最后 5 bit 的话，这个 byte 就应该是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x05&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;DER 中还有一个类型叫做 OCTET STRING，它定义的是一个 8 bit (OCTET，或者说字节) 组成的字节串流。而 BIT STRING 传输的单位是一个 bit，要注意区分。(我们在本文中不会用到 OCTET STRING，它通常用来传输一些 ACSII 字符串等)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接下来按照 SEC 和 X9.62 的规定，整个 BIT STRING 就应该是 ECDSA 公钥的 x 和 y 的值了。这里第一位的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x04&lt;/code&gt; 表示这个密钥中的整数值是没有被压缩的。这个 byte 其他可选的值有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; (椭圆曲线取点在无穷)，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x02&lt;/code&gt; (压缩，even y)，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x03&lt;/code&gt; (压缩， odd y)。通常我们见到的 (以及 iOS 默认能接受的) 都是无压缩的整数值。之后 BIT STRING 还剩 64 位，它们分别就是 32 位的 x 和  32 位的 y 值了！&lt;/p&gt;

&lt;p&gt;如果你回到&lt;a href=&quot;/2018/12/jose-1/&quot;&gt;第一篇文章&lt;/a&gt;，我们曾经给过一个示例的 JWK：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;EC&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;alg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ES256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;use&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3829b108279b26bcfcc8971e348d116&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;crv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;P-256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你可以尝试一下将这里的 x 和 y 的值转换为字节：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x -&amp;gt;
11 5b 3f a3 9f ae 41 b4 e3 2f 77 21 ca 72 f8 c1 
78 14 83 64 7d ab d5 14 f0 8e 66 12 8b d4 7f ce

y -&amp;gt;
90 67 b9 0e 04 88 c9 c2 a9 f3 0f 5a 26 6a 07 84 
1d 6c 07 74 13 ba 07 e7 45 69 b9 9d 4f d3 ce c6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;是不是感觉有点眼熟？没错，它们就是上面 DER 编码的 BITSTRING 里 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x04&lt;/code&gt; 后面的部分。嗯…要不你也可以试试看自己分析一下本文一开始给出的那个 PEM 格式的 RSA 公钥？它和系列第一篇文中的 JWK 格式的 RSA 公钥也是等价的。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果你遇到困难，可以利用搜索引擎。你也可以在 &lt;a href=&quot;https://tools.ietf.org/html/rfc2315&quot;&gt;RFC 2315&lt;/a&gt; 或者 PKCS#7 中找到 RSA 公钥的编码方式。&lt;/p&gt;

  &lt;p&gt;另外，顺带一提，作为 iOS 开发者经常从 Keychain 导出证书时使用的 .p12 文件，其实就是遵守 PKCS#12，来将一个 X.509 证书和私钥打包到一起。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;非对称密钥的签名和验证&quot;&gt;非对称密钥的签名和验证&lt;/h2&gt;

&lt;p&gt;至此，我们完整了解了密钥，至少是 RSA 和 ECDSA 公钥。使用 RSA 进行数据签名和验证的流程如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/crypto-sign.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/crypto-verify.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意，上面的图是 RSA 算法的情况，对于 ECDSA 来说，流程稍有不同。ECDSA 在验证时&lt;strong&gt;并不是&lt;/strong&gt;把收到的 Signature 还原成摘要数据，然后进行对比。ECDSA 的 Signature 是由两个大数组成 (通常写作 {r, s})，通过 Signature 中的整数 s，以及收到的原始数据所计算出的摘要，可以计算出另一个整数 v。如 r 和 v 相等的话，就认为验证通过，否则失败。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;RSA 签名时，实际上是对摘要进行稳定的变换运算 (当然通常你也可以把它叫做加密)，所以对于给定的数据，散列算法和加密私钥，得到的签名是一致的。但是对于 ECDSA 来说，签名时需要一个随机选择的 k 值，因此每次进行 ECDSA 所得到的签名内容是不同的。不太了解这一点的话，可能会在看到每次签名变化时感到疑惑。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;RSA 或者 ECDSA 具体的算法原理在这里就不展开了，都是一些基本的数学运算。几乎所有的平台对这些算法都会有现成的实现，如果没有特殊必要，一般不会需要自己去进行实现。比如在 iOS 平台上，Security.framework 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyCreateSignature&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKeyVerifySignature&lt;/code&gt;  就为我们实现了签名和验证的相关接口，我们只需要传入合适的参数即可。&lt;/p&gt;

&lt;h2 id=&quot;openid-connect-discovery&quot;&gt;OpenID Connect Discovery&lt;/h2&gt;

&lt;p&gt;作为这部分的结尾，让我们看一点轻松的话题吧。在理论的海洋里“畅游”了一番以后，应该回顾一下我们最初的目的，就是这张流程图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/jose-flow.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一步，从 Auth Server 获取 JWT，并完成解析，是很简单的事情。最后一步，将 JWK 转换成 Security 框架中的密钥，并且对 JWT 的数据进行验证，我们在详细了解了密钥和签名/验证的知识以后，大概也能解决。剩下的问题是中间的框图：我们应该去哪儿寻找 JWT Header 中定义的公钥？&lt;/p&gt;

&lt;p&gt;把 JWK Host 的 URL 写死在客户端当然是最简单省事儿的方法，但是其实有业界更通用一些的做法，那就是用 &lt;a href=&quot;https://developers.google.com/identity/protocols/OpenIDConnect#discovery&quot;&gt;Discovery Document&lt;/a&gt;。最初的起源是 &lt;a href=&quot;https://openid.net/connect/&quot;&gt;OpenID Connect&lt;/a&gt;，或者说 OAuth2 中需要一系列 API (发起验证请求，交换 token 等，都是不同的 API entry)。这一套内容都可以通过配置来改变，相比于把每个 API 都写死在客户端，我们可能更愿意选择只写死一个入口，而 Discovery Document 就是这个入口。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;有时候有人会把 OpenID Connect 和 OAuth2 弄混淆，它们其实是不一样的东西：OpenID Connect 负责的是“验证”，也就是负责“你真的是你吗”的问题；而 OAuth2 负责“授权”，也就是“我可以访问你的数据吗”的问题。不过两者有时候会被一起执行，所以不需要分得那么清楚。比如 Google 的 OAuth 2.0 API 也负责了验证的工作。(LINE 的 Login API 亦是如此，&lt;a href=&quot;https://github.com/line/line-sdk-ios-swift&quot;&gt;LINE SDK&lt;/a&gt; 在授权时同时也完成了验证。)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;你可以找到一些 Discovery Document 的例子，比如 &lt;a href=&quot;https://accounts.google.com/.well-known/openid-configuration&quot;&gt;Google 的&lt;/a&gt;，或者 &lt;a href=&quot;https://access.line.me/.well-known/openid-configuration&quot;&gt;LINE 的&lt;/a&gt;。在里面可以找到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jwks_uri&lt;/code&gt; 这个 key，它就是我们的 JWK Host 的位置，这个位置会放置了若干个 JWK (它们合在一次称为 &lt;a href=&quot;https://auth0.com/docs/jwks&quot;&gt;JWK Set&lt;/a&gt;)。里面应该包括之前在 JWT Header 中所指定的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kid&lt;/code&gt; 的密钥。&lt;/p&gt;

&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;

&lt;p&gt;本文主要介绍了如何处理和编码的密钥，以及进行数字签名和验证的基本流程。有一部分内容虽然在处理 JOSE 时用不到 (比如 PEM 格式)，但是作为密码学和网络交换中最常用的知识，了解后相信会在未来的某一天派上用场。&lt;/p&gt;

&lt;p&gt;我们在之后&lt;a href=&quot;/2018/12/jose-3/&quot;&gt;实践一篇&lt;/a&gt;里，会先解析收到的 JWT。然后依靠本篇文章的这些知识，将 JWK 转换为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKey&lt;/code&gt;，并使用 Security.framework 提供的 API 和算法，来完成 JWT 的验证工作。同时，也会讨论一些工程中的经验和选择。&lt;/p&gt;
</description>
        <pubDate>Wed, 05 Dec 2018 00:38:00 +0000</pubDate>
        <link>https://u3dc.github.io//2018/12/jose-2/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/12/jose-2/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>与 JOSE 战斗的日子 - 写给 iOS 开发者的密码学入门手册 (基础)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/2018/matrix.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;概述&quot;&gt;概述&lt;/h2&gt;

&lt;p&gt;事情的缘由很简单，工作上在做 &lt;a href=&quot;https://github.com/line/line-sdk-ios-swift&quot;&gt;LINE SDK&lt;/a&gt; 的开发，在拿 token 的时候有一步额外的验证：从 Server 会发回一个 JWT (JSON Web Token)，客户端需要对这个 JWT 进行签名和内容的验证，以确保信息没有被人篡改。Server 在签名中使用的算法类型会在 JWT 中写明，验证签名所需要的公钥 ID 也可以在 JWT 中找到。这个公钥是以 JWK (JSON Web Key) 的形式公开，客户端拿到 JWK 后即可在本地对收到的 JWT 进行验证。用一张图的话，大概是这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/jose-flow.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;步骤&quot;&gt;步骤&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果你现在对下面说步骤不理解的话 (这挺正常的，毕竟这篇文章都还没正式开始 😂)，可以先跳过这部分，等我们有一些基础知识以后再回头看看就好。如果你很清楚这些步骤的话，那真是好棒棒，你应该能无压力阅读该系列剩余部分内容了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LINE SDK 里使用 JWT 验证用户的逻辑如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;向登录服务器请求 access token，登录服务器返回 access token，同时返回一个 JWT。&lt;/li&gt;
  &lt;li&gt;JWT 中包含应该使用的算法和密钥的 ID。通过密钥 ID，去找预先定义好的 Host 拿到 JWK 形式的该 ID 的密钥。&lt;/li&gt;
  &lt;li&gt;将 1 的 JWT 和 2 的密钥转换为 Security.framework 接受的形式，进行签名验证。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个过程想法很简单，但会涉及到一系列比较基础的密码学知识和标准的阅读，难度不大，但是枯燥乏味。另外，由于 iOS 并没有直接将 JWK 转换为 native 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecKey&lt;/code&gt; 的方式，自己也没有任何密码学的基础，所以在处理密钥转换上也花了一些工夫。为了后来者能比较顺利地处理相关内容 (包括 JWT 解析验证，JWK 特别是 RSA 和 EC 算法的密钥转换等)，也为了过一段时间自己还能有地方回忆这些内容，所以将一些关键的理论知识和步骤记录下来。&lt;/p&gt;

&lt;h3 id=&quot;系列文章的内容&quot;&gt;系列文章的内容&lt;/h3&gt;

&lt;p&gt;整个系列会比较长，为了阅读压力小一些，我会分成三个部分：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;基础 - 什么是 JWT 以及 JOSE (本文)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/12/jose-2/&quot;&gt;理论 - JOSE 中的签名和验证流程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/12/jose-3/&quot;&gt;实践 - 如何使用 Security.framework 处理 JOSE 中的验证&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;全部读完的话应该能对网络相关的密码学有一个肤浅的了解，特别是常见的签名算法和密钥种类，编码规则，怎么处理拿到的密钥，怎么做签名验证等等。如果你在工作中有相关需求，但不知道如何下手的话，可以仔细阅读整个系列，并参看开源的 &lt;a href=&quot;https://github.com/line/line-sdk-ios-swift&quot;&gt;LINE SDK Swift&lt;/a&gt; 的相关实现，甚至直接 copy 部分代码 (如果可以的话，也请顺便点一下 star)。如果你只是感兴趣想要简单了解的话，可以只看 JOSE 和 JWT 的基础概念和理论流程部分的内容，作为知识面的扩展，等以后有实际需要了再回头看实践部分的内容。&lt;/p&gt;

&lt;p&gt;在文章结尾，我还列举了一些常见的问题，包括笔者自己在学习时的思考和最后的选择。如果您有什么见解，也欢迎发表在评论里，我会继续总结和补充。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;声明：笔者自身对密码学也是初学，而本文介绍的密码学知识也都是自己的一些理解，同时尽量不涉及过于原理性的内容，一切以普通工程师实用为目标原则。其中可以想象在很多地方会有理解的错误，还请多包涵。如您发现问题，也往不吝赐教指正，感激不尽。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;jwt-以及-jose&quot;&gt;JWT 以及 JOSE&lt;/h2&gt;

&lt;h3 id=&quot;什么是-jwt&quot;&gt;什么是 JWT&lt;/h3&gt;

&lt;p&gt;估计大部分 Swift 的开发者对 JWT 会比较陌生，所以先简单介绍一下它是什么，以及可以用来做什么。JWT (JSON Web Token) 是一个编码后的字符串，比如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一个典型的 JWT 由三部分组成，通过点号 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; 进行分割。每个部分都是经过 &lt;strong&gt;Base64Url&lt;/strong&gt; 编码的字符串。第一部分 (Header) 和第二部分 (Payload) 在解码后应该是有效的 JSON，最后一部分 (签名) 是通过一定算法作用在前两部分上所得到的签名数据。接收方可以通过这个签名数据来验证 token 的 Header 及 Payload 部分的数据是否可信。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;为了视觉上看起来轻松一些，在上面的 JWT 例子中每个点号后加入了换行。实际的 JWT 中不应该存在任何换行的情况。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;严格来说，JWT 有两种实现，分别是 JWS (JSON Web Signature) 和 JWE (JSON Web Encryption)。由于 JWS 的应用更为广泛，所以一般说起 JWT 大家默认会认为是 JWS。JWS 的 Payload 是 Base64Url 的明文，而 JWE 的数据则是经过加密的。相对地，相比于 JWS 的三个部分，JWE 有五个部分组成。本文中提到 JWT 的时候，所指的都是用于签名认证的 JWS 实现。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 Base64Url 编码和处理，在本文后面部分会再提到。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;header&quot;&gt;Header&lt;/h4&gt;

&lt;p&gt;Header 包含了 JWT 的一些元信息。我们可以尝试将上面的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&lt;/code&gt; 这个 Header 解码，得到：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;alg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HS256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;typ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;JWT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于在数据的不同格式之间互相转换 (明文，Base64，Hex Bytes 等)，我推荐&lt;a href=&quot;https://cryptii.com/pipes/base64-to-hex&quot;&gt;这个&lt;/a&gt;非常不错的 web app。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 JWT Header 中，”alg” 是必须指定的值，它表示这个 JWT 的签名方式。上例中 JWT 使用的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HS256&lt;/code&gt; 进行签名，也就是使用 SHA-256 作为摘要算法的 HMAC。常见的选择还有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RS256&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ES256&lt;/code&gt; 等等。总结一下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HSXXX&lt;/code&gt; 或者说 &lt;a href=&quot;https://en.wikipedia.org/wiki/HMAC&quot;&gt;HMAC&lt;/a&gt;：一种对称算法 (symmetric algorithm)，也就是加密密钥和解密密钥是同一个。类似于我们创建 zip 文件时设定的密码，验证方需要知道和签名方同样的密钥，才能得到正确的验证结果。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSXXX&lt;/code&gt;：使用 &lt;a href=&quot;https://en.wikipedia.org/wiki/RSA_(cryptosystem)&quot;&gt;RSA&lt;/a&gt; 进行签名。RSA 是一种基于极大整数做因数分解的非对称算法 (asymmetric algorithm)。相比于对称算法的 HMAC 只有一对密钥，RSA 使用成对的公钥 (public key) 和私钥 (private key) 来进行签名和验证。大多数 HTTPS 中验证证书和加密传输数据使用的是 RSA 算法。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ESXXX&lt;/code&gt;：使用 &lt;a href=&quot;https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm&quot;&gt;椭圆曲线数字签名算法 (ECDSA)&lt;/a&gt; 进行签名。和 RSA 类似，它也是一种非对称算法。不过它是基于椭圆曲线的。ECDSA 最著名的使用场景是比特币的数字签名。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PSXXX&lt;/code&gt;: 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSXXX&lt;/code&gt; 类似使用 RSA 算法，但是使用 PSS 作为 padding 进行签名。作为对比，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSXXX&lt;/code&gt; 中使用的是 PKCS1-v1_5 的 padding。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果你对这些介绍一头雾水，也不必担心。关于各个算法的一些更细节的内容，会在后面实践部分再详细说明。现在，你只需要知道 Header 中 “alg” key 为我们指明了签名所使用的签名算法和散列算法。我们之后需要依据这里的指示来验证签名。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;除了 “alg” 外，在 Header 中发行方还可以放入其他有帮助的内容。JWS 的标准定义了一些&lt;a href=&quot;https://tools.ietf.org/html/rfc7515#section-4&quot;&gt;预留的 Header key&lt;/a&gt;。在本文中，除了 “alg” 以外，我们还会用到 “kid”，它用来表示在验证时所需要的，从 JWK Host 中获取的公钥的 key ID。现在我们先集中于 JWT 的构造，之后在 JWK 的部分我们再对它的使用进行介绍。&lt;/p&gt;

&lt;h4 id=&quot;payload&quot;&gt;Payload&lt;/h4&gt;

&lt;p&gt;Payload 是想要进行交换的实际有意义的数据部分。上面例子解码后的 Payload 部分是：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sub&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1234567890&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1516239022&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;和 Header 类似，payload 中也有一些&lt;a href=&quot;https://tools.ietf.org/html/rfc7519#section-4&quot;&gt;预先定义和保留的 key&lt;/a&gt;，我们称它们为 claim。常见的预定义的 key 包括有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“iss” (Issuer)：JWT 的签发者名字，一般是公司名或者项目名&lt;/li&gt;
  &lt;li&gt;“sub” (Subject)：JWT 的主题&lt;/li&gt;
  &lt;li&gt;“exp” (Expiration Time)：过期时间，在这个时间之后应当视为无效&lt;/li&gt;
  &lt;li&gt;“iat” (Issued At)：发行时间，在这个时间之前应当视为无效&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然，你还可以在 Payload 里添加任何你想要传递的信息。&lt;/p&gt;

&lt;p&gt;我们在验证签名后，就可以检查 Payload 里的各个条目是否有效：比如发行者名字是否正确，这个 JWT 是否在有效期内等等。因为一旦签名检查通过，我们就可以保证 Payload 的东西是可靠的，所以这很适合用来进行消息验证。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;注意，在 JWS 里，Header 和 Payload 是 Base64Url 编码的&lt;strong&gt;明文&lt;/strong&gt;，所以你不应该用 JWS 来传输任何敏感信息。如果你需要加密，应该选择 JWE。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;signature&quot;&gt;Signature&lt;/h4&gt;

&lt;p&gt;一个 JWT 的最后一部分是签名。首先对 Header 和 Payload 的原文进行 Base64Url 编码，然后用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; 将它们连接起来，最后扔给签名散列算法进行签名，把签名得到的数据再 Base64Url 编码，就能得到这个签名了。写成伪代码的话，是这样的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 比如使用 RS256 签名：
let 签名数据: Data = RS256签名算法(Base64Url(string: Header).Base64Url(string: Payload), 私钥)
let 签名: String = Base64Url(data: 签名数据)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后，把编码后的 Header，Payload 和 Signature 都用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; 连在一起，就是我们收发的 JWT 了。&lt;/p&gt;

&lt;h3 id=&quot;什么是-jose&quot;&gt;什么是 JOSE&lt;/h3&gt;

&lt;p&gt;JWT 其实是 JOSE 这个更大的概念中的一个组成部分。JOSE (Javascript Object Signing and Encryption) 定义了一系列标准，用来规范在网络传输中使用 JSON 的方式。我们在上面介绍过了JWS 和 JWE，在这一系列概念中还有两个比较重要，而且相互关联的概念：JWK 和 JWA。它们一起组成了整个 JOSE 体系。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/jose.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;jwk&quot;&gt;JWK&lt;/h4&gt;

&lt;p&gt;不管签名验证还是加密解密，都离不开密钥。JWK (JSON Web Key) 解决的是如何使用 JSON 来表示一个密钥这件事。&lt;/p&gt;

&lt;p&gt;RSA 的公钥由模数 (modulus) 和指数 (exponent) 组成，一个典型的代表 RSA 公钥的 JWK 如下：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;alg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RS256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ryQICCl6NZ5gDKrnSztO3Hy8PEUcuyvg_ikC-VcIo2SFFSf18a3IMYldIugqqqZCs4_4uVW3sbdLs_6PfgdX7O9D22ZiFWHPYA2k2N744MNiCD1UE-tJyllUhSblK48bn-v1oZHCM0nYQ2NqUkvSj-hwUU3RiWl7x3D2s9wSdNt7XUtW05a_FXehsPSiJfKvHJJnGOX0BgTvkLnkAOTdOrUZ_wK69Dzu4IvrN4vs9Nes8vbwPa_ddZEzGR0cQMt0JBkhk9kU_qwqUseP1QRJ5I1jR4g8aYPL_ke9K35PxZWuDp3U0UPAZ3PjFAh-5T-fc7gzCs9dPzSHloruU-glFQ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;use&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;b863b534069bfc0207197bcf831320d1cdc2cee2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;e&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AQAB&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RSA&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;模数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; 和指数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt; 构成了密钥最关键的数据部分，这两部分都是 Base64Url 编码的大数字。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 RSA 的原理，不在本文范围内，你可以在其他很多地方找到相关信息。&lt;/p&gt;

  &lt;p&gt;如果你接触过几个 RSA 密钥，可能会发现 “e” 的值基本都是 “AQAB”。这并不是巧合，这是数字 65537 (0x 01 00 01) 的 Base64Url 表示。选择 AQAB 作为指数已经是业界标准，它同时兼顾了运算效率和安全性能。同样，这部分内容也超出了本文范畴。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;类似地，一个典型的 ECDSA 的 JWK 内容如下：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;EC&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;alg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ES256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;use&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3829b108279b26bcfcc8971e348d116&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;crv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;P-256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AJBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;决定一个 ECDSA 公钥的参数有三个：  “crv” 定义使用的密钥所使用的加密曲线，一般可能值为 “P-256”，”P-384” 和 “P-521”。”x” 和 “y” 是选取的椭圆曲线点的座标值，根据曲线 “crv” 的不同，这个值的长度也会有区别；另外，推荐使用的散列算法也会随着 “crv” 的变化有所不同：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;crv&lt;/th&gt;
      &lt;th&gt;x/y 的字节长度&lt;/th&gt;
      &lt;th&gt;散列算法&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;P-256&lt;/td&gt;
      &lt;td&gt;32&lt;/td&gt;
      &lt;td&gt;SHA-256&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P-384&lt;/td&gt;
      &lt;td&gt;48&lt;/td&gt;
      &lt;td&gt;SHA-384&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P-521&lt;/td&gt;
      &lt;td&gt;66&lt;/td&gt;
      &lt;td&gt;SHA-512&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;注意  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P-521&lt;/code&gt; 对应的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SHA-512&lt;/code&gt;，不是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SHA-521&lt;/code&gt; (不存在 521 位的散列算法 😂)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;同样，使用的曲线也决定了签名的长度。在使用 ECDSA 对数据签名时，通过椭圆曲线计算得到 r 和 s 两个值。这两个值的字节长度也应该符合上表。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;细心的同学可能会发现上面的 ECDSA 密钥中 “y” 的值转换为 hex 表示后是 33 个字节：&lt;/p&gt;

  &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00 90 67 b9 0e 04 88 c9 c2 a9 f3 0f 5a 26 6a 07 84 
1d 6c 07 74 13 ba 07 e7 45 69 b9 9d 4f d3 ce c6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;我们知道，在密钥中 “x” 和 “y” 都是大的整数，但是在某些安全框架的实现 (比如一些版本的 OpenSSL) 中，使用的会是普通的整数类型 (Int)，而非无符号整数 (UInt)。而如果一个数字首 bit 为 1 的话，在有符号的整数系统中会被认为是负数。在这里，”y” 原本第一个 byte 其实是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x90&lt;/code&gt; (bit 表示是 0b_1001_0000)，首 bit 为 1，为了避免被误认为负数，有的实现会在前面添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt;。但是实际上把这样一个 33 byte 的值作为 “y” 放在 JWK 中，是不符合标准的。如果你遇到了这种情况，可以和负责服务器的小伙伴商量一下让他先处理一下，给你正确的 key。当然，你也可以自己在客户端检查和处理长度不符合预期的问题，以增强本地代码的健壮性。&lt;/p&gt;

  &lt;p&gt;在这个例子中，如果服务器在生成 JWK 时就帮我们处理了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 的问题的话，那么 “y” 的值应该是&lt;/p&gt;

  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY&lt;/code&gt;&lt;/p&gt;

  &lt;p&gt;我们还会在后面看到更多的处理 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; 添加或删除的情况，对于首字节是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x80&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0b_1000_0000&lt;/code&gt;) 或者以上的值，我们可能都需要考虑具体实现是接受 Int 还是 UInt 的问题。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;jwa&quot;&gt;JWA&lt;/h4&gt;

&lt;p&gt;JWA (JSON Web Algorithms) 定义的就是在 JWT 和 JWK 中涉及的算法了，它为每种算法定义了具体可能存在哪些参数，和参数的表示规则。比如上面 JWK 例子中的 “n”，”e”，”x”，”y”，”crv” 都是在 JWA 标准中定义的。它为如何使用 JWK，如何验证 JWT 提供支持和指导。&lt;/p&gt;

&lt;p&gt;除了 RSA 和 ECDSA 以外，JWA 里还定义了 AES 相关的加密算法，不过这部分内容和 JWS 没什么关系。另外，在签名算法定义的后面，也附带了如果使用签名和如何进行验证的简单说明。我们在之后会对 JOSE 中的签名和验证过程进行更详细的解释。&lt;/p&gt;

&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;

&lt;p&gt;本文简述了 JWT 和 JOSE 的相关基础概念。您现在对 JWT 是什么，JOSE 有哪些组成部分，以及它们大概长什么样有一定了解。&lt;/p&gt;

&lt;p&gt;你可以访问 &lt;a href=&quot;https://jwt.io&quot;&gt;JWT.io&lt;/a&gt; 来实际试试看创建和验证一个 JWT 的过程。如果你想要更深入了解 JWT 的内容和定义的话，JWT.io 还提供了免费的 JWT Handbook，里面有更详细的介绍。我们在系列文章的最后还会对 JWT 的应用场景，适用范围和存在的风险进行补充说明。&lt;/p&gt;

&lt;p&gt;系列文章后面两篇，会分别针对 &lt;a href=&quot;/2018/12/jose-2/&quot;&gt;JOSE 中的签名和验证过程&lt;/a&gt;)以及作为 iOS 开发者如何&lt;a href=&quot;/2018/11/jose-3/&quot;&gt;使用 Security.frame 来处理 JOSE 相关的概念实践&lt;/a&gt;进行更详细的说明。&lt;/p&gt;
</description>
        <pubDate>Mon, 03 Dec 2018 01:38:00 +0000</pubDate>
        <link>https://u3dc.github.io//2018/12/jose-1/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/12/jose-1/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>关于 Swift defer 的正确使用</title>
        <description>&lt;p&gt;其实这篇文章的缘起是由于在对 &lt;a href=&quot;https://github.com/onevcat/Kingfisher/&quot;&gt;Kingfisher&lt;/a&gt; 做重构的时候，因为自己对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的理解不够准确，导致了一个 bug。所以想藉由这篇文章探索一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 这个关键字的一些 edge case。&lt;/p&gt;

&lt;h3 id=&quot;典型用法&quot;&gt;典型用法&lt;/h3&gt;

&lt;p&gt;Swift 里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 大家应该都很熟悉了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 所声明的 block 会在当前代码执行退出后被调用。正因为它提供了一种延时调用的方式，所以一般会被用来做资源释放或者销毁，这在某个函数有多个返回出口的时候特别有用。比如下面的通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileHandle&lt;/code&gt; 打开文件进行操作的方法：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;operateOnFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fileHandle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FileHandle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fileDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readDataToEndOfFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* onlyRead */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;closeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shouldWrite&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* 是否需要写文件 */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shouldWrite&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;closeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;seekToEndOfFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;someData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;closeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们在不同的地方都需要调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fileHandle.closeFile()&lt;/code&gt; 来关闭文件，这里更好的做法是用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 来统一处理。这不仅可以让我们就近在资源申请的地方就声明释放，也减少了未来添加代码时忘记释放资源的可能性：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;operateOnFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fileHandle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FileHandle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fileDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;closeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readDataToEndOfFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* onlyRead */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shouldWrite&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* 是否需要写文件 */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shouldWrite&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;seekToEndOfFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;someData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;defer-的作用域&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的作用域&lt;/h3&gt;

&lt;p&gt;在做 Kingfisher 重构时，对线程安全的保证我选择使用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSLock&lt;/code&gt; 来完成。简单说，会有一些类似这样的方法：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[:]&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tasks&lt;/code&gt; 的操作可能发生在不同线程中，用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock()&lt;/code&gt; 来获取锁，并保证当前线程独占，然后在操作完成后使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unlock()&lt;/code&gt; 释放资源。这是很典型的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的使用方式。&lt;/p&gt;

&lt;p&gt;但是后来出现了一种情况，即调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 方法之前，我们在同一线程的 caller 中获取过这个锁了，比如：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doSomethingThenRemove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// 操作 `tasks`&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// 最后，移除 `task`&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样做显然在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 中造成了死锁 (deadlock)：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock()&lt;/code&gt; 在等待 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doSomethingThenRemove&lt;/code&gt; 中做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unlock()&lt;/code&gt; 操作，而这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unlock&lt;/code&gt; 被 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 阻塞了，永远不可能达到。&lt;/p&gt;

&lt;p&gt;解决的方法大概有三种：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;换用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRecursiveLock&lt;/code&gt;：&lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsrecursivelock&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRecursiveLock&lt;/code&gt;&lt;/a&gt; 可以在同一个线程获取多次，而不造成死锁的问题。&lt;/li&gt;
  &lt;li&gt;在调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 之前先 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unlock&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 传入按照条件，避免在其中加锁。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;1 和 2 都会造成额外的性能损失，虽然在一般情况下这样的加锁性能微乎其微，但是使用方案 3 似乎也并不很麻烦。于是我很开心地把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; 改成了这样：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;acquireLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acquireLock&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很好，现在调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove(123, acquireLock: false)&lt;/code&gt; 不再会死锁了。但是很快我发现，在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acquireLock&lt;/code&gt; 为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; 的时候锁也失效了。再仔细阅读 Swift Programming Language 关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的描述：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; statement is used for executing code just before transferring program control outside of &lt;strong&gt;the scope that the defer statement appears in&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以，上面的代码其实相当于：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;acquireLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acquireLock&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;GG 斯密达…&lt;/p&gt;

&lt;p&gt;以前很单纯地认为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 是在函数退出的时候调用，并没有注意其实是&lt;strong&gt;当前 scope 退出的时候&lt;/strong&gt;调用这个事实，造成了这个错误。在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;guard&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try&lt;/code&gt; 这些语句中使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 时，应该要特别注意这一点。&lt;/p&gt;

&lt;h3 id=&quot;defer-和闭包&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 和闭包&lt;/h3&gt;

&lt;p&gt;另一个比较有意思的事实是，虽然 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 后面跟了一个闭包，但是它更多地像是一个语法糖，和我们所熟知的闭包特性不一样，并不会持有里面的值。比如：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Statement 2: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Statement 1: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将会输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Statement 1: 100
Statement 2: 100
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 中如果要依赖某个变量值时，需要自行进行复制：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;closureNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Statement 2: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;closureNumber&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Statement 1: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Statement 1: 100&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Statement 2: 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;defer-的执行时机&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的执行时机&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的执行时机紧接在离开作用域之后，但是是在其他语句之前。这个特性为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 带来了一些很“微妙”的使用方式。比如从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; 开始的自增：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// 没有 `defer` 的话我们可能要这么写&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// func foo() -&amp;gt; Int {&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//    num += 1&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//    return num - 1&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// }&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出结果 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo()&lt;/code&gt; 返回了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+1&lt;/code&gt; 之前的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num&lt;/code&gt;，而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f.num&lt;/code&gt; 则是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 中经过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+1&lt;/code&gt; 之后的结果。不使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的话，我们其实很难达到这种“在返回后进行操作”的效果。&lt;/p&gt;

&lt;p&gt;虽然很特殊，&lt;strong&gt;但是强烈不建议在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 中执行这类 side effect&lt;/strong&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This means that a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; statement can be used, for example, to perform manual resource management such as closing file descriptors, and to perform actions that need to happen even if an error is thrown.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;从语言设计上来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; 的目的就是进行资源清理和避免重复的返回前需要执行的代码，而不是用来以取巧地实现某些功能。这样做只会让代码可读性降低。&lt;/p&gt;

</description>
        <pubDate>Fri, 16 Nov 2018 01:38:00 +0000</pubDate>
        <link>https://u3dc.github.io//2018/11/defer/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/11/defer/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>Result&amp;lt;T&amp;gt; 还是 Result&amp;lt;T, E: Error&amp;gt;</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;我之前在&lt;a href=&quot;https://xiaozhuanlan.com/topic/4718350296&quot;&gt;专栏文章&lt;/a&gt;里曾经发布这篇文章，由于这个话题其实还是挺重要的，可以说代表了 Swift 今后发展的方向流派，所以即使和专栏文章内容有些重复，我还是想把它再贴到博客来。经过半年以后，自己对于这个问题也有了更多的实践和想法，所以同时也更新了一下。我没有直接改动原文，而是把新的想法和需要补充的说明，用类似这段话的引用的方式写在合适的上下文里。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;开始先打个广告&quot;&gt;开始先打个广告&lt;/h3&gt;

&lt;p&gt;我个人经常会在&lt;a href=&quot;https://www.lizhi.io&quot;&gt;数码荔枝&lt;/a&gt;用优惠价格购买面向中国用户的一些软件，相比于花美金直接购买，价格非常实惠。近年来国内的正版风气和对知识知识产权的尊重的进步，是有目共睹的，这很大程度上也要归功于像数码荔枝这样的分销商可以和开发商讨论出更适合国内的定价和销售策略。让我，或者让像我一样的开发者，能节省出一些奶粉钱和尿布钱…&lt;/p&gt;

&lt;p&gt;最近我和数码荔枝有一些接触，有一些长期合作的推广。如果大家对某款软件有兴趣，不妨先到数码荔枝的店面找找看，也许能为你省下不少银子。另外也可以访问我的&lt;a href=&quot;https://partner.lizhi.io/onevcat/cp&quot;&gt;推广页面领取通用的优惠券&lt;/a&gt;，然后再使用优惠券购买任意你中意的软件。优惠券也可以多次重复领取，任意使用。&lt;/p&gt;

&lt;p&gt;最后，他们定期也会推出一些力度很大的半价促销，比如 ¥94 就能买到 $79.99 的 PDF Expert 这种不可思议的事情..这个促销到年底为止，如果有在 macOS 上看 PDF 又不满足于系统预览的羸弱功能和性能的小伙伴们可&lt;a href=&quot;https://partner.lizhi.io/onevcat/pdf_expert_for_mac&quot;&gt;千万不要错过&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://partner.lizhi.io/onevcat/pdf_expert_for_mac&quot;&gt;&lt;img src=&quot;/assets/images/2018/pdf-expert.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;背景知识&quot;&gt;背景知识&lt;/h3&gt;

&lt;p&gt;Cocoa API 中有很多接受回调的异步方法，比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataTask(with:completionHandler:)&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有些情况下，回调方法接受的参数比较复杂，比如这里有三个参数：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Data?, URLResponse?, Error?)&lt;/code&gt;，它们都是可选值。当 session 请求成功时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt; 参数包含 response 中的数据，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt;；当发生错误时，则正好相反，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 指明具体的错误 (由于历史原因，它会是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt; 对象)，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt; 为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于这个事实，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataTask(with:completionHandler:)&lt;/code&gt; 的&lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsession/1407613-datatask&quot;&gt;文档的 Discussion 部分&lt;/a&gt;有十分详细的说明。另外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response: URLResponse?&lt;/code&gt; 相对复杂一些：不论是请求成功还是失败，只要从 server 收到了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response&lt;/code&gt;，它就会被包含在这个变量里。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这么做虽然看上去无害，但其实存在改善的余地。显然 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error&lt;/code&gt; 是互斥的：事实上是不可能存在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error&lt;/code&gt; 同时为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; 或者同时非 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; 的情况的，但是编译器却无法静态地确认这个事实。编译器没有制止我们在错误的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; 语句中对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; 值进行解包，而这种行为将导致运行时的意外崩溃。&lt;/p&gt;

&lt;p&gt;我们可以通过一个简单的封装来改进这个设计：如果你实际写过 Swift，可能已经对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 很熟悉了。它的思想非常简单，用泛型将可能的返回值包装起来，因为结果是成功或者失败二选一，所以我们可以藉此去除不必要的可选值。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;把它运用到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; 中的话，包装一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; 方法，上面调用可以变为：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 如果 Result 存在于标准库的话，&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 这部分代码应该由标准库的 Foundataion 扩展进行实现&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URLSession&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URLSessionDataTask&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;completionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;completionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dataTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;这里原文代码中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completionHandler&lt;/code&gt; 里 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Result&amp;lt;(Data, URLResponse), NSError&amp;gt;) -&amp;gt; Void)&lt;/code&gt; 这个类型是错误的。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt; 存在时 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLResponse&lt;/code&gt; 一定存在，但是我们上面讨论过，当 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt; 不为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; 时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLResponse&lt;/code&gt; 也可能存在。原文代码忽略了这个事实，将导致 error 状况时无法获取到可能的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLResponse&lt;/code&gt;。正确的类型应该是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Result&amp;lt;(Data), NSError&amp;gt;, URLResponse?) -&amp;gt; Void&lt;/code&gt;&lt;/p&gt;

  &lt;p&gt;当然，在回调中对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt; 的处理也需要对应进行修改。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;调用的时候看起来很棒，我们可以避免检查可选值的情况，让编译器保证在对应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; 分支中有确定的非可选值。这个设计在很多存在异步代码的框架中被广泛使用，比如 &lt;a href=&quot;https://github.com/apple/swift-package-manager/blob/master/Sources/Basic/Result.swift&quot;&gt;Swift Package Manager&lt;/a&gt;，&lt;a href=&quot;https://github.com/Alamofire/Alamofire/blob/master/Source/Result.swift&quot;&gt;Alamofire&lt;/a&gt; 等中都可觅其踪。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;上面代码注释中提到，「如果 Result 存在于标准库的话，这部分代码应该由标准库的 Foundataion 扩展进行实现」。但是考虑到原有的可选值参数 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Data?, URLResponse?, Error?)&lt;/code&gt;) 作为回调的 API 将会共享同样的函数名，所以上面的函数命名是不可取的，否则将导致冲突。在这类 public API 发布后，如何改善和迭代确实是个难题。一个可行的方法是把 Foundation 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; deprecate 掉，提取出相关方法放到诸如 Network.framework 里，并让它跨平台。另一种可行方案是通过自动转换工具，强制 Swift 使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 的回调，并保持 OC 中的多参数回调。如果你正在打算使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 改善现有设计，并且需要考虑保持 API 的兼容性时，这会是一个不小的挑战。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;错误类型泛型参数&quot;&gt;错误类型泛型参数&lt;/h3&gt;

&lt;p&gt;如此常用的一个可以改善设计的定义，为什么没有存在于标准库中呢？关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt;，其实已经有&lt;a href=&quot;https://github.com/apple/swift-evolution/pull/757&quot;&gt;相关的提案&lt;/a&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2018/result-type.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个提案中值得注意的地方在于，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 的泛型类型只对成功时的值进行了类型约束，而忽略了错误类型。给出的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 定义类似这样：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很快，在 1 楼就有人质疑，问这样做的意义何在，因为毕竟很多已存在的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 实现都是包含了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 类型约束的。确定的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 类型也让人在使用时多了一份“安全感”。&lt;/p&gt;

&lt;p&gt;不过，其实我们实际类比一下 Swift 中已经存在的错误处理的设计。Swift 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 只是一个协议，在 throw 的时候，我们也并不会指明需要抛出的错误的类型：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;methodCanThrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;somethingGoesWrong&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 在这里可以 throw 任意类型的 Error&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;methodCanThrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SomeErrorType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnotherErrorType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，在带有错误类型约束的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt; 中，我们需要为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;E&lt;/code&gt; 指定一个确定的错误类型 (或者说，Swift 并不支持在特化时使用协议，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;Response, Error&amp;gt;&lt;/code&gt; 这样的类型是非法的)。这与现有的 Swift 错误处理机制是背道而驰的。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 Swift 是否应该抛出带有类型的错误，曾经存在过一段时间的争论。最终问题归结于，如果一个函数可以抛出多种错误 (不论是该函数自身产生的错误，还是在函数中 try 其他函数时它们所带来的更底层的错误)，那么 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throws&lt;/code&gt; 语法将会变得非常复杂且不可控 (试想极端情况下某个函数可能会抛出数十种错误)。现在大家一致的看法是已有的用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;protocol Error&lt;/code&gt; 来定义错误的做法是可取的，而且这也编码在了语言层级，我们对「依赖编译器来确定 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try catch&lt;/code&gt; 会得到具体哪种错误」这件事，几乎无能为力。&lt;/p&gt;

  &lt;p&gt;另外，半开玩笑地说，要是 Swift 能类似这样 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extension Swift.Error: Swift.Error {}&lt;/code&gt;，支持协议遵守自身协议的话，一切就很完美了，XD。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;选择哪个比较好&quot;&gt;选择哪个比较好？&lt;/h3&gt;

&lt;p&gt;两种方式各有优缺点，特别在如果需要考虑 Cocoa 兼容的情况下，更并说不上哪一个就是完胜。这里将两种写法的优缺点简单比较一下，在实践中最好是根据项目情况进行选择。&lt;/p&gt;

&lt;h4 id=&quot;resultt-e-error&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/h4&gt;

&lt;h5 id=&quot;优点&quot;&gt;优点&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;可以由编译器帮助进行确定错误类型&lt;/p&gt;

    &lt;p&gt;当通过使用某个具体的错误类型扩展 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 并将它设定为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 的错误类型约束后，在判断错误时我们就可以比较容易地检查错误处理的完备情况了：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserRegisterError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duplicatedUsername&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unsafePassword&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        
 &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserRegisterError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;User registered: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duplicatedUsername&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
             &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafePassword&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
             &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;上例中，由于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 的类型已经可以被确定是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserRegisterError&lt;/code&gt;，因此在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failure&lt;/code&gt; 分支中的检查变得相对容易。&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;这种编译器的类型保证给了 API 使用者相当强的信心，来从容进行错误处理。如果只是一个单纯的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 类型，API 的用户将面临相当大的压力，因为不翻阅文档的话，就无从知晓需要处理怎样的错误，而更多的情况会是文档和事实不匹配…&lt;/p&gt;

      &lt;p&gt;但是带有类型的错误就相当容易了，查看该类型的 public member 就能知道会面临的情况了。在制作和发布框架，以及提供给他人使用的 API 的时候，这一点非常重要。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;按条件的协议扩展&lt;/p&gt;

    &lt;p&gt;使用泛型约束的另一个好处是可以方便地对某些情况的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 进行扩展。&lt;/p&gt;

    &lt;p&gt;举例来说，某些异步操作可能永远不会失败，对于这些操作，我们没有必要再使用 switch 去检查分支情况。一个很好的例子就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timer&lt;/code&gt;，我们设定一个在一段时间后执行的 Timer 后，如果不考虑人为取消，这个 Timer 总是可以正确执行完毕，而不会发生任何错误的。我们可能会选择使用一个特定的类型来代表这种情况：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
    
 &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TimeInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kt&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scheduledTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withTimeInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repeats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;在使用的时候，本来我们需要这样的代码：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;fatalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Never happen&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;但是，通过对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;E&lt;/code&gt; 为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NoError&lt;/code&gt; 的情况添加扩展，可以让事情简单不少：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;E&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NoError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
             &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;fatalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Never happen&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
 &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;// $0.value is the timer object&lt;/span&gt;
     &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;blockquote&gt;
      &lt;p&gt;这个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timer&lt;/code&gt; 的例子虽然很简单，但是可能实际上意义不大，因为我们可以直接使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timer.scheduledTimer&lt;/code&gt; 并使用简单的 block 完成。但是当回调 block 有多个参数时，或者需要链式调用 (比如为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter&lt;/code&gt; 之类的支持时)，类似 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NoError&lt;/code&gt; 这样的扩展方式就会很有用。&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;blockquote&gt;
      &lt;p&gt;在 NSHipster 里有一篇&lt;a href=&quot;https://nshipster.com/never/&quot;&gt;关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Never&lt;/code&gt; 的文章&lt;/a&gt;，提到使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Never&lt;/code&gt; 来代表无值的方式。其中就给出了一个和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 一起使用的例子。我们只需要使 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extension Never: Error {}&lt;/code&gt; 就可以将它指定为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt; 的第二个类型参数，从而去除掉代码中对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.failure&lt;/code&gt; case 的判断。这是比 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NoError&lt;/code&gt; 更好的一种方式。&lt;/p&gt;

      &lt;p&gt;当然，如果你需要一个只会失败不会成功的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 的话，也可以将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Never&lt;/code&gt; 放到第一个类型参数的位置：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;Never, E: Error&amp;gt;&lt;/code&gt;。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h5 id=&quot;缺点&quot;&gt;缺点&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;与 Cocoa 兼容不良&lt;/p&gt;

    &lt;p&gt;由于历史原因，Cocoa API 中表达的错误都是”无类型“的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt; 的。如果你跳出 Swift 标准库，要去使用 Cocoa 的方法 (对于在 Apple 平台开发来说，这简直是一定的)，就不得不面临这个问题。很多时候，你可能会被迫写成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;SomeValue, NSError&amp;gt;&lt;/code&gt; 的形式，这样我们上面提到的优点几乎就丧失殆尽了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;可能需要多层嵌套或者封装&lt;/p&gt;

    &lt;p&gt;即使对于限定在 Swift 标准库的情况来说，也有可能存在某个 API 产生若干种不同的错误的情况。如果想要完整地按照类型处理这些情况，我们可能会需要将错误嵌套起来：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;c1&quot;&gt;// 用户注册可能产生的错误&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;// 当用户注册的请求完成且返回有效数据，但数据表明注册失败时触发&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserRegisterError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duplicatedUsername&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unsafePassword&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
 &lt;span class=&quot;c1&quot;&gt;// Server API 整体可能产生的错误&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;// 当请求成功但 response status code 不是 200 时触发&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;APIResponseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permissionDenied&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 403&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entryNotFound&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;// 404&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverDied&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;// 500&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
 &lt;span class=&quot;c1&quot;&gt;// 所有的 API Client 可能发生的错误&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;APIClientError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;// 没有得到响应&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestTimeout&lt;/span&gt;
    
     &lt;span class=&quot;c1&quot;&gt;// 得到了响应，但是 HTTP Status Code 非 200&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apiFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APIResponseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                
     &lt;span class=&quot;c1&quot;&gt;// 得到了响应且为 200，但数据无法解析为期望数据&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invalidResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
     &lt;span class=&quot;c1&quot;&gt;// 请求和响应一切正常，但 API 的结果是失败 (比如注册不成功)&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apiResultFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;blockquote&gt;
      &lt;p&gt;上面的错误嵌套比较幼稚。更好的类型结构是将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserRegisterError&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIResponseError&lt;/code&gt; 定义到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClientError&lt;/code&gt; 里，另外，因为不会直接抛出，因此没有必要让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserRegisterError&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIResponseError&lt;/code&gt; 遵守 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 协议，它们只需要承担说明错误原因的任务即可。&lt;/p&gt;

      &lt;p&gt;对这几个类型加以整理，并重新命名，现在我认为比较合理的错误定义如下 (为了简短一些，我去除了注释)：&lt;/p&gt;

      &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;APIClientError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ResponseErrorReason&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permissionDenied&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entryNotFound&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverDied&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ResultErrorReason&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserRegisterError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duplicatedUsername&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unsafePassword&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;userRegisterError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UserRegisterError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestTimeout&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apiFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ResponseErrorReason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invalidResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apiResultFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ResultErrorReason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;

      &lt;p&gt;当然，如果随着嵌套过深而缩进变多时，你也可以把内嵌的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reason&lt;/code&gt; enum 放到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClientError&lt;/code&gt; 的 extension 里去。&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;上面的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClientError&lt;/code&gt; 涵盖了进行一次 API 请求时所有可能的错误，但是这套方式在使用时会很痛苦：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kt&quot;&gt;API&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;requestTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Timeout!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apiFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;apiFailedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
             &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;apiFailedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;permissionDenied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;403&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;entryNotFound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;404&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;serverDied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;500&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;invalidResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
             &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Invalid response body data: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apiResultFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;apiResultError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
             &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;apiResultError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiResultError&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserRegisterError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiResultError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;duplicatedUsername&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;User already exists.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                     &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;unsafePassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Password too simple.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;相信我，你不会想要写这种代码的。&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;经过半年的实践，事实是我发现这样的代码并没有想象中的麻烦，而它带来的好处远远超过所造成的不便。&lt;/p&gt;

      &lt;p&gt;这里代码中有唯一一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;as?&lt;/code&gt; 对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserRegisterError&lt;/code&gt; 的转换，如果采用更上面引用中定义的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ResultErrorReason&lt;/code&gt;，则可以去除这个类型转换，而使类型系统覆盖到整个错误处理中。&lt;/p&gt;

      &lt;p&gt;相较于对每个 API 都写这样一堆错误处理的代码，我们显然更倾向于集中在一个地方处理这些错误，这在某种程度上“强迫”我们思考如何将错误处理的代码抽象化和一般化，对于减少冗余和改善设计是有好处的。另外，在设计 API 时，我们可以提供一系列的便捷方法，来让 API 的用户能很快定位到某几个特定的感兴趣的错误，并作出处理。比如：&lt;/p&gt;

      &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;APIClientError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;isLoginRequired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apiFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;permissionDenied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;

      &lt;p&gt;用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error.isLoginRequired&lt;/code&gt; 即可迅速确定是否是由于用户权限不足，需要登录，产生的错误。这部分内容可以由 API 的提供者主动定义 (这样做也起到一种指导作用，来告诉 API 用户到底哪些错误是特别值得关心的)，也可以由使用者在之后自行进行扩展。&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;另一种”方便“的做法是使用像是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyError&lt;/code&gt; 的类型来对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 提供封装：&lt;/p&gt;

    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Error&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;这可以把任意 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 封装并作为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;Value, AnyError&amp;gt;&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.failure&lt;/code&gt; 成员进行使用。但是这时 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt; 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;E&lt;/code&gt; 几乎就没有意义了。&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;Swift 中存在不少 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt; 开头的类型，比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyIterator&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCollection&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyIndex&lt;/code&gt; 等等。这些类型起到的作用是类型抹消，有它们存在的历史原因，但是随着 Swift 的发展，特别是加入了 Conditional Conformance 以后，这一系列 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt; 类型存在的意义就变小了。&lt;/p&gt;

      &lt;p&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyError&lt;/code&gt; 来进行封装 (或者说对具体 Error 类型进行抹消)，可以让我们抛出任意类型的错误。这更多的是一种对现有 Cocoa API 的妥协。对于纯 Swift 环境来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyError&lt;/code&gt; 并不是理想中应该存在的类型。因此如果你选择了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt; 的话，我们就应该尽可能避免抛出这种无类型的错误。&lt;/p&gt;

      &lt;p&gt;那问题就回到了，对于 Cocoa API 抛出的错误 (也就是以前的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt;)，我们应该怎样处理？一种方式是按照文档进行封装，比如将所有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSURLSessionError&lt;/code&gt; 归类到一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSessionErrorReason&lt;/code&gt;，然后把从 Cocoa 得到的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt; 作为关联值传递给使用者；另一种方式是在抛出给 API 使用者之前，在内部就对这个 Cocoa 错误进行“消化”，将它转换为有意义的特定的某个已经存在的 Error Reason。后者虽然减轻了 API 使用者的压力，但是势必会丢失一些信息，所以如果没有特别理由的话，第一种的做法可能更加合适。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;错误处理的 API 兼容存在风险&lt;/p&gt;

    &lt;p&gt;现在来说，为 enum 添加一个 case 的操作是无法做到 API 兼容的。使用侧如果枚举了所有的 case 进行处理的话，在 case 增加时，原来的代码将无法编译。(不过对于错误处理来说，这倒可能对强制开发者对应错误情况是一种督促 233..)&lt;/p&gt;

    &lt;p&gt;如果一个框架或者一套 API 严格遵守 &lt;a href=&quot;https://semver.org&quot;&gt;semantic version&lt;/a&gt; 的话，这意味着一个大版本的更新。但是其实我们都心知肚明，增加一个之前可能忽略了的错误情况，却带来一个大版本更新，带来的麻烦显然得不偿失。&lt;/p&gt;

    &lt;p&gt;Swift 社区现在对于增加 enum case 时如何保持 API compatibility 也有一个&lt;a href=&quot;https://github.com/apple/swift-evolution/blob/af284b519443d3d985f77cc366005ea908e2af59/proposals/0192-non-exhaustive-enums.md&quot;&gt;成熟而且已经被接受了的提案&lt;/a&gt;。将 enum 定义为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frozen&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonFrozen&lt;/code&gt;，并对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonFrozen&lt;/code&gt; 的 enum 使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unknown&lt;/code&gt; 关键字来保证源码兼容。我们在下个版本的 Swift 中应该就可以使用这个特性了。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;result&quot;&gt;Result&lt;T&gt;&lt;/T&gt;&lt;/h4&gt;

&lt;p&gt;不带 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 类型的优缺点正好和上面相反。&lt;/p&gt;

&lt;p&gt;相对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; 不在外部对错误类型提出任何限制，API 的创建者可以摆脱 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyError&lt;/code&gt;，直接将任意的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt; 作为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.failure&lt;/code&gt; 值使用。&lt;/p&gt;

&lt;p&gt;但同时很明显，相对的，一个最重要的特性缺失就是我们无法针对错误类型的特点为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 进行扩展了。&lt;/p&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;p&gt;因为 Swift 并没有提供使用协议类型作为泛型中特化的具体类型的支持，这导致在 API 的强类型严谨性和灵活性上无法取得两端都完美的做法。硬要对比的话，可能 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt; 对使用者更加友好一些，因为它提供了一个定义错误类型的机会。但是相对地，如果创建者没有掌握好错误类型的程度，而将多层嵌套的错误传递时，反而会增加使用者的负担。同时，由于错误类型被限定，导致 API 的变更要比只定义了结果类型的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; 困难得多。&lt;/p&gt;

&lt;p&gt;不过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; 暂时看起来不太可能被添加到标准库中，因为它背后存在一个更大的协程和整个语言的异步模型该如何处理错误的话题。在有更多的实践和讨论之前，如果没有革命性和语言创新的话，对如何进行处理的话题，恐怕很难达成完美的共识。&lt;/p&gt;

&lt;p&gt;结论：错误处理真的是一件相当艰难的事情。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;最近这半年，在不同项目里，我对  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; 两种方式都进行了一些尝试。现在看来，我会更多地选择带有错误类型的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&amp;lt;T, E: Error&amp;gt;&lt;/code&gt;  的形式，特别是在开发框架或者需要严谨的错误处理的时候。将框架中可能抛出的错误进行统一封装，可以很大程度上减轻使用者的压力，让错误处理的代码更加健壮。如果设计得当，它也能提供更好的扩展性。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Wed, 31 Oct 2018 02:38:00 +0000</pubDate>
        <link>https://u3dc.github.io//2018/10/swift-result-error/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/10/swift-result-error/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
      <item>
        <title>十年前的日记们</title>
        <description>&lt;h2 id=&quot;假如我有时光机&quot;&gt;假如我有时光机&lt;/h2&gt;

&lt;p&gt;最近把工作上的事情忙完了，也把主机从美国换到了日本的机房，解决了国内的访问问题，所以准备开始好好重新拾掇一下，恢复定期更新 blog。&lt;/p&gt;

&lt;p&gt;其实我从大学时就有开始写 blog 的习惯了。不过不像最近的独立博客，那时候更多地是用新浪或者搜狐这样的平台，所以也就在那些地方也留下了不少“足迹”。既然是自己“存在过的证明”，我想可能还是把它们汇总一下，留个存档为好。于是就有了这篇和“技术”没什么关系的文章。&lt;/p&gt;

&lt;p&gt;这里面是我从 2006 年底到 2010 年三月期间的一些碎碎念，时间跨越从大二上半学期开始，到研究生二年级为止，大概三年半的时间。在此之前的文章大约已经佚失，不再可寻了，此后的文章在本站的最后几页应该可以看到。所以这一系列日记的集合，把这个站点的生命向前拓展了四年，也算是对自己青涩时光的一些记录和回顾。&lt;/p&gt;

&lt;p&gt;大部分其实都是碎碎念，抱怨学习生活的枯燥和繁重，各种姿势求安慰求抚摸。还有一些是少年得志的优越感的展现，以及一些年轻气盛的骄傲。不过其中还是夹杂了一些观点，里面有一些，现在看来可能很幼稚，但是这确实是我当时的想法。而更多的思考，则坚实地成为了自己这一路走来的基盘，至今依然指导着我的行为和前进方向。&lt;/p&gt;

&lt;p&gt;所谓“人不中二枉少年”，能在十年后的今天，翻出这些真实记载了自己是怎样一个人的日记，幸甚幸甚。所以将它们加以整理，一并发在这里，一方面可以杜绝原有平台消失的风险，一方面也可以作为留念，提醒自己的过去。&lt;/p&gt;

&lt;p&gt;当然，同时也可以满足读者的好奇心…(如果大家对这个平淡无奇的我的过去，有所好奇的话。)&lt;/p&gt;

&lt;h2 id=&quot;十年前的日记们&quot;&gt;十年前的日记们&lt;/h2&gt;

&lt;h3 id=&quot;2006-12-21&quot;&gt;2006-12-21&lt;/h3&gt;

&lt;p&gt;今天13点，一个伟大的时刻，历时三周后，我终于完成了长达33页的数字电路大作业报告。欣喜若狂；&lt;/p&gt;

&lt;p&gt;今天13点01分，一个无语的时刻，我可爱的报告被舍友强行“征用”，不知道应该高兴还是沮丧。万般无奈。&lt;/p&gt;

&lt;p&gt;不过在一个小时后，我又投入了另一项伟大的工作——烧开水中…好吧，烧开水的学名叫做“用传感器测空气相对压力系数”。其实觉得物理的实验挺无聊的，照章办事而已，上至仪器原理，下到步骤数据，手把手教给你，这真的能够达到那传说中的“培养学生独立实验能力”的美好目的么？我不知道…我只知道要是把这本实验指导书弄丢了，我的实验课绝对得挂。但是同样是实验，电子信息技术的就要好很多。虽说插面包板实在是郁闷，但是没有了教条一般的实验指导，感觉确实要自由许多……至少我可以很大气的和老师说：“弄出来了就行，你管我怎么做DI~” 而不用埋头对着一个不是温度表的表去烧开水…读不够数据还要重烧…浪费资源，没记错的话北京好像是缺水城市..&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;说实话，我还从来没像现在这样忙过。期末选修课一共有三篇论文，还没动笔；这周末有英语考试；下周需要交两个实验报告；还要准备期末的主课考试了。数学物理方程完全没有概念，也许等我能把勒让德方程和贝塞尔函数以及各种积分变换法烂熟于胸，就能拿到个很高的成绩吧。但是现实是，我关注的是去年这门课的挂课人数是15人。还好，15人，应该还轮不到我…但是要是万一轮到了呢…可恶的正态分布..还有更可恶的麦克斯韦分布…&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;赶论文吧…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ye, 全过了！棒，去吃好吃的…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2006-12-22&quot;&gt;2006-12-22&lt;/h3&gt;

&lt;p&gt;昨天给一些朋友发短信，把这个blog公布出去。没想到的是我刚一发完，就同时收到了五六条回复。看来大家都是把手机带身边的，回头看看自己，经常会有连续充电一星期的事情发生…恩恩，我比较宠爱电池啦~一定要把它喂得饱饱的 😂&lt;/p&gt;

&lt;p&gt;今天很郁闷，起了个大早，五点多的样子吧。别以为我伟大，我是被吓醒的…做了恶梦..梦到厄~梦到一个D触发器坏了，然后实验出不来结果…再然后，就吓醒了…很没志气，居然这都会被吓醒。更要命的是，越想重新睡着，就越来越清醒….索性起床。心里还是很不平衡，在冬至这样一年中黑夜最长的日子里，我却…那么早就…&lt;/p&gt;

&lt;p&gt;不过后来就平衡了。在我睡了整节的复变函数课和后半节的数字电路课，回到宿舍之后，发现了睡眼惺忪的舍友们问我有没有吃午饭。恩，我没撬任何课，而且和他们享受睡眠的时间相差无几。赚了…&lt;/p&gt;

&lt;p&gt;明天英语考试，祝大家好运~也祝自己好运…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2006/locker.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;数字电路大作业第三题仿真电路图，这个电路实现了一个密码锁的功能&lt;del&gt;嘀嘀嘟嘟嘟&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;咔哒~&lt;/p&gt;

&lt;p&gt;（薄薄一张电路图，引无数英雄尽折腰。偷看隔壁宿舍的孩子们埋头苦画，窃笑…今天是Deadline，不知道他们在12点前能不能搞定）&lt;/p&gt;

&lt;p&gt;看来一切还是提前做完比较好，嘿嘿 ^_^&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-02-09&quot;&gt;2007-02-09&lt;/h3&gt;

&lt;p&gt;呀呀呀…果然是没有定时更新的习惯。回家以后睡太爽，直接把Blog忘了…罪过罪过&lt;/p&gt;

&lt;p&gt;今天和杨扬李宁去打了两个小时的羽毛球，长久不动，稍微出点汗，好舒服的。宁宁上次去看老班就见过了，杨扬也没有多大变化，就是开始留长头发了``呵呵&lt;/p&gt;

&lt;p&gt;中午一起去车车吃了饭，路上说道主人还曾经在MAMAFU打过工`在门口迎宾，好厉害啊..有空的话，我也想去尝试一下，找份事情做做..不过估计不会要我迎宾吧.难说去算帐也不错哦。&lt;/p&gt;

&lt;p&gt;今天2月9号了，不知不觉这个假期就过了整整一半了。还是很颓废，本来说假期里多看看下学期的书的，但是总是睡太多..不管了，好好休息也是应该的嘛。有个广告怎么说来着：一个月夏威夷的海滩度假，是为了一年纳斯达克的XXXX…人家都夏威夷了，我只不过是在家多睡了几小时,,不为过哉。可是..还是..很不..恩..因为现在的做法和最初的想法还是有了差距，心中有些不安。&lt;/p&gt;

&lt;p&gt;这个假期可能组织不起大规模的同学聚会了，忙着过年的啊什么的，好多人都不在昆明。不过最想见的人都已经见到了的，所以也没太大关系了。另外，李宁计划5月长假去南京，恩，我犹豫中。到时候再看吧..&lt;/p&gt;

&lt;p&gt;呀.该喝午茶了~~~&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-02-11&quot;&gt;2007-02-11&lt;/h3&gt;

&lt;p&gt;阳光依然灿烂，提前体会到了夏天的感觉。&lt;/p&gt;

&lt;p&gt;昆明的天气，就是很怪。全国未冷昆明先寒，全国未热昆明先燥。在好多地方经历了一个暖冬，最近开始有降温趋势的时候，昆明却从几年不遇的严寒中摇身一变，以火热的胸怀迎接了远方的来客。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;骄阳。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;西大堤的红嘴鸥飞翔漫天，二十年了吧，这些小生灵年年不殆，飞越几千里，来到这高山上的明珠度过一个惬意的冬季。开春之时，一片白色之间已然看不到跋涉的辛苦，有的只是丰满的羽毛和亮丽的身姿。昆明人给了它们安定的环境，这些精灵们用自己完美的表演给了人们欢笑，这也算是最好的回报。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;和风。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;北京不似昆明。京城的冬天，肃杀和萧索是主旋律，深秋毫不留情的带走了一切绿色，严冬便肆意妄为，人们只好躲到大衣里，藏到房间里。昆明一年四季都有绿色的，即使是在如今年这样的冬天。也大概是如此，才让本来应当狂虐嚣张的风有了一些的收敛。正是这一些的收敛，风的性质便全然不同了。寒风凛冽变成了微风拂面，心情自然也就不同。昆明果然是一座闲适的城市。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-03-16&quot;&gt;2007-03-16&lt;/h3&gt;

&lt;h4 id=&quot;题目&quot;&gt;题目&lt;/h4&gt;

&lt;p&gt;求证：一维束缚态的波函数相位必是常数&lt;/p&gt;

&lt;h4 id=&quot;证明&quot;&gt;证明&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;由共轭定理，
ψ*(x)也是方程的解

由于是一维束缚态，对于给定的E，该能级是非简并的

设ψ*(x)=Cψ(x) ①

同时取共轭 有ψ(x)=|C|^2ψ(x)
|C|=1，即C=exp(iα)

代入①式
得 ρ(x)exp(-iθ(x))=ρ(x)exp(iα+iθ(x))
所以 θ=kπ-α/2
是与x无关的常数
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;证毕。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;几行的证明，用了一天时间……今天体会到，量子力学量力学了…………&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-03-24&quot;&gt;2007-03-24&lt;/h3&gt;

&lt;p&gt;好累…&lt;/p&gt;

&lt;p&gt;才第三周，作业快做不完了&lt;/p&gt;

&lt;p&gt;编一个星期的C++，憔悴中…&lt;/p&gt;

&lt;p&gt;休息…&lt;/p&gt;

&lt;p&gt;不知道还能坚持多久；&lt;/p&gt;

&lt;p&gt;大概是，真的老了吧..呵呵…或许是未老先衰？&lt;/p&gt;

&lt;p&gt;已经开始期待五一了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-03-28&quot;&gt;2007-03-28&lt;/h3&gt;

&lt;p&gt;手球果然是危险的运动&lt;/p&gt;

&lt;p&gt;昨天打手球，估计是冲动了一点点，把手给拧了。&lt;/p&gt;

&lt;p&gt;当时没觉得怎么，后来越来越不对，到了晚上直接小严重，左手完全不能动了…痛得一晚上没有合眼。啊啊好困…又睡不着，真惨哦&lt;/p&gt;

&lt;p&gt;今早的课…算了吧，去了也是睡。&lt;/p&gt;

&lt;p&gt;而且现在刚刚有点好转，要是在去的路上又弄到的话，估计这手是真的废了。&lt;/p&gt;

&lt;p&gt;恩，开始学习吧…&lt;/p&gt;

&lt;p&gt;大家做运动的时候一定要小心啊&lt;/p&gt;

&lt;p&gt;(单手打字还真是难受，看来是只适合看书这种不需要左手的工作了)&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-04-05&quot;&gt;2007-04-05&lt;/h3&gt;

&lt;p&gt;紫荆是清华的代表，不过这次却被玉兰抢了先机。去六教的路上偶然发现已然满树玉兰，才知道春天真的到了。不知道为什么，感觉玉兰是一种挺忧伤的花，洁白无暇，却长在这污浊之世。据说玉兰花开过之后，花瓣就会渐渐变黑，似是沾染了污秽。想来也很可惜，原本好好的花，却最终是要不复洁白的——恰如人之在世吧。真正能出淤泥而不染，濯清涟而不妖的，又有几人？&lt;/p&gt;

&lt;p&gt;玉兰花还有一大特点是没有叶衬，远望去就如满树白鸽，自守高洁。春风起时，又是落英缤纷的洒脱。莫约一周之后，待得满地花儿落尽，才开始不紧不慢的长叶，就好像那些花儿从来不存在过一样。也许真的简单才是美，安静才是美….&lt;/p&gt;

&lt;p&gt;“朝饮木兰之坠露兮，夕餐菊之落英” 是《离骚》中说玉兰的句子。不知道屈原如何做到的早春暮秋，跨越时空的振颤我自然是体会不到了，惟能做到的，也就是将这一树玉兰凝为永恒了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-04-08&quot;&gt;2007-04-08&lt;/h3&gt;

&lt;p&gt;自从上周手受伤以后就一直颓废ing&lt;/p&gt;

&lt;p&gt;改变,,,,要努力,,,,不能放弃！！&lt;/p&gt;

&lt;p&gt;下周开始每天都去自习，就这样，嗯！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-04-10&quot;&gt;2007-04-10&lt;/h3&gt;

&lt;p&gt;仔细看了《逆光》的歌词，蛮喜欢。一种给人希望的感觉….&lt;/p&gt;

&lt;p&gt;往往是在刺眼的逆光中，才能看到一些真正的感情，那应当是别人无意之间的流露吧。&lt;/p&gt;

&lt;p&gt;也许我需要一点光芒？&lt;/p&gt;

&lt;p&gt;摇头，笑。&lt;/p&gt;

&lt;p&gt;呵呵，其实谁又不需要呢？&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我一直害怕有答案&lt;/p&gt;

  &lt;p&gt;也许爱情轻轻在风里打转&lt;/p&gt;

  &lt;p&gt;离开释怀很短暂又重来&lt;/p&gt;

  &lt;p&gt;于是乎自问自答&lt;/p&gt;

  &lt;p&gt;我不要困难把我们击散&lt;/p&gt;

  &lt;p&gt;我自卑自己那么不勇敢&lt;/p&gt;

  &lt;p&gt;遗憾没有到达　拥抱过还是害怕&lt;/p&gt;

  &lt;p&gt;用力推开你我依然留下&lt;/p&gt;

  &lt;p&gt;有一束光，那瞬间，是什么痛的刺眼！&lt;/p&gt;

  &lt;p&gt;你的视线是谅解为什么舍不得熄灭&lt;/p&gt;

  &lt;p&gt;我逆着光，却看见&lt;/p&gt;

  &lt;p&gt;那是泪光，那力量，我不想再去抵挡！&lt;/p&gt;

  &lt;p&gt;面对希望，逆着光，感觉爱存在的地方&lt;/p&gt;

  &lt;p&gt;一直就在，我身旁！&lt;/p&gt;

  &lt;p&gt;我以为我不后退反复证明这份爱有多不对&lt;/p&gt;

  &lt;p&gt;背对着你如此漆黑&lt;/p&gt;

  &lt;p&gt;忍住疲惫　睁开眼　打开窗　才发现你就是光芒&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-04-18&quot;&gt;2007-04-18&lt;/h3&gt;

&lt;p&gt;记得以前自己是很爱收拾的。书桌一向很干净，容不下一点灰尘…看看现在自己杂乱的桌子，发现变化真是可怕。是太忙，或是太懒 =_=&lt;/p&gt;

&lt;p&gt;Hilbert空间，Guassian波包、强反型层和栅氧电荷、Markov链、各种运放….迷茫….真不知道这些东西除了说出来吓人以外还能有啥太大用途..（好像有点矛盾，最近貌似是天天在用凌乱思绪）…&lt;/p&gt;

&lt;p&gt;今天去PSpice实验居然把水杯丢了….不爽&lt;/p&gt;

&lt;p&gt;不爽啊不爽！~呜喵…爪子，抓！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-04-22&quot;&gt;2007-04-22&lt;/h3&gt;

&lt;p&gt;这几天挺郁闷的。（不过貌似每天都很郁闷，郁闷综合症，笑~~）&lt;/p&gt;

&lt;p&gt;前天卡巴斯基的报告，有量子力学冲突，听不到，错过。&lt;/p&gt;

&lt;p&gt;昨天比尔盖茨的演讲，有模电实验冲突，去不了，错过。&lt;/p&gt;

&lt;p&gt;今天火箭爵士的比赛，有英语考试冲突，看不成，错过。&lt;/p&gt;

&lt;p&gt;好像今天还有Ayumi的演唱会吧…就只能遥望上海方向发半分钟呆了，继续错过。&lt;/p&gt;

&lt;p&gt;王菲唱了，错也是一种美…虽然那个是犯错而非错过..
​  &lt;br /&gt;
下周期中考试，又是检验半学期成果的时候了。这半学期自己有很努力的学习，希望能有个不错的结果吧。&lt;/p&gt;

&lt;p&gt;昨天和班上某牛聊天，才发现自己和人家差得不是一个档次。聊天说起做作业，当我还在对某一道题目研究的时候，人家说的是，“作业嘛~做完了以后我还是会去翻翻答案，看看答案有没有写错的”…为啥我先想到的是看看自己有没有错捏，差距啊差距~~&lt;/p&gt;

&lt;p&gt;牛大概是不需要理由的吧….有幸和连续两年的年段第一一个班，从来没见他看过课本上过课做过作业，一到考试，7科5满，最低98…自己就只能在一边望天兴叹了..=_=&lt;/p&gt;

&lt;p&gt;貌似牛是天生的…&lt;/p&gt;

&lt;p&gt;不过每天和各种大牛在一起也好,,,,幻想某天自己也变成牛….&lt;/p&gt;

&lt;p&gt;厄…不对，我是猫，怎么能够错变成牛呢？&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-04-25&quot;&gt;2007-04-25&lt;/h3&gt;

&lt;p&gt;所谓旧馆，乃老图书馆，新馆东侧是也。那可是代代THU学生的一块自习圣地。&lt;/p&gt;

&lt;p&gt;今日在旧馆奋斗于信号与系统之中，忽见一ppmm从身边飘过。此mm黑色上衣，短裙，且绝对可算得上是天使面容，魔鬼身材。偶不禁感慨：“THU能见到这景象也是不容易啊。”何况在旧馆这自习之圣地。mm坐的位置离偶不远，走之前，偶又恋恋不舍地瞟此mm一眼。发现其看书的姿势也颇为淑女。但总觉得这一瞟给偶留下了一定的心理阴影，并之后百思不得其解为什么。到宿舍了，突然意识到了问题的所在：&lt;/p&gt;

&lt;p&gt;那mm正在看的书是：&lt;/p&gt;

&lt;p&gt;《电路原理》&lt;/p&gt;

&lt;p&gt;不禁感慨：“大概也就THU能见到这景象了……”&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-01&quot;&gt;2007-05-01&lt;/h3&gt;

&lt;p&gt;又一个五一。&lt;/p&gt;

&lt;p&gt;该走的都走了。每每幻想自己能独自“霸占”这间30平米的屋子，尽情做想做的事情，这次五一居然成了真。做完程序设计的大作业，刚才到园子里逛了逛，回来还觉着不够，搬了椅子，拿了茶点到阳台赏起月来，真没想到自己还有这般如此的兴致~^O^&lt;/p&gt;

&lt;p&gt;每次五一十一开始之时，都觉着7天漫漫假期，总可以做出一些事情。但是往往倏忽之间，便已到假期之末，回首一周，时光皆为虚度。不知道自己现在是否还可以称作是青春年少，不知道自己现在是否还有资格说什么天长地久，知道的，只是北坡之上的那轮狡黠的月亮，只是钟表之中那嘀嗒流过的声音。记忆中上次赏月已是高二了，晚自习后，往草坪上那么一躺，忘掉整天的烦恼，呼吸绿色的芬芳，伴着花儿虫儿，陪着鸟儿鱼儿，一起看一轮月亮。幻想着自己成为云彩，拥有那份变化莫测的飘逸；或者是成为星星，享受永不磨灭的光辉。不过现在赏月的心境与那时的浪漫天真却有不同了。没了那随意一躺得洒脱，没了那无忧无虑的幻想。看一轮圆月，不自然地就想起家人和朋友；估计看到残月的话，会更悲哀吧。大概还是受传统文化影响深了……只能是但愿人长久，千里共婵娟了。&lt;/p&gt;

&lt;p&gt;晚上的清华园确实很静，朱老先生的荷花塘便更加得静了——那本就是人所罕至之处。可惜的是荷二楼一缕灯光，却使得塘子辛苦倒映的月黯淡无光，丢掉了本来的意境。时候还没到，花都没有开，不过好像已经含苞待放了，再有机会闲逛的荷塘的话，兴许能够体验一次朱老的月下荷塘吧。&lt;/p&gt;

&lt;p&gt;这几天园子里的游人明显多了，不说那些在二校门前搔首弄姿的散客，单说团体大规模出动，我就碰上好几次。先是什么什么高中的来参观一次，中午的时候在观畴园门口停了两大排自行车；然后换什么什么小学来，所有孩子戴着小黄帽，在大礼堂门口拍照，也颇算一套新风景；更令人叫绝得是，什么什么幼儿园居然也全员出动，开了3辆大巴直杀主楼而去，孩子们一个一个手拉手往主楼一过，弄得差点实验迟到…sigh…都想着进清华…其实，清华真的是个要人折寿的地方。没有下决心拼命的，没有想清楚即使你拼了命也不一定有好结果的，也没有必要来了。每次看上课名单上什么02年入学的还在重修的，就会为他们悲哀——在一个不属于自己的舞台上舞蹈，又怎么能够精彩？其实要是换一个学校，换一个不是那么能让人疯狂的学校，结果会更好。&lt;/p&gt;

&lt;p&gt;今天去还了考信号前借的参考书，那图书馆得阿姨用一种很奇怪的眼神看我，想了好久不明白为什么。后来在翻准备五一借回来看的书的时候才反应过来，原来是奇怪我怎么才借3天就还…因为清华的习惯向来是满一个月还得拖3天才还书的…恩，我知道了…下次记得，图书馆不催的书，坚决不还。嗯，就这么决定了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-12&quot;&gt;2007-05-12&lt;/h3&gt;

&lt;p&gt;考完收工。&lt;/p&gt;

&lt;p&gt;还不错，心情和天气一样，晴晴的。半个学期的努力似乎是没有白费````&lt;/p&gt;

&lt;p&gt;让考试占去太多时间了，现在终于能做些自己的事情了…吼下先。开始玩！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-15&quot;&gt;2007-05-15&lt;/h3&gt;

&lt;p&gt;额滴神啊…终于找到时间写写blog了&lt;/p&gt;

&lt;p&gt;本来以为期中考以后会闲一阵子，都准备好疯玩几天了，但现在看来不仅没得闲，反而被因为考试所落下的很多事情一涌而来给冲的找不着南北了。甲级团支部的评比、电子技术实验的仿真和CAD作业、与期中以前完全不同的学习内容….还好传说中的程序大作业在五一就做好了，要不真不知道应该怎么对付了..莫非叫我去抄？哼哼….不是我的风格呐！坚决自己做！&lt;/p&gt;

&lt;p&gt;最近浮躁得很，大概和天气热了也有关系….做不出题的时候越来越多了，设计失败的时候越来越多了，生气的时候也越来越多了，直接后果就是开始长痘…烦得很。有时候会胡思乱想一些东西，结果也没想出个结果..浪费挺多时间的，划不来阿划不来。心果然还是要静才行，很多时候甘于寂寞才能成大气候，这句话是不错的…&lt;/p&gt;

&lt;p&gt;专业分流的事情越来越近了，说实话我还没想好到底去哪里。电信每天就分析波传输啊各种变换啊，微电弄半导体啊集成电路啊什么的….随大流的话，就去电信咯，但是据说电信数学用得挺多…怕…但是去微电的话，估计身边会少很多大牛吧..也是一种损失。踌躇中….偏向微电一些吧..喜欢搞搞设计…去电信的话，也许要面对的都是什么“两三百年前的手算技巧”..会蛮无趣吧- -呵呵.只能再看咯。&lt;/p&gt;

&lt;p&gt;不知道大家都过得怎么样…好想你们。感觉好多人假期里都要去上GRE啊或者做一些实践，估计回家的时间会比较少了吧。还是希望假期能好好聚一聚…虽然我时间也不是很多 =_=&lt;/p&gt;

&lt;p&gt;唔…假期实践，3周FPGA，2周MatLab，应该能学不少东西的，期待,,,,&lt;/p&gt;

&lt;p&gt;不过好像还早..那是8月份的事情了呵呵。&lt;/p&gt;

&lt;p&gt;只能到这里了….要去做实验了….可恨又可爱的电路板..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-19&quot;&gt;2007-05-19&lt;/h3&gt;

&lt;p&gt;I am lost. Who can save me…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-20&quot;&gt;2007-05-20&lt;/h3&gt;

&lt;p&gt;昨天和前天情绪都不是很好，今早起来的时候终于发现原因了——睡眠不足啊。昨天睡了将近10个小时，早上起来以后又去小跑了一圈，很舒服，好多了。吼吼…没有什么是美美睡一觉解决不了的，如果有，那就睡两觉。&lt;/p&gt;

&lt;p&gt;可能是退化了，现在好像不能像以前那样很随意的控制自己的情绪了，经常会做出一些很感性的事情来，不知道是好还是不好。或者我本来就缺乏感性，这样正好弥补了一点缺点？也许吧..不知道=_=&lt;/p&gt;

&lt;p&gt;前些天去校内注了个册报了个到，但是我不喜欢那里的感觉，太公众吧，觉得乱乱的…还是蜷回自己的小窝比较好~已经有一种很亲切的感觉了。校内那边估计不太会去的了，不过好像我看高中的同学基本也都不怎么弄校内的，大学的同学还多一些…恩，不管怎么样，就先这样吧。&lt;/p&gt;

&lt;p&gt;今天晚上甲级团支部评比了，团工作一年，其实成败就在此定论。觉着挺假的…一年组织团活动，绞尽脑汁安排内容，想方设法抽出时间，结果感觉基本是没有达到什么预想中的活动目的的，最后就仅只是有几张照片几份资料去参加所谓的评选而已。而最后的评比结果很大程度取决于报告做得怎么样，却不关心活动的效果怎么样。面子工程…实际上这样的评比流于形式了。但是还是得参加…去年我们评上了，今年的班团架子压力也不小。全班同学翘首期待的….厄，说实际一点，数目不少的奖金….还是得去拿。不过嘛..今晚就作陈述了，着急也没用的啦~~~&lt;/p&gt;

&lt;p&gt;评比一完，今年的团工作也就算是彻底over啦。可以卸任，专心做其他的事情了~OYOY&lt;/p&gt;

&lt;p&gt;翻了翻以前的那篇《人淡如菊》，决定把它打印出来贴在衣柜上….好多事情现在自己处理的很是不好，反省..反省…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-21&quot;&gt;2007-05-21&lt;/h3&gt;

&lt;p&gt;天气，晴。&lt;/p&gt;

&lt;p&gt;到昨天晚上为止，本学期的团工作算是基本结束了。年级评选的时候，我真的体会到了什么叫做王道…九班一出，谁与争锋…香香的粉PP的PPT，昭时超级严谨的展示，班上所有同学的不懈努力，让这个第一来得如此容易….这一届的团支部任务确实圆满达成了吧^O^&lt;/p&gt;

&lt;p&gt;不过已经决定选微电了，大概现在开始是我在九班的最后两个月了。心存不舍，但我的目标却依然明确。继续努力….&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-24&quot;&gt;2007-05-24&lt;/h3&gt;

&lt;p&gt;难得，北京居然会下一整天的雨，在北京待两年了，这还是第一次。&lt;/p&gt;

&lt;p&gt;早上上课以后就一直下，到中午看到雨还没停，又熬不住午饭的诱惑，只好冒雨去吃饭。还好雨不大，天气又比较暖和，淋淋雨也还是不错….但是一想到北京的酸雨…天，快去洗澡先。&lt;/p&gt;

&lt;p&gt;恩…回来了，继续写。&lt;/p&gt;

&lt;p&gt;很奇怪自己现在对光线很敏感，昨天晚上没有拉上窗帘，导致了今早4点半就醒了过来…要知道我昨天1点半才睡着。迷迷糊糊继续睡去，但是很不幸的是，昨晚忘了关灯…于是6点的时候眼前哗的一闪，再次醒来。还好，今天也没觉着困…我一直睡得很少，偶尔来这样一下，问题应该也不是很大….&lt;/p&gt;

&lt;p&gt;睡眠确实很差，5点多必然会醒来…可恶的夏天….为什么会亮这么早，呜…注定不让我多睡一会儿么。不知道能有什么办法改善一下啦…希望明天能睡到7点..否则真的会是慢性自杀吧 呵呵 =_=…&lt;/p&gt;

&lt;p&gt;到此，不能再写了…做模电去了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-26&quot;&gt;2007-05-26&lt;/h3&gt;

&lt;p&gt;突然，想喝一点酒。&lt;/p&gt;

&lt;p&gt;仙三紫萱结局，重楼只能默默注视紫萱，一向只以和景天切磋为乐趣的他有句台词：“今天，我只想喝酒….”。重楼使用了自己的力量，可以说是将紫萱从宿命之中解救了出来，可紫萱却只晓得执着甚至盲目地守护着一份早该结束的感情。重楼的郁闷，我想我现在是能知道一点了..自己守护之人却对自己似若惘闻，多年的无私换来的只是心爱之人为他人的垂泪。也不得不说重楼是可悲的，他被这段莫名而来的缘份羁绊太久吧，到最终一届堂堂魔尊也只能借酒消愁。&lt;/p&gt;

&lt;p&gt;何以解忧，惟有杜康….千年前古人早已道出酒的妙用。从孔老夫子庙礼之上的一樽清酌，到东坡兄问青天时手中的一瓮醍醐，再到今人愁思时那月光下的清澈明亮，飘飘零一杯一杯，却怎么也解不尽这世间的苦愁。或许，把酒当作一种艺术，更能以一份豁然面对吧。&lt;/p&gt;

&lt;p&gt;学校超市貌似只有红酒啤酒，除了它们..应该就只有威士忌了..虽然我是有专门的广口威士忌酒杯，但实在没兴趣喝那样乏味的酒…其实很想试试精心调制的鸡尾..琴酒底的，配上一点白兰地，再按自己的情绪，随意弄些莱姆椰奶之类的，选择一种无拘无束的自由，摇出一份自由自在的快乐，找一个偏僻安静的角落，享受自己内心的一份佳酿，将那些平时的憋闷彻底释放，何其乐哉…&lt;/p&gt;

&lt;p&gt;很多事情，我不能说。有些事情本身我无法控制，所以我只能控制自己。也许偶尔是可以靠酒发泄一下的吧（笑）..&lt;/p&gt;

&lt;p&gt;可惜现在没酒…恩，就连啤酒都没有…月光下的清澈明亮…好，我想到了，可以以水代酒。&lt;/p&gt;

&lt;p&gt;就以水代酒，在这个夜，忘掉烦恼。天乐地乐，山乐水乐，我为何不也乐在其中？！同乐！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-05-31&quot;&gt;2007-05-31&lt;/h3&gt;

&lt;p&gt;忙着写程序去了，就忘了写blog..哼哼…越来越糊涂了，都分不清哪一个更重要了。&lt;/p&gt;

&lt;p&gt;这几天过得还好啦…也许会有一点点low mood，但是总体来说，并没有想象的那么差，呵呵。硬要说的话~都是程序惹的祸….&lt;/p&gt;

&lt;p&gt;好多时候在想，要是世界上没那么多发现或者发明，我今天的生活会不会轻松很多？至少要是Shockley没有发明晶体管，我就不用学微电；要是Kilby没有弄出IC，模电就会和蔼很多；要是Shordinger没有那该死的假设，那么量子一直会是感性认识而不是数学推导吧…不过要是没有这些东西的话..我也就没办法在这里写blog了=_=..&lt;/p&gt;

&lt;p&gt;真是恐怖….我在念大学前..唔..从幼儿园大班开始吧，到高二左右的样子，十来年才把古典的一套东西学完。大学了..两年时间，我学到的东西将近是以前十多年总合有余吧…回首看这两年，四个字，苦不堪言…但是，我想这是值得的吧。清华教给我最多的，其实不是知识，也不是什么品行或者是做学问的精神，而是在逆境中如何保持自我，如何让自己内心充满勇气——不论受到怎样的打击….&lt;/p&gt;

&lt;p&gt;大一的时候就把《自杀论》好好地研究过一遍了。不过那时候看那本书，更多的是为了别人。现在又拿出来开始看第二遍，发现一年前的理解确实很浅，一年了…生活教给了我很多，于是现在我开始为自己看这本书^O^ 两年的时光，呵呵，两年在清华的时光，相信能把大部分人当年那霸气式的锐气磨平了…我不幸成为那大部分人中的一员。很多时候，看身边的人，看身边的事，惟有苦笑。终于体会了什么叫做无奈，什么叫做心有余而力不足，什么叫做人上有人天外有天….其实来到这里，我是做过心理准备的，但是貌似准备不足，让我又白白花了两年时间来真正的做“准备”吧。但是也还是不知道现在自己，是不是已经准备好了(笑…)&lt;/p&gt;

&lt;p&gt;哎呀呀，发现自己现在笑得都很涩了….也许只有到假期才会好一些了吧。现在明白“如释重负”是什么意思了…呵呵&lt;/p&gt;

&lt;p&gt;不用担心我…我。很。坚。强。~~~！！&lt;/p&gt;

&lt;p&gt;牢骚发过，一切都会没事的~~~~哎呀呀&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-05&quot;&gt;2007-06-05&lt;/h3&gt;

&lt;p&gt;今天好热啊！！！&lt;/p&gt;

&lt;p&gt;36度…过几天估计40….&lt;/p&gt;

&lt;p&gt;好热好热好热好热好热……抓狂中…..&lt;/p&gt;

&lt;p&gt;热…&lt;/p&gt;

&lt;p&gt;还会更热….&lt;/p&gt;

&lt;p&gt;会不会一直热下去…..？&lt;/p&gt;

&lt;p&gt;不会吧….&lt;/p&gt;

&lt;p&gt;但是，现在，真的，很热。&lt;/p&gt;

&lt;p&gt;心静自然凉。狂心顿歇，歇即菩提。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-05-1&quot;&gt;2007-06-05&lt;/h3&gt;

&lt;p&gt;它萎缩在角落,漠然的看着这周遭。周围只有冷漠,鄙夷的眼光，寒意包围着它，这种寒由心而生。逃避、低头躲避着人们的目光。它拼命的逃跑。她走到它的面前，蹲下身出手。它警戒的退了退,她抚摩着它，给它吃的。然后站起身走了。它惊呆了,等它回过神来,她走远了。&lt;/p&gt;

&lt;p&gt;它沿路追赶，但只有两旁表情僵硬的路人。她已不见踪影。它每天走着这条路，它期盼能遇见她。等她… …&lt;/p&gt;

&lt;p&gt;它怀疑了,不该等了。或许这就是猫的本性吧。不能像狗一样忠诚吧。这段时间她们来看它。它以为会因为她们忘掉她。但最近她们也不来了。在它身边的人越来越少了。它不想要求什么，只要她们很好就够了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-07&quot;&gt;2007-06-07&lt;/h3&gt;

&lt;p&gt;今天高考。&lt;/p&gt;

&lt;p&gt;两年了，回首看看高考的一段历程，淡笑。也无风雨也无晴吧….&lt;/p&gt;

&lt;p&gt;高考的日子，自然是很难忘的，特别是考前一个月，日子很充实的感觉吧…觉着终于有了一个明确的目标，为之努力，为之拼搏。很幸运，最后老天看得起我，给了我个不错的结果….&lt;/p&gt;

&lt;p&gt;昨晚闲，于是把各省的模拟题稍微看了一下，令人庆幸的是，很多题目虽然没法很快完成，但总还是有个思路。于是心里踏实了一些——就算被退学了，再读一年还是有实力可以考考的（真实打算是要是被退学了就在清华卖煎饼好了..）。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-08&quot;&gt;2007-06-08&lt;/h3&gt;

&lt;p&gt;唰，一张纸一交，我就离开了电子工程系，到了微纳电子学系。&lt;/p&gt;

&lt;p&gt;兴趣所在，按自己兴趣办事，没什么不好，虽然也许会有一点逃避的因素，但这绝对是良性的。&lt;/p&gt;

&lt;p&gt;其实我觉着微纳电子才是真正的电子工程的内容，信号那边的还是应该叫无线电的好。&lt;/p&gt;

&lt;p&gt;班里班外也有大牛被调剂到微电的，选了电信然后又写了服从调剂，大抵都是会被调剂的，虽然也有人是为了“形式”上好看一点…比起他们我要幸福多了，至少命运掌握在自己手里，呵呵…&lt;/p&gt;

&lt;p&gt;今天选课，进了选课系统，发现跳出的欢迎框已经赫然写着“微纳电子学系”了，说实话，有一点点伤感。毕竟是自己待了两年的地方，恋恋不舍是会有的，就像当初离开初中，离开高中那样….&lt;/p&gt;

&lt;p&gt;忽然想到了毕业。一生中能有多少同学…一生中能有多少相处得很好的同学？很多的人，毕业之后，也许一生中就再也见不到一面…“珍惜”二字，写来容易，品来心酸….&lt;/p&gt;

&lt;p&gt;看自己的课表，挺空的…一点都不像传说中的炼狱般的大三应有的课表。于是疯狂地找各种选修课，甚至是别的系的课或者是本系在高年级的课程，来充实自己的课表…德语，VHDL，文献检索，魏晋风度…终于选到一个差不多的分数，然后突然发现自己的空虚….空虚的可怕。&lt;/p&gt;

&lt;p&gt;总结了下现在的自己。强迫症，偏执狂，双重人格，精神分裂，也许就差个自闭症了…要是什么时候我的blog很久没更新的话，就基本可以断言我“五毒俱全，病入膏肓”了。呵，我也知道不好..但是，就暂时这样吧，应该，没事的…&lt;/p&gt;

&lt;p&gt;当一个人习惯了一切….他就变得麻木了。不知道这样说，对还是不对。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-10&quot;&gt;2007-06-10&lt;/h3&gt;

&lt;p&gt;寂寞的空气，我累的浑身疲惫。看着窗外星儿闪烁的夜空，我无法言语。&lt;/p&gt;

&lt;p&gt;始终思念着的人，会常常忘了自己是在想念。就象人总在呼吸一样，常常忘记空气的存在。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我选择忘记，&lt;/p&gt;

  &lt;p&gt;我选择纪念。&lt;/p&gt;

  &lt;p&gt;我总在逃避，&lt;/p&gt;

  &lt;p&gt;我总在淡漠。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们选择了不同的方式，在过去某个时间里。我们微笑想着彼此，早已失去话语。眼神的注视，神情的冷漠。&lt;/p&gt;

&lt;p&gt;透过阴霾，穿过人群，挨过无数个寂静的黑夜，我在角落里等待。我梦见那个微笑着的温暖。可是却隔得远，无法越过去。我站在这边看着对岸的你，我无能为力，消失离去。&lt;/p&gt;

&lt;p&gt;永恒是什么，我的字典里早已不存在的字眼。没有想象，没有期待，默默看着一切慢慢发生。原来只是因为懦弱，因为怕伤害，怕伤痕累累地无法愈合。&lt;/p&gt;

&lt;p&gt;季节、天气。变幻蔓延。&lt;/p&gt;

&lt;p&gt;看浮躁的人群，孤独在想念你。&lt;/p&gt;

&lt;p&gt;这个城市，我可以沉没。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-11&quot;&gt;2007-06-11&lt;/h3&gt;

&lt;p&gt;有点微醺的感觉。&lt;/p&gt;

&lt;p&gt;同学聚会，有点醉了。但是很高兴。&lt;/p&gt;

&lt;p&gt;“那种微酸的滋味，有点微醺的感觉，梦做一半比较美…不想睡，我要陪你一整夜我要幸福的催眠，天旋地转的晕眩。”&lt;/p&gt;

&lt;p&gt;说不上天旋地转，倒也是昏昏沉沉。&lt;/p&gt;

&lt;p&gt;不错，这样的状态，肩上的压力顿时小了许多。&lt;/p&gt;

&lt;p&gt;还不够醉，很多事情，还不能说。也许会成为我一生的秘密吧，呵呵….&lt;/p&gt;

&lt;p&gt;恩，据说醉的时候，做题效率最高，我去试试看。&lt;/p&gt;

&lt;p&gt;期末考了，也许会不经常更新了，但绝对不是自闭症能够哦。还请大家放心。&lt;/p&gt;

&lt;p&gt;桌面换了张很可爱很可爱的猫咪，光看着都会很开心，呼呼&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-19&quot;&gt;2007-06-19&lt;/h3&gt;

&lt;p&gt;习惯了每周二被面包板煎熬，似乎已经开始逐渐习惯了，不知道是好兆头还是坏兆头…&lt;/p&gt;

&lt;p&gt;今天终于最后一次了，虽然只是暂时的最后一次，但是一想到马上就可以从万恶的实验中解脱1个月，说实话心里还是很爽的。&lt;/p&gt;

&lt;p&gt;最后一节量子课和体育课已经过去了，看着课程一科科结束，才真的感到时光的易逝。假期时候的信誓旦旦仿佛就在昨日，而这个学期我对自己诺言的履行，还算差强人意吧….&lt;/p&gt;

&lt;p&gt;体育课下得早，就顺便到书店看看书去了。发现原来数字集成电路并非像想象中的那样简单哦，当然，电动力学依旧还是很BT，郭硕鸿那本书讲得倒确实通俗易懂，但是如果只看那本书的话，估计是没啥前途的了…&lt;/p&gt;

&lt;p&gt;最后买了本VHDL，为了下学期的课程。貌似数集也需要熟悉一点HDL的。也许假期有事情做了。呵呵…呵呵…&lt;/p&gt;

&lt;p&gt;总之，先祈祷今天插板顺利吧。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-22&quot;&gt;2007-06-22&lt;/h3&gt;

&lt;p&gt;今早翘课了…一节信号，一节微电，都是我喜欢的课…翘了。&lt;/p&gt;

&lt;p&gt;说来很悲哀..我翘课去了图书馆，看了一整天的随机数学。才打定主意随机只求一过，但今天发现以现在的水平，或许过都会有困难。平时上随机课确实蛮认真听了的，作业也很好的做了的，但是回头一看，还是很多东西不懂。或许这就是所谓的天份吧。想想下周就开考了，复习工作还缺很多..不安。&lt;/p&gt;

&lt;p&gt;考试要持续两周..6门，虽然不少，但也还好。比较一下修双学位的同志们从上周开始基本算一直考一个月来说，是要轻松很多了。不过三个星期以后就能回家咯，想想还是不错的…&lt;/p&gt;

&lt;p&gt;假期的安排，也许会出去玩一下吧。多陪陪父母，多会会同学….每天都和书打交道，觉着自己都有点脱离社会了….以前从没感到的“两耳不闻窗外事，一心只读圣贤书”的境界，似乎离我比较近了…莫非真的成了书呆子=_=哎&lt;/p&gt;

&lt;p&gt;晚上和在清华的高中同学聚了一聚，走出南门的一瞬间，才发现自己真的好像很久没出过清华了。复习很紧张，抽出一点时间来放松一下，挺好的。&lt;/p&gt;

&lt;p&gt;懒懒的和小刘艳艳丹丹聊着各自的生活，晒着5点半钟半沉的太阳。熟悉的南门，不熟悉的心境；熟悉的地点，不熟悉的时间。疲惫的一个学期的最后一个周末，休息一晚上吧，当给自己放假…&lt;/p&gt;

&lt;p&gt;两年前的我们，面前是同样的起跑线；两年后的今天，早已有人分道扬镳。看着06级的孩子们谈笑风生，陡然发现自己的时代好像已经过去，心不由一凉…或许是未老先衰，也或许真的老了。&lt;/p&gt;

&lt;p&gt;夏季学期…连续一个星期的实验…!@$!@#!@#@%#@&lt;/p&gt;

&lt;p&gt;好吧…说实话，我还是有点期待的….不过就是不知道到时候是什么心情了。也许真的会做到吐出来…&lt;/p&gt;

&lt;p&gt;再说吧~~~&lt;/p&gt;

&lt;p&gt;胡言乱语…睡觉了。明天的课，不能再翘了~~&lt;/p&gt;

&lt;p&gt;期末~fighting!!&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-24&quot;&gt;2007-06-24&lt;/h3&gt;

&lt;p&gt;今天整理内务。把大一到现在的书整理了下…看起来还是很多的，而且很杂。书多，但是真正学好的貌似却没有几本…本科过了一半，书柜已然摆满，我又是向来舍不得卖书的人，真不知道以后这些书该怎么办了…现在只能希望妹妹也考上清华，而且还“不幸”地也跑到电子系..这样这些书就能有个着落了吧..呵呵&lt;/p&gt;

&lt;p&gt;在外人看来，能在THU这样的学校念书，是一种幸福吧…但是同学们经常讨论的却是“是不是后悔来这里”，或者是“是不是后悔当初选择了电子”。诚然，THU的变态是全国有名的，电子系的变态在THU里也是有名的，但是真的就那么惨么？不见得啦…昨天和艳艳的聊天，今天和宁宁的聊天，发现其实每个人都很辛苦的。大家有自己的生活，有自己的烦恼，也必须在这人生青春的几年内，为自己写下一谱绚丽的乐章。我一直想相信，其实没有谁比谁更累，只要心甘情愿，一切都会变得十分简单。&lt;/p&gt;

&lt;p&gt;这个假期似乎开始有很多人不回家了吧…大抵都是GRE班弄的…奇怪的是，好像自己身边顶尖人才荟萃的电子系里，出国气氛不是那么的浓重，倒是走出电子就发现G和T满天飞…也有可能是电子大牛们都喜欢背地里阴着干吧….恩…我应该算是打定主意不出国了…(其实是条件差出不了，哈哈…真酸…)&lt;/p&gt;

&lt;p&gt;假期，能回去还是回去…至少半年没见的父母了。树欲静而风不止，子欲养而亲不待…以后的时间，肯定是会越来越少，一定不要把遗憾留到最后，那样的话，绝对是一种悲哀…不论对亲人，还是对自己。这个假期应该会和家人一起出去旅游了..想来也至少有两年没有陪他们好好地出去走走了，期待中….&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-06-26&quot;&gt;2007-06-26&lt;/h3&gt;

&lt;p&gt;明天开考了。一个学期的最后的精彩吧。&lt;/p&gt;

&lt;p&gt;和以前不太一样，完全没有紧迫感。今天很懒散的讨论了一些比较难的题目，回顾了下模电的基础中的基础…恩，上学期学的那些内容…不知道为啥就突然想看，虽然知道和这次考试无关。不知道这样的感觉是好是坏…总之今晚就没看书了，打开PPLive，打开Blog，给自己一点其他事情做做。被人家说这学期学成神了..其实我很冤枉，我一直只是在做自己喜欢的事情嘛…&lt;/p&gt;

&lt;p&gt;这个学期确实挺累，但也还算充实饱满。没有什么目的，一味向前冲就是了…自我感觉良好吧…或许这就只是自我感觉而已，呵呵。趁着学期快结束的时候，感谢下这段时间帮过我的所有人..班里的所有兄弟姐妹们，同寝的哥们，远方的以前的同学，家乡的亲人..要是没有了你们的鼓励和支持，我是坚持不到现在的。&lt;/p&gt;

&lt;p&gt;天气很热，要是明天能够凉快一点，那就完美了~~呵呵^^p&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-01&quot;&gt;2007-07-01&lt;/h3&gt;

&lt;p&gt;莫名其妙地，突然想听Canon。&lt;/p&gt;

&lt;p&gt;怀念它的一分清新明快吧，飞舞的节奏，时而欢乐，时而深沉，在善变中蕴着对生活追求不变的坚持。第一次听到这歌不知道是什么时候了，但是印象最深的大概还是野蛮女友里那Canon背景音下礼堂中一朵鲜红的玫瑰了。还有就是拉威尔的波莱罗舞曲…算是陪伴了整个学年的东西了吧…恩，浪漫的钢琴，可惜我不会….有点后悔小时候没有学上一两样乐器，没办法让自己重复这些旋律…不过转念再一想…以我家三代乐盲的资质，我想要学会什么乐器，大概会比随机数学考试拿满分更难吧…呵呵&lt;/p&gt;

&lt;p&gt;一下子就考完一半了，真快。&lt;/p&gt;

&lt;p&gt;一直听到的是，当一个人全心投入一件事情中以后，便会觉得时间过得特别快。现今看来，没有全身心投入，时间也过得挺快耶。果然青春易逝，得抓紧才行…这个假期，一定拿出行动来~！等我。&lt;/p&gt;

&lt;p&gt;吼吼~~~&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-03&quot;&gt;2007-07-03&lt;/h3&gt;

&lt;p&gt;VODKA…真好喝&lt;/p&gt;

&lt;p&gt;但是到后来，有一点苦……虽然已经兑了果汁，但是清冽仍在…全身发热，果然很厉害….&lt;/p&gt;

&lt;p&gt;我决定&lt;/p&gt;

&lt;p&gt;睡一下去，呼呼&lt;/p&gt;

&lt;p&gt;睡个好觉，补偿自己。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-07&quot;&gt;2007-07-07&lt;/h3&gt;

&lt;p&gt;….连考六门十二天，人都考糊了。&lt;/p&gt;

&lt;p&gt;考完收工。好好玩两天，然后回家….&lt;/p&gt;

&lt;p&gt;这个假期注定会很忙吧，一个小学期紧接在后。昨天拿到了小学期里数模混合的一些材料，看着头还是很大…三天时间要做出一个像模像样的系统，对于一个还基本没太接触专业的本科学生来说，算是有点难吧。不过既然有人能做出来，那我也不要比他差！哼哼~~&lt;/p&gt;

&lt;p&gt;但数模混合还只是整个小学期的三分之一…MATLAB和FPGA还都是未知数…下个学期的电动和固物也还得照顾…惨哦…下学期的话，看课表是蛮松的，但是现在我貌似有被抓到总支工作的危险，到时候是忙是松就说不定鸟~~先祈祷吧。&lt;/p&gt;

&lt;p&gt;不对不对，刚考完，不能想这些东西..我还不自虐。所以说，今晚去唱歌，明天疯一天~还约了人去吃麻辣诱惑…恩恩。期待呢，好久没有吃辣了，我恨北京这充斥着小粉和甜面酱的世界..呼``&lt;/p&gt;

&lt;p&gt;陆陆续续好多高中同学到北京GRE了，各自都怀有一份梦想吧。挺可惜的是，不是住得远就是时间不对，见一面都难。只好回昆明聚了。&lt;/p&gt;

&lt;p&gt;还没考完的同志们，坚持住，胜利就在不远处~~！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-10&quot;&gt;2007-07-10&lt;/h3&gt;

&lt;p&gt;11点30的飞机&lt;/p&gt;

&lt;p&gt;听了ZYC的建议,起了个大早..提早了很多到机场。无聊地等待了将近两个小时，到了应该登机的时候，被告知飞机刚刚到达本站，还要准备…于是等待又继续了下去。&lt;/p&gt;

&lt;p&gt;想不到这还只是噩梦的开始。&lt;/p&gt;

&lt;p&gt;其实没让我们等太久，就能登机了。上飞机，继续等待…为什么总是有一两个乘客会不顾其他大多数人的时间，迟迟不肯上机呢….又半小时后，终于离开地面了~~&lt;/p&gt;

&lt;p&gt;噩梦还在继续….预计中的3个小时，到达昆明上空了。无尽的盘旋。据说大雨。这我是知道的，云南的天气，说变就变..明明刚才还在说晴25度，一下子就直接不能降落了。在机场上面转了半个小时，然后直接被拉去成都了=_=&lt;/p&gt;

&lt;p&gt;一直在天上的感觉真的很不爽..但是值得庆幸的是还能在成都降落…飞机加油，然后再次起飞…&lt;/p&gt;

&lt;p&gt;早上8点半出门，晚上6点半到昆明…呼&lt;/p&gt;

&lt;p&gt;其实要是飞机上坐我旁边的是个PLMM的话，我也就不说什么了..结果却是一个一直在挖脚的大叔…唔..我真不知道该说什么了。&lt;/p&gt;

&lt;p&gt;不过想想还好，比起第一次寒假回家时从早上9点一直等到凌晨3点的经历，这不算什么了。&lt;/p&gt;

&lt;p&gt;不过为什么这种事情都让我碰上了呢…四次回家两次不顺…百分之五十的几率，未免太高了吧=,=&lt;/p&gt;

&lt;p&gt;sigh…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-12&quot;&gt;2007-07-12&lt;/h3&gt;

&lt;p&gt;雨一直下，三天了。&lt;/p&gt;

&lt;p&gt;北京可不会有这样的雨，恩，这就是故乡的味道吧~&lt;/p&gt;

&lt;p&gt;成绩陆续出来了，已经知道的几科，比自己预想的要差一点。但是关系不大吧…很多事情尽力就好。只是这学期90+的目标算是彻底破灭了呵呵~~嘛，也好，下学期有继续努力的动力了！&lt;/p&gt;

&lt;p&gt;今天猛地意识到，自己居然也会大半天的守在电脑前面一遍一遍的登陆info去关心自己的成绩…和以前的我比，真的变了..变得对这些事情关心了…活得潇洒确实很难耶。开始佩服三年前的自己了，有洒脱的实力，真的是一种幸福，怀旧ing&lt;/p&gt;

&lt;p&gt;窗外雨不知道还得下多久。高中同学能回来的大都回来了。唔~该见见面叙叙旧了…&lt;/p&gt;

&lt;p&gt;稍微看了下电动力学，很晕…教材都很难看，更别说题了。说实话，我已经没有信心学这门课了..不知道该怎么办。&lt;/p&gt;

&lt;p&gt;sigh..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-21&quot;&gt;2007-07-21&lt;/h3&gt;

&lt;p&gt;刚回到家…&lt;/p&gt;

&lt;p&gt;好像没太多想说的..感触的话，是有一点，但是都不值一提吧。始终是跟着别人去的，所以很多事情都没我什么说话的权利，恩，情况就是这样的..&lt;/p&gt;

&lt;p&gt;分数是全部出来了的，和理想的有不小差距，但毕竟是进步了，这就行了…在考试分数很低的时候，我往往就安慰自己说，其实课程的内容都掌握的啦，就是不会考试而已，偶然偶然。但是每每这样想过，心中便会觉着不安..莫名的不安，总是欠一点什么，自己又说不出来~…&lt;/p&gt;

&lt;p&gt;开始庆幸自己选择微电了，因为这几天继续看电动力学的时候，发现这门课绝对需要花费我海量的时间，否则下场很可能就是挂掉（当然如果老师比较水像吕嵘那样的话，还是很有希望过的）。电信的那么多课程..什么DSP什么通电..当然还有电动力学…唔，好吧我弃了。想都怕了..&lt;/p&gt;

&lt;p&gt;有时候想，一个学期学那么多东西，真正掌握到手的有多少，真正以后能用到的又有多少。好吧..就算它是基础课，我觉得也就学到以后真正需要用的程度就好了…比如工科微积分，会算积分会解偏微分再会个Taylor展开，也就是了..至少到现在真正用到的就这些。至于什么一致收敛啊，实数完备性啊，学了干嘛？我一辈子也用不到的吧估计..但是不学还不行，考试要的，danm it。感觉确实是浪费了不少的时间呢。&lt;/p&gt;

&lt;p&gt;牢骚先到这…开始干活..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-23&quot;&gt;2007-07-23&lt;/h3&gt;

&lt;p&gt;今天很不爽。他们一个二个都去上新东方了…&lt;/p&gt;

&lt;p&gt;我就在家颓废的看看魔兽比赛…&lt;/p&gt;

&lt;p&gt;鲜明对比…&lt;/p&gt;

&lt;p&gt;该死的雨，下这么得意，据说好多天了。猫儿们都过不来了..可以隐约听到它们叫，但是不想召唤，怕淋了它们弄生病..也许是多虑了，没有那么脆弱吧..苦了它们了。特别是新生的小猫..再坚持几天吧…&lt;/p&gt;

&lt;p&gt;移位乘法的实现，想了很久了，还是没有结果。本来思路是清楚的，但是一坐到电脑前面真正开始写的时候，脑袋又浆糊了。转头一看，假期过了一半了吧。还好并非一事无成..比较欣慰的。&lt;/p&gt;

&lt;p&gt;现在计划彻底乱了，烦躁..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-26&quot;&gt;2007-07-26&lt;/h3&gt;

&lt;p&gt;星期四，天气晴。&lt;/p&gt;

&lt;p&gt;终于不下雨了…&lt;/p&gt;

&lt;p&gt;久违的阳光，茵茵的草地，这才是假期嘛```下了不知多久的雨呢，人都发霉了..恩，要好好的晒一晒！&lt;/p&gt;

&lt;p&gt;看日历的时候突然发现都26号了..记得回家还没几天呢啊..为啥总感觉假期过得特别快而学期过得特别慢呢~~真是的。不过转念一想，以后要是干起活来，估计是没有什么所谓假期的吧，那就是说我大学的这几个假期，很可能成为一生中最后的比较传统的假期了..好可怕..赶快享受假期才是王道….&lt;/p&gt;

&lt;p&gt;天又阴了….刚才还能见到阳光阿…老天爷，别吓我&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-07-27&quot;&gt;2007-07-27&lt;/h3&gt;

&lt;p&gt;击鼓传花的问答游戏？有意思..&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;你会选择过去还是未来？理由&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;当然是未来。总活在过去是懦弱的表现，而且我渴望更多不同的生活体验。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果你的爱人背叛了你怎么办？是分手还是给她一次机会？&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;爱她，希望她幸福。离我而去，自然是因为有人能给她更大的幸福吧.精神上彼此的相互独立是我所追求的。一切随缘。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果以前的你是不快乐的，有什么方式可以让自己快乐呢？&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;饱饱的吃一顿，轻轻的吹吹风，懒懒的睡一觉，再用另一种眼光来看世界，自然就会快乐起来&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当你的幸福消失了你怎么办？&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;大叫着谁动了我的奶酪，然后穿上跑鞋寻找新的奶酪^O^。还有什么办法呢？总不能原地打转坐哭到天明吧呵呵&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;希望以后的你是什么样子的？&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;随遇而安，追求独立自由，秉持猫的心态，冷眼看尽繁华。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-08-05&quot;&gt;2007-08-05&lt;/h3&gt;

&lt;p&gt;我回来了…&lt;/p&gt;

&lt;p&gt;真是受不了，雨啊雨啊雨。快疯了…..一直下个不停&lt;/p&gt;

&lt;p&gt;莫非我真的是雨神..?!&lt;/p&gt;

&lt;p&gt;下周就要回北京了。新的生活等着我&lt;/p&gt;

&lt;p&gt;回北京再慢慢写，恩&lt;/p&gt;

&lt;p&gt;大家假期过得都好吧，要一直愉快哦&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-08-14&quot;&gt;2007-08-14&lt;/h3&gt;

&lt;p&gt;这几天忙着玩仙剑四了，一直没写博，从今天开始恢复更新频率。&lt;/p&gt;

&lt;p&gt;从95年的仙一就一直陪着仙剑走到现在，对仙剑的感情自是不凡，或许也有一点点依赖。仙四结局CG和制作人员名单结束的那一刻，我知道，这个充满悲欢离合的舞台，再一次拉下了帷幕——又一部仙剑作品离去了。&lt;/p&gt;

&lt;p&gt;真的是很不错的游戏，至少近两年来我已经不曾有过这样的感动了。天河、菱纱、梦璃，甚至紫英、玄霄都陪伴了度过了几天不错的时光。恩，其实小学期开始都已经两天了，虽说假期里有所准备，但是还是不能太过放纵自己。收心，收心~~&lt;/p&gt;

&lt;p&gt;回想假期还是做了不少事情的，但是一想到很多人现在才陆续回昆明..而且又要组织聚会..我却在北京整天对着电脑写程序…呜。怎么会这样….&lt;/p&gt;

&lt;p&gt;写MatLab去了….恩，写程序的效率，其实是和键盘是否顺手密切相关的…&lt;/p&gt;

&lt;p&gt;事实就是这样的。从今晚开始好好学习了..吼吼~~~&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-08-16&quot;&gt;2007-08-16&lt;/h3&gt;

&lt;p&gt;入学两周年祭。&lt;/p&gt;

&lt;p&gt;说“祭”有点夸张了，不过确实想写点东西。&lt;/p&gt;

&lt;p&gt;去上课的时候人家问今天是什么日子，还提示说是很有意义的一天。说来惭愧，我首先想到的却是日本投降纪念日后一天…最后提示两年前，我才恍然大悟..原来是入学两周年了。时光飞逝，晃眼两年，周围大家也都是一片嘘唏..&lt;/p&gt;

&lt;p&gt;两年前的今天，一样是个雨天。雨中我踏上这片土地，伴随的是泥土的芬馨，怀着很高的理想。如今我却身在一个关键的转折..两年来我所经历的一切，确实改变了我很多很多，其中有失落的沮丧，也有成功的欢欣。正是在这次次的得失中，我学会了用自己的方式看待这个世界。虽然以前也曾有些想法，但是真正清晰起来，靠得还是这两年。&lt;/p&gt;

&lt;p&gt;人生是什么？有人说“人生如戏”，游戏人生，倘若真能做到快意畅达，固是好事，但是若仅仅只是游戏，又有多少人有快意畅达的资本？要认真做事，积累实力去追求精神的解放，这样的话，又哪里会有游戏人生的一份豪爽？有人说“人生若梦”，就算是梦，也需知生死浮沉，只不过是这梦的始末峰谷…而假若人生真如场梦，我们却又哪里知道自己是在梦中？或是在梦中的何处？梦醒之后，又会是怎样的一个活了一辈子确又十分陌生的世界？….&lt;/p&gt;

&lt;p&gt;一直很欣赏一句话，“平淡对待得失，冷眼看尽繁华”。大学的两年，可以说就是我对自我感情加以限制的两年吧…太多的得失，太多的权衡，真的让我很疲惫。每天在应接种种选择的利弊的同时，我早已感到力不从心….不如看淡吧，像菊那样，选一个无人问津的时节，再美丽的绽放自己…一时的功利名望，却是永远抵不过一生的清新淡雅的。不止一个人和我说过，觉着周围人的功利心太强，凡事想求一个捷径通途。大学里尚且如此，外面的社会更何其厉！不能说“众人皆醉唯我独醒”，但最少也应该争取“出泥不染，濯涟不妖”..自己的信念，我会一直坚持，就算会吃亏，就算会失败，至少我也能昂着头说道，我是一个有自己准则的人。我知道，很多时候，人活着为的，就仅只是自己的选择。&lt;/p&gt;

&lt;p&gt;已然过了午夜整点，已然不是两周年正，但我的选择依然不会改变。&lt;/p&gt;

&lt;p&gt;上善若水，人淡如菊….&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-08-20&quot;&gt;2007-08-20&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FPGAFPGAFPGAFPGAFPGAFPGAFPGA
FPGAFPGAFPGAFPGAFPGAFPGAFPGA
FPGAFPGAFPGAFPGAFPGAFPGAFPGA
FPGAFPGAFPGAFPGAFPGAFPGAFPGA
FPGAFPGAFPGAFPGAFPGAFPGAFPGA
FPGAFPGAFPGAFPGAFPGAFPGAFPGA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;发泄完毕..继续回去写代码。&lt;/p&gt;

&lt;p&gt;晚上再来更新..&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;回来了..&lt;/p&gt;

&lt;p&gt;VHDL写多了。&lt;/p&gt;

&lt;p&gt;很乏味的生活呐，每天早上起来第一件事情就是写代码，然后到11点半，午饭。回寝室继续写代码，然后5点半吃晚饭。回寝室继续写代码，然后到12点左右休息一下…还好这种生活不会持续太久了..FPGA的必做和前边几个项目的选做都搞定了。最后的选做？让它见鬼去吧…对于出题者本身诚意就不足的题目，我向来不太愿意花时间去揣测的…&lt;/p&gt;

&lt;p&gt;FPGA完了以后还有更恶的MATLAB和数模混合…说小学期比正式上起课来要变态，那是一点都不为过。恩，估计以后两三周的日子，会继续乏味了吧。不过也许以后想起来，会是一段很充实的时光…&lt;/p&gt;

&lt;p&gt;昨天是七夕，找惯例喝了摩卡…咖啡，牛奶，糖浆，巧克力…不得不说摩卡正是一个甜蜜的选择。不过，独峰的摩卡很一般就是了，让人从口中品不到浓郁的香甜…还好柚右漂亮的衣服弥补了咖啡的不足，嘿嘿~~^_^&lt;/p&gt;

&lt;p&gt;哎。…其实还是很伤感的…不符合我年龄的..&lt;/p&gt;

&lt;p&gt;长大了..其他人都是从感性趋向理性，为什么我恰恰相反….&lt;/p&gt;

&lt;p&gt;FPGA，KO。YES！..~~&lt;/p&gt;

&lt;p&gt;终于可以暂时不写代码了不看波形了。我大概需要休息一下&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-08-23&quot;&gt;2007-08-23&lt;/h3&gt;

&lt;p&gt;可以休息一天了。&lt;/p&gt;

&lt;p&gt;今天的FPGA检测还算顺利，虽然GW48-PK2和我闹了点别扭，但是它数码管本身存在的问题却还是被俺一眼洞穿….应蓓华小姐硬是不信我逻辑的正确性..结果换了旁边的板子才最后使她心服口服，哼哼。&lt;/p&gt;

&lt;p&gt;所以可以稍微休息一下了。虽然还有最后的一个选做依然循环在无尽的错误中，但是我还是决定今晚不弄FPGA了，恶心中。据说本科毕业写两年FPGA以后，就可以达到一个很高的水平了，但是也仅限于语法的应用…算法的东西还是需要..恩，智慧加勤奋…一个写代码的前辈如是说…&lt;/p&gt;

&lt;p&gt;刚才没事上网查了下，发现我们有几个选做实验直接是一些学校的毕业设计的题目了..很是寒。最后的选做实验的难度还超过了一些发表在灌水期刊上的paper..更寒…突然对thu严酷的训练有了一丝好感，小得意…&lt;/p&gt;

&lt;p&gt;两年了，第一次体会到自己还是变强了些的。以前总是沉浸于横向对比，一定要和周围的人决出个高下，然后就被各种巨牛三百六十度碾压，现在想想还真是可笑的。地球60亿人，人上有人…每天去横向岂不累死？纵向一看，自己的进步确实不可否认的..欣慰中``&lt;/p&gt;

&lt;p&gt;明天开始，估计就要天天面对传说中的“东方红”了…so so la re,do do la re….&lt;/p&gt;

&lt;p&gt;想起头就痛，特别对我这种音乐盲，估计更是煎熬了``&lt;/p&gt;

&lt;p&gt;先不管它…恩，先放假一晚。就这样&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-08-25&quot;&gt;2007-08-25&lt;/h3&gt;

&lt;p&gt;刚看完了《好奇害死猫》，很好的片子…其实上个学期就看到宣传片了，因为片名的curiosity kills a cat本来是我很喜欢的一句英国谚语，并且cat这种生灵有确实与我有些缘分，就决定了一定要好好看看这部片子。&lt;/p&gt;

&lt;p&gt;好奇害死猫，真实的惨淡。在欲望面前，原来人是如此的脆弱；在平静的表面下，却有那么多盘根错节的关系。也许郑重永远不会猜到，自己一直只是棋局中的棋子；也许晓霞至死也不明白，为何只是单纯的对爱追求，却换来的是如此凄惨的结局；也许千羽在孤零赎罪的后半生中，永远不会清楚自己到底喜欢的是红玫瑰还是白玫瑰；也许momo还会拿着手机看照片，但是她却也不会懂得这个世界的险恶与复杂。&lt;/p&gt;

&lt;p&gt;偏执的对立和偏执的爱情，成就了一段悲剧。在这段故事里，没有猫，但却人人都是那只猫。对富贵阶层的好奇，让人堕入了欲望的深渊；对激情生活的好奇，让人失去了选择的余地；对真挚感情的好奇，让人丢掉了宝贵的生命；对绝对完美的好奇，让人丧失了心智上的宁静。每个人都过着自己的生活，每个人都猜不透别人的世界….一个一个连环的计算中，有着太多的诱惑，而最终的受害者，还只不过是那只好奇的拥有九条命但是一样会死掉的猫罢了。&lt;/p&gt;

&lt;p&gt;其实，看完片子之后，觉得他们都很坏，自私占有，欲求不满…但是，仔细想想，他们全都挺可怜的。代表爱情的玫瑰，最终成全的不过是情的复仇。该迷茫的，依旧迷茫；能清醒的，却已经不在…社会造就了一切，社会变了，人心就变了，人心变了，就什么都变了…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;将约克的白色和兰开斯特的红色结合，与其它花不一样的地方，是这种蔷薇在一朵花里面要么就是全白，要么就是全红。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;….一本无字的《玫瑰圣经》，一段普通的花卉描述，却将人性整个剖出。为何要如此极端..要么全白，要么全红…守护与毁灭，就在一瞬间…&lt;/p&gt;

&lt;p&gt;生活还要继续，也许好奇也还会继续..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-09-01&quot;&gt;2007-09-01&lt;/h3&gt;

&lt;p&gt;首先向大家汇报这几天的生活学习和工作情况…和我奋战在thu2007年夏季小学期的每天被&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5562|1162&lt;/code&gt;和“电灯比油灯进步多了”折磨的战友们可以跳过这一段了..因为我不想再让你们重新回首那不堪的往事..鲁迅曾说过“痛苦才是人生的原貌”..什么..？哦..也许是泰戈尔说的..记不清了..好吧，鲁迅曾说过要“敢于面对惨淡的人生”..所以我将勇敢地独自地回忆这几天曾经发生的事情….&lt;/p&gt;

&lt;p&gt;….&lt;/p&gt;

&lt;p&gt;还是算了吧..实在太残忍太血腥了..而且我也不算是“真的勇士”..那回忆还是略去的比较好..毕竟来这博也可能会有未成年人或者心脏病患者。在这里只告诫后来的孩子们..一定一定要做好充足的准备，否则这几天真的会死得很难看..厄？你说你有大作业的种子..好吧，那请无视我好了..&lt;/p&gt;

&lt;p&gt;明明在几年以前的，今天是开学的好日子了，但现在呢，怎么好像比正式开学了还惨很多…&lt;/p&gt;

&lt;p&gt;唉呀呀,本来说是要写很多很多的，可是结果真正到写的时候又不知道该些啥…胡言乱语，也许这就叫空虚？&lt;/p&gt;

&lt;p&gt;也许明天能想到要说什么..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-09-01-1&quot;&gt;2007-09-01&lt;/h3&gt;

&lt;p&gt;风中之烛，戴妃十年..&lt;/p&gt;

&lt;p&gt;戴安娜永远那么美丽，不论是外貌还是内心。&lt;/p&gt;

&lt;p&gt;否则不会有人能记住她这么久。&lt;/p&gt;

&lt;p&gt;十年前，香消玉殒，十年后，扼腕叹息。精神上的时空错乱，却又把我带回十年之前….&lt;/p&gt;

&lt;p&gt;车祸？那是车祸么？那简直就是皇室的谋杀…天妒人嫉不可怕，可怕的是要一个风中之烛的女子来承担这一切。至今再追问重重幕后，已然失去了意义，但是回首审视，却还是被戴妃倾倒，为之惋惜。&lt;/p&gt;

&lt;p&gt;灰姑娘般的出身，白天鹅般的高贵，公主殿下并不是浪得虚名。她的一切，都似童话，唯一缺的，就是童话般的结局了。她总是能把感召力赋予实际的行动，而不像某女王那样只是在深宫怨己怨民；她的出现终于使皇室和民众有了接触，却反被皇室恩将仇报。须知道，不是每个人都能那么真诚地去拥抱爱滋病人、麻风病人，也不是每个人都能在平等地和那些卑微苦难、被人遗弃的弱势群体促膝长谈，给他们安抚和支持；不是每个人都有勇气穿上防护衣戴上头盔走在危险的雷区，并去为那些被地雷误炸的小孩子包扎伤口，与双腿残疾的男子并肩交谈。可是，她能，那么真诚感人，又带动那么多人投入到慈善事业当中来。正如一本书中所写——她曾高高在上，但她也低头去抚慰那苍凉的大地。因为，她是天使，带给处于贫困、疾病及灾难中的人们以希望与信心，除了天使无人能及，那是一种与生俱来的亲和力与感染力。&lt;/p&gt;

&lt;p&gt;但是天使，却折翼了。我只是希望，她当时不要凄凉地带着无助和绝望…&lt;/p&gt;

&lt;p&gt;母亲是很喜欢戴安娜的，但是戴妃香殒之时，她却并不悲伤。她只摸着我的头告诉我，也许这才是最好的结局。十年前，我不懂；十年后，我有所感触。&lt;/p&gt;

&lt;p&gt;戴妃的美，凝结到了一瞬间。&lt;/p&gt;

&lt;p&gt;如果有机会，我会去她墓前，默默送上一朵白玫瑰。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-09-02&quot;&gt;2007-09-02&lt;/h3&gt;

&lt;p&gt;上次摸面包板已经是两个月前了，为了明天开始的“最后的疯狂”，又拿起了板子..&lt;/p&gt;

&lt;p&gt;感慨板子问题..四排二十行六十列，加上四排四行五十列，1400个彼此交连而又不连的小孔，渗着的是整个大二的酸甜苦辣。跟着班长学委领板子的时候，一人五盒塞在车筐里，高高兴兴奔回宿舍；第一次做门延时的实验，死插活插出不了结果，最后莫名其妙得以解决；一插就出结果，叫助教检查，等了5分钟，示波器上就空空如也，不得不怀疑自己人品；考试时才发现自己当初设计的时候为了一点点便利，却造成了原理上无法修正的错误导致失败，体会到了“着急也没办法”的心境；整个模电实验从始至终非常顺利，对理论知识的沾沾自喜。01-234，一块超好记的编号的面包板，真的陪我走了重要的一年…&lt;/p&gt;

&lt;p&gt;这次数模混合以后，板子们就要到它们的新主人的手里了。希望它们能一路走好，希望他们人品常在，希望他们能在这180平方厘米的板子上找到自己的人品~~&lt;/p&gt;

&lt;p&gt;嘿嘿嘿嘿…&lt;/p&gt;

&lt;p&gt;祝自己明天好运~~&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;9月4日&lt;/p&gt;

  &lt;p&gt;做不出来，抓狂啊….&lt;/p&gt;

  &lt;p&gt;快死了，谁来…救救我….&lt;/p&gt;

  &lt;p&gt;救救我…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-09-04&quot;&gt;2007-09-04&lt;/h3&gt;

&lt;p&gt;终于快完了。&lt;/p&gt;

&lt;p&gt;可怕的小学期…&lt;/p&gt;

&lt;p&gt;大部分都已经过去了，剩下的内容虽然包括一个考试，但是相比起以前的东西来说，应该是仁慈太多了。&lt;/p&gt;

&lt;p&gt;好累呢，写不动字了…喝牛奶，睡觉。我需要休息一下。&lt;/p&gt;

&lt;p&gt;祝自己好梦~&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-09-17&quot;&gt;2007-09-17&lt;/h3&gt;

&lt;p&gt;懒得数这是从小学一年级开始的第几个开学日了，总之，经过了不长的假期和比假期还长的小学期之后，又开学了。&lt;/p&gt;

&lt;p&gt;今天的感觉就一个字：困。也许是前几天的放松还在继续，睡眠也不是很有规律，再加上本来就是阴天，黑黑的，况且老师为了让大家看清讲义，还特地关了几盏灯，更是营造出睡觉的气氛。早上的两节课都是在六教A区的中等大小的教室上的，说实话除了考水平一和物理实验前的自习，我还从没在那种教室看过书，也许还不习惯吧。以后慢慢来…&lt;/p&gt;

&lt;p&gt;中午吃过饭以后到教材中心把书买了..发现已经快念不起书了，一本本得贼贵。不是很明白为什么都喜欢上原版教材，更不明白既然用了原版教材为什么并不按照那教材讲课…每次上课以后都会有种白买书的感觉。哎。只有自己看好了。上学期一本原版就看得半死不活了，这学期上来就先两本.要命哦。谁来救救我的英语…&lt;/p&gt;

&lt;p&gt;唔..看样子居然快下雨了…原本说去洗澡的…残念=_=&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-08&quot;&gt;2007-10-08&lt;/h3&gt;

&lt;p&gt;大雁飞过，菊花满头…很早的一首歌，也许叫做..中华民谣？&lt;/p&gt;

&lt;p&gt;天气转凉了。&lt;/p&gt;

&lt;p&gt;早晨出去洗漱的时候，才发现一件短袖已经撑不住了。北方的天，说变就变快得很，前天还是炎炎烈日热得不行，现今就已经需要秋装上阵。上课路上留意到旁边花圃里菊花正开得灿烂，不由感叹这次花开怎会如此迅猛强烈，毫无征兆。最后才想起..整个十一假期就没有留意过教学楼周围，自然也就不会有什么发现。&lt;/p&gt;

&lt;p&gt;菊是见得不少了，自己也很喜欢这种花。总觉得那种菊的那种意境，确实正是我所追求的，但是又觉着它是那么的高高在上，仿佛就是纯粹理想中的..曳曳摇摆，却丝毫不可及的脱俗:不与百花争相艳，百花开遍我自芳..&lt;/p&gt;

&lt;p&gt;这里的秋天和家乡很不同。昆明常绿植物很多，一年四季整个城市就算不叫葱郁，至少也花花绿绿。北京的树叶都是要掉的，学校主干道的叶子，每年都全落一次，提醒着大家秋冬已至。特别遇到大风天的晚上，一觉起来，满地黄叶，若正好起得早不赶时间，路上人少的话，便可以悠悠的下车，一步一步踩在颇有厚度的落叶上，享受清晨的掺着芬芳的空气。绝对是一席美景，绝对是一番乐事…恩…今年还没落叶，还有机会…&lt;/p&gt;

&lt;p&gt;感叹一下…电动力学好难…&lt;/p&gt;

&lt;p&gt;不过计算机网络好像更难…不知道该笑还是该哭….得过且过？sigh…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-16&quot;&gt;2007-10-16&lt;/h3&gt;

&lt;p&gt;自己越来越可怕了。&lt;/p&gt;

&lt;p&gt;早有人说过我有精神分裂症，我向来是嗤之以鼻的，不过今天我确实感受到了。&lt;/p&gt;

&lt;p&gt;前一分钟，还在困扰，忧郁….&lt;/p&gt;

&lt;p&gt;后一分钟，却在享受阳光….&lt;/p&gt;

&lt;p&gt;然后，又转到阴天和别人吵架——要知道我向来是，不发脾气的&lt;/p&gt;

&lt;p&gt;这不是分裂，还能是什么？&lt;/p&gt;

&lt;p&gt;内心的痛苦？说不上…只是有点淡淡的伤感…还好我相信时间能够抚平一切&lt;/p&gt;

&lt;p&gt;或许将要，面对新的生活&lt;/p&gt;

&lt;p&gt;告别混沌态，是不是也意味着，无限的可能也就此终结？笑..量子力学中毒症…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-18&quot;&gt;2007-10-18&lt;/h3&gt;

&lt;p&gt;一起撒花。&lt;/p&gt;

&lt;p&gt;这几天，挺好的，也挺忙的，终于有点时间来写写BLOG。&lt;/p&gt;

&lt;p&gt;从何说起呢？我一直生活的准则是独立于世，两年来也早已习惯了每天在自己的世界中享受一份宁静和恬适，并且同时守护着一份并不真实的寂寞。直到前天，一个仿佛能永远带着微笑的孩子敲开了我的房门，礼貌又客气，小心而谨慎，说道，想搬到我的心里住..&lt;/p&gt;

&lt;p&gt;恩，就是这么突然，但又却在情理中——只不过我太粗心。我曾经错过了许多珍爱我和我珍爱的人，等到我真正反应过来的时候，常常是为时已晚。也许在七夕我就应该主动一些，但是最后却弄成这样..我真的好笨好笨…明明知道你不喜欢暧昧的。恩，这次，我不要再犯同样的错误了。（其实真的又差点错过你..但是幸好有你的耐心和勇敢..谢谢你）&lt;/p&gt;

&lt;p&gt;好啦，不再多说什么..总之，这几天，很开心&lt;/p&gt;

&lt;p&gt;但是最重要的是，你要每天开心~~因为只有你开心，我才会真正快乐。  ^_^&lt;/p&gt;

&lt;p&gt;爱你。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-20&quot;&gt;2007-10-20&lt;/h3&gt;

&lt;p&gt;城市的空中，往往星光黯淡，可见的依稀就一颗金星，还得仔细找寻才有踪迹。&lt;/p&gt;

&lt;p&gt;看星星，本来是要去数的，但经常的情形是漫天的找星星。能找到的时候，便很开心，欣赏它们在天空的飞舞，像精灵一般跳跃；找不到的时候，便安慰自己。虽然看不到星星，但是它们始终在那里：也许在某个不经意的晚上，我会不经意地抬头，伴着一些蒲公英，被那满天的星震撼~&lt;/p&gt;

&lt;p&gt;其实地上也是有星星的。好像在北京是没有见过萤火虫们了，也是，毕竟是现代化的大都市，就算是城边上的清华园，也不会再给它们合适的水源生存了。对萤火虫最早的印象要追溯到好小好小还在农家生活的时候了，盛夏的晚上，走到田埂上，就会看到“繁星”点点了。记得曾经捉过萤火虫的，但是每每一捉到手上，它们便不发光了，难道是怕人？所以弄得我现在一直很怀疑车胤囊萤夜读的故事了。&lt;/p&gt;

&lt;p&gt;嗯…说了那么多，其实是想找机会和你伴着地上星，去数天上星~~&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-21&quot;&gt;2007-10-21&lt;/h3&gt;

&lt;p&gt;42.195，一个对我来说难以置信的数字&lt;/p&gt;

&lt;p&gt;2:04:26，我相信这是一个用生命创造的奇迹&lt;/p&gt;

&lt;p&gt;今天，又是每年的北京马拉松比赛了。和往年“事不关己，高高挂起”差不多，我始终没有去跑~~嗯…天生不喜欢跑步吧，加上老妈也不喜欢我去…俗话说百善孝为先…他们也是担心我这本来就“脆弱”的身体吧…不过，好像真的却是应该加强锻炼了。可是每每有这种想法，就会被窗外阵阵寒风打消…唉，没毅力的孩子…&lt;/p&gt;

&lt;p&gt;班里好像有三个人去跑了吧，据说成绩都还不错，祝贺他们咯，至少是完成了一次对自己的挑战吧~~&lt;/p&gt;

&lt;p&gt;突然发现，有时候也该好好面对自己了。我从小生长的环境，都是很优越的。有着一贯传承算是书香门第的家庭氛围，有着衣食无忧开心快乐的富足生活，有着美满幸福互爱互敬的父亲母亲，从小受到良好教育，没遇到过什么挫折，可以说是活得蛮潇洒…然后就混到了这里，发现整个世界满不是自己当年想的样子。懵了，再然后就被生活的现实直接打到谷底…当初的锐气早就被消磨殆尽，但是心中还是会有不甘~~唔..&lt;/p&gt;

&lt;p&gt;这周最后一天了…下星期传说中的教学评估就开始了，超级大扰民的活动呐，真是的..全校上上下下都在拿这说事..但是不能随便迟到了是真的，万一一个不小心被抓住..厄厄，想太多了…两年大学，除了逃课，我还真一次都没迟到过呢，哼哼…下周会很忙，也会很充实和愉快吧呵呵&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-22&quot;&gt;2007-10-22&lt;/h3&gt;

&lt;p&gt;抽这团委例会前的一小段时间，写点东西。&lt;/p&gt;

&lt;p&gt;今天篮球小组赛的最后一场，打5班。谁赢谁出线，蛮残酷的，在缺阵了小红和昭时以后，班上的篮球水平下的还真不是一点点多。居然要打到最后一场比赛来争夺出线权，不过还好过程有惊无险，顺利拿下了。希望能走得更远吧~~毕竟这次篮球赛，我参与的算挺多得了..恩，当然是指组织方面的事情。&lt;/p&gt;

&lt;p&gt;比较悲惨的是，球赛的时候伤到腿了。捡球的时候不小心不小心磕到了台阶上，当时只觉得有点痛，心想最多擦破些皮就是了，到后来愈加厉害，拉上裤腿一看，竟然全是血..恩，伤口虽然面不大，可是不知道为什么却非常深，回来处理的时候才发现居然都快能看到里面的骨头了..轻轻碰一下就成这样，看来我真的是老了。幸运的是，正好穿了一条深色的裤子..比较好洗吧。要是白的，可能就麻烦了..对付血渍，我还不知道该怎么办。&lt;/p&gt;

&lt;p&gt;买了些水果，在女生楼门口徘徊了一阵子，最后还是没有上去..恩，估计规定也不允许的吧。不知道。弄了一晚上的固物，挺难的。刚才柚右过来，顺便帮她带过去的时候，看到好多大一的孩子在练习跳绳，嚷嚷的，很开心的感觉。还没有期中考试吧..好像记得两年前这时候的我们，应该也和他们差不多，呵呵。恩..等考完试，楼下应该不会有那么吵了~~不知道以前的学长学姐们..是不是也这样评论过我们.唉唉。&lt;/p&gt;

&lt;p&gt;10点有例会，不能再写了…就这样吧&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-24&quot;&gt;2007-10-24&lt;/h3&gt;

&lt;p&gt;乐器果然都是很高深的东西，特别是古琴。&lt;/p&gt;

&lt;p&gt;现场听过古琴回来，确实是有些感触。以前高中的时候有一套正版的民乐CD合集，就很喜欢听，特别是筝和琵琶，当然古琴也有听，不过当时蛮受不了那沉闷的低音和缓慢的节奏，听了会很想睡，大概是不够成熟吧。今天正好有个古琴讲座，正好晚上没事，正好被拉去，于是就正好被熏陶一下…&lt;/p&gt;

&lt;p&gt;讲课的老爷爷穿了身褂子，很白很素的那种，加上一头银发，端坐在琴前，真的有点仙风道骨，让人肃然起敬。先是谈琴，后是弹琴，都听得很有味道，很大程度改变了我对古琴的偏见。仔细想想，一把古琴流传了两千多年，一直是知识分子身份的象征。远到孔子、伯牙，再到公瑾、孔明，又至李白、王勃，史上凡有些名誉的士人，大多都会弹上那么两曲吧~~以琴会友，不仅是现世的交流，更是与古人的共鸣。&lt;/p&gt;

&lt;p&gt;其实突然有种想学古琴的冲动…不过看看手边那么多事情和现在忙的样子..以及老爷爷的演奏手法…- -，好吧，还是算了，没有音乐天赋的我..&lt;/p&gt;

&lt;p&gt;听听就挺好。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-27&quot;&gt;2007-10-27&lt;/h3&gt;

&lt;p&gt;香山行，天气不错。其实夜里下雨下得挺厉害，早上起来的时候还飞些毛毛细雨。但是自出发开始雨就停了，到了香山，居然还出了一点点太阳。山上叶子红的不少，加上水汽一蒸，掺在一起感觉蛮好的。要是不是人山人海的话，一切会很完美啦。&lt;/p&gt;

&lt;p&gt;一口气爬到了山顶，说实话来北京两年，除了一开始的时候象征性的在香山脚下逛了一圈，这还是第一次真正爬香山。北京附近嘛，山也大不到哪里去，比起家乡动辄2XXX米的山，这个完全只能算个小高地。没什么困难就到顶了，恩，最大的阻碍就是人多..爬不快咯。&lt;/p&gt;

&lt;p&gt;山顶的几张照片彻底废了…按快门的时机那叫一个差T_T…算了，说起来都是伤心。&lt;/p&gt;

&lt;p&gt;下山的时候人更多，一步一个台阶慢慢踱下来的。&lt;/p&gt;

&lt;p&gt;不想写了，不知道能不能凑合算一篇游记…Over…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-10-31&quot;&gt;2007-10-31&lt;/h3&gt;

&lt;p&gt;临近期中了，虽然注意提前做了好多的事情，可是还是觉得很忙很忙。&lt;/p&gt;

&lt;p&gt;三门期中考试都很硬…固物数集电动，其实哪一门学得都不是很扎实；两门期中就完的课，文献大部分算弄好了但是还想做个PPT展示，VHDL在和奶奶长谈许久之后还是前途未卜；DIC作业一周一周压过来，还掺些Project来为难人；电动作业攒了三周，想来也马上会有一个爆发；两篇古代经典的读书笔记也拦到了面前；还要抽出时间忙团委那摊子事，去组织GG李的讲座…唯一值得欣慰的是量子作业写完了。唔，30分的体力活呐。&lt;/p&gt;

&lt;p&gt;不能写了，要不今天的数集又看不完了&lt;/p&gt;

&lt;p&gt;恩，忙归忙，过得还是很开心的——因为有你~~^_^&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-11-16&quot;&gt;2007-11-16&lt;/h3&gt;

&lt;p&gt;其实也已经习惯了北京天气的说变就变，前天还是好好的，昨天一下雨，地上结一点冰，突然就那么冷下来了。&lt;/p&gt;

&lt;p&gt;不过今天倒是一个好天气。早晨起来的时候，发现一缕阳光射到屋子里（我们是朝南的房间，足见我起得多晚了），暖暖的很舒服。一两只鸟儿还跳到阳台上叽叽喳喳，冬日里也还是一副活泼的模样。&lt;/p&gt;

&lt;p&gt;吃了几片面包，然后去买花。一个月的时间倏忽而过，彼此也有了更多的交集。一束并不鲜艳的玫瑰，给你，也给我自己。&lt;/p&gt;

&lt;p&gt;每天都要快乐~ :）&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-11-18&quot;&gt;2007-11-18&lt;/h3&gt;

&lt;p&gt;这一周很忙也很累，好多门的期中考试，除了一开始的固体物理，其他好像都不是很理想——或许固物也只不过是自我感觉良好罢了，希望明天出成绩的时候不要哭出来才好。（开玩笑…好像至今为止我还没为考试伤过心 :P）。今天的电动回来的时候本来已经基本万念俱灰了，但是很happy的发现长征gg在网络学堂上贴了公告说最后一题题目出错，所有同学那个题目按满分记…乍一看是很好，不过仔细想想，为了分数分布，估计会在别的地方更加克扣吧，结果估计都差不多了。果然是不可能让你那么容易蒙混过关的，哎哎，下一周新的生活就将开始。希望冬日阳光依旧~~&lt;/p&gt;

&lt;p&gt;傍晚的时候和亲爱的一起去看了色戒，还是不错的片子的，但并没有宣传的那么出彩，也不如外界声势那样宏大。演员的演技还是值得肯定的，那种人在江湖身不由己的感觉还是体现出来的了，在绝对冷漠和残忍下的一点点真情，不得不说还是能让人心头为之一颤，不过也就不过如此了。&lt;/p&gt;

&lt;p&gt;写博快一年了，翻了翻以前的东西，又大致的整理了一下，发现自己写的大抵都是空发点牢骚，或者就是到处转发别人写的东西，真正属于自己的并不很多。不过转念一想，我本来就是那种属于“情感冷漠，知觉麻木”的人，所以想法很少也尚属正常吧。只是希望这里能够记录下真实的自己，和真实的感情——不论它们是什么，只要是可以分享的，就无私地拿出来就好~~&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-11-22&quot;&gt;2007-11-22&lt;/h3&gt;

&lt;p&gt;最近读庄子，有些感慨。&lt;/p&gt;

&lt;p&gt;关于物化的本质的一些想法。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;庄周梦蝴蝶， 蝴蝶为庄周。&lt;/p&gt;

  &lt;p&gt;一体更变易， 万事良悠悠。&lt;/p&gt;

  &lt;p&gt;乃知蓬莱水， 复作清浅流。&lt;/p&gt;

  &lt;p&gt;青门种瓜人， 旧日东陵侯。&lt;/p&gt;

  &lt;p&gt;富贵故如此， 营营何所求？&lt;/p&gt;

  &lt;p&gt;——李白 《古风》五十九首之九&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;“庄周梦蝶”的故事可谓是家喻户晓的。这则典故出自《庄子·齐物论》：“昔者庄周梦为胡蝶，栩栩然胡蝶也，自喻适志与，不知周也。俄然觉，则蘧蘧然周也。不知周之梦为胡蝶与，胡蝶之梦为周与？周与胡蝶，则必有分矣。此之谓物化。”&lt;/p&gt;

&lt;p&gt;《齐物论》是《庄子》全书中非常重要的一篇。庄子提出了“齐生死，等万物”的妙论，在庄子看来，世上一切矛盾对立的双方皆无差别，不管是大与小，贵与贱，荣与辱，甚至是生与死，都可以“道通为一”，而这样万物化而为一的思想，便是所谓的“物化”。庄子用“梦蝶”这一寓言故事形象地说明了他的“物化”意境：因为它们都是由“道”变化出来的不同物象，所以在根本上是完全一致的。它们之间的生死差别、彼我区分没有必要去追究了，就像没有必要去搞清楚究竟是庄周梦为蝴蝶，还是蝴蝶梦为庄周一样。圣人就是根本取消了万物间的差别、对立，而任之自然、随物变化，从而进入“物化”的境界。&lt;/p&gt;

&lt;p&gt;庄周所提出的这样的哲学观点，在当时看来可以说是大胆，甚至在颇为开放的世人看来也是有一些荒谬的。我们每天所面对的，确实是实在存在的现实，而相对的矛盾体也是客观存在的。初读庄子，总会觉得一个人怎么能把生与死这样明显差异巨大的事物都等量齐观呢？再读庄子，我终于能体会到庄子内心的一点想法。&lt;/p&gt;

&lt;p&gt;让我们先来看看庄周给我们提出的寓言故事吧。庄子自称一天做梦时梦见自己变成了蝴蝶，而梦醒之后发现自己还是庄子，于是他不知道自己到底是梦到庄子的蝴蝶呢，还是梦到蝴蝶的庄子。在这里，庄子提出一个哲学问题，那就是人如何才能认识真实。假使梦足够真实，人是没有任何能力知道自己是在做梦的。既然我们很可能连自己到底是谁都分不清楚，又怎么可能去把世间万物逐一分而化之呢？于是，他想到了“齐物”之说。庄子认为，一切都是相对的，“物无非彼，物无非是”，“彼出于是，是亦因彼”，“是亦彼也，彼亦是也”，“彼亦一是非，此亦一是非”。庄子的这几句话，说明了在他眼中并不存在有彼此和是非的区别，世上的万物都是必然有差而不相同的，但是在它们之中，必然蕴含着能让人们认知的共同本质。既然本质相同，为何又要硬加区分呢？而事物的共同本质，便是所谓的“道”。超脱是非，便能掌握大道的枢要；掌握了道的枢要，便能进入事物的中心，就能顺应是非而无穷变化：这也正是庄子所追求的一种境界。&lt;/p&gt;

&lt;p&gt;庄子提出的这样的哲学观点，可以说是超越了时空的惊天手笔。直到2000多年以后，西方哲学界才有类似的观点出现。1641年，笛卡尔在《形而上学的沉思》一文中阐述了类似的观点，他认为人通过意识来感知世界，世界万物都是间接被感知的，因此外部世界有可能是真实的，同样也有可能是虚假的。这一论点以后成为了怀疑论的重要前提。仔细品味，这和庄子的观点还是有一些区别的。庄子更强调“相对”概念，他并不怀疑相对事物的存在性，而却对它们相对的本质更感兴趣，将本来不同事物加以“物化”。这样的思想是充满了辩证观点的，也为人能观察自然和社会问题提供了很好的理论依据。事实证明，历史上和当今世界许多的自然现象和社会问题，都确实是遵循着这样的辩证规律发展的，而之后的哲学家们所新提出的可行的哲学理论，也被证明了是需要依照辩证观点来进行的。&lt;/p&gt;

&lt;p&gt;庄子能从“物化”观点的提出，也是有一定历史背景的。在百家争鸣的时代，诸子各执一言，多少对其他学派怀有偏见。而诸子百家因此生是生非也令庄子有所不屑。总观《庄子》全书，在很多地方都能看到庄子类似的“妙论”，可以看出其旷豁的胸襟。但是后人在注读庄子之时，常会曲解了“物化”的本意，认为庄子的这种思想，只是片面夸大了事物差别的相对性，而从根本上取消了人类认识世界的必要。事实上“物化”是庄子“万物一齐”相对主义哲学的论点，其内涵和外延都不仅只是有人解释的那样，是事物之间的转化。扬雄作在《甘泉赋》中说：“事变物化，目骇耳回。”杜甫作《天育骠图歌》说：“年多物化空形影，呜呼健步无由骋。”清代宣颖作《南华经解》，以佛教唯心主义观点解释“物化”，他说：“物化则一片清虚，四大皆空虚，真淡之至也。”这些“物化”均已经不是庄子所谓的“物化”了。&lt;/p&gt;

&lt;p&gt;最后用清人张潮写的《幽梦影》的一句妙语作结，它点出了庄子哲学的精髓：“庄周梦为蝴蝶，庄周之幸也；蝴蝶梦为庄周，蝴蝶之不幸也。”确实如此，庄周化为蝴蝶，从喧嚣的人生走向逍遥之境，是庄周的大幸；而蝴蝶梦为庄周，从逍遥之境步入喧嚣的人生，恐怕就是蝴蝶的悲哀了。但是这幸与不幸，在庄子看来，又会有多大区别呢？答案恐怕是，它们是“齐而为一”的。也许要能在无意识中融入道的思想，能在不经意间运用齐物理论，这才算是真正理解了庄子，真正读懂了《庄子》。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-11-27&quot;&gt;2007-11-27&lt;/h3&gt;

&lt;p&gt;印象里京城的冬天是没有这么好的阳光的吧，也可能是心情好了，阳光也显得格外好了。说起来今年老天也还真不赖，给了很多的好天气，虽然每天还是天黑很早，但却也给了更多的看星星的时间。&lt;/p&gt;

&lt;p&gt;突然觉得昆明这个词好遥远…一个生我养我的地方，现在却对她生疏了，心中有些忐忑不安。现在每年回家的时间加起来恐怕不到两个月，以后估计会越来越少吧。不过一份眷乡的感情总还是有的，也常常期盼回到家乡…那个给了我无数美好回忆的地方。&lt;/p&gt;

&lt;p&gt;给以前的老师打了电话，和很多同学又联系上了，不知道为什么，特别想念你们…或许是向往以前的像猫一样恬适的生活吧…笑。自己是活得越来越像狗了。&lt;/p&gt;

&lt;p&gt;是不是应该自嘲一下呢？自己也不知道..呵呵&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-12-05&quot;&gt;2007-12-05&lt;/h3&gt;

&lt;p&gt;真的好久没有去大礼堂了哦，看了中戏的巡演的《伪君子》，真是不错。&lt;/p&gt;

&lt;p&gt;其实在拿票的时候，并不知道竹子也要上台演的。之后看到了是中戏05级排的，想起来她不正是在那里么，打了个电话一问，还真是巧…虽然只是个很小的角色，不过还是很为她高兴…恩，主角都是从边上伴舞的小配角做起的。加油~&lt;del&gt;相信你&lt;/del&gt;下次再来演一定是主角哦，我也一定给你献花捧场~~^_^&lt;/p&gt;

&lt;p&gt;总觉得结局有点突兀，怀疑是他们自己改过，回来查了查原著，发现剧本完全是忠于原著的…甚至很多台词都照搬了。只能是赞叹下莫里哀大人活跃的思想了…或者是他到最后发现一直写下去收不住笔而草草来个结尾？总之是。结局有点遗憾…&lt;/p&gt;

&lt;p&gt;恩…今天终于学会骑车带人了…原来很多事情并不是很难…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-12-14&quot;&gt;2007-12-14&lt;/h3&gt;

&lt;p&gt;大学的教授们基本没把心思花在讲课上，备课的疏忽和讲解思路的混乱很容易就能看得出来。不过毕竟他们主要任务不是教书吧，所以也算差强人意了。课后往往只好靠自学来弥补，而一个一个的Project的价值也在一次次对概念和思想的考验中体现了出来。&lt;/p&gt;

&lt;p&gt;最近身边包括自己最常被提到的词儿大概就是Project了。不知道为什么，这样一个几乎是每天都在接触的概念居然缺乏一个完美的中文译名，只好每天说这次Project如何如何。确实也算是舶来物吧，在教育全盘西化的今天，貌似就连一个名词都鲜有人想去弄一个好的译名了。&lt;/p&gt;

&lt;p&gt;电信那边的同志们刚刚交了计网的Project，又要马上投身到CPU的开发，而微电的我们也在为一个加法器优化绞尽脑汁。每天面对一个小小的HSpice窗口，没有了图形化设计的繁杂与虚华，在长长的令人生畏的表单和程序后面，却有了更加细致深邃的思考。写代码，跑仿真，看输出，找原因…之后再改代码，跑仿真，看输出，找原因…我知道在不远的某处会有我想得到的结果，但是在那光的彼岸和自己所站立的孤岛之间，却没有一条宽敞的大道。&lt;/p&gt;

&lt;p&gt;有时候真的会觉得做各样的Project是一件很有趣的事情。从一开始的懵懂，一步步地对一个新鲜事物有了了解，最后好像是完全明白一样的豁然开朗。再之后，也许有发现严重错误后的低落，有一次成功的惊喜，有逐步总结经验后对满满的收获的笑脸，当然也许也会有历尽荆棘伤痕累累却还是失败的泪水。从体验人生来看，Project恰如人生…喜怒哀乐怆伤悲，人间百味都融在了这几页代码…在外人看来，这也许是满纸荒唐言，但自身体会后，方解其中味。&lt;/p&gt;

&lt;p&gt;现在能做的，大概是尽量心情淡泊地完成它们吧&lt;/p&gt;

&lt;p&gt;仿真尚未完成，同志仍须努力…&lt;/p&gt;

&lt;p&gt;自信！拼搏！坚持！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2007-12-25&quot;&gt;2007-12-25&lt;/h3&gt;

&lt;p&gt;一切平安，勿念。惟忙而已…圣诞节什么的，不存在的。&lt;/p&gt;

&lt;p&gt;流水账一样地汇报下最近的情况吧。&lt;/p&gt;

&lt;p&gt;本来以为DIC的Project已经算圆满了，但是又飞出组数据直接把我打回原形…所以这几天的生活主旋律还是无尽地调电路。在有了传说中的CosmosCsope的帮助下，我终于可以抛弃一直不发送的Avanwaves，并且清楚地看到电路的问题所在。最后在各位同志的帮助下，达到了一个比较好的结果..在这里先谢谢帮忙的大家啦~~&lt;/p&gt;

&lt;p&gt;往前可以回忆到周六…恩，去考了六级。反正不管从哪里都是说六级如何如何BT，所以作弊满天，怨声载道。舆论威力确实大..害得我小紧张了一下，从周五中午就开始看英语——难得还会在考前准备啊…很久没有过那样做题的感觉了…恩..最近一次大概是两年半前了。结果..什么嘛…这就是六级…这不是轻松裸考么？总感觉那几天时间有些浪费…&lt;/p&gt;

&lt;p&gt;昨天是平安夜…胡乱出去吃了顿饭，算是小小的庆祝下。三年了，我一直说北京没有圣诞气氛，昨天更应证了这一点。出去倒是人山人海，但是却找不到一个能算是有圣诞餐吃的地方…残念…不过仅止于此罢了。&lt;/p&gt;

&lt;p&gt;汇报完毕..继续干活。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-01-20&quot;&gt;2008-01-20&lt;/h3&gt;

&lt;p&gt;DIC给了10分钟的延长时间，我什么字都没写…因为提前了5分钟我就做完了，在增加的时间里也不想再思考什么了…于是，这个学期就这么Over了。&lt;/p&gt;

&lt;p&gt;总结下一个学期的得失，对学习的投入不如上个学期，但是最终的结果应该会稍好…也许确实是微电的东西比较简单吧~~或者说是学起来比较对我的胃口？笑。&lt;/p&gt;

&lt;p&gt;接下来的日子，好好休整一下。发两天呆先…嗯。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-02-03&quot;&gt;2008-02-03&lt;/h3&gt;

&lt;p&gt;其实还是蛮顺利的，不过也还是没有跳出每次回家总会有点事情的怪圈。&lt;/p&gt;

&lt;p&gt;这次本来要坐的航班居然被直接取消了..还好手脚快及时改签了另一班的最后的座位，居然还能提前半个小时回家…也算是因祸得福。&lt;/p&gt;

&lt;p&gt;昆明确实是一个让人犯懒的地方哈，带回家的书一点想要翻开的冲动都没有。算了吧，一心要休息..那就好好休息好了。&lt;/p&gt;

&lt;p&gt;踏上家乡的土地，这个学期才算真正的完结了…喵，一个很happy和有点小完美的学期。啦啦~开始玩&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-02-08&quot;&gt;2008-02-08&lt;/h3&gt;

&lt;p&gt;11点了，在学校的话，正是拉闸断电人声鼎沸的时候，而现在的我，在家里享受一份淡淡的静谧。&lt;/p&gt;

&lt;p&gt;一年前的誓言，在今天回首看来，似乎完成的并不是那么理想，不过却也算是基本履行了。差强人意之中，是妈妈温馨的笑容和爸爸依旧不变的大孩子脾气。我不知道自己欠了他们多少，我也不知道以后是不是能偿还他们那么多。于是我想，珍惜现在，才是最真切的。&lt;/p&gt;

&lt;p&gt;大年过到了现在，也觉得就那么一回事儿了。大概人都是会疲惫的，在快一年的奔忙之后，回到安逸的家里，却一时有些适应不了。走亲戚串巷子访同学拜老师，一切过后，我赫然发现，在园子里默默无闻的我原来在这边还有这般的影响。接近三年过去了，当年的小小的光环虽不算光辉耀眼，却也还未泯然众人吧。决心，一直是有的，行动，也一直在做的。诚然，起点本来就低，如果再不以后天努力加以弥补的话，也许真的是无可救药了….但是脑中有中声音一直告诫我，淡定…&lt;/p&gt;

&lt;p&gt;是呵…成就一番功名，立下千秋伟业，哪一个男儿不曾有过这样的豪迈壮志，但是现实总会将人无情地打入深渊。我也许还算个幸运儿，想想看，有多少人在自己所谓事业都还没有起步之时，就被扼杀在襁褓之中，又有多少人被迫接受社会的现实，为了生计疲于奔波，却还无法经济自主。于是，我开始庆幸起来，庆幸起自己的命运。我知道这是可耻的，我知道“我命在我不在天”，但很多时候，我又想起另一句“谋事在人，成事在天”的话来。说来好笑，中国的古人们真是把辩证法运用到了极致，怎么说怎么有理，但是正因如此，中国古代的哲学显得凌乱无章，缺乏希腊哲学那一份严谨。后人往往便在前贤的自相矛盾的“谆谆教诲”中苦苦找寻一个可能并不存在的答案，越陷越深，无法自拔。&lt;/p&gt;

&lt;p&gt;于是，我不想那么多。望窗外望去，天空泛出一些微红，也许明天会是一个雪天。北京今年很奇怪，居然一直大晴天，多么期盼一场雪呀..可是雪又怎样？这些雪花落下来，多么白，多么好看。过几天太阳出来，每一片雪花都变得无影无踪。到得明年冬天，又有许许多多雪花，只不过已不是今年这些雪花罢了…过眼浮云，看似实在，实则缥缈，去去来来，反复无常。并不记得自己是什么时候有了第一次对人生的感慨，只记得那时候的心似乎和现在一样，空空的。我向来不在意什么积极的想法消极的想法，那只是统治者们所谓的标准罢了。一个人，应该有他独立的思考，而非人云亦云的如木偶愚民般任人操纵。&lt;/p&gt;

&lt;p&gt;望着渐渐浓郁的夜色，我困了。脑子里蛮乱的，好罢，什么都不想，睡觉才是真的。活到我的年纪上，也应该知道什么是伤感了，整天学老爸做小孩子也不是办法。&lt;/p&gt;

&lt;p&gt;想你，希望今天能梦到你。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-02-24&quot;&gt;2008-02-24&lt;/h3&gt;

&lt;p&gt;吼，新学期开始了！&lt;/p&gt;

&lt;p&gt;一起努力吧！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-03-03&quot;&gt;2008-03-03&lt;/h3&gt;

&lt;p&gt;腰痛中。可能是还没有掌握好打网球的用力方式，每次下来都会觉得很疲，不过洗过澡以后果然还是神清气爽呐&lt;/p&gt;

&lt;p&gt;已经感受不到冬天了，风吹过来，都没有了刀割的凉意，而一天天温暖起来的太阳，也告诉我春天就在眼前了。北京的春天的短我是感受过的了，这个春天一定要把很多很多的春装拿出来穿。在家的念高中的时候，一年四季都可以穿的衣服，到这里只有短短几十天甚至十几天能够穿出来，每次只挑那么几件，其余的全部躺在衣柜的角落里..可惜了可惜了…&lt;/p&gt;

&lt;p&gt;顺便抱怨一句…我现在有点恨费曼….&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-07-08&quot;&gt;2008-07-08&lt;/h3&gt;

&lt;p&gt;实习的第二天。&lt;/p&gt;

&lt;p&gt;早上提前了半小时来办公室，用无比难用的键盘敲东西…&lt;/p&gt;

&lt;p&gt;上海果然很热，前天来的时候似乎是38.8，不过从昨儿开始终于是长期进驻空调房了。办公室生物还是很可怕的，长期坐在办公桌前面吹吹空调各干各的事，都不想起来，我也基本没发现什么交流的机会。学生生活比起办公室生活来说真可谓小宅见大宅…&lt;/p&gt;

&lt;p&gt;现在还没有什么具体的活儿给我们做，都还在处于基础知识恶补阶段。这么多年以来，终于明白了“大学里学的是学习的方法”这句话。学的知识那是一点用都没有…学了C和C++，人家随便一用都是C#…还建议用2008版的VS。这辈子学再快估计也不会有更新速度快了。于是预计这个星期刻苦猛补，争取能在这段时间把C#和SQL弄得比较清楚吧。据说之后会是一个为公司软件做一个类似wiki一样的东西..其实还是蛮期待的。&lt;/p&gt;

&lt;p&gt;就到这里吧，受不了这个磨手的键盘，等我找到好用的键盘再继续…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-07-16&quot;&gt;2008-07-16&lt;/h3&gt;

&lt;p&gt;实习的第二周。&lt;/p&gt;

&lt;p&gt;换了新键盘，快乐地写程序中。&lt;/p&gt;

&lt;p&gt;这学期的成绩都出来了，本来这个学期就没有太好好学习吧，结果最后的结果要比预想的好很多，除了可恶的MEMS和…恩，和教MEMES的“严苛”的老师和助教，其他可以说基本是满意的了。也许这是最后一个为成绩挣扎的日子了吧，大四貌似据说是能过就行…没有压力…&lt;/p&gt;

&lt;p&gt;还是说实习的事情，已经开始动手做WIKI了，进展很顺利，除了某人的用户管理感觉有一些些跟不上进度…不过应该很快会好起来咯。我的工作任务就是….没人做的都我做….听起来很惨…实际嘛.. 也还是很惨&lt;/p&gt;

&lt;p&gt;和好几个同事已经蛮熟了。姜宁是个好人，带着一堆啥都不懂的孩子搞开发..其实关键代码都是他写的…好人啊好人…会不会有背后发卡的嫌疑..自己汗一个先~~~&lt;/p&gt;

&lt;p&gt;老妈20号过来审查，王燕21号过来审查..还有电信的老师25号过来审查…不知道是过来看工作还是担心我们在被虐..&lt;/p&gt;

&lt;p&gt;就这样先~有人偷看不写了…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-09-29&quot;&gt;2008-09-29&lt;/h3&gt;

&lt;p&gt;尘埃落定。&lt;/p&gt;

&lt;p&gt;从8月中就开始的推研大战至今圆满结束。&lt;/p&gt;

&lt;p&gt;幕落下，有人得意欢笑，也有人暗自神伤…幸运地，我站在了前一类人当中。嘛，或者只是自认为站在了前一类人当中。不过不管怎样，在经历了各种艰难险阻和几番紧张又带有些许失意的心态后，我还是那到了一个蛮不错的结果。在今年如此险恶的推研环境下，以一个看上去没有任何优势的名次，踉踉跄跄拿到了基本可以说最后一个系内名额。于是就这样，今后的三年还是会在这个熟悉又陌生的园子里度过了…&lt;/p&gt;

&lt;p&gt;也没有什么好总结的，几句话而已：看好合适的，联系可能的，排序很重要，机会都不放。前面三年的GPA固然很重要，但是这几天的努力联系和准备还是能加上不少分。不论如何，机会其实是很多的，忠告就是，就算分数不够看，也不要认为自己不行，主动跑动积极联系机会才会到来，自信丧失坐以待毙就真的走投无路咯。&lt;/p&gt;

&lt;p&gt;嗯…在中科院的经历再一次让我发现清华的牌子还是很有用的…感谢自己一直的努力…&lt;/p&gt;

&lt;p&gt;不过回头看看老爸的月收入，发现自己还没到可以松懈的时候，继续加油…&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-10-18&quot;&gt;2008-10-18&lt;/h3&gt;

&lt;p&gt;3点，似乎已经成为我周末睡觉的标准时间了。我自然知道长此以往必将出现种种问题，但是周末的晚上确实是我整周之中唯一能够踏踏实实拿出来的整片的时间了。&lt;/p&gt;

&lt;p&gt;前夜和昨夜的月都很圆，但是在北京永远的雾朦胧下，却是难见到那久违的狡黠。今天无意中翻到一篇文章，教你怎么用傻瓜相机拍月亮。起初还对这样的“入门级”文章嗤之以鼻，认为玩儿来玩去不就是个傻瓜数码机么，顶多就对对焦按按快门的事情。结果随意一扫就被几张效果图震住了。说实话那样的月亮我只有在游戏里才能看得到，想不到居然还能照出那样好的效果：整个月亮拉到很大，表面笼罩一层白光，上面的阴影坑洼都清晰可见，边缘似乎有些模糊，整个月亮几乎要跃纸而出。然后赶忙仔细看了看拍摄教程，很不幸的是，一段之后就头晕脑胀了。那哪儿是什么傻瓜相机教程呀..一段里面不懂的名词就七八个。直到最后才以“就算学会了，北京的月亮也绝不可能达到那种效果”为由，心安理得的去做其他事情了。&lt;/p&gt;

&lt;p&gt;研是推完了，但其实生活并没有想象的那么轻松惬意。发现在这里念了三年书，把自己原来的一些优点给丢掉了。也许也是因为高手太多吧…三年以前的我，绝不会认得现在这么一个被功利主义渲染的我…很多事情会不由自主的想到于己的利益。如此，往往做事就蹑手蹑脚，思此顾彼。人变得不单纯了，不知道该算是优点还是缺点..至少，我很讨厌现在这样的自己，我从来都不愿意成为那种斤斤计较一点点得失的人。&lt;/p&gt;

&lt;p&gt;不止一次被人说看起来不成熟，现在来看，貌似已经成为我的典型形象了。也好也好…至少可以显得不那么可怕…&lt;/p&gt;

&lt;p&gt;城府这种东西，从来不是被看出来的。&lt;/p&gt;

&lt;p&gt;以上胡言乱语，写在睡前…&lt;/p&gt;

&lt;p&gt;现在，是时候去睡觉了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2008-10-22&quot;&gt;2008-10-22&lt;/h3&gt;

&lt;p&gt;不知道是因为深秋天凉人容易犯困还是看应变硅材料比较无味，整天的都很没有精神干活儿。微机原理的课已经两节没有去上了，X86的保护模式还真的一点都不懂…不知道这样一直下去的话会不会被挂掉…&lt;/p&gt;

&lt;p&gt;生活方面倒是很顺利，又到了一年秋冬，万物萧索的时令了。秋风的肃杀算不上什么，但是一旦配上黄叶凋落和夕阳的余晖，就不免让人有些伤感。叶绿叶黄，春夏秋冬，人生的韶华就这样一点点流逝。古典音乐的美好能让人沉醉能让人保有恬淡的心境，但是青春本身的不安和变动却让人更向往着新的生活。新老交替乃是自然常理，但是听着楼下大一小朋友们的那饱有激情又略显生涩的合唱排练，心中却是不知自己是算新，抑或是老…&lt;/p&gt;

&lt;p&gt;但是，至少明天的太阳还是新的~阔里瓦~~要抓紧时间加油呀&lt;/p&gt;

&lt;p&gt;有句话很好：&lt;/p&gt;

&lt;p&gt;英雄的少年呀~朝着朝阳奔跑，去创造属于自己的奇迹吧~~…&lt;/p&gt;

&lt;p&gt;可是..貌似我早已不是少年了&lt;/p&gt;

&lt;p&gt;笑…或者是苦笑。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2010-02-28&quot;&gt;2010-02-28&lt;/h3&gt;

&lt;p&gt;鞭炮齐鸣，礼花冲天，我又回来写日记了。&lt;/p&gt;

&lt;p&gt;回头看看之前的BLOG，发现写博确实是一件有百益无一害的事情呀。生活的点点滴滴总是在不经意间就被遗忘，但若是能在回首时看到一路走来的足迹，这确实是一种幸福。&lt;/p&gt;

&lt;p&gt;一年多过去了，的确发生了很多的事情。&lt;/p&gt;

&lt;p&gt;回来的时候看到一个07级EE的小师弟/妹到BLOG里找一题束缚态波函数证明的博文，不由会心一笑。一代又一代的清华EEer，一次又一次的基础练习。而正是这样的轮回，推动着时代的进步吧。&lt;/p&gt;

&lt;p&gt;说实话，人变得成熟了不少，但是自己也能感觉到，还需要太多的磨砺。心态上早已没有了当初的锋芒，但是行动上却还是咄咄逼人。今天是元宵，也算是过年的真正结束。再过40分钟将迎来3月，新的一年的工作和学习马上就要正式开始。窗外的小雪零零落落，大概会是冬天结束的标志吧~&lt;/p&gt;

&lt;p&gt;新的春天，必会有新的希望，而只要通过努力，也必将有新的果实。&lt;/p&gt;

&lt;p&gt;以上与君共勉，也作为归来感言。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2010-03-02&quot;&gt;2010-03-02&lt;/h3&gt;

&lt;p&gt;星期一是开例会的日子。照例早早在实验室等待，顺便研究Sentaurus，一直到七点开会，BOSS布置近期任务。&lt;/p&gt;

&lt;p&gt;今天除了原来的几个老家伙外，又来了两只新的小朋友…基本都是外校过来做本科生毕设的，貌似据说过两天还会有两个深圳的工硕和两个本校的本科生加入。对比起自己做本科毕设时候郭磊+赵硕的双人组合来说，现在的阵容还真是前所未有的强大啊。&lt;/p&gt;

&lt;p&gt;但是很不幸的是人多就意味着活儿也多..本来还想着刚开始能轻松几天，但是天煞的IEDM2009刚刚结束，论文成果一堆一堆，头儿的点子也一堆一堆。于是乎被活生生从Sentaurus里拽出来，然后被淹没在各种pdf文档里了…&lt;/p&gt;

&lt;p&gt;其实看论文也是蛮享受的一件活儿，特别是国际大会议的论文集。格式规范，书写严谨，论证充足，思维新颖，这都是国内小期刊没得比的。假期的时候看过一篇Sentaurus的45nmStrain-Si的仿真，是某野鸡大学的一篇硕士毕业论文，还得到了08年的优秀硕士毕业论文。仔细看的时候，越来越觉得眼熟，到后来看到结果图时发现这完全就是Synopsys的技术文档的中文不完全翻译版，相当囧。论文的后半篇是一个FinFET的3D建模，一开始还以为原作者原来自己动过那么一点点手的，结果今天找SolvNet来一看，后半部分的FinFET也是人家现有的技术文档，这次是相当囧RZ了…就靠两篇技术文档的翻译，不仅顺利从野鸡大学毕业，还混到一篇国内核心期刊，以及优秀毕业生论文这样的奖项..人才..不愧是野鸡大学..&lt;/p&gt;

&lt;p&gt;不过最可恶的还是Synopsys，好好的搞什么SolvNet认证才能下载参考资料..弄得想要看技术文档还得巴巴地跑去设计室找管理员，麻烦的一米..这么做完全不利于软件发展嘛..这种专业软件还不把参考用法公开，让人去刨那几千页的Manual，完全不现实。今天一位小朋友还要用Sentaurus做他的本科毕设哩..跑过来说Sentaurus的事情，头疼中..应该怎么速成呢..要知道我可是学了两个月才刚刚入门啊..&amp;gt;-&amp;lt;&lt;/p&gt;

&lt;p&gt;但是现在貌似可以把Sentaurus放到一边去了，下阶段主攻锗器件的几个技术难点，25nm的主流依然会是应变硅+HighK金属栅，但是远一点看11nm时候应变硅肯定失效了，新材料才是王道呀~锗器件在工作电压0.7V以下才能有可接受的漏电，这是最根本的问题所在。11nm以下更换材料已经是共识，到底最终谁会胜出？锗，III-V，亦或是碳纳米管？也难说会有意想不到的新东西出来呢。15nm或以上的时候时候硅就是无可替代的呢，有没有办法在工作电压大于0.7V的时候让锗基提前应用，将是我今后几个月主要研究的课题。&lt;/p&gt;

&lt;p&gt;祝我好运吧..&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2010-03-14&quot;&gt;2010-03-14&lt;/h3&gt;

&lt;p&gt;窗外大雪纷飞。&lt;/p&gt;

&lt;p&gt;这已经是3月中了，按京城惯例的话，明天就停止供暖了。本是桃花含苞樱花漫舞的三月，却还在下如此大的雪。已经记不清这是入冬到现在的第几场雪了，只知道今年的冬天就别冷，而且如春以后还保持每周一次的降雪，不得不说是气候的异常。可以预见的是，今年真正的春天，会短暂得很的了。&lt;/p&gt;

&lt;p&gt;去年10月国庆60周年，为了得到一个晴天，之前进行了人工干预天气的降雨；再往前08的奥运，似乎也被报道人工控制天气。不知道现在这样的降雪算不算是老天的回应或者是警告咧…&lt;/p&gt;

&lt;p&gt;相反的..昆明自从入冬以来就只有一个持续10分钟的小雨。今年春节回家的第一感受就是干旱，恩..据说是百年未遇的大旱..而且一直持续到了现在。昆明周边的供水据说已经开始限制了，希望快快下雨，不要影响春天的大好时光吧.哎~&lt;/p&gt;

&lt;p&gt;两会闭幕了，早上在青鸟锻炼的时候顺便看了温总的记者会。温总在的表现只能说差强人意，除了拿出拿手的引用以外，并没有回到什么实质的问题。..算了不提也罢~&lt;/p&gt;

&lt;p&gt;锻炼，学车，研究…加油&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2010-03-20&quot;&gt;2010-03-20&lt;/h3&gt;

&lt;p&gt;今天是3月20日，春分之前一天。&lt;/p&gt;

&lt;p&gt;起了个大早，结果发现天色泛黄，拉开窗帘一看，果然是沙尘..这大概是我在北京所见到的第二大的沙尘天了。&lt;/p&gt;

&lt;p&gt;前几天大雪纷飞，现在又沙尘肆虐，不得不让人唏嘘北京的生存环境之恶劣啊~以后还是离开这个地方为好…&lt;/p&gt;

&lt;p&gt;本来应该是阳春三月，按往年来看，学校里应当已然是新芽绽出一篇春光景致了，可以说是春游踏青的好时节。但今年实际情况却让人提不起兴致，走到哪儿都是光秃秃一片，树木死气沉沉，映衬着发黄的天空和瑟瑟的冷风，再加上大片大片被铲掉已死草皮以后被翻起的堆堆黄土，俨然一副破败景象。&lt;/p&gt;

&lt;p&gt;直接怀疑今年春天是否还会来…本来北京春天就已经很短，经过这么一折腾，我觉得可以直接进入夏天了吧。这悲剧的…&lt;/p&gt;

&lt;p&gt;早起就是为了干活..那么就开始干活了。&lt;/p&gt;

&lt;p&gt;写完日志再往窗外一看，比刚才更黄了…唉..不知道今天还能不能出去吃午饭。&lt;/p&gt;
</description>
        <pubDate>Tue, 23 Oct 2018 01:11:26 +0000</pubDate>
        <link>https://u3dc.github.io//2018/10/diary/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/10/diary/</guid>
        
        <category>胡言乱语集</category>
        
        
      </item>
    
      <item>
        <title>开发者所需要知道的 WWDC 2018 新特性</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/2018/wwdc.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一直阅读我的博客的朋友可能知道，我在每年 WWDC 之后都会写 (水) 一篇关于新 SDK 和开发工具的文章。之前这个系列叫做《开发者所需要知道的 iOS SDK 新特性》，但是最近虽然 Craig 嘴上说着不要，身体却很诚实地将 iOS 和 macOS 带到一起，所以今年我觉得可以改一改题目，就总览一下作为 Apple 生态圈的开发者，在今年 WWDC 上我个人的一些观察，以及可能应该注意的有趣的地方。&lt;/p&gt;

&lt;p&gt;在会前，Apple 就已经放出消息要放慢增加新功能的脚步，转而提升软件稳定性和可靠性。所以 iOS 12 和 macOS 10.14 中对开发者来说并没有特别大的新功能，这也是意料之中。在一片“平平淡淡”的评论中，也许我们会更有时间和信息来打磨一款 app，正如前几天 deno 项目下的哥们儿说得：“求别更新了，老子学不动了”。&lt;/p&gt;

&lt;p&gt;当然这是一句戏谑，作为程序员或者开发者，应该是这个星球上最需要学习的职业 (之一)，我们就来看看今年的 WWDC 带给了我们哪些东西吧。&lt;/p&gt;

&lt;h2 id=&quot;machine-learning&quot;&gt;Machine Learning&lt;/h2&gt;

&lt;p&gt;这算是时代的潮流。前几天 Google I/O 所展示的 AI 打电话当然令人印象深刻，不过其实 Apple 也很早就押宝 AI 和 ML 了。区别在于，Apple 家的天赋分配和 Google 不太一样，Google 更多地是用“集中式”的方式，通过第一方资源完成训练和模型部署，用户进行使用，在此期间第三方开发者能做的事情十分有限。今年 Google 推出了基于 Firebase 的 ML Kit，让移动开发者可以处理一些现成的常见 ML 任务 (比如文本识别，面部检测等)，这部分任务在 iOS 中其实很早就已经在 Foundation 中有提供，之后又抽离出了像是 Vision 之类的专用框架。而第三方开发者自行训练模型的部分，Google 今年则给出了 TensorFlow Lite 和 Firebase 进行 host 的组合。&lt;/p&gt;

&lt;p&gt;而 Apple 与之不同，从一开始 Apple 就寄希望于“复制” App Store 的成功模式，也就是将 ML 相关的功能提供给开发者，去年的 Core ML 框架就是一个这样的尝试。当然 Core ML 本身比较简陋，它通过将已有的其他格式的模型“转换”为自己能够理解的模型，然后对新的输入进行求值。模型训练在 Mac 平台上一直是一个软肋，因为最近 Mac 已经不再配置 Nvidia 的显卡，TensorFlow 甚至都已经不再为 macOS 提供带 GPU 支持的版本，对于日常工作在 Apple 平台的开发者来说，训练一个自己的模型一度是相当困难的事情。&lt;/p&gt;

&lt;p&gt;2016 年 Apple 曾经收购了一家 ML 的初创公司 Turi，在今年这一收购案终于是开花结果。基于 Turi 的&lt;a href=&quot;https://developer.apple.com/documentation/create_ml&quot;&gt;模型训练框架 Create ML&lt;/a&gt; 可以利用 Mac 的 GPU (以及 Metal) 进行训练，并直接得到 Core ML 可用的模型。结合自家 Playground 的更新，Apple 更是祭出了 CreateMLUI 这种“傻瓜式”但效果还很不错的可视化方式，现在开发者只需要将训练数据和测试数据拖拽到 Playground UI 中，就能在 Apple 自家的模型的基础上，得到很好的结果。这大大降低了一般开发者在第三方 app 中集成 AI 特性的门槛，有很多时候用户并不一定需要 Google Assistant 那样复杂的应用，而在一般的各类 app 中集成的 AI，对 app 易用性的提升会相当显著。&lt;/p&gt;

&lt;p&gt;另外，去年的 Vision 框架在结合 CoreML 使用时表现很不错，让不少第三方 app 可以很简单地利用计算机视觉的一些成果。而今年 Apple 在这方面更进一步，带来了自然语言处理的框架。这也是今年 WWDC 在系统中新增的少数几个框架之一。&lt;/p&gt;

&lt;p&gt;Apple 虽然在 Keynote 上一句 AI 都没有提过，但是它却“润物细无声”，贯穿在照片搜索，Finder，Shortcuts 等方方面面。不管你是否承认，Apple 也已经从移动导向逐渐过度到了 AI 导向，而对于移动开发者来说，也许基本的 AI 开发将是不可回避的话题。虽然对模型的训练现在的业界主流依然是 TensorFlow，或者说套上 Keras 的前端，但 Apple 的出现和对 Turi 的持续投资，让它成为了另一种可能。&lt;/p&gt;

&lt;h4 id=&quot;推荐观看的相关-session&quot;&gt;推荐观看的相关 Session&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/110/&quot;&gt;Machine Learning Get-Together&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/703/&quot;&gt;Introducing Create ML&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;What’s New in Core ML &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/708/&quot;&gt;Part 1&lt;/a&gt; &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/709/&quot;&gt;Part 2&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/712/&quot;&gt;A Guide to Turi Create&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/713/&quot;&gt;Introducing Natural Language Framework&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/717/&quot;&gt;Vision with Core ML&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;xcode&quot;&gt;Xcode&lt;/h2&gt;

&lt;p&gt;暗色的 Xcode 果然是大家的最爱！但是除了“科技以换色为本”以外，在一段不长的时间的试用后，我已经决定把 Xcode 10 beta 作为日常使用的工具了。Apple 看起来是确实履行了改善软件质量的承诺，相比于前几个版本的 Xcode beta，这次的稳定性相当值得称赞。新的编译系统和更加顺滑的编辑器，以及一些深度集成的小功能 (边栏的 git status indicator，或者多光标编辑模式等)，让 Xcode 的编辑功能追上了主流。另外，语法高亮和自动补全的处理显然也得到了照料，即使是 beta 版本，也比 Xcode 9 正式版本身要稳定得多。&lt;/p&gt;

&lt;p&gt;基本上现在开始将 Xcode 10 作为日常主力工具，使用 Swift 4.1 兼容的语法进行开发，这样可以让自己每天过得舒服一点，并且可以保持用 CI 上的 Xcode 9 进行测试构建和提交。我想这大概会是我到九月前这段时期的选择了。&lt;/p&gt;

&lt;p&gt;Playground 在推出时被寄予厚望，但是实际表现却非常一般：响应速度慢，经常崩溃无响应，&lt;/p&gt;

&lt;h4 id=&quot;推荐观看的相关-session-1&quot;&gt;推荐观看的相关 Session&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/402/&quot;&gt;Getting the Most out of Playgrounds in Xcode&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/408/&quot;&gt;Building Faster in Xcode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;swift&quot;&gt;Swift&lt;/h3&gt;

&lt;p&gt;Swift 4.2 的编译器同时支持 Swift 3 (虽然应该是最后一个支持 3 的版本了) 和 Swift 4 的代码，所以不需要预先进行代码迁移。新版本继续向着更易用的 Swift 的方向进行努力，比如将更多的 C-like 的代码 (比如 CoreAnimation 的一整套 API) 改成更 modern 的形式，比如为数字和数组添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random&lt;/code&gt;，比如将很多 String-base 的 API 加上强类型或者 KeyPath 等等。&lt;/p&gt;

&lt;p&gt;Swift 5 被延期到了明年，ABI 稳定显然是最近 Swift 的主攻方向，因为只有 ABI 稳定，Apple 才可能开始在自家框架中使用 Swift。&lt;/p&gt;

&lt;p&gt;除了自家平台之外，可能值得一提的是 &lt;a href=&quot;https://github.com/tensorflow/swift&quot;&gt;Swift for TensorFlow&lt;/a&gt; 的分支。这是 Swift 的创始人 Chris Lattner 在加入 Google 后所主导的一样项目，希望使用 Swift 来操作 TensorFlow 来构建 tensor graph 模型，由于有编译阶段的支持，可以纵览全局，所以往往可以比 Python 提供更好的性能。它使用了 dynamicMember 来提供 Python 兼容的代码形式，思路非常有趣。鉴于 Chris 在 Swift 社区的影响力，以及 Apple 本身想要把 Swift 打造成多平台，多用途的语言，这个分支最终合并也非不可能。&lt;/p&gt;

&lt;h4 id=&quot;推荐观看的相关-session-2&quot;&gt;推荐观看的相关 Session&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/401/&quot;&gt;What’s New in Swift&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/406/&quot;&gt;Swift Generics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/411/&quot;&gt;Getting to Know Swift Package Manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;性能调优和工具支持&quot;&gt;性能调优和工具支持&lt;/h3&gt;

&lt;p&gt;优化是没有极限的，这部分属于内力比拼了。每年的性能相关或者偏底层的内容，可能不能理解给你带来什么收益，但是却是作为一个技术者，在成长道路上不可或缺的部分。今年因为新的 feature 比较少，所以优化部分的内容要比以往都多。&lt;/p&gt;

&lt;h4 id=&quot;推荐观看的相关-session-3&quot;&gt;推荐观看的相关 Session&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/403/&quot;&gt;What’s New in Testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/405/&quot;&gt;Measuring Performance Using Logging&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/407/&quot;&gt;Practical Approaches to Great App Performance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/414/&quot;&gt;Understanding Crashes and Crash Logs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/416/&quot;&gt;iOS Memory Deep Dive&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/223/&quot;&gt;Embracing Algorithms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;siri-with-shortcuts&quot;&gt;Siri with Shortcuts&lt;/h2&gt;

&lt;p&gt;和 ML 领域类似，另一个开花结果的案子是对 Workflow 的收购：Shortcuts app 整个就是 Siri 版的 Workflow。Siri 在面对 Amazon 和 Google 这样的“后来者”挑战时，确实显得力不从心，而从 Keynote 的演示来看，Shortcuts 可能会是一剂良药。&lt;/p&gt;

&lt;p&gt;和近几年的其他很多功能一样，开发者通过操作 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSUserActivity&lt;/code&gt; 来配置简单的 Shortcuts 以获取 Siri 的推荐。如果你想要更加自定义的行为和表现，那还需要定义和开发新的 Intent app extension。这部分内容都被作为 SiriKit 的追加内容，希望能够为用户带来每天使用 Siri 的理由吧。&lt;/p&gt;

&lt;h4 id=&quot;推荐观看的相关-session-4&quot;&gt;推荐观看的相关 Session&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/211/&quot;&gt;Introduction to Siri Shortcuts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/214/&quot;&gt;Building for Voice with Siri Shortcuts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;当然，上面的都是我个人认为会比较重要的内容。每个人经历不同，所关注的兴趣点自然也不一样。WWDC 18 里还有不少规模更小一些的话题，可能也会在某种场合下特别重要，可以纵览以后根据需求再深入研究。&lt;/p&gt;

&lt;p&gt;总之，今年的 WWDC 应该是对开发者比较友好的。在从 iOS 7 改头换面开始，iOS 就在添加新功能的快车道上一路狂奔。而今年虽然也有新功能，但是很明显在力度上有所缓和，Apple 更多地将精力放在了改善现有软件的品质上，期望能在资本市场和用户体验之间找到平衡。对于开发者来说，抓住这个空隙，多想一想如何改善自己的产品，如何定义未来 app 的走向，甚至如何打磨自己，并对今后的三五年进行规划，可能更多地是我们当下需要踏实下来多加思考的东西。&lt;/p&gt;

</description>
        <pubDate>Tue, 05 Jun 2018 03:15:00 +0000</pubDate>
        <link>https://u3dc.github.io//2018/06/wwdc-2018/</link>
        <guid isPermaLink="true">https://u3dc.github.io//2018/06/wwdc-2018/</guid>
        
        <category>能工巧匠集</category>
        
        
      </item>
    
  </channel>
</rss>
