<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>ddddxxx's Blog</title>
    <description></description>
    <link>https://ddddxxx.github.io/</link>
    <atom:link href="https://ddddxxx.github.io/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sat, 04 Jun 2022 06:35:00 +0000</pubDate>
    <lastBuildDate>Sat, 04 Jun 2022 06:35:00 +0000</lastBuildDate>
    <generator>Jekyll v3.9.2</generator>
    
      <item>
        <title>封装 NSRegularExpression</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;本文是关于 &lt;a href=&quot;https://github.com/ddddxxx/Regex&quot;&gt;Regex&lt;/a&gt; 库的设计心得。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 Swift 中使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt; 非常繁琐。实际使用中，我们通常都会对其进行扩展（例如去除匹配时必须传入的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRange&lt;/code&gt; 参数），或是另起炉灶，重新设计一套 API。&lt;/p&gt;

&lt;p&gt;出于个人使用需求，我早在 Swift 3 时期就制作了一个 &lt;a href=&quot;https://github.com/ddddxxx/Regex&quot;&gt;Regex&lt;/a&gt; 库。当时我找遍了 GitHub 上类似的项目，无一能满足我的要求，只好自己造轮子。如今两年过去了，这样的项目在数量上多了不少，但逐一看来，质量竟还不如我两年前的设计。遂决定翻修项目，增加文档说明，发布 1.0.0 版本，并在此记录一些设计思路。&lt;/p&gt;

&lt;h1 id=&quot;整体设计&quot;&gt;整体设计&lt;/h1&gt;

&lt;p&gt;从正则表达式本身的属性出发，很容易得出基本的设计：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;每个正则表达式匹配结果都是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&lt;/code&gt; 实例&lt;/li&gt;
  &lt;li&gt;可以直接从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&lt;/code&gt; 中获取范围和匹配的字符串，不需要像 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTextCheckingResult&lt;/code&gt; 一样再截取一次&lt;/li&gt;
  &lt;li&gt;可以从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&lt;/code&gt; 中取得 capture group，并从 capture group 中获取范围和匹配的字符串&lt;/li&gt;
  &lt;li&gt;可以获取到 0 号 capture group，即整个匹配&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;从第 4 点出发，我们很容易得出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&lt;/code&gt; 就是 capture group 的集合。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&lt;/code&gt; 本身不必携带额外的信息，只需从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$0&lt;/code&gt; 中获取即可。那么 capture group 就不能是简单的 String, 内部要有自己的结构才行，即 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Capture&lt;/code&gt; 的实例。&lt;/p&gt;

&lt;p&gt;此时 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&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;Match&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;captures&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;Capture&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;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Match&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Capture&lt;/code&gt; 必须是 struct 而不是 class。二者的实例可能非常小且数量多。值类型可以避免申请内存和引用计数的开销&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Capture&lt;/code&gt; 可空，因为正则表达式允许 capture group 不参与匹配&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Capture&lt;/code&gt; 的内部是什么样的呢？显然它应该包含范围和字符串。为了避免不必要的字符串复制，这里应该使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Substring&lt;/code&gt; 而非 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;。由于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Substring&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;Capture&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;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Substring&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&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;kt&quot;&gt;Index&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;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endIndex&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;h1 id=&quot;unicode&quot;&gt;Unicode&lt;/h1&gt;

&lt;p&gt;这是正则表达式和 Swift 间不可调和的矛盾。正则表达式（ICU标准，即 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt; 采用的标准）匹配的单位是 Unicode Scalar，可以大致看成码点(Code Point)，而 Swift 中字符（Character）的标准是 Extended Grapheme Cluster。所以 Swift 中一个字符可能被拆成多个码点分别匹配。例如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;é&lt;/code&gt;(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;U+0065 U+0301&lt;/code&gt;) 在 Swift 中是一个字符，但可以从中单独匹配出一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt;。匹配到的范围无法用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Range&amp;lt;String.Index&amp;gt;&lt;/code&gt; 表达。&lt;/p&gt;

&lt;p&gt;这并非是罕见情况，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt;就是一个组合字符（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;\r\n&quot;.count == 1&lt;/code&gt;），Emoji 中也有大量组合字符。GitHub 上大量项目遇到不可表达的范围时就返回空字符串，这是不可容忍的。&lt;/p&gt;

&lt;p&gt;但完全退回 Foundation的世界，只使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRange&lt;/code&gt; 也不可取。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt; 毕竟是二等公民，比不上精心调优的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;，特别是对于小字符串，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; 值类型的优势是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt; 无论如何都赶不上的。&lt;/p&gt;

&lt;p&gt;我的做法是对于有效的范围使用 Substring，无效的范围则回退到 NSString。在保证正确的前提确保性能。&lt;/p&gt;

&lt;h1 id=&quot;桥接&quot;&gt;桥接&lt;/h1&gt;

&lt;p&gt;由于 Objective-C 头文件的导入规则，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt; 相关的函数签名使用的是 Swift 原生字符串。这给了很多人一个幻觉，认为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt; 可以直接操作 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;。但事实上这个字符串是被桥接过去的。虽然桥接没有开销，但由于底层编码不同，对字符串的操作需要支付转换编码的代价。最糟糕的是，你可能需要反复支付这个代价。GitHub 上一些类似的项目在不经意间造成了 O(n^2) 的额外开销。&lt;/p&gt;

&lt;p&gt;事实上，在匹配前转换成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt; 并一次性支付转换开销对性能更有帮助。在测试中，仅这一项就导致了10倍的性能差异。即使你使用裸 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt;，也要小心这一点。&lt;/p&gt;

&lt;h1 id=&quot;构造器&quot;&gt;构造器&lt;/h1&gt;

&lt;p&gt;这是一个语法糖，我没见过其它项目用过，但是我认为它很甜。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Regex&lt;/code&gt; 的构造器显然是可以抛出错误的。但是如果我们使用编译期已知的常量字符串，这样的错误就没必要处理。这是程序编写有误，直接崩溃就好了。利用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StaticString&lt;/code&gt;，我们可以重载一个不会抛出错误的构造器。除此之外，这里还需要一个非公开的标签 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@_disfavoredOverload&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;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;staticPattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;StaticString&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;Options&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;@_disfavoredOverload&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;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pattern&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;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Options&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;throws&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;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;regex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(foo|bar)&quot;&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;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;(foo|bar)&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;regex&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;kt&quot;&gt;Regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 强制要求错误处理&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;跨平台&quot;&gt;跨平台&lt;/h1&gt;

&lt;p&gt;不知为何，&lt;a href=&quot;https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/NSRegularExpression.swift#L170&quot;&gt;Linux 平台下 NSRegularExpression.enumerateMatches 要求传入逃逸闭包&lt;/a&gt;，这和 macOS 下的函数签名不一致。如果要支持 Linux，这里必须用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withoutActuallyEscaping&lt;/code&gt; 转换一下。&lt;/p&gt;

&lt;h1 id=&quot;其它&quot;&gt;其它&lt;/h1&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;switch&lt;/code&gt; 来匹配正则。还有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReferenceConvertible&lt;/code&gt;，实现之后 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Regex&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&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;replacingMatches(of:with:options:range)&lt;/code&gt;，命名参考了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.replacingOccurrences(of:with:)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;其实整个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt; 的 API 非常小，全部封装一遍也就一两百行代码，工作量很少。但是由于涉及到 Objective-C 和 Swift 的桥接，以及正则表达式和 Unicode 本身的复杂性，封装过程有很多小坑。事实上，如果你直接使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt;，很容易踩进这些坑里。而好的封装有助于你避开这些坑。&lt;/p&gt;
</description>
        <pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2021/02/28/nsregularexpression/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2021/02/28/nsregularexpression/</guid>
        
        <category>Swift</category>
        
        
      </item>
    
      <item>
        <title>Swift 中 String 和 Index 的奇妙行为</title>
        <description>&lt;p&gt;在 Playground 中运行这段代码，猜猜最后一行的结果是什么：&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;s&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;format&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;s&quot;&gt;&quot;world!&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;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;firstIndex&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;w&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;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reserveCapacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;答案是 Xcode (10.2.1, 10E1001) 会崩溃。如果把最后一行换成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print(s[i])&lt;/code&gt;，你会得到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\345&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;无论如何，这都不是我们预期的结果。在第二行，我们获取了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt; 这个字符的索引。然后我们改变了字符串容器的大小。这显然没有改变字符串的内容，所以上一行的索引仍然有效。最后我们使用这个索引寻找对应的字符，发现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt; 不在那了。&lt;/p&gt;

&lt;p&gt;如果你使用的 Swift 版本低于 Swift 5（即 Xcode 低于 10.2.0），则不会有这个问题。如果把第一行换成 “你好 world!”，同样不会有问题。可能有些人已经猜到了问题所在，不过还是让我们从头说起。&lt;/p&gt;

&lt;h1 id=&quot;string-和-index&quot;&gt;String 和 Index&lt;/h1&gt;

&lt;p&gt;要解开这个问题，我们首先要知道 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.Index&lt;/code&gt; 是怎么工作的。从外部来看，这是一个不透明结构体。而根据&lt;a href=&quot;https://github.com/apple/swift/blob/85a30989fa8fabc599200f9d29864d8009fb3cd7/stdlib/public/core/StringIndex.swift#L39&quot;&gt;源代码&lt;/a&gt;我们可以看到，其中只包含了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UInt64 &lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_rawBits&lt;/code&gt;。抛开其中缓存和转码相关内容，和索引相关的是其中的高 48 位。“An offset into the string’s code units”，这一偏移量是基于码元（Code Unit）的。我们可以通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encodedOffset&lt;/code&gt; 直接访问这一值。&lt;/p&gt;

&lt;p&gt;注意，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.Index&lt;/code&gt; 是基于码元的偏移量。而同一个码点，使用不同的编码方式，得到的码元数量也不同。所以同一个字符串的同一个位置，在不同编码下，偏移量是不同的。&lt;/p&gt;

&lt;p&gt;这时候原因就很明显了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reserveCapacity&lt;/code&gt; 这个操作，改变了字符串的编码方式，同时作废了相关的所有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Index&lt;/code&gt;。使用无效的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Index&lt;/code&gt; 当然不能得到正确结果。&lt;/p&gt;

&lt;h1 id=&quot;编码&quot;&gt;编码&lt;/h1&gt;

&lt;p&gt;但我们还要问，为什么编码会变？&lt;/p&gt;

&lt;p&gt;继续看源码，&lt;a href=&quot;https://github.com/apple/swift/blob/85a30989fa8fabc599200f9d29864d8009fb3cd7/stdlib/public/core/StringObject.swift#L83-L85&quot;&gt;一个字符串对象可能以三种方式存储&lt;/a&gt;。忽略静态不可变字符串，一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; 对象的底层还可能是原生 Swift String，或是桥接来的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt;。我们已经知道 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt; 是用 UTF-16 编码，那 Swift String 呢？&lt;/p&gt;

&lt;p&gt;好吧，正如你猜到的，&lt;a href=&quot;https://forums.swift.org/t/string-s-abi-and-utf-8/17676&quot;&gt;从 Swift 5.0 起，native Swift string 的编码方式由 UTF-16 切换到 UTF-8&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;回到原来的问题，字符串和 index 由执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reserveCapacity&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;你    好    ␣     w     o     r     l     d     !
\4F60 \597D \0020 \0077 \006F \0072 \006C \0064 \0021
                  ^ index
&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;你          好          ␣   w   o   r   l   d   !
\E4 \BD \A0 \E5 \A5 \BD \20 \77 \6F \72 \6C \64 \21
            ^ index
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;好像哪里不对。虽然指向了不同的字符，但还在正确的字符边界上。为什么还是会崩溃呢？&lt;/p&gt;

&lt;h1 id=&quot;cache&quot;&gt;Cache&lt;/h1&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;s&quot;&gt;&quot;你好 world!&quot;&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;kt&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;encodedOffset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 好&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;确定不是因为偏移导致的崩溃。由于 Index 是简单结构体，我们可以直接强转成其中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_rawBits&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;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;%X&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unsafeBitCast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UInt64&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;c1&quot;&gt;// 30100&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;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;%X&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unsafeBitCast&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;kt&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;encodedOffset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UInt64&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;c1&quot;&gt;// 30000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;找到区别了，b8 开始多了个 1。我们可以直接查源码：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;b13:b8: grapheme cache: A 6-bit value remembering the distance to the next grapheme&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这几位缓存了到下一个字符的距离。可以加速下标访问。回到我们的字符串，使用 UTF-16 时，这个距离是 1，使用 UTF-8 时，这个距离是3。我们可以尝试构造出这个下标&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;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unsafeBitCast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x30300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;to&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;kt&quot;&gt;Index&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;s&quot;&gt;&quot;你好 world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 好&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当我们使用 30100 这个下标时，只有一个码元被取出，得到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xE5&lt;/code&gt;，即为文章开头打印出的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0o345&lt;/code&gt;。Xcode 试图使用这个非法的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Character&lt;/code&gt; 构造 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;。导致了崩溃。&lt;/p&gt;

&lt;h1 id=&quot;迷思&quot;&gt;迷思&lt;/h1&gt;

&lt;p&gt;事实上这个问题在 Swift 5 之前就存在了，如果你在一个静态 c 字符串上计算 index，然后拷贝一份，稍作修改再索引，也可能得到未定义行为。但是 Swift String 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt; 的分裂加重了问题的影响范围。现在对一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; 做出最轻微的修改，甚至不必修改其内容，就会作废之前的所有的 index （而非修改处之后的 index）。这在使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRegularExpression&lt;/code&gt; 时尤其恼人。&lt;/p&gt;

&lt;p&gt;要避免这一问题，你可以：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.UTF16View.Index&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;缺点：某些情况下性能糟糕&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSString&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;缺点：需要不断类型转换，对性能也有影响&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Character].Index&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;缺点：需要将待处理字符串转为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Character]&lt;/code&gt;，无法使用字符串相关方法&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现有的设计并不理想。很难想象 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.Index&lt;/code&gt; 居然不能兼顾到性能，安全性和易用性的任何一方。我们需要使用冗长的语法构建出 Index，并且承担了大量的性能代价。然而这些脆弱的 Index 不能承受最轻微的修改。如果这是 Unicode Correctness 的代价，未免也太重了些。&lt;/p&gt;
</description>
        <pubDate>Thu, 02 May 2019 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2019/05/02/surprising-string-behavior/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2019/05/02/surprising-string-behavior/</guid>
        
        <category>Swift</category>
        
        
      </item>
    
      <item>
        <title>Method Swizzling 与 NSProxy</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;本文假设读者有关于 Method Swizzling，和 objc 消息处理流程的基本知识。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;hook-delegate-方法&quot;&gt;Hook delegate 方法&lt;/h2&gt;

&lt;p&gt;使用 Method Swizzling 可以轻松 hook 一个类中的已知方法，但是对于 delegate 方法，其实际调用的类是不确定的, 如何针对未知调用类来 hook 呢？&lt;/p&gt;

&lt;p&gt;这是一个很经典的问题，网上已经有很成熟的方案了。这里只大概讲一下思路。&lt;/p&gt;

&lt;p&gt;由于 delegate 的调用对象是一个遵循 delegate 协议的类的实例，我们首先需要 hook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setDelegate&lt;/code&gt; 这个方法。找到实际的 delegate 对象，然后动态进行 Hook。这也分两种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;delegate 对象实现了目标方法。则交换实现。&lt;/li&gt;
  &lt;li&gt;delegate 对象未实现目标方法。则动态添加该方法。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们是如何判断 delegate 对象是否实现了目标方法的呢？是通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class_getInstanceMethod&lt;/code&gt; 来查找 class 中的实例方法，如果查到了，则认为该 class 实现了该方法。&lt;/p&gt;

&lt;p&gt;停！这里有一个很隐蔽的漏洞。我们并不是要判断 &lt;strong&gt;delegate 对象是否实现了某个方法&lt;/strong&gt;，而是要判断 &lt;strong&gt;delegate 对象能否响应某条消息&lt;/strong&gt;。这两个命题一般是相等的，但在某些特殊情况下有着细微的差别。&lt;/p&gt;

&lt;h2 id=&quot;坑&quot;&gt;坑&lt;/h2&gt;

&lt;p&gt;如果你用 Method Swizzling 实现了 hook UIScrollView 的 delegate 方法，你会遇到一个很诡异的的 Bug。具体来说，UIWebView 对点击事件的响应会相当奇怪。你可能无法和页面中的某些元素交互。&lt;/p&gt;

&lt;p&gt;你可能想看一下动态 hook 的过程发生了什么。在 UIWebView 的例子中，scroll view 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UIWebViewScrollView&lt;/code&gt;，而设置的 delegate 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UIWebViewScrollViewDelegateForwarder&lt;/code&gt; 的实例。试图 hook 时，我们发现 delegate 并没有实现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIScrollViewDelegate&lt;/code&gt; 中的方法，于是我们添加了这些方法。但是如果我们不添加这些方法，而是打上符号断点的话，会发现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UIWebViewScrollViewDelegateForwarder&lt;/code&gt; 确实处理了这些消息。&lt;/p&gt;

&lt;p&gt;如果你熟悉 proxy 设计模式的话，这个类名已经提示你发生什么事了。&lt;/p&gt;

&lt;h2 id=&quot;proxy-与-消息转发&quot;&gt;Proxy 与 消息转发&lt;/h2&gt;

&lt;p&gt;上面例子中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UIWebViewScrollViewDelegateForwarder&lt;/code&gt; 是一个 proxy 对象。它本身不处理任何消息，也没有实现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIScrollViewDelegate&lt;/code&gt; 中的方法。当它收到一条消息时，会通过消息转发流程，将消息转发给真正的接收者。&lt;/p&gt;

&lt;p&gt;所以，当你通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class_getInstanceMethod&lt;/code&gt; 查询时，会发现 proxy 并没有这些实例方法。于是你动态添加了这些方法。然而，一旦 proxy 对象实现了某个方法，对应的消息就不会走转发流程，而是直接被 proxy 处理掉了。也就是说，动态添加的方法“吃掉”了本应被转发的消息。&lt;/p&gt;

&lt;p&gt;我们可以通过 &lt;a href=&quot;https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/UIKit.framework/_UIWebViewScrollViewDelegateForwarder.h&quot;&gt;dump 出来的头文件&lt;/a&gt;看到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_UIWebViewScrollViewDelegateForwarder&lt;/code&gt; 到底做了什么。它在内部保存了一个真实的 delegate，然后通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forwardInvocation:&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;methodSignatureForSelector:&lt;/code&gt; 将消息转发给这个 delegate。&lt;/p&gt;

&lt;h2 id=&quot;解决方案&quot;&gt;解决方案&lt;/h2&gt;

&lt;p&gt;任何动态添加的方法都可能“吃掉”本应被转发的消息。不过我们可以选择不要 Hook 可能转发消息的类。改进后的 Method Swizzling 流程是这样的：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class_getInstanceMethod&lt;/code&gt; 查找实例方法。&lt;/li&gt;
  &lt;li&gt;如果没有实例方法，检查 delegate 是否是 NSProxy 的子类，或者实现了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forwardingTargetForSelector:&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forwardInvocation:&lt;/code&gt; 这一类的转发方法&lt;/li&gt;
  &lt;li&gt;若是，则说明可能是 proxy 对象，终止 hook 流程&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;更好的方法是通过 Proxy 对象拦截发送的消息。我会再写一篇文章描述这一方案。&lt;/p&gt;
</description>
        <pubDate>Tue, 14 Aug 2018 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2018/08/14/method-swizzling-and-nsproxy/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2018/08/14/method-swizzling-and-nsproxy/</guid>
        
        <category>Runtime</category>
        
        
      </item>
    
      <item>
        <title>Swift 中基于闭包的 KVO 的隐患</title>
        <description>&lt;p&gt;KVO (Key-Value Observing) 是 Foundation 提供的极其强大的特性，但是使用起来十分啰嗦。好在 Swift 4 提供了一个简洁的包装。我们可以轻松写出以下的代码：&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;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@objc&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;dynamic&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&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;kt&quot;&gt;Int&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;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&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;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;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSKeyValueObservation&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;nf&quot;&gt;init&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;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;observe&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;v&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;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&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;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&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;new value: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newValue&lt;/span&gt;&lt;span class=&quot;o&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;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;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; 时手动移除观察者，因为 NSKeyValueObservation 在生命周期结束后就会自动停止观察：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;when the returned NSKeyValueObservation is deinited or invalidated, it will stop observing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;真的是这样吗？我们来加两个符号断点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-[NSObject addObserver:forKeyPath:options:context:]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-[NSObject removeObserver:forKeyPath:context:]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&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;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;B&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;kt&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;b&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行到第一行时，我们可以看到确实调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addObserver&lt;/code&gt;，然而，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeObserver&lt;/code&gt; 却没有如预期调用。这可不是我们想要的结果，带着观察者被销毁可能会导致一些难以调试的崩溃。&lt;/p&gt;

&lt;p&gt;如果我们在第一行和第二行之间插入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b.token = nil&lt;/code&gt;，手动释放 KVO，可以发现此时又会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeObserver&lt;/code&gt;，而靠 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&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;o&quot;&gt;-&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;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSKeyValueObservation&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;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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有些同学可能已经猜到问题在哪了。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSKeyValueObservation&lt;/code&gt; 内保存了一个观察对象的弱引用，以便在合适的时机注销观察者。第一个例子中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.a&lt;/code&gt; 先销毁，等到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.token&lt;/code&gt; 销毁时，已经找不到目标来注销观察者了。&lt;/p&gt;

&lt;p&gt;这里有个非常令人迷惑的逻辑漏洞，token只被 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; 引用，保证了token的生命周期不长于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;。而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; 引用了观察对象，保证了观察对象生命周期不短于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;。那么观察对象被销毁前，token也会被销毁。如此就保证了观察对象就不会带着观察者被销毁。而事实上，这还取决于成员变量销毁的顺序。&lt;/p&gt;

&lt;p&gt;为了避免这种时机问题，最保险的办法是，在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.deinit&lt;/code&gt; 中手动销毁 KVO。&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;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;kd&quot;&gt;deinit&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;token&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;invalidate&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;事实上，第一个例子中的代码并不完全符合&lt;a href=&quot;https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html&quot;&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;o&quot;&gt;...&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;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;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;kd&quot;&gt;@objc&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;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;observe&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;v&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;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&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;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;observe&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;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&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;nv&quot;&gt;options&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;k&quot;&gt;new&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;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;o&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;self&lt;/code&gt;，就不会出现上述的问题了。不过需要把对象标注为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@objc&lt;/code&gt;，我并不喜欢这种方式。&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;如果直接对观察对象调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;observe(_:options:changeHandler:)&lt;/code&gt; 并保存 token，可能导致观察对象早于 token 被释放。需要在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deinie&lt;/code&gt; 中手动调用 token 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidate&lt;/code&gt; 方法以注销观察者。&lt;/p&gt;

</description>
        <pubDate>Sun, 29 Apr 2018 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2018/04/29/swift-block-based-kvo-trouble/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2018/04/29/swift-block-based-kvo-trouble/</guid>
        
        <category>Swift</category>
        
        
      </item>
    
      <item>
        <title>在 Swift 中捕获 Objc 抛出的异常</title>
        <description>&lt;h1 id=&quot;错误还是异常&quot;&gt;错误还是异常&lt;/h1&gt;

&lt;p&gt;一般来说错误分为两种情况。一种是可以预见，可恢复的错误。称为错误（error）。另一种则是不能预见，通常也是不可恢复的致命错误。称为异常（exception）。例如，文件无法打开是错误，数组访问越界则是异常。&lt;/p&gt;

&lt;p&gt;在 Objective-C 中，这两种错误分别以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSException&lt;/code&gt; 表示。Apple 有两篇关于此的文档，&lt;a href=&quot;https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html&quot;&gt;Dealing with Errors&lt;/a&gt; 和 &lt;a href=&quot;https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html&quot;&gt;Handling Exceptions&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Swift 从 2.0 开始有了内建错误处理。虽然形式上是我们熟悉的 try-catch(do-catch) 但实际上做的是错误处理，而不是异常。Objc 中原有使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSError&lt;/code&gt; 的代码被标记为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throws&lt;/code&gt;。而原本会抛出异常的代码则使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fatalError&lt;/code&gt; 直接崩溃。&lt;/p&gt;

&lt;h1 id=&quot;接不住的异常&quot;&gt;接不住的异常&lt;/h1&gt;

&lt;p&gt;我个人喜欢 Swift 的设计。它要求开发者显示处理可能的错误，并且其抛出错误的效率远高于 C++ （相比兼任错误处理的异常处理），而对于异常部分，其被设计为尽早退出，避免程序进入未定义区域。但是，这样的设计也造成了一些尴尬的情况，完全排除异常处理的 Swift 无法处理 Objc 中抛出的异常。&lt;/p&gt;

&lt;p&gt;例如，反序列化方法 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSKeyedUnarchiver.unarchiveObject(with:)&lt;/code&gt; 在失败时会抛出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidArchiveOperationException&lt;/code&gt;，这个错误无法通过 Swift 的 do-catch 接住。事实上，当你试图用纯 Swift 反序列化一段来源不明的数据时，没有任何办法能避免程序崩溃。&lt;/p&gt;

&lt;p&gt;我认为这是一个设计上的失误，这个异常明显是开发阶段可以预见的，但却没有按照错误来处理。iOS 9 / macOS 10.11 引入了另一个不会抛异常的方法 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unarchiveTopLevelObjectWithData(:) throws&lt;/code&gt;。可惜对于大多数应用来说，系统版本要求太高了。&lt;/p&gt;

&lt;h1 id=&quot;封装异常处理&quot;&gt;封装异常处理&lt;/h1&gt;

&lt;p&gt;我们可以在 Objc 做一层封装，以便在纯 Swift 中处理异常：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-objective-c&quot;&gt;void catchException(void (NS_NOESCAPE ^ _Nonnull tryBlock)(), void(NS_NOESCAPE ^ _Nullable catchBlock)(NSException * _Nonnull e)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        if (catchBlock) {
            catchBlock(exception);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个方法导入到 Swift 中是这样的：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;catchException(_: () -&amp;gt; Void, _: ((NSException) -&amp;gt; Void)?)&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;result&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;nf&quot;&gt;catchException&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;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;k&quot;&gt;try&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;contentsOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&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;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSKeyedUnarchiver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unarchiveObject&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;data&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;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&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;c1&quot;&gt;// error handling&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;e&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// exception handling&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// use `result`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还是很不方便，这样做有几个问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;无法在 block 中返回值到外部&lt;/li&gt;
  &lt;li&gt;不保证控制流&lt;/li&gt;
  &lt;li&gt;和 Swift 原生 error 嵌套时难以处理&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们再用 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;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSException&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;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;catch&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;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;block&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;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;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;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;var&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;T&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;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;catchException&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;n&quot;&gt;_result&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;nf&quot;&gt;block&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;n&quot;&gt;_error&lt;/span&gt; &lt;span class=&quot;o&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;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;n&quot;&gt;_error&lt;/span&gt; &lt;span class=&quot;o&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;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;_error&lt;/span&gt; &lt;span class=&quot;o&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;k&quot;&gt;throw&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_result&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;NSException&lt;/code&gt; 的类方法以避免污染命名空间。注意我们让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSException&lt;/code&gt; 遵循 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt;，这样允许我们直接抛出把 exception 作为错误抛出。&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;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;obj&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;kt&quot;&gt;NSException&lt;/span&gt;&lt;span class=&quot;o&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;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;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&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;data&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;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;contentsOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&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;NSKeyedUnarchiver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unarchiveObject&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;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;c1&quot;&gt;// use `obj`&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;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// try NSException.catch&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;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSException&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// exception handling&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;c1&quot;&gt;// error handling&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;NSException&lt;/code&gt; 并允许程序继续运行是极不推荐的。Objc 被假定不能从异常中恢复，所以 ARC 默认不保证异常安全，抛出异常可能导致内存泄漏。务必只在必要时这样做。&lt;/p&gt;
</description>
        <pubDate>Fri, 29 Dec 2017 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2017/12/29/catching-objc-exceptions-in-swift/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2017/12/29/catching-objc-exceptions-in-swift/</guid>
        
        <category>Swift</category>
        
        
      </item>
    
      <item>
        <title>【译】Swift 4 中的弱引用</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;本文译自 &lt;a href=&quot;https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html&quot;&gt;Friday Q&amp;amp;A 2017-09-22: Swift 4 Weak References&lt;/a&gt;，作者 &lt;a href=&quot;https://mikeash.com/&quot;&gt;Mike Ash&lt;/a&gt;。&lt;/p&gt;

  &lt;p&gt;译者 &lt;a href=&quot;https://ddddxxx.github.io/&quot;&gt;ddddxxx&lt;/a&gt;，自由转载，请注明出处。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swift 刚开源不久，我写了一篇关于&lt;a href=&quot;https://mikeash.com/pyblog/friday-qa-2015-12-11-swift-weak-references.html&quot;&gt;弱引用如何实现的文章&lt;/a&gt;。时过境迁，这一实现已经发生了改变。我将谈谈当前的实现方法，以及和旧版本的对比。&lt;/p&gt;

&lt;!--Soon after Swift was initially open sourced, I wrote an article about how weak references are implemented. Time moves on and things change, and the implementation is different from what it once was. Today I'm going to talk about the current implementation and how it works compared to the old one, a topic suggested by Guillaume Lessard.--&gt;

&lt;h2 id=&quot;原有的实现&quot;&gt;原有的实现&lt;/h2&gt;

&lt;!--Old Implementation--&gt;

&lt;p&gt;鉴于有些人已经忘了原有的实现，且不想读之前的文章，让我们快速回顾一下它是怎么实现的&lt;/p&gt;

&lt;p&gt;在原来的实现中，Swift 对象有两个引用计数：强引用计数和弱引用计数。当强引用计数归零，而弱引用计数仍不为零时，这个对象将被销毁（destroy），但内存不会被释放（deallocate）。这导致被弱引用指向的对象成为某种僵尸对象驻留在内存中。&lt;/p&gt;

&lt;p&gt;加载弱引用时，runtime 将会检查对象是否为僵尸对象。若是，则清零弱引用，并将弱引用计数减 1。当弱引用计数归零时则释放内存。这意味着一旦访问了对它们的弱引用，僵尸对象就会被清除。&lt;/p&gt;

&lt;p&gt;我喜欢这个简洁的实现，但是它也有缺陷。一个缺陷是，僵尸对象可能在内存中停留很长时间。对于那些有大型实例的类（因为它包含很多成员变量，或使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ManagedBuffer&lt;/code&gt; 这样的方法来分配额外内存），可能是极大的浪费。&lt;/p&gt;

&lt;p&gt;编写之前的文章后我&lt;a href=&quot;https://bugs.swift.org/browse/SR-192&quot;&gt;还发现了另一个问题&lt;/a&gt;，它在并发访问时不是线程安全的。这个问题随后修复了，但是对其的讨论表明，它的实现者需要更好的方法来实现弱引用，为了能更好的适应类似的状况。&lt;/p&gt;

&lt;!--For those of you who have forgotten the old implementation and don't feel like reading through the last article, let's briefly recall how it works.

In the old implementation, Swift objects have two reference counts: a strong count and a weak count. When the strong count reaches zero while the weak count is still non-zero, the object is destroyed but its memory is not deallocated. This leaves a sort of zombie object sitting in memory, which the remaining weak references point to.

When a weak reference is loaded, the runtime checks to see if the object is a zombie. If it is, it zeroes out the weak reference and decrements the weak reference count. Once the weak count reaches zero, the object's memory is deallocated. This means that zombie objects are eventually cleared out once all weak references to them are accessed.

I loved the simplicity of this implementation, but it had some flaws. One flaw was that the zombie objects could stay in memory for a long time. For classes with large instances (because they contain a lot of properties, or use something like ManagedBuffer to allocate extra memory inline), this could be a serious waste.

Another problem, which I discovered after writing the old article, was that the implementation wasn't thread-safe for concurrent reads. Oops! This was patched, but the discussion around it revealed that the implementers wanted a better implementation of weak references anyway, which would be more resilient to such things.--&gt;

&lt;h2 id=&quot;对象数据&quot;&gt;对象数据&lt;/h2&gt;

&lt;!--Object Data--&gt;

&lt;p&gt;Swift 中的“一个对象”是由多个部分构成的。&lt;/p&gt;

&lt;p&gt;首先，最明显的是，源代码中声明的的存储属性。它们可以由程序员直接访问。&lt;/p&gt;

&lt;p&gt;第二部分，是对象的类。它被用于动态派发，以及内建的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type(of:)&lt;/code&gt; 函数。大多数情况下，它是隐藏的，尽管动态派发和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type(of:)&lt;/code&gt; 暗示了它的存在。&lt;/p&gt;

&lt;p&gt;第三部分，是各种引用计数。它们完全不可见，除非你做一些骚操作，例如读对象的原始内存，或者说服编译器让你调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CFGetRetainCount&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;第四部分，通过 Objective-C runtime 添加的辅助信息，例如 Objective-C 弱引用表（Objective-C 会单独追踪每个弱指针）和关联对象（associated object）。&lt;/p&gt;

&lt;p&gt;你要把这些信息储存在哪？&lt;/p&gt;

&lt;!--There are many pieces of data which make up &quot;an object&quot; in Swift.

First, and most obviously, there are all of the stored properties declared in the source code. These are directly accessible by the programmer.

Second, there is the object's class. This is used for dynamic dispatch and the type(of:) built-in function. This is mostly hidden, although dynamic dispatch and type(of:) imply its existence.

Third, there are the various reference counts. These are completely hidden unless you do naughty things like read the raw memory of your object or convince the compiler to let you call CFGetRetainCount.

Fourth, you have auxiliary information stored by the Objective-C runtime, like the list of Objective-C weak references (the Objective-C implementation of weak references tracks each weak reference individually) and associated objects.

Where do you store all of this stuff?--&gt;

&lt;p&gt;在 Objective-C 中，类型信息和存储属性（即实例变量）都内联存储在对象的内存中。类型占用了第一个指针大小的块，实例变量尾随其后。辅助信息存储在额外的表中。当你使用关联对象（associated object）时，runtime 会查找一个大哈希表，它的键是对象的指针。这有些慢，并且这一操作必须加锁以避免多线程操作失败。引用计数有时保存在对象的内存中，有时保存在外部表中，这取决于系统版本和 CPU 架构。&lt;/p&gt;

&lt;p&gt;在 Swift 原有的实现中，类，引用计数和存储属性都内联存储。而辅助信息仍然存储在单独的表中。&lt;/p&gt;

&lt;p&gt;让我们把这些语言的实现放在一边，先问一个问题：它们&lt;em&gt;应该&lt;/em&gt;怎样存储？&lt;/p&gt;

&lt;p&gt;每个位置各有优劣。存储在对象的内存中的数据访问起来很快，但始终占用空间。存储在额外表中的数据访问起来稍慢，但是若是对象没有这些数据，它们就不占用空间。&lt;/p&gt;

&lt;p&gt;Objective-C 传统上不在对象内部保存引用计数，至少有一部分原因就是为此。Objective-C 加入引用计数时，电脑性能远不如现在，并且内存极为有限。典型的 Objective-C 程序中的大多数对象只有一个所有者，即引用计数为 1。在对象内存中留出 4 字节的空间用于存储 1 是很浪费的。通过使用外部表，1 这样的常见值可以由表项不存在来表示，从而减少内存消耗。&lt;/p&gt;

&lt;!--In Objective-C, the class and stored properties (i.e. instance variables) are stored inline in the object's memory. The class takes up the first pointer-sized chunk, and the instance variables come after. Auxiliary information is stored in external tables. When you manipulate an associated object, the runtime looks it up in a big hash table which is keyed by the object's address. This is somewhat slow and requires locking so that multithreaded access doesn't fail. The reference count is sometimes stored in the object's memory and sometimes stored in an external table, depending on which OS version you're running and which CPU architecture.

In Swift's old implementation, the class, reference counts, and stored properties were all stored inline. Auxiliary information was still stored in a separate table.

Putting aside how these languages actually do it, let's ask the question: how should they do it?

Each location has tradeoffs. Data stored in the object's memory is fast to access but always takes up space. Data stored in an external table is slower to access but takes up zero space for objects which don't need it.

This is at least part of why Objective-C traditionally didn't store the reference count in the object itself. Objective-C reference counting was created when computers were much less capable than they were now, and memory was extremely limited. Most objects in a typical Objective-C program have a single owner, and thus a reference count of 1. Reserving four bytes of the object's memory to store 1 all the time would be wasteful. By using an external table, the common value of 1 could be represented by the absence of an entry, reducing memory usage.--&gt;

&lt;p&gt;每一个对象都有一个类，并且会经常访问。每次调用动态方法都需要它。它应该直接保存在对象内存中。存储在外部也不会有任何好处。&lt;/p&gt;

&lt;p&gt;存储属性预计可以快速访问，对象是否拥有它们是在编译期决定的。如果一个对象没有没有存储属性，就可以不分配空间。所以它们应该直接保存在对象内部。&lt;/p&gt;

&lt;p&gt;每一个对象都有引用计数。虽然不是所有对象的引用计数都不为 1，但这仍然是很常见的情况，而且现在内存已经很大了。它或许应该直接保存在对象内存里。&lt;/p&gt;

&lt;p&gt;大多数对象都没有弱引用计数或是关联对象。为它们在对象内存中分配空间是很浪费的。它们应该保存在外部。&lt;/p&gt;

&lt;p&gt;这是合理的权衡，但也很讨厌。对于那些有弱引用计数和关联对象的对象来说，这实在有些慢。我们怎样能解决这个问题呢？&lt;/p&gt;

&lt;!--Every object has a class, and it is constantly accessed. Every dynamic method call needs it. This should go directly in the object's memory. There's no savings from storing it externally.

Stored properties are expected to be fast. Whether an object has them is determined at compile time. Objects with no stored properties can allocate zero space for them even when stored in the object's memory, so they should go there.

Every object has reference counts. Not every object has reference counts that aren't 1, but it's still pretty common, and mem ory is a lot bigger these days. This should probably go in the object's memory.

Most objects don't have any weak references or associated objects. Dedicating space within the object's memory for these would be wasteful. These should be stored externally.

This is the right tradeoff, but it's annoying. For objects that have weak references and associated objects, they're pretty slow. How can we fix this?--&gt;

&lt;h2 id=&quot;side-tables&quot;&gt;Side Tables&lt;/h2&gt;

&lt;p&gt;Swift 新的弱引用的实现引入了一个名为 side table 的概念。&lt;/p&gt;

&lt;p&gt;Side table 是一个单独的内存块，用于保存一个对象的额外信息。它是&lt;em&gt;可选&lt;/em&gt;的，这意味着一个对象可能有 side tables，也可能没有。那些需要 side table 功能的对象会造成额外开销，而那些不需要的对象则不必付出任何开销。&lt;/p&gt;

&lt;p&gt;每个对象都有一个指向其 side table 的指针，而 side table 中也有一个指向原始对象的指针。side table 还可以存储其它信息，例如关联对象。&lt;/p&gt;

&lt;p&gt;为了避免 side table 带来的 8 字节空间开销，Swift做了一个漂亮的优化。对象的第一个字是它的类，下一个字是引用计数。当一个对象需要 side table 时，第二个字将被重新用作 side table 指针。因为对象仍需引用计数，所以引用计数将保存在 side table 中。这两种情况将由其中的一个位来区分，从而决定保存的是引用计数还是 side table。&lt;/p&gt;

&lt;p&gt;side table 允许 Swift 保持原有引用计数的基本形式，并修复其缺陷。弱引用不再指向对象本身，而是直接指向 side table。&lt;/p&gt;

&lt;p&gt;因为 side table 已知很小，这样就不会有弱引用指向大对象导致的内存浪费，所以问题自然就消失了。这也指明了线程安全问题的简单解决方案：不用提前清零弱引用。既然已知 side table 比较小，指向它的弱引用可以持续保留，直到这些引用自身被覆盖或销毁。&lt;/p&gt;

&lt;p&gt;我要提醒一下，当前的 side table 实现只保存引用计数和指向原始对象的指针。其它用途，例如关联对象，只是一个假设。Swift 没有内建关联对象功能，而 Objective-C API 仍在使用全局表。&lt;/p&gt;

&lt;p&gt;该技术有很大的潜力，我们或许能在不久的将来看到其它用法，例如关联对象。我希望这将打开在类扩展中声明存储属性等其它漂亮功能的大门。&lt;/p&gt;

&lt;!--Swift's new implementation of weak references brings with it the concept of side tables.

A side table is a separate chunk of memory which stores extra information about an object. It's optional, meaning that an object may have a side table, or it may not. Objects which need the functionality of a side table can incur the extra cost, and objects which don't need it don't pay for it.

Each object has a pointer to its side table, and the side table has a pointer back to the object. The side table can then store other information, like associated object data.

To avoid reserving eight bytes for the side table, Swift makes a nifty optimization. Initially, the first word of an object is the class, and the next word stores the reference counts. When an object needs a side table, that second word is repurposed to be a side table pointer instead. Since the object still needs reference counts, the reference counts are stored in the side table. The two cases are distinguished by setting a bit in this field that indicates whether it holds reference counts or a pointer to the side table.

The side table allows Swift to maintain the basic form of the old weak reference system while fixing its flaws. Instead of pointing to the object, as it used to work, weak references now point directly at the side table.

Because the side table is known to be small, there's no issue of wasting a lot of memory for weak references to large objects, so that problem goes away. This also points to a simple solution for the thread safety problem: don't preemptively zero out weak references. Since the side table is known to be small, weak references to it can be left alone until those references themselves are overwritten or destroyed.

I should note that the current side table implementation only holds reference counts and a pointer to the original object. Additional uses like associated objects are currently hypothetical. Swift has no built-in associated object functionality, and the Objective-C API still uses a global table.

The technique has a lot of potential, and we'll probably see something like associated objects using it before too long. I'm hopeful that this will open the door to stored properties in extensions class types and other nifty features.--&gt;

&lt;h2 id=&quot;代码&quot;&gt;代码&lt;/h2&gt;

&lt;p&gt;由于 Swift 已经开源，关于这些的代码都能直接访问。&lt;/p&gt;

&lt;p&gt;大多数关于 side table 的代码都在 &lt;a href=&quot;https://github.com/apple/swift/blob/c262440e70896299118a0a050c8a834e1270b606/stdlib/public/SwiftShims/RefCount.h&quot;&gt;stdlib/public/SwiftShims/RefCount.h&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;更高层次的弱引用 API，以及丰富的注释，都在 &lt;a href=&quot;https://github.com/apple/swift/blob/c262440e70896299118a0a050c8a834e1270b606/stdlib/public/runtime/WeakReference.h&quot;&gt;swift/stdlib/public/runtime/WeakReference.h&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;更多关于堆对象的实现和注释在 &lt;a href=&quot;https://github.com/apple/swift/blob/c262440e70896299118a0a050c8a834e1270b606/stdlib/public/runtime/HeapObject.cpp&quot;&gt;stdlib/public/runtime/HeapObject.cpp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我链接到了这些文件的特定版本，以便以后的读者也能看到我在说什么。如果你想看最新，最好的代码，请在点击链接后切换到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; 分支，或任何你感兴趣的内容。&lt;/p&gt;

&lt;!--Since Swift is open source, all of the code for this stuff is accessible.

Most of the side table stuff can be found in stdlib/public/SwiftShims/RefCount.h.

The high-level weak reference API, along with juicy comments about the system, can be found in swift/stdlib/public/runtime/WeakReference.h.

Some more implementation and comments about how heap-allocated objects work can be found in stdlib/public/runtime/HeapObject.cpp.

I've linked to specific commits of these files, so that people reading from the far future can still see what I'm talking about. If you want to see the latest and greatest, be sure to switch over to the master branch, or whatever is relevant to your interests, after you click the links.--&gt;

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

&lt;p&gt;弱引用是一个重要的语言特性。Swift 的初始实现非常聪明，有很多优势，但也有一些问题。通过添加 side table，Swift 的工程师能够在解决问题的同时保留这些优势。side table 也为未来的新功能铺平了道路。&lt;/p&gt;

&lt;!--Weak references are an important language feature. Swift's original implementation was wonderfully clever and had some nice properties, but also had some problems. By adding an optional side table, Swift's engineers were able to solve those problems while keeping the nice, clever properties of the original. The side table implementation also opens up a lot of possibilities for great new features in the future.

That's it for today. Come back again for more crazy programming-related ghost stories. Until then, if you have a topic you'd like to see covered here, please send it in!--&gt;
</description>
        <pubDate>Wed, 27 Sep 2017 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2017/09/27/swift-4-weak-references/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2017/09/27/swift-4-weak-references/</guid>
        
        <category>翻译</category>
        
        <category>Swift</category>
        
        
      </item>
    
      <item>
        <title>macOS 10.13 SDK 的一个错误，以及解决方案</title>
        <description>&lt;p&gt;最近升级了 Xcode 9 GM，并把几个主要项目迁移到了 Swift 4。过程中发现了一个小坑，记下来以供参考。&lt;/p&gt;

&lt;h2 id=&quot;0&quot;&gt;0&lt;/h2&gt;

&lt;p&gt;Xcode 9 附带的 macOS 10.13 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;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;NSFontPanel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ModeMask&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;NSFontPanel.ModeMask&lt;/code&gt; 是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OptionSet&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;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&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;kt&quot;&gt;NSFontPanelSizeModeMask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanelCollectionModeMask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanelFaceModeMask&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;return&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;size&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;collection&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;face&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;kd&quot;&gt;@available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;OSX&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.13&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;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ModeMask&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;OptionSet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这就很尴尬了，这个类要求 macOS 10.13 或更高的系统版本。我们显然不能接受这个条件。&lt;/p&gt;

&lt;h2 id=&quot;1&quot;&gt;1&lt;/h2&gt;

&lt;p&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;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;c1&quot;&gt;// error: method does not override any method from its superclass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因为方法签名改了，原有的方法自然没有覆盖新的 API。所以我们删掉 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;override&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;validModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再次运行程序，发现 FontPanel 并没有遵循我们的设置。这是因为 Swift 4 新的特性，根据 &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md&quot;&gt;SE-0160&lt;/a&gt;，NSObject 子类的方法不会自动生成 Objective-C 入口。所以这个方法在 Objective-C 运行时是不可见的。&lt;/p&gt;

&lt;p&gt;根据要求，我们添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@objc&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;@objc&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;c1&quot;&gt;// error: Overriding method with selector 'validModesForFontPanel:' has incompatible type '(NSFontPanel) -&amp;gt; Int'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;方法签名不一致，编译器拒绝生成 Objective-C 入口。&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;@available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;OSX&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.13&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;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;NSFontPanel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ModeMask&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// error: Overriding 'validModesForFontPanel' must be as available as declaration it overrides&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;到这里似乎所有的路都堵死了，难道只能退回到 Objective-C 来实现吗？&lt;/p&gt;

&lt;h2 id=&quot;2&quot;&gt;2&lt;/h2&gt;

&lt;p&gt;其实纯 Swift 的方法也是有的，不过有些复杂。有请 Method Swizzling：&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;MyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;swizzler&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;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cls&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyClass&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;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;#selector(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NSObject.validModesForFontPanel&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dummySel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;#selector(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MyClass.dummyValidModesForFontPanel&lt;/span&gt;&lt;span class=&quot;kd&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;dummyIMP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;class_getMethodImplementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummySel&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;dummyImpl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;class_getInstanceMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummySel&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;typeEncoding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;method_getTypeEncoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dummyImpl&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;fatalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;failed to replace method &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sel&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; from &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&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;nf&quot;&gt;class_replaceMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummyIMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typeEncoding&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;@objc&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dummyValidModesForFontPanel&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;fontPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanel&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;UInt32&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Do what you want&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSFontPanelStandardModesMask&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;NSObject.validModesForFontPanel&lt;/code&gt; 的实现。为了使其生效，我们需要在合适的时机调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ = MyClass.swizzler&lt;/code&gt;。由于不需要原来的实现，这里使用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class_replaceMethod&lt;/code&gt; 而不是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;method_exchangeImplementations&lt;/code&gt;，事实上，这里的 MyClass 原本并没有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;validModesForFontPanel&lt;/code&gt; 的实现。&lt;/p&gt;

&lt;p&gt;这里的 swizzler 是一个小技巧，由于 Swift 3 废弃了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch_once&lt;/code&gt;，这样写可以保证 swizzler 仅构造一次，即 Method Swizzling 仅执行一次。我会再写一篇文章详细解释这一方法。&lt;/p&gt;
</description>
        <pubDate>Sat, 23 Sep 2017 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2017/09/23/macos-sdk-compatibility-issue/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2017/09/23/macos-sdk-compatibility-issue/</guid>
        
        <category>Swift</category>
        
        <category>Cocoa</category>
        
        <category>Runtime</category>
        
        
      </item>
    
      <item>
        <title>如何优雅地在 Jekyll 博客中插入高清图片</title>
        <description>&lt;p&gt;用 Markdown 插入图片是一件很简单的事，只需要这样写：&lt;/p&gt;

&lt;div class=&quot;language-markdown 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;nv&quot;&gt;AltText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;/path/to/img.jpg&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;然而，如果你需要进行更多的控制，情况立刻变得复杂了起来。由于 Markdown 秉持结构和样式分离的原则，你无法在插入图片时控制其大小。要达到这样的目的，你需要使用普通的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 标签：&lt;/p&gt;

&lt;div class=&quot;language-html 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;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/path/to/img.jpg&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;100&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;100&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AltText&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相对前一种语法，后者明显麻烦许多。所幸这种情况一般都是极少数。一般来说，我们只需要使用前一种语法。然而，有一种状况会让你需要大量使用后一种语法 —— Retina 屏幕的截图。一张 100pt * 100pt 的截图事实上是 200px * 200px。贴到文章里之后，电脑又会以 200pt * 200pt 的大小来渲染。你的截图立刻变得大而模糊。为了避免这种状况，你需要手动计算图片的实际大小，然后使用 HTML 语法插入图片。当你需要插入大量图片时，这一工作很快变得枯燥而难以忍受。&lt;/p&gt;

&lt;p&gt;当然，你可以通过指定比例从而避免手工计算大小：&lt;/p&gt;

&lt;div class=&quot;language-html 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;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/path/to/img.jpg&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;50%&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AltText&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;虽然免去了计算大小，你依然需要使用冗长的 HTML 语法。能不能用 Markdown 语法来做这件事呢？&lt;/p&gt;

&lt;p&gt;我们知道有些 Markdown 方言支持调整图片大小，例如：&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Mou:
&lt;span class=&quot;p&quot;&gt;![&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AltText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;/path/to/img.jpg&lt;/span&gt; =100x)

MWeb:
&lt;span class=&quot;p&quot;&gt;![&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AltText-w100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;/path/to/img.jpg&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;显然，这些在 Jekyll 中都是不支持的。不过，我们有更好的方法：&lt;/p&gt;

&lt;div class=&quot;language-markdown 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;nv&quot;&gt;AltText @2x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&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;@2x&lt;/code&gt; 后缀，图片就会自动缩放为50%大小&lt;/p&gt;

&lt;p&gt;这显然不是 Markdown 或 Jekyll 的功能，在你的博客中使用不会有任何效果。真正的魔法在 CSS 样式表中：&lt;/p&gt;

&lt;div class=&quot;language-css 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;nc&quot;&gt;.post-container&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;@2x&quot;&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;py&quot;&gt;zoom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;50%&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;我们用到了 CSS 中的属性选择器，它可以对拥有指定属性的 HTML 元素设置样式。在这里，我们对 alt 中包含 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@2x&lt;/code&gt; 的所有图片添加了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zoom: 50%&lt;/code&gt; 属性。&lt;/p&gt;

&lt;p&gt;同理，你也可以为 iPhone * Plus 的截图设置三倍缩放：&lt;/p&gt;

&lt;div class=&quot;language-css 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;nc&quot;&gt;.post-container&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;@3x&quot;&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;py&quot;&gt;zoom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;33%&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;
</description>
        <pubDate>Mon, 21 Aug 2017 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2017/08/21/insert-retina-image-in-md/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2017/08/21/insert-retina-image-in-md/</guid>
        
        <category>Jekyll</category>
        
        <category>CSS</category>
        
        
      </item>
    
      <item>
        <title>制作跨  平台的动态链接库</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;注意：这篇文章旨在解决多平台项目中 target 过多的问题，而不会教你制作 Framework 的基础知识。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;引言&quot;&gt;引言&lt;/h2&gt;

&lt;p&gt;如果你制作过跨  平台的 framework，你一定遇到过这种情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/Alamofire-targets.png&quot; alt=&quot;Alamofire-targets @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Alamofire 支持 iOS/macOS/tvOS/watchOS，加上测试，总共有 7 个 target。&lt;/p&gt;

&lt;p&gt;再看 SnapKit 的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/SnapKit-targets.png&quot; alt=&quot;SnapKit-targets @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;同样是支持多平台，然而只有两个 Target。它要怎么编译到不同的平台呢？于是我们来看看它的 scheme：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/SnapKit-scheme.png&quot; alt=&quot;SnapKit-scheme @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;只需要一个 scheme！其中包含了所有的目标平台。每一个都能运行单元测试并通过。&lt;/p&gt;

&lt;p&gt;这样的好处是显而易见的。通常多平台的项目都会在不同的 target 间使用相同的编译规则。而多个 target 意味着你需要手动逐个修改编译选项，极其容易出错。我在 SwiftyJSON 的工程中就发现了&lt;a href=&quot;https://github.com/SwiftyJSON/SwiftyJSON/issues/845#issuecomment-304756920&quot;&gt;这样的错误&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;多个 target 本身也导致了更多 target，一个 test target 只能运行在一个 scheme 上。所以如果你的项目中有多个 target，你还需要创建等量的 test target 以运行单元测试。&lt;/p&gt;

&lt;h2 id=&quot;解决方案&quot;&gt;解决方案&lt;/h2&gt;

&lt;p&gt;其实要做到单 target 非常简单，但是有很多细节需要注意。&lt;/p&gt;

&lt;p&gt;单 target 的核心在于，Xcode 使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Supported Platforms&lt;/code&gt;&lt;sup id=&quot;fnref:SupportedPlatforms&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:SupportedPlatforms&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; 来标记一个 target 支持的设备，你可以手动添加需要支持的平台。我创建一个新项目作为例子：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/new-project.png&quot; alt=&quot;new-project @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Supported Platforms&lt;/code&gt; 的默认设置：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/supported-platforms-original.png&quot; alt=&quot;supported-platforms-original @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;添加我们需要支持的平台：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/supported-platforms-modified.png&quot; alt=&quot;supported-platforms-modified @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此时在右上角 scheme 中选择平台，发现已经可以选择 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;My Mac&lt;/code&gt; 作为目标，但是 看不到 tvOS 和 watchOS 的模拟器。这是因为除了支持的平台以外，我们还要标记支持的设备。这个选项叫做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Target Device Family&lt;/code&gt;&lt;sup id=&quot;fnref:TargetDeviceFamily&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:TargetDeviceFamily&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;，可选的值有 1，2，3，4。&lt;/p&gt;

&lt;p&gt;不幸的是，Xcode并不允许我们选择全部的组合：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/target-device-family-select.png&quot; alt=&quot;target-device-family-select @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;你需要手动修改工程文件。文件的位置在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH/TO/YOUR/PROJECT/YourProjectName.xcodeproj/project.pbxproj&lt;/code&gt;。搜索 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TARGETED_DEVICE_FAMILY&lt;/code&gt;，将对应行的值改为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1,2,3,4&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/target-device-family-modified.png&quot; alt=&quot;target-device-family-modified @2x&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果文件中没有对应字段，请在 Xcode 中修改 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Target Device Family&lt;/code&gt; 的值，并确认该行变为加粗字体。（细字体意味着 pbxproj 中没有对应字段，自动取默认值）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在你应该可以在 scheme 中选择所有的设备和模拟器了，你还可以在任一平台上编译。不过还有一些事要处理，是关于动态链接库的。&lt;/p&gt;

&lt;p&gt;iOS 和 macOS 的程序包中，动态链接库的位置是相同的，都在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Contents/)Frameworks／&lt;/code&gt;。然而二者的可执行文件位置却不同。iOS 的可执行文件直接位于最外层，而 macOS 的可执行文件位于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contents/MacOS/&lt;/code&gt;。所以 iOS 中 framework 的相对位置是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Frameworks/&lt;/code&gt;，而 macOS 中则是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;../Frameworks/&lt;/code&gt;。我们需要设置不同的加载地址。&lt;/p&gt;

&lt;p&gt;这个选项叫做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runpath Search Path&lt;/code&gt;，我们需要额外设置针对 macOS 的选项。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/runpath-search-path.png&quot; alt=&quot;runpath-search-path @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;展开该项目，点击 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; 符号以添加一个针对 macOS 的设置。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/post-cross-platform-frameworks/runpath-search-path-modified.png&quot; alt=&quot;runpath-search-path-modified @2x&quot; /&gt;&lt;/p&gt;

&lt;p&gt;将 macOS 的值改为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@loader_path/../Frameworks&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@executable_path/../Frameworks&lt;/code&gt;。并在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Release&lt;/code&gt; 中进行相同的设置。&lt;/p&gt;

&lt;h2 id=&quot;迁移现有的项目&quot;&gt;迁移现有的项目&lt;/h2&gt;

&lt;p&gt;你可能已经有包含数个 target 的 framework。你可以遵循下列步骤将其合并为单个 target：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;选定一个平台的 target 作为目标结果。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;遵照&lt;a href=&quot;#解决方案&quot;&gt;上一节&lt;/a&gt;的步骤使其支持多个平台。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;检查其他 target 的编译选项。对于在 Xcode 中显示为加粗的项目，将其选项合并到目标 target 中。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;你的源文件中可能有一些仅用于特定平台的代码。你需要将其添加到目标 target 中，并使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#if TARGET_OS_XXX&lt;/code&gt;(Objc), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#if os(XXX)&lt;/code&gt;(Swift) 等方法使其仅在特定平台编译。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果你为每个 target 指定了不同的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Info.plist&lt;/code&gt;，我强烈建议你只保留一份。通常来说它们的内容都是相同的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;为目标 target 选择不同的平台编译并测试。通常来说这不会有任何问题。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;删除剩余的 target。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;开始新的跨平台项目&quot;&gt;开始新的跨平台项目&lt;/h2&gt;

&lt;p&gt;你可能已经看过一些文章教你如何制作多平台 framework。通常办法是新建一个空的 Xcode 工程，然后依次添加每个平台的 target。整理文件夹以使用同一份源代码。这些都完成后，你就可以遵照&lt;a href=&quot;#迁移现有的项目&quot;&gt;上一节&lt;/a&gt;的步骤合并这些 target。&lt;/p&gt;

&lt;p&gt;不过还有更好的方法。如果你使用 Swift Package Manager 的话，这一切都会简单到难以置信。&lt;/p&gt;

&lt;p&gt;To be continued…&lt;/p&gt;

&lt;h2 id=&quot;引用-carthage-包&quot;&gt;引用 Carthage 包&lt;/h2&gt;

&lt;p&gt;To be continued…&lt;/p&gt;

&lt;h2 id=&quot;注&quot;&gt;注&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:SupportedPlatforms&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Supported Platforms&lt;/code&gt; 可选值集合&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;macosx&lt;/li&gt;
        &lt;li&gt;iphoneos&lt;/li&gt;
        &lt;li&gt;iphonesimulator&lt;/li&gt;
        &lt;li&gt;appletvos&lt;/li&gt;
        &lt;li&gt;appletvsimulator&lt;/li&gt;
        &lt;li&gt;watchos&lt;/li&gt;
        &lt;li&gt;watchsimulator&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;&lt;a href=&quot;#fnref:SupportedPlatforms&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:TargetDeviceFamily&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Target Device Family&lt;/code&gt; 数字对应关系&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;1 = iPhone and iPod touch&lt;/li&gt;
        &lt;li&gt;2 = iPad&lt;/li&gt;
        &lt;li&gt;3 = Apple TV&lt;/li&gt;
        &lt;li&gt;4 = Apple Watch&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;&lt;a href=&quot;#fnref:TargetDeviceFamily&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 05 Aug 2017 00:00:00 +0000</pubDate>
        <link>https://ddddxxx.github.io/2017/08/05/cross-platform-frameworks/</link>
        <guid isPermaLink="true">https://ddddxxx.github.io/2017/08/05/cross-platform-frameworks/</guid>
        
        <category>Swift</category>
        
        <category>Xcode</category>
        
        <category>Apple</category>
        
        
      </item>
    
  </channel>
</rss>
