bool 周的博客
2018-06-03T13:15:18.478Z
http://yoursite.com/
bool周
Hexo
iOS Auto Layout-栈视图(译)
http://yoursite.com/2018/04/13/iOS Auto Layout-栈视图(译)/
2018-04-13T12:22:46.000Z
2018-06-03T13:15:18.478Z
<a id="more"></a>
<p>下面的内容主要介绍了如何使用 stack view 创建一些复杂的布局。Stack view 是一个很强悍的工具,使<br>用它设计用户界面会十分便捷。Stack view 的一些属性在很大程度上只能控制它的子视图如何排列。你可以通过增加一些额外的自定义约束,来更加精确地控制子视图的排列方式;然而,那样将会使布局变得十分复杂。</p>
<p>有关于这部分内容的源码,请查看 <a href="https://developer.apple.com/sample-code/xcode/downloads/Auto-Layout-Cookbook.zip" target="_blank" rel="external">Auto Layout Cookbook</a> 这个项目。</p>
<h3 id="简单的-Stack-View"><a href="#简单的-Stack-View" class="headerlink" title="简单的 Stack View"></a>简单的 Stack View</h3><p>这里用了一个竖直排版的 stack view 对一个 label、一个 image view 和一个 button 进行了布局。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Simple_Stack_View_Screenshot_2x.png" width="375" height="670"><br></div>
<h4 id="视图和约束"><a href="#视图和约束" class="headerlink" title="视图和约束"></a>视图和约束</h4><p>在 Interface Builder 上,拖拽一个垂直方向排布的 stack view,然后在上面添加 flowers label、image view 和 edit button。然后按照下面的方式设置约束条件。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/simple_stack_2x.png" width="238" height="428"><br></div>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1. Stack View.Leading = Superview.LeadingMargin</span><br><span class="line">2. Stack View.Trailing = Superview.TrailingMargin</span><br><span class="line">3. Stack View.Top = Top Layout Guide.Bottom + Standard</span><br><span class="line">4. Bottom Layout Guide.Top = Stack View.Bottom + Standard</span><br></pre></td></tr></table></figure>
<h4 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h4><p>在属性检查器中,设置以下属性:</p>
<table>
<thead>
<tr>
<th>Stack</th>
<th>Axis</th>
<th>Alignment</th>
<th>Distribution</th>
<th>Spacing</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Stack View</strong></td>
<td>Vertical</td>
<td>Fill</td>
<td>Fill</td>
<td>8</td>
</tr>
</tbody>
</table>
<p>下一步,给 Image View 设置以下属性:</p>
<table>
<thead>
<tr>
<th>View</th>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Image View</strong></td>
<td>Image</td>
<td>(一张花的图片)</td>
</tr>
<tr>
<td><strong>Image View</strong></td>
<td>Mode</td>
<td>Aspect Fit</td>
</tr>
</tbody>
</table>
<p>最后,在 Size 检查器中,设置 Image View 的 content-hugging 和 compression-resistance (CHCR) 权重。</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Horizontal hugging</th>
<th>Vertical Hugging</th>
<th>Horizontal resistance</th>
<th>Vertical resistance</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Image View</strong></td>
<td>250</td>
<td>249</td>
<td>750</td>
<td>749</td>
</tr>
</tbody>
</table>
<h4 id="讨论"><a href="#讨论" class="headerlink" title="讨论"></a>讨论</h4><p>你需要通过设置约束,将 stack view 固定在 superview 上,另一方面,你还需要处理 stack view 的内部布局逻辑。</p>
<p>在上图中,stack view 以一个标准边距充满了整个父视图。Stack View 中的子视图通过调整充满整个 stack view 的边缘。水平方向上,每个视图通过拉伸以适应 stack view 的宽度。竖直方向上,view 按照之前设置的 CHCR 权重来进行拉伸。Image View 应该按照预留空间的大小进行适配。因此,在竖直方向上,image view 的 CHCR 权重应该要低于 label 和 button 的默认权重。</p>
<p>最后,设置 image view 的 mode 为 Aspect Fit。这个设置会强制 image 去调整比例以适应 image view 的大小,以防止 image 因为 image view 的改变而比例失调。这样设置可以允许 stack view 随意改变大小,不用担心图片变形。</p>
<p>关于如何通过布局使一个 view 充满 superview 的更多内容,请查看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSimpleConstraints.html#//apple_ref/doc/uid/TP40010853-CH12-SW5" target="_blank" rel="external">Attributes</a> 和 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSimpleConstraints.html#//apple_ref/doc/uid/TP40010853-CH12-SW4" target="_blank" rel="external">Adaptive Single View</a>。</p>
<h3 id="嵌套-Stack-Views"><a href="#嵌套-Stack-Views" class="headerlink" title="嵌套 Stack Views"></a>嵌套 Stack Views</h3><p>这部分内容讲述了一个由多重嵌套的 stack view 构建成的一个复杂的布局。但是,在下面这个布局示例中,并不是只用 stack view 就能创建的,还需要一些额外的约束条件,更加精确地控制布局。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Nested_Stack_Views_Screenshot_2x.png" width="375" height="670"><br></div>
<p>添加完视图层级之后,如果添加约束将在下一小节讲解。</p>
<h4 id="视图和约束-1"><a href="#视图和约束-1" class="headerlink" title="视图和约束"></a>视图和约束</h4><p>当处理一个嵌套 stack view 布局时,比较容易的做法是从里向外布局。例如在下图中,先在 Interface Builder 布局 “姓名” 这一行。将 label 和 text field 并排放在一起,然后选中它们两个,点击 Editor > Embed In > Stack View 菜单项。这将为这一行创建一个水平布局的 stack view。</p>
<p>然后,布局剩下的 “姓名” 相关的两行,选中,然后点击 Editor > Embed In > Stack View 菜单项,将会创建另外两个水平布局的 stack view。类似的,完成如下显示的一个布局。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/nested_stack_views_2x.png" width="520" height="554"><br></div>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">1. Root Stack View.Leading = Superview.LeadingMargin</span><br><span class="line">2. Root Stack View.Trailing = Superview.TrailingMargin</span><br><span class="line">3. Root Stack View.Top = Top Layout Guide.Bottom + 20.0</span><br><span class="line">4. Bottom Layout Guide.Top = Root Stack View.Bottom + 20.0</span><br><span class="line">5. Image View.Height = Image View.Width</span><br><span class="line">6. First Name Text Field.Width = Middle Name Text Field.Width</span><br><span class="line">7. First Name Text Field.Width = Last Name Text Field.Width</span><br></pre></td></tr></table></figure>
<h4 id="属性-1"><a href="#属性-1" class="headerlink" title="属性"></a>属性</h4><p>每个 stack view 有一系列它们自己的属性,这些属性定义了 stack view 里的内容如何排布。在属性检查器中,你可以看到如下属性:</p>
<table>
<thead>
<tr>
<th>Stack</th>
<th>Axis</th>
<th>Alignment</th>
<th>Distribution</th>
<th>Spacing</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>First Name</strong></td>
<td>Horizontal</td>
<td>First Baseline</td>
<td>Fill</td>
<td>8</td>
</tr>
<tr>
<td><strong>Middle Name</strong></td>
<td>Horizontal</td>
<td>First Baseline</td>
<td>Fill</td>
<td>8</td>
</tr>
<tr>
<td><strong>Last Name</strong></td>
<td>Horizontal</td>
<td>First Baseline</td>
<td>Fill</td>
<td>8</td>
</tr>
<tr>
<td><strong>Name Rows</strong></td>
<td>Vertical</td>
<td>Fill</td>
<td>Fill</td>
<td>8</td>
</tr>
<tr>
<td><strong>Upper</strong></td>
<td>Horizontal</td>
<td>Fill</td>
<td>Fill</td>
<td>8</td>
</tr>
<tr>
<td><strong>Button</strong></td>
<td>Horizontal</td>
<td>Fist Baseline</td>
<td>Fill Equally</td>
<td>8</td>
</tr>
<tr>
<td><strong>Root</strong></td>
<td>Vertical</td>
<td>Fill</td>
<td>Fill</td>
<td>8</td>
</tr>
</tbody>
</table>
<p>除此之外,在布局中设置 text view 的背景色为亮灰色。这样当布局发生变化时,你可以很容易看到 text view 的具体大小。</p>
<table>
<thead>
<tr>
<th>View</th>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Text View</strong></td>
<td>Background</td>
<td>Light Gray Color</td>
</tr>
</tbody>
</table>
<p>最后,CHCR 的权重决定了在填充剩余空间时哪个 view 应该被拉伸。在 Size 检查器中,你可是看到如下每个 view CHCR 的权重:</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Horizontal</th>
<th>Vertical hugging</th>
<th>Horizontal resistance</th>
<th>Vertical resistance</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Image View</strong></td>
<td>250</td>
<td>250</td>
<td>48</td>
<td>48</td>
</tr>
<tr>
<td><strong>Text View</strong></td>
<td>250</td>
<td>249</td>
<td>250</td>
<td>250</td>
</tr>
<tr>
<td><strong>First,Middle,and Last Name Labels</strong></td>
<td>251</td>
<td>251</td>
<td>750</td>
<td>750</td>
</tr>
<tr>
<td><strong>First,Middle,and Last Text Fields</strong></td>
<td>48</td>
<td>250</td>
<td>749</td>
<td>750</td>
</tr>
</tbody>
</table>
<h4 id="讨论-1"><a href="#讨论-1" class="headerlink" title="讨论"></a>讨论</h4><p>在这部分内容中,多个 stack view 互相作用,共同完成了一个复杂的布局。但是,这些 stack view 并不能独立完成所有的布局效果。例如,在一个 image view 改变大小时,里面的 image 应该保持纵横比不变。不幸的是,在 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW2" target="_blank" rel="external">简单的 Stack View</a> 这部分内容用的的技术并没有用到这里。在这里,image 需要紧贴 image view 头部和底部边缘。如果将 image view 的 mode 设置为 Aspect Fit 可能会导致上下留白。幸运的是,在这里 image 的纵横比永远保持为正方形,所以你可以让 image 完全充满 image view,然后约束 image view 的纵横比为 1:1。</p>
<blockquote>
<p><strong>备注</strong></p>
<p>在 Interface Builder 中,一个纵横比的约束是一个 view 的高度和宽度约束的合成。Interface Builder 可以以多种方式去显示多条约束。一般情况下,对于纵横比约束,会以一个比例式的形式展示。例如 一个 <code>View.Width = View.Height</code> 约束代表一个 1:1 的长宽比约束。</p>
</blockquote>
<p>除此之外,所有的 text fields 宽度应该相同。不幸的是,他们被分布在不同的 stack view 中,这样就不能通过 stack view 进行处理。因此,你必须对这些 view 添加 equal width 约束。</p>
<p>和其他简单的 stack view 一样,你需要改变其中一些 stack view 的 CHCR 属性。以此来定义当父视图的大小发生改变时,view 应该如何压缩或扩展。</p>
<p>竖直方向上,你想让 text view 充满整个 stack view。因此在进行设置时,text view 的 Vertical Hugging 权重一定要低于其他 view。</p>
<p>水平方向上,Label 的大小一般为固有内容的大小。当 text field 进行填充适配时,默认的 CHCR 权重设置可以使 label 不会受到挤压。虽然 Interface Builder 早已经将 label 的 content hugging 权重设置为 251,使它要高于 text fields;但是,你仍然需要将 text fields 的 CHCR 权重设置的更低。</p>
<p>为了使 image view 可以和 name row stack view 一样高,需要适当对其进行压缩。然而,stack view 为了让内容能显示出来,只会尽可能地扩大空间。这意味着一定要将 image view 的 vertical compression resistance 设置的非常低,这样 image view 才会压缩而不是充满 stack view。除此之外,设置 image view 的纵横比之后,会使布局变得十分复杂,因为设置纵横比之后,水平和竖直方向将会相互作用。这意味着还需要将 text fields 的 horizontal content hugging 权重设置一个较低值,以此避免 image view 被压缩。综合考虑,将权重设置为 48 或者更低最为合适。</p>
<h3 id="动态-Stack-View"><a href="#动态-Stack-View" class="headerlink" title="动态 Stack View"></a>动态 Stack View</h3><p>这部分内容展示了如何在运行时动态插入或删除一条 item。所有的变化以动画形式展示。除此之外,图中的 stack view 加在了一个 scroll view 内部,如果一屏幕展示不开,可以进行滚动。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Dynamic_Stack_View_Screenshot_2x.png" width="440" height="380"><br></div>
<blockquote>
<p><strong>备注</strong></p>
<p>这部分内容意在介绍如何动态操作 stack view,以及如何在 scroll view 中使用 stack view。在真实的 APP 开发中,这种场景一般使用 <a href="https://developer.apple.com/documentation/uikit/uitableview" target="_blank" rel="external">UITableView</a>。一般情况下,你不应该使用动态 stack view 来代替 table view。并且,使用这种方式创建的界面不能灵活使用其他的布局技巧。</p>
</blockquote>
<h4 id="视图和约束-2"><a href="#视图和约束-2" class="headerlink" title="视图和约束"></a>视图和约束</h4><p>一开始的界面十分简单。在画布上放置一个 scroll view 并使其充满画布。然后,在 scroll view 中放置一个 stack view,并且在 stack view 中放置一个 button。所有控件放置好之后,设置如下约束:</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/dynamic_stack_view_2x.png" width="280" height="366"><br></div>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">1. Scroll View.Leading = Superview.LeadingMargin</span><br><span class="line">2. Scroll View.Trailing = Superview.TrailingMargin</span><br><span class="line">3. Scroll View.Top = Superview.TopMargin</span><br><span class="line">4. Bottom Layout Guide.Top = Scroll View.Bottom + 20.0</span><br><span class="line">5. Stack View.Leading = Scroll View.Leading</span><br><span class="line">6. Stack View.Trailing = Scroll View.Trailing</span><br><span class="line">7. Stack View.Top = Scroll View.Top</span><br><span class="line">8. Stack View.Bottom = Scroll View.Bottom</span><br><span class="line">9. Stack View.Width = Scroll View.Width</span><br></pre></td></tr></table></figure>
<h4 id="属性-2"><a href="#属性-2" class="headerlink" title="属性"></a>属性</h4><p>在 Attributes 检查器中,给 stack view 设置如下属性:</p>
<p>Stack | Axis | Alignment | Distribution | Spacing<br><strong>Stack View</strong> | Vertical | Fill | Equal Spacing | 0</p>
<h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>这里会通过使用一些代码进行布局。创建一个自定义的 view controller,然后将 scroll view 和 stack view 以 outlets 的方式引入。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">class DynamicStackViewController: <span class="built_in">UIViewController</span> {</span><br><span class="line"></span><br><span class="line">@IBOutlet <span class="keyword">weak</span> private var scrollView: <span class="built_in">UIScrollView</span>!</span><br><span class="line">@IBOutlet <span class="keyword">weak</span> private var stackView: <span class="built_in">UIStackView</span>!</span><br><span class="line"></span><br><span class="line"><span class="comment">// Method implementations will go here...</span></span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>接下来,重写 <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621495-viewdidload" target="_blank" rel="external">viewDidLoad</a> 方法,在其中设置 scroll view 的位置。如果你想让 scroll view 的内容开始于 status bar 的下面,按照下面代码设置:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">override func viewDidLoad() {</span><br><span class="line"><span class="keyword">super</span>.viewDidLoad()</span><br><span class="line"></span><br><span class="line"><span class="comment">// setup scrollview</span></span><br><span class="line">let insets = <span class="built_in">UIEdgeInsetsMake</span>(<span class="number">20.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>)</span><br><span class="line">scrollView.contentInset = insets</span><br><span class="line">scrollView.scrollIndicatorInsets = insets</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在,为 button 添加一个 action。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: Action Methods</span></span><br><span class="line"></span><br><span class="line">@IBAction func addEntry(sender: AnyObject) {</span><br><span class="line"></span><br><span class="line">let stack = stackView</span><br><span class="line">let index = stack.arrangedSubviews.count - <span class="number">1</span></span><br><span class="line">let addView = stack.arrangedSubviews[index]</span><br><span class="line"></span><br><span class="line">let scroll = scrollView</span><br><span class="line">let offset = <span class="built_in">CGPoint</span>(x: scroll.contentOffset.x,</span><br><span class="line">y: scroll.contentOffset.y + addView.frame.size.height)</span><br><span class="line"></span><br><span class="line">let newView = createEntry()</span><br><span class="line">newView.hidden = <span class="literal">true</span></span><br><span class="line">stack.insertArrangedSubview(newView, atIndex: index)</span><br><span class="line"></span><br><span class="line"><span class="built_in">UIView</span>.animateWithDuration(<span class="number">0.25</span>) { () -> Void <span class="keyword">in</span></span><br><span class="line">newView.hidden = <span class="literal">false</span></span><br><span class="line">scroll.contentOffset = offset</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法首先计算了 scroll view 的 offset,然后创建了一个新视图。将新视图设置为隐藏并加入 stack view。被隐藏的视图不会影响 stack view 的显示和布局——所以 stack view 的显示效果保持不变。然后,在一个动画的 block 中,设置 view 的显示并更新 scroll view 的 offset,使 view 以动画形式展示出来。</p>
<p>类似的,添加一个删除视图的方法。但是,与 <code>addEntry</code> 方法不同,这个方法不会直接关联 Interface Builder 上的任何控件。而是在 view 创建时,以编码的方式关联上每个 view。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">func deleteStackView(sender: <span class="built_in">UIButton</span>) {</span><br><span class="line"><span class="keyword">if</span> let view = sender.superview {</span><br><span class="line"><span class="built_in">UIView</span>.animateWithDuration(<span class="number">0.25</span>, animations: { () -> Void <span class="keyword">in</span></span><br><span class="line">view.hidden = <span class="literal">true</span></span><br><span class="line">}, completion: { (success) -> Void <span class="keyword">in</span></span><br><span class="line">view.removeFromSuperview()</span><br><span class="line">})</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法里,在 animation 的 block 中隐藏 view。完成动画之后,将 view 从 view 层级中移除。这样就可以使 view 自动从 stack view 中移除。</p>
<p>添加到 stack view 中的条目可以是任意样式,在这个例子中,每个条目是一个 stack view,这个 stack view 中包含了一个显示日期的 label,一个显示十六进制字符串的 label,一个删除 button。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: - Private Methods</span></span><br><span class="line">private func createEntry() -> <span class="built_in">UIView</span> {</span><br><span class="line">let date = <span class="built_in">NSDateFormatter</span>.localizedStringFromDate(<span class="built_in">NSDate</span>(), dateStyle: .ShortStyle, timeStyle: .NoStyle)</span><br><span class="line">let number = <span class="string">"\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())"</span></span><br><span class="line"></span><br><span class="line">let stack = <span class="built_in">UIStackView</span>()</span><br><span class="line">stack.axis = .Horizontal</span><br><span class="line">stack.alignment = .FirstBaseline</span><br><span class="line">stack.distribution = .Fill</span><br><span class="line">stack.spacing = <span class="number">8</span></span><br><span class="line"></span><br><span class="line">let dateLabel = <span class="built_in">UILabel</span>()</span><br><span class="line">dateLabel.text = date</span><br><span class="line">dateLabel.font = <span class="built_in">UIFont</span>.preferredFontForTextStyle(<span class="built_in">UIFontTextStyleBody</span>)</span><br><span class="line"></span><br><span class="line">let numberLabel = <span class="built_in">UILabel</span>()</span><br><span class="line">numberLabel.text = number</span><br><span class="line">numberLabel.font = <span class="built_in">UIFont</span>.preferredFontForTextStyle(<span class="built_in">UIFontTextStyleHeadline</span>)</span><br><span class="line"></span><br><span class="line">let deleteButton = <span class="built_in">UIButton</span>(type: .RoundedRect)</span><br><span class="line">deleteButton.setTitle(<span class="string">"Delete"</span>, forState: .Normal)</span><br><span class="line">deleteButton.addTarget(<span class="keyword">self</span>, action: <span class="string">"deleteStackView:"</span>, forControlEvents: .TouchUpInside)</span><br><span class="line"></span><br><span class="line">stack.addArrangedSubview(dateLabel)</span><br><span class="line">stack.addArrangedSubview(numberLabel)</span><br><span class="line">stack.addArrangedSubview(deleteButton)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> stack</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">private func randomHexQuad() -> String {</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">NSString</span>(format: <span class="string">"%X%X%X%X"</span>,</span><br><span class="line">arc4random() % <span class="number">16</span>,</span><br><span class="line">arc4random() % <span class="number">16</span>,</span><br><span class="line">arc4random() % <span class="number">16</span>,</span><br><span class="line">arc4random() % <span class="number">16</span></span><br><span class="line">) as String</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="讨论-2"><a href="#讨论-2" class="headerlink" title="讨论"></a>讨论</h4><p>在这个样例中,stack view 可以在 APP 运行时动态添加或删除 view。然后 stack view 可以根据内容变化动态改变布局。最后,这里有一些重要的点值得我们记住:</p>
<ul>
<li>隐藏的 view 一直存在于 stack view 的子 view 数组中. 然而,它们不会展示,也不会影响布局和其他子 view.</li>
<li>将一个 view 加到 stack view 的子 view 数组中时,这个 view 会自动添加到 view 层级.</li>
<li>将一个 view 从 stack 的子 view 数组中移除时,不会从 view 层级中自动移除;将一个 view 从 view 层级中移除,同样也不会从 stack 的子 view 数组中移除.</li>
<li>在 iOS 系统中,view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622585-ishidden" target="_blank" rel="external">hidden</a> 属性通常情况下么有动画效果.然而在这里,将 view 放到 stack view 的子 view 里面时会有动画效果,这个效果是 stack view 实现的,而不是 view 通过使用 <code>hidden</code> 属性实现的.</li>
</ul>
<p>这部分内容同时还简单介绍了如何在 scroll view 中使用自动布局。这里在 stack view 和 scroll view 之间设置了一系列的约束,以此来定义 scroll view 内容区域大小。在水平方向上,设置 stack view 的宽度充满 scroll view。竖直方向上,scroll view 的 content size 由 stack view 的大小来决定。stack view 会随着加入的条目越多而变得越长。相应的,scroll view 滚动区域也会随之增加,以适应 stack view 的内容大小。</p>
<p>关于更多信息,请查看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1" target="_blank" rel="external">Working with Scroll Views</a>.</p>
<a id="more"></a>
<p>下面的内容主要介绍了如何使用 stack view 创建一些复杂的布局。Stack view 是一个很强悍的工具,使<br>用它设计用户界面会十分便捷。Stack view 的一些属性在很大程度上只能控制它的子视图如何排列。你可以通过增加
iOS Auto Layout-使用 Interface Builder 布局(译)
http://yoursite.com/2018/04/08/iOS Auto Layout-使用 Interface Builder 布局(译)/
2018-04-08T14:02:47.000Z
2018-06-03T13:15:04.347Z
<a id="more"></a>
<p>在Interface Bulider 上有三种方式可以设置约束:你可以通过按住 control 键并拖拽设置约束,可以通过 Pin 和 Align 这两个工具设置约束,还可以让 Interface Builder 自动设置约束,然后在这基础上手动做一些改变。每一种设置方式都有它的优势与劣势。大部分开发者比较倾向于只是用一种方式。但是,熟悉三种方式的使用,能够让你在开发过程中根据实际需求进行快速切换,提高效率。</p>
<p>你可以这样将三种方式配合使用。首先从 Object library 中拖拽几个 view 和控件到画布上。根据你的需要改变他们的大小和位置。当你将 view 放到画布上之后,Interface Builder 会以左上角为基准,自动创建一些约束来定义 view 的大小和位置。</p>
<p>你的 APP 在使用默认约束的情况下,可以正常编译和运行。先通过设置这些约束,对界面进行测试和预览,然后根据你自己的需求使用新的约束替换到默认约束。永远不要直接在 APP 中使用系统默认设置的约束。</p>
<p>一旦你创建了自己的约束,被影响到的系统的默认的约束会被移除掉。没有了原来的这些约束,就不能再准确地定义 view 的大小和位置。这样就编程一个模糊不清的布局。被影响的约束会突然变成红色,Xcode 也会报出一些警告。</p>
<p>不要慌,你可以逐条添加约束,直到完成布局。一旦你添加了一条约束,你需要按照你的布局方式,去完成整个布局。</p>
<p>关于如何修复布局警告和错误的更多信息,请查看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/TypesofErrors.html#//apple_ref/doc/uid/TP40010853-CH22-SW1" target="_blank" rel="external">Debugging Auto Layout</a>。</p>
<h3 id="通过拖拽添加约束"><a href="#通过拖拽添加约束" class="headerlink" title="通过拖拽添加约束"></a>通过拖拽添加约束</h3><p>在两个 view 之间创建约束,选择一个 view,按住 control 键拖拽到另一外一个 view 上。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ControlDrag_2x.png" width="187" height="72"><br></div>
<p>当你释放鼠标之后,Interface Builder 会弹出一个 HUD 菜单,菜单中列出了可以选择的约束。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Popup_Menu_2x.png" width="244" height="244"><br></div>
<p>根据你布局的控件和拖拽的方向,Interface Builder 智能的为你选出了一系列的约束供你选择。如果你拖拽时稍微偏向水平方向,你会得到一组设置 view 水平间距的约束,和设置 view 竖直方向对齐方式的约束。如果你拖拽时稍微偏向竖直方向,你会得到一组设置 view 竖直方向的约束,和设置水平方向对齐方式的约束。两种手势中,还可能包含一些其他选项(例如设置 view 之间的关联大小)。</p>
<blockquote>
<p><strong>备注</strong></p>
<p>你可以直接在画布上面使用拖拽手势,设置两个 view 之间的约束。你还可以在画布的大纲视图中通过过拽设置 view 之间的约束,当 view 非常难找到的时候,这种方式是非常有效的。例如在对 top 和 bottom layout guide 设置约束时。在使用大纲视图拖拽设置约束时,Interface Builder 会将所有方向的约束选项都显示出来,不会进行过滤。</p>
</blockquote>
<p>Interface Builder 会根据 view 当前的 frame 创建约束。因此,在你设置约束条件之前,给 view 一个合适的位置。在使用 Interface Builder 的大纲视图排列 view 时,最后你应该设置一个符合要求的约束布局。在这过程中,你可能要反复修改约束条件。</p>
<p>通过拖拽方式设置约束时一个十分便捷的方法;然而,因为约束的常量值是根据 view 当前布局计算的,很容易出现一些小数。如果你想要设置更精确优雅的布局,在设置完约束后,在重新对一些细节进行编辑,或者使用 Pin 和 Align 工具设置约束。</p>
<p>关于更多通过拖拽设置约束的信息,请查看 “Adding Layout Constraints by Control-Dragging” 这一章节。</p>
<h3 id="使用-Stack-Align-Pin-和-Resolve-这些工具"><a href="#使用-Stack-Align-Pin-和-Resolve-这些工具" class="headerlink" title="使用 Stack,Align,Pin 和 Resolve 这些工具"></a>使用 Stack,Align,Pin 和 Resolve 这些工具</h3><p>Intercae Builder 为 Auto Layout 提供了四个工具,这四个工具在编辑窗口的右下角,分别为 Stack,Align,Pin 和 Resolve Auto Layout Issues 工具。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Auto_Layout_Tools_2x.png" width="136" height="125"><br></div>
<p>当你想要更好的控制布局约束,或者你想一次性设置多条约束,可以使用 Pin 和 Align 这两个工具。使用这两个工具还有一个好处,那就是你在设置约束时,不需要去精确设置 view 的位置。你可以先大致摆放一下 view,然后添加约束。待添加约束成功之后,更新 view 的 frame,然后 Auto Layout 会替你计算出 view 的正确位置。</p>
<h4 id="Stack-工具"><a href="#Stack-工具" class="headerlink" title="Stack 工具"></a>Stack 工具</h4><p>Stack 工具可以帮你快速创建一个 stack view。选中一个或者多个控件视图,然后点击 stack 工具。Interface Builder 会将这些选中视图放到一个 stack view,并根据当前内容给 stack view 设置一个合适大小。</p>
<blockquote>
<p><strong>备注</strong></p>
<p>系统会根据相关 view 的位置去设置 stack view 的中心轴和对齐方式.你可以通过属性检查器来改变中心轴和对齐方式(同样还可以修改 view 排布方式和间距).</p>
</blockquote>
<h4 id="Align-工具"><a href="#Align-工具" class="headerlink" title="Align 工具"></a>Align 工具</h4><p>Align 工具可以帮助你快速设置 view 的对齐方式。选中你想要设置的控件视图,然后点击 Align 工具。Interface Builder 会显示一个弹框,里面包含了一系列可以选择的对齐方式。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Align_Tool_Popup_2x.png" width="330" height="345"><br></div>
<p>选中你想要的对齐方式,并点击 Add Constraints 按钮。Interface Builder 会根据选择的对齐方式,自动创建一些约束来使这些 view 对齐。默认情况下,通过这种方式使 view 对齐不会时 view 发生位移(view 变得边缘对齐或者中心对齐),更不会更新 frame。你可以在创建约束前改变这一设置。</p>
<p>通常情况下,你需要选择两个或者多个 view 才能使用 Align 工具。然而,在设置“水平居中于父视图”和“垂直居中于父视图”时只选择一个 view 就可以设置。你可以通过弹框一次性选择很多条约束,然而实际情况中一般只选择一两条,很少会多于两条。</p>
<p>更多信息,请查看 “Adding Auto Layout Constraints with the Pin and Align Tools”。</p>
<h4 id="Pin-工具"><a href="#Pin-工具" class="headerlink" title="Pin 工具"></a>Pin 工具</h4><p>使用 Pin 工具可以帮你快速地定义一个 view 与相邻 view 的相对位置和大小。选中你想设置的 view,然后点击 Pin 工具。Interface Builder 呈现一个弹出框,你可以通过弹框上面的各个选项设置 view 的约束。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Pin_Popover_View_2x.png" width="290" height="416"><br></div>
<p>通过弹框顶部提供的一些功能,你可以设置 view 头部、顶部、尾部或者底部边缘与相邻 view 之间的距离。上面的数字代表当前 view 与其他 view 之间的间距。你可以自定义这个间距,同时你还可以通过点击“小三角”去选择与哪个 view 设置关联。”Constrain to margins” 复选框决定在设置约束时,是相对于 margins 设置,还是相对于 view 边缘设置。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Pin_Settings_2x.png" width="408" height="180"><br></div>
<p>下半部分提供了另外一些功能,你可以通过这些功能设置 view 的宽度和高度。Width 和 Height 显示的是 view 当前的宽高,你可以修改这些值并创建新的约束。Aspect Ratio 显示的也是当前的纵横比,如果你想修改这个比例,你需要重新编辑刚才设置的约束。</p>
<p>一般情况下,你只可以通过 pin 工具设置一个 view 的约束。然而,你也可以选择多个 view,并将它们设置为相等宽高。你可以一次性设置多条约束,也可以通过设置约束去重置 view 的 frame。当你选择了想要设置的约束之后,点击 Add Constraints 按钮去添加这些约束。</p>
<p>关于更多信息,请查看 “Adding Auto Layout Constraints with the Pin and Align Tools”。</p>
<h4 id="Resolve-Auto-Layout-Issues-工具"><a href="#Resolve-Auto-Layout-Issues-工具" class="headerlink" title="Resolve Auto Layout Issues 工具"></a>Resolve Auto Layout Issues 工具</h4><p>Resolve Auto Layout Issues 工具提供了一些修复约束的功能。如下图所示,上半部分的所提供的功能用来修改当前选中 view 的约束;下半部分提供的功能用来修改当前界面内所有 view 的约束。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Resolve_Auto_Layout_Popup_2x.png" width="322" height="260"><br></div>
<p>通过这个工具,你可以基于当前约束,更新 view 的 frame;或者基于当前 view 的位置,更新 view 的约束。你也可以通过这个工具添加漏掉的约束,清除约束,或者重置一系列的约束。</p>
<p>对于添加或者重置约束的命令,在 <strong>Letting Interface Builder Create Constraints</strong> 章节进行了详细的讨论。</p>
<h3 id="让-Interface-Builder-自动创建约束"><a href="#让-Interface-Builder-自动创建约束" class="headerlink" title="让 Interface Builder 自动创建约束"></a>让 Interface Builder 自动创建约束</h3><p>Interface Buidler 可以为你创建部分约束或者全部约束。当使用这个功能时,Interface Builder 会根据当前 view 在画布上的位置和大小,尝试创建最合适的约束。所以一定要小心摆放你的 view —— 位置稍有不同,所产生的约束可能会千差万别。</p>
<p>如果你想通过 Interface Builder 创建所有的约束,你可以点击 Resolve Auto Layout Issues tool -> Add Missing Constraints.通过这个功能可以确定一个清晰的布局。如上面所说,你可以只为选中的 view 添加约束,也可以为当前界面上所有 view 添加约束。</p>
<p>通过这个功能,你可以快速创建一个清晰、满意的布局。但是,除非界面上的元素十分简单的时候,你可以使用这种方式布局。当界面元素比较复杂的时候,如果你还使用这种方式布局,结果很有可能并不是你想要的。</p>
<h3 id="查看并编辑约束"><a href="#查看并编辑约束" class="headerlink" title="查看并编辑约束"></a>查看并编辑约束</h3><p>当你创建约束之后,你可能需要编辑这些约束。这里提供了一些方法去编辑这些约束,每种方法都各有各的特色。</p>
<h4 id="在画布上查看约束"><a href="#在画布上查看约束" class="headerlink" title="在画布上查看约束"></a>在画布上查看约束</h4><p>在下面显示的编辑器中,以有色线的形式显示了当前收到影响的约束。线条的形状、划线类型和线条颜色可以告诉你关于当前约束的很多信息。</p>
<ul>
<li><strong>I-BARS(以 T 形作为末端的线).</strong>I-bars 展示了一段间距。这段间距可以是两个 view 之间的间距,也可以是一个 view 的高度或者宽度.</li>
<li><strong>普通直线(没有任何末端直线).</strong>普通直线表示 view 的对齐方式。例如,Interface Builder 使用简单的一条线来作为两个对齐 view 的基准线.</li>
<li><strong>实线.</strong>实线代表必须满足的约束(权重 = 1000).</li>
<li><strong>虚线.</strong>虚线代表可选的约束(权重 < 1000).</li>
<li><strong>红色线.</strong>标明被这条约束影响的 view 有一个错误.当出现红线时,说明当前布局时一个不满足需求的布局.关于错误约束更多信息,你可以通过 issues 导航器查看,也可以通过点击大纲视图中右上角的红色箭头进行查看.</li>
<li><strong>橘黄色线.</strong>橘黄色线直接标明了在当前约束条件下,view 没有处于正确的位置。Interface Builder 以虚线的方式显示了 view 应该处于的位置.你可以通过 Resolve Auto Layout Issues tool -> Update Frames 命令将 view 移动到正确的位置。</li>
<li><strong>蓝色线.</strong>被当前约束影响,并且符合当前约束条件布局的控件.</li>
<li><strong>等于号.</strong>当通过约束设置两个 view 等宽或者等高时,Interface Builder 会使用两个分隔条来显示着两天约束,每个分隔条上面有一个蓝底白色的等号.</li>
<li><strong>大于等于号和小于等于号.</strong>Interface Builder 会将表示大于等于或者小于等于关系的约束标志上 >= 或者 <= 符号.</li>
</ul>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Constraints_In_The_Canvas_2x.png" width="386" height="230"><br></div>
<h4 id="在文件大纲视图中以列表形式查看约束"><a href="#在文件大纲视图中以列表形式查看约束" class="headerlink" title="在文件大纲视图中以列表形式查看约束"></a>在文件大纲视图中以列表形式查看约束</h4><p>Interfae Builder 在文件大纲视图中以列表形式列出了所有的约束,并且按照与之关联的情况进行分组,然后将这组约束放在关联 view 的下方。在下面图中,view 包含了它自己和其子视图,还有与这些视图相关的约束。对于 top layout guide 和 bottom layout guide 被包含在当前界面的跟视图层级下面。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Constraints_In_Document_Outline_2x.png" width="340" height="386"><br></div>
<p>一般情况下,约束分布在大纲视图的各个层级,截止于当前界面根视图那一层级。所以,如果你想要找到所有的约束,你需要逐次展开 view 的层级。</p>
<p>这些约束条件在列表中一般以伪代码的形式显示。这些伪代码通常包含一系列的视图元素,不能完全显示出来。如果你想查看某条约束的具体信息,在这之前你需要增加大纲视图的宽度。在平时开发中,你可以在画布上选中一条约束,然后通过大纲视图来快速查看约束并校验具体信息。</p>
<p>在一些简单的布局中,你可以通过大纲视图快速浏览所有的约束。然而,随着布局变得越来越复杂,要想寻找一条具体的约束变得很困难。这时候你最好直接在 view 上面查看,或者选中一条约束后在 Size 检查器中进行审查。</p>
<h4 id="在-Size-检查器中查看约束"><a href="#在-Size-检查器中查看约束" class="headerlink" title="在 Size 检查器中查看约束"></a>在 Size 检查器中查看约束</h4><p>Size 检查器中列出了与当前选中 view 有关的所有约束。一些必要的约束以实线形式展示,一些可选约束以虚线形式展示。在检查器中,列出了关于约束的很多重要信息。通常包含约束的属性信息和一些其他信息,例如约束的关联关系、常量值、乘积因子和比例等。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Constraints_in_Size_Inspector_2x.png" width="258" height="428"><br></div>
<p>在上述截图上部分的简图中,列出了受影响的相关约束。如果你想单独查看某一条约束的信息,可以在简图中选中这条约束,下面的列表会将其他约束过滤掉以方便查看。</p>
<p>关于更多信息,请查看 “Viewing the Complete List of Layout Constraints for an Item”。</p>
<h4 id="检查并编辑约束"><a href="#检查并编辑约束" class="headerlink" title="检查并编辑约束"></a>检查并编辑约束</h4><p>当你在画布上或者在大纲视图中选中一条约束,属性检查器会显示出这条约束的所有属性信息。同时属性检查器还显示了这条约束的权重和标识。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Editing_Constraints_in_the_Attribute_Inspector_2x.png" width="258" height="250"><br></div>
<blockquote>
<p><strong>备注</strong></p>
<p>通过约束的 <code>identifier</code> 属性,你可以为约束设置一个别名。这样在调试时,可以在调试日志中辨别出这条约束。</p>
</blockquote>
<p>你可以将一条约束标记为 placeholder(预置)。被标记为预置的约束只会存在于设计时。在 APP 运行起来后,这些约束将不被包含在内。一般情况下,如果你想在运行时动态添加约束,你可以添加预置约束。当在 Interface Builder 上有一些约束错误或者警告,但你又不想在实际运行中修改这些约束。你可以通过添加这些临时约束,创建一个清晰地、满足需求的布局,以此来消除一些错误与警告。</p>
<p>你可以任意改变 Constant,Priority,Multiplier,Relation,Identifier 和 Placeholder 这些属性的值。但是对于 ‘First Item’ 和 ‘Relation’ 这两项,你能做出的修改非常有限。你可以通过 ‘First Item’ 将被约束的两个控件进行对调(反向去设置约束值和比例系数)。你可以通过这些功能改变控件的属性,但是不能改变控件本身。如果你想将约束移动到一个完全不同的控件上,需要先将控件原有约束删除,然后使用新的约束进行替换。</p>
<p>通过使用 Size 检查器你可以直接编辑一些属性值。随意点击一条约束的 ‘Edit’ 按钮,会出现一个弹窗,通过这个弹窗你可以修改约束的 relationship,constant,priority 或者 multiplier 这些值。或者使用另外一种方式,直接双击一条约束,会打开属性检查器,然后你可以通过属性检查器修改这些值。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Editing_Constraints_in_the_Size_Inspector_2x.png" width="254" height="140"><br></div>
<p>关于更多信息,请查看 “Editing Auto Layout Constraints”.</p>
<h3 id="设置-Content-Hugging-和-Compression-Resistance-权重"><a href="#设置-Content-Hugging-和-Compression-Resistance-权重" class="headerlink" title="设置 Content-Hugging 和 Compression-Resistance 权重"></a>设置 Content-Hugging 和 Compression-Resistance 权重</h3><p>如果你想设置 view 的 content-hugging 和 compression-resistance 权重(CHCR 权重),你可以通过画布或者大纲视图选中一个 view。然后打开 Size 检查器,向下滚动属性检查器,直到你看到 CHCR 权重的设置栏。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/CHCR_Priorities_2x.png" width="260" height="172"><br></div>
<p>你还可以通过 Interface Builder 设置 view 的固有大小。Interface Builder 默认情况下使用 view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622600-intrinsiccontentsize" target="_blank" rel="external">intrinsicContentSize</a> 方法的返回值。如果你在设计时需要不同的固有大小,可以通过这个功能设置设置一个预置值。这个值只会在 Interface Builder 上面影响到 view 大小,在程序运行时不会起到作用。</p>
<p>关于更多信息,请查看 “Setting the Placeholder Intrinsic Size for a Custom View”.</p>
<h3 id="iOS-仅有特征"><a href="#iOS-仅有特征" class="headerlink" title="iOS 仅有特征"></a>iOS 仅有特征</h3><p>iOS 针对 Auto Layout 添加了一些独有的特征。主要包括 top layout guide 和 bottom layout guide、view 的 layout marigins、view 的 readable content guides(最佳阅读区域)和 view 的 semantic content(语义学内容)。</p>
<h4 id="Top-and-Bottom-Layout-Guides"><a href="#Top-and-Bottom-Layout-Guides" class="headerlink" title="Top and Bottom Layout Guides"></a>Top and Bottom Layout Guides</h4><p>Top and Bottom Layout Guides 代表当前控制器下可视区域的上下两个边界。如果你不想你的内容被一些透明或者半透明的 bar(例如,状态栏,导航栏,tab 栏)遮挡住,使用 Auto Layout 将你的内容布局在边界内。</p>
<p>Layout Guides 遵循了 <a href="https://developer.apple.com/documentation/uikit/uilayoutsupport" target="_blank" rel="external">UILayoutSupport</a> 协议,并提供了一个<code>length</code> 属性,这个属性代表每个 guide 到对应一侧边缘的距离。具体可以这样描述:</p>
<ul>
<li>对于 Top Layout Guide,<code>length</code> 代表当前控制器的 view 的上边缘,到遮挡内容的Bar的下边缘的距离。</li>
<li>对于 Bottom Layout Guide,<code>lenght</code> 代表当前控制器 view 的下边缘,到遮挡内容 Bar(例如 tab bar) 的上边缘的距离。</li>
</ul>
<p>这些 guides 在约束布局中充当一个控件,并提供了顶部、底部和高度这些属性。一般情况下,你会约束 view 与 top layout guide 的底部关联,或者约束 view 与 bottom layout guide 的顶部关联。Guides 也提供了 <code>topAnchor</code>,<code>bottomAnchor</code>,和 <code>heightAnchor</code> 属性,通过使用这些属性,可以简化使用代码创建约束的过程。</p>
<p>当相对于根视图设置约束时,Interface Builder 会提供 top 和 bottom layout guides 作为可选项。如果 view 与 layout guide 相邻,设置上下边缘约束时,Interface Builder 会默认选择使用 guide。你也可以通过使用 Pin 工具,点击小三角,手动设置是与 layout guide 关联还是与 view 本身边缘关联。</p>
<h4 id="Layout-Margins"><a href="#Layout-Margins" class="headerlink" title="Layout Margins"></a>Layout Margins</h4><p>Auto Layout 针对每个 view 定义了默认内容边距。这些边距代表了 view 的边缘与父视图之间想要添加的一个额外距离。你可以通过 <a href="https://developer.apple.com/documentation/uikit/uiview/1622566-layoutmargins" target="_blank" rel="external">layoutMargins</a> 属性或者 <a href="https://developer.apple.com/documentation/uikit/uiview/1622651-layoutmarginsguide" target="_blank" rel="external">layoutMarginsGuide</a> 属性来获取 view 的 margins 的值。</p>
<p><code>layoutMargins</code> 属性是一个 readwrite 属性,你可以用一个 <a href="https://developer.apple.com/documentation/uikit/uiedgeinsets" target="_blank" rel="external">UIEdgeInsets</a> 类型数据结构来对其赋值,也可以通过它的 get 方法返回一个这样的数据结构。<code>layoutMarginsGuide</code> 只提供了 readOnly 属性,因此你可以通过 get 方法返回一个 <a href="https://developer.apple.com/documentation/uikit/uilayoutguide" target="_blank" rel="external">UILayoutGuide</a> 对象。除此之外,你还可以通过设置 view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622653-preservessuperviewlayoutmargins" target="_blank" rel="external">preservesSuperviewLayoutMargins</a> 属性去决定是否去适应父视图的边距。</p>
<p>一个 view 的默认边距是 8-point。你可以根据需要去改变这个值。</p>
<blockquote>
<p><strong>备注</strong></p>
<p>系统自动为一个 view controller 的根视图设置了边距。顶部和底部边距设置为 0,方便内容延伸到 bar 的下面(如果需要的话)。两边的边距会根据当前 controller 展示方式而改变,这个边距可以为 16-point 或者 20-point。你不能改变这些边距。</p>
</blockquote>
<p>当你为一个 view 设置与父视图相关联的约束时,你应该使用 Layout Margins 而不是使用 view 的 edge。在 UIKit 中,<a href="https://developer.apple.com/documentation/appkit/nslayoutattribute" target="_blank" rel="external">NSLayoutAttribute</a> 定义了一系列的属性来代表上部、下部、头部、尾部、左部和右部的边距。同时还包括了与 center X 和 center Y 边距相关的枚举。</p>
<p>在 Interface Builder 中,通过拖拽方式为一个 view 和它的父视图之间设置约束时,默认情况下使用的是 “边距属性” 来设置。当你使用 Pin 工具设置约束时,你可以勾选 “Constrain to margins” 这个复选框。如果这一项被勾选,将会通过设置父视图边距的方式来满足约束;如果未被勾选,则通过设置与父视图边缘距离的方式来满足约束。类似的,当通过 Attribute 检查器编辑约束时,在第一个 Item 和第二个 Item 的展开菜单中有一项为 “Relative to margin”。如果勾选这一项,则按照 “边距属性” 方式设置约束;没有勾选则按照边缘距离方式设置约束。</p>
<p>最后,当你通过编码来设置与父视图相关联的约束时,可以使用 <code>layoutMarginsGuide</code> 属性直接创建与 layout guide 相关联的约束。</p>
<h4 id="Readable-Content-Guides"><a href="#Readable-Content-Guides" class="headerlink" title="Readable Content Guides"></a>Readable Content Guides</h4><p>view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622644-readablecontentguide" target="_blank" rel="external">readableContentGuide</a> 属性返回了一个 layout guide,这个 layout guide 定义了对象在 view 内部的最佳阅读区域。理想情况下,内容在这个区域内,用户不需要摆头就可以轻松阅读。</p>
<p>这条 guide 将内容集中在 view 的边距内,永远不会超出边距。最佳阅读区域的大小会随着字体大小的改变而改变。当用户选择较大字体时,系统会创建一个更宽阔的阅读区域。因为有时候用户阅读时,会距离设备稍远一些,这时候系统需要进行适应以达到最佳阅读效果。</p>
<p>在 Interface Builder 中,你既可以通过设置来确定是用 layout margins 来代表 view 的边距,还是用 “最佳阅读区域” 来制定 view 的边距。选中一个 view(一般是 view controller 的根视图),然后打开 Size 检查器。如果你勾选了 “Follow Readable Width” 复选框,在设置约束时,会按照 “最佳阅读区域” 来制定 view 的边距。</p>
<blockquote>
<p><strong>备注</strong></p>
<p>在大多数设备上面,readable content guide 和 layout margins 没有太大区别。仅当在 iPad 上并且横屏时,这两个项有比较明显的区别。</p>
</blockquote>
<h4 id="语义内容"><a href="#语义内容" class="headerlink" title="语义内容"></a>语义内容</h4><p>如果你使用头部和尾部约束进行布局,当你将语言从 <strong>左->右</strong> 切换为 <strong>右->左</strong> 时,布局会自动改变左右方向。然而,有些布局元素是不需要根据阅读方向改变的。例如,根据物理方位(上下左右)设置的一些 button,无论阅读方向如何它们都保持不变。</p>
<p>view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622461-semanticcontentattribute" target="_blank" rel="external">semanticContentAttribute</a> 属性决定了 view 是否会随着阅读方向而改变布局方位。</p>
<p>在 Interface Builder 中,你可以在属性检查器中手动设置 Semantic 项。如果设置项为 Unspecified,view 的内容会随着阅读方向改变而改变;如果设置为 Spatial,Playback 或者 Force Left-to-Right, view 设置约束时,leading edges 代表左边缘,trailing edges 代表右边缘;如果设置为 Force Right-to-Left,则 leading edges 代表右边缘,trailing edges 代表左边缘。</p>
<h3 id="经验法则"><a href="#经验法则" class="headerlink" title="经验法则"></a>经验法则</h3><p>下面一些建议可以帮助你成功地使用 Auto Layout。如果你不遵循这些规则,将会产生一些异常。所以,在设计时一定要考虑清楚。</p>
<ul>
<li>永远不要使用 view 的 frame、bounds 或者 center 这些属性来指定 view 具体几何结构.</li>
<li>尽可能使用 stack view.<br><br> Stack view 会自动为其内容设置约束,你只需要对 stack view 设置简单的约束即可.除非使用 stack view 不能满足你的需求,否则尽量使用 stack view,避免进行自定义布局.</li>
<li>与 view 相邻的一些控件设置约束.<br><br> 如果你有两个 button 并排在一起,直接将第二个 button 的头部与第一个 button 的尾部关联。最好不要使第二个 button 越过第一个 button 直接与父视图的边缘想关联。</li>
<li>避免给 view 设置固定宽高.<br><br>使用 Auto Layout 目的就是能够让 view 进行动态适应。如果将 view 的大小设置为固定值将会使其失去这一特性.</li>
<li>如果在设置约束时感觉很困难,尝试使用 Pin 和 Align 这两个工具.尽管使用这两个工具设置约束可能会相对于拖拽方式稍慢一些,但是它可以让你在创建约束前精准地控制一些数值。这些小功能会非常有用,特别是当你初次使用 Auto Layout 时.</li>
<li>当你自动更新一个控件的 frame 时,需要小心执行。如果一个控件没有足够的约束来确定它的大小和位置,那么更新行为将是未定义的.有时候更新 frame 之后,view 会消失不见。这是因为 view 的大小变为了 0 或者它的位置跑到了屏幕外面.<br><br>如果有需要的话,你可以随时试着更新一下控件的 frame,然后撤销操作.</li>
<li>确定你的所有 view 都有一个有意义的命名。这样在使用工具进行操作时能够很容易作出区分.<br><br>系统会根据 label 的内容和 button 的标题自动为控件命名.对于其他的 view,你可能需要通过 Identify 检查器为它们设置一个具体的标识(或者直接在大纲视图中双击 view 并编辑它的名字).</li>
<li>使用 leading 和 trailing 约束,而不是 right 和 left 约束.<br><br>你可以通过设置 view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622461-semanticcontentattribute" target="_blank" rel="external">semanticContentAttribute</a> 属性(iOS)或者 <a href="https://developer.apple.com/documentation/appkit/nsview/1483254-userinterfacelayoutdirection" target="_blank" rel="external">userInterfaceLayoutDirection</a> 属性(OS X)来确定 leading 和 trailing 边缘的具体意义.</li>
<li>在 iOS 系统中国,当约束一个控件与 view controller 的根视图边缘做关联时,使用以下约束:</li>
<li><strong>Horizontal constraints</strong>.对于大多数控件,会通过约束设置与父视图距离为 0.系统会根据设备型号和 view controller 展示形式,自动提供一些间距.<br><br>对于文本类对象,会根据边距设置情况充满根视图,当然在布局时使用的是 “最佳阅读区域” 设置的边距,而不是普通边距.<br><br>对于其他控件,会直接铺满视图的边缘(例如,背景图片),在布局时使用的是 leading 和 trailing edges.</li>
<li><strong>Vertical constraints</strong>.如果想让 view 延伸到一些 bar 的下面,通过设置上下边距来控制.这在设置 scroll view 布局时十分常见,在 scroll view 中经常允许内容直接滚动到 bar 的下面.需要注意的是,你需要改变 scroll view 的 <a href="https://developer.apple.com/documentation/uikit/uiscrollview/1619406-contentinset" target="_blank" rel="external">contentInset</a> 和 <a href="https://developer.apple.com/documentation/uikit/uiscrollview/1619427-scrollindicatorinsets" target="_blank" rel="external">scrollIndicatorInsets</a> 属性来确定内容正确的初始位置.<br><br>如果不想让 view 延伸到 bar 的下面,使用 top 和 bottom layout guides 来设置约束.</li>
<li>当你通过编码实例化一个 view 时,必须将它的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco" target="_blank" rel="external">translatesAutoresizingMaskIntoConstraints</a> 属性设置为 <code>NO</code>.否则在默认情况下,系统会基于 view 的 frame 和 autoresizing mask 自动创建一系列的约束.当你创建自己的约束时,他们必然会与自动创建的一些约束发生冲突.这样的布局时不符合要求的.</li>
<li>需要意识到 OS X 系统和 iOS 系统计算布局的方式是不同的.<br><br>在 OS X 系统中,Auto Layout 即可以改变 window 内容的布局,也可以改变 window 的布局.<br><br>在 iOS 系统中,系统会通过当前场景的大小和布局.Auto Layout 只能改变场景中内容的大小和位置.<br><br>这些不同看起来无关紧要,但是他对你布局的设计有着很大影响,特别是使用一些属性的时候.</li>
</ul>
<a id="more"></a>
<p>在Interface Bulider 上有三种方式可以设置约束:你可以通过按住 control 键并拖拽设置约束,可以通过 Pin 和 Align 这两个工具设置约束,还可以让 Interface Builder 自动设置约束,然后在这基
iOS Auto Layout-约束的本质(译)
http://yoursite.com/2018/04/08/iOS Auto Layout-约束的本质(译)/
2018-04-08T06:23:40.000Z
2018-06-03T13:15:13.394Z
<a id="more"></a>
<p>对视图层级布局,实际上是定义了一系列的线性方程式。每一条约束代表一个方程式。你的目的就是通过声明一些列的方程式,并使其只有一组最优解。</p>
<p>下面展示了一组简单的方程式.</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/view_formula_2x.png" width="530" height="230"><br></div>
<p>这条约束声明了红色视图的头部必须与蓝色视图的尾部保持 8-point 距离。这个方程式可以分为以下几部分:</p>
<ul>
<li><strong>Item 1.</strong> 方程式的第一个 item,这里代表的是红色视图。这一项必须为一个视图或者 layout guide.</li>
<li><strong>Attribute 1.</strong> 被约束的布局属性,在这里即红色视图的头部(左侧)边缘.</li>
<li><strong>RelationShip.</strong> 左右两边的逻辑关系。逻辑关系有三种情况:等于,大于等于,小于等于。在这里,左右两边是相等的关系.</li>
<li><strong>Multiplier.</strong> 需要与 attribute2 相乘的值,这里相乘的值为 1.0.</li>
<li><strong>Item 2.</strong> 方程式中的第二个 item,在这里代表红色视图。与 item1 不同,这里可以为空。</li>
<li><strong>Attribute 2.</strong> 第二个 item 中被约束的布局属性。在这里代表蓝色视图的尾部(右侧)边缘。如果 item2 为空的话,那么这就不应该是一个布局属性.</li>
<li><strong>Constant.</strong> 一个浮点型常量,用来表示偏移量。在这里代表 attribute2 加上 8-point.</li>
</ul>
<blockquote>
<p><strong>备注</strong><br>这里的 layout guide 是指 UIViewController 的 topLayoutGuide 或者 bottomLayoutGuide,直译过来可能会改变意思,这里直接使用 layout guide.</p>
</blockquote>
<p>大部分约束用来定义界面上两个元素的相互关系。这些元素可以是视图,也可以是 layout guides.约束也可以用来定义同一个元素两个不同属性之间的相互关系,例如,通过约束可以设置一个视图的长宽比。你这可以直接给一个视图的长或宽进行常量赋值,如果你这样做,那么上述方程式中的 item2 就会为空,对应的 attribute2 也不再是一个布局属性,multiplier 也会设置为 0.0。</p>
<h3 id="自动布局属性"><a href="#自动布局属性" class="headerlink" title="自动布局属性"></a>自动布局属性</h3><p>在 Auto Layout 中,一些特定的属性才可以用来进行设置各种约束。一般情况下,有以下几个属性:四条边缘(前,后,上,下)、高和宽、垂直居中线、水平居中线。对于文本类视图,可能还会有几条基准线属性。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/attributes_2x.png" width="470" height="420"><br></div>
<p>关于全部的布局属性,请查看 <a href="https://developer.apple.com/documentation/uikit/nslayoutattribute" target="_blank" rel="external">NSLayoutAttribute</a>。</p>
<blockquote>
<p><strong>备注</strong><br>尽管 OS X 和 iOS 使用的都是 NSLayoutAttribute,但是两个系统定义的这个枚举的值是不同的。当你查看时,注意选择正确的系统。</p>
</blockquote>
<h3 id="方程式样例"><a href="#方程式样例" class="headerlink" title="方程式样例"></a>方程式样例</h3><p>在方程式中有很多参数和布局属性,你可以通过改变这些参数创建各种各样的约束。你可以通过约束来定义两个 view 之间的间隔,多个 view 的对齐方式,两个 view 之间的大小关系,甚至一个 view 的长宽比。然而,并不是所有的属性与约束直接都可以互相兼容。</p>
<p>这里主要将属性分为两类。大小属性(例如,高和宽)和位置属性(例如,头,左,上)。大小属性用来指定视图的大小。位置属性用来指定 view 之间相对位置。</p>
<p>考虑到两者的差异性,在进行约束布局时你需要遵循以下原则:</p>
<ul>
<li>你不能将一个大小属性和一个位置属性进行约束关联.</li>
<li>你不能为一个位置属性进行常量赋值.</li>
<li>两个位置属性之间进行约束关联时,不应该使用不同的倍率(原则上 multiplier 的值都为 1.0).</li>
<li>对于位置属性,你不能将垂直线和水平线两个属性进行约束关联.</li>
<li>对于位置属性,你不能将头部边缘或者尾部边缘,与对应的左边缘属性和右边缘进行约束关联.di</li>
</ul>
<p>例如,你直接设置一个 view 的顶部距离为 20.0-point 没有任何意义。你必须设置两个 view 之间的相对位置关系,例如,一个 view 距离 superview 顶部下方 20.0-point。当然,直接通过约束设置一个 view 的高度为 20.0 是最佳使用方法。更多信息,请查看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW22" target="_blank" rel="external">Interpreting Values</a>。</p>
<p>代码 3-1 展示了一些常见的方程式。</p>
<blockquote>
<p><strong>备注</strong><br>本章节中所展示的方程式全都是伪代码,如果想要看真实的方程式,请查看<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1" target="_blank" rel="external">通过编码创建约束</a>或者<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH3-SW1" target="_blank" rel="external">Auto Layout 宝典</a>。</p>
</blockquote>
<p><strong>代码 3-1</strong> 常见约束方程式示例<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">// 通过约束设置 view 的高度</span><br><span class="line">View.height = 0.0 * NotAnAttribute + 40.0</span><br><span class="line"></span><br><span class="line">// 通过约束设置两个 button 之间距离为固定值</span><br><span class="line">Button_2.leading = 1.0 * Button_1.trailing + 8.0</span><br><span class="line"></span><br><span class="line">// 使两个 button 头部对齐</span><br><span class="line">Button_1.leading = 1.0 * Button_2.leading + 0.0</span><br><span class="line"></span><br><span class="line">// 设置两个 button 等宽</span><br><span class="line">Button_1.width = 1.0 * Button_2.width + 0.0</span><br><span class="line"></span><br><span class="line">// 设置 view 居中与父视图</span><br><span class="line">View.centerX = 1.0 * Superview.centerX + 0.0</span><br><span class="line">View.centerY = 1.0 * Superview.centerY + 0.0</span><br><span class="line"></span><br><span class="line">// 设置 view 宽高比为 1:2</span><br><span class="line">View.height = 2.0 * View.width + 0.0</span><br></pre></td></tr></table></figure></p>
<h3 id="等于,代表的不是赋值"><a href="#等于,代表的不是赋值" class="headerlink" title="等于,代表的不是赋值"></a>等于,代表的不是赋值</h3><p>这里有必要说明一下,上述方程式代表的是左右两边等价,不是赋值。</p>
<p>当 Auto Layout 处理这些方程式的时候,并不是直接将右边的值赋给左边。而是计算能够是两边关系保持等价的值。这意味着我们可以随意调换等式两边元素的方向。例如,代码 3-2 与之前 3-1 中的一些方程式等价但却反转了方向。</p>
<p><strong>代码 3-2</strong> 变换的方程式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">// 通过约束设置两个 button 之间距离为固定值</span><br><span class="line">Button_1.trailing = 1.0 * Button_2.leading - 8.0</span><br><span class="line"></span><br><span class="line">// 使两个 button 头部对齐</span><br><span class="line">Button_2.leading = 1.0 * Button_1.leading + 0.0</span><br><span class="line"></span><br><span class="line">// 设置两个 button 等宽</span><br><span class="line">Button_2.width = 1.0 * Button.width + 0.0</span><br><span class="line"></span><br><span class="line">// 设置 view 居中与父视图</span><br><span class="line">Superview.centerX = 1.0 * View.centerX + 0.0</span><br><span class="line">Superview.centerY = 1.0 * View.centerY + 0.0</span><br><span class="line"></span><br><span class="line">// 设置 view 宽高比为 1:2</span><br><span class="line">View.width = 0.5 * View.height + 0.0</span><br></pre></td></tr></table></figure>
<blockquote>
<p><strong>备注</strong><br>当你进行左右元素转换时,确定你转换了常量和倍率。例如,如果一个常量之前是 8.0,转换完了就是 -8.0;如果之前倍率是 2.0,转换完了就会是 0.5;如果常量为 0.0 或者倍率为 1.0 则保持不变。</p>
</blockquote>
<p>你大概可以发现,对于解决同一个问题 Auto Layout 提供了多种解决方案。理想情况下,你应该选择一个最优的解决方案。但是,不同的开发者对理想方案的定义不同。所以,不要去争论谁的方案最优,只要统一方案就好。如果你选择一个合适的方案,并且始终遵循这种方案,那么你遇到的问题将会减少很多。例如,这篇指南中遵循了一下原则:<br>1.尽量用整数,少用小数.<br>2.尽量使用正数,少用负数.<br>3.如果可能的话,你需要遵循这个顺序去设置约束:从头到尾,从上到下。</p>
<h3 id="创建一个清晰的,满足要求的布局"><a href="#创建一个清晰的,满足要求的布局" class="headerlink" title="创建一个清晰的,满足要求的布局"></a>创建一个清晰的,满足要求的布局</h3><p>当你使用 Auto Layout 时,你的目的是通过提供一系列的方程式,并使其只有一个最优解。设置模糊不定的约束,会使其有多组解。设置不满足条件的约束,得不到有效的解。</p>
<p>通常情况下,需要通过约束将每个 view 的大小和位置都设置好。假设 superview 的大小已经被设置(例如,iOS 系统上一个页面的根视图),那么一个清晰、满足需求的布局条件是这样的:给每个 view(不算 superview) 在每个维度(水平和竖直维度)各设置两个约束,用来布局 view 的大小和位置。然而,满足需求的方案有很多。例如,下面展示了三种布局方式(只显示了水平方向的约束),都是比较简洁并满足需求的布局方案:</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/constraint_examples_2x.png" width="660" height="280"><br></div>
<ul>
<li>第一个布局方案,通过设置约束,使 view 的左边缘与 superview 的左边缘关联。并且给 view 的宽度设置一个常量值。这样 view 的右边缘位置可以根据 superview 的大小和其他约束条件计算出来.</li>
<li>第二个布局方案,通过设置约束,使 view 的左边缘与 superview 的左边缘关联,使 view 的右边缘与 superview 的右边缘关联。这样 view 的宽度可以根据 superview 的大小和其他约束条件计算出来.</li>
<li>第三个布局方案,通过设置约束,使 view 的左边缘与 superview 的左边缘关联,并且使 view 垂直居中于 superview.这样的话,view 的宽度和 右边缘位置可以根据 superview 大小和其他约束计算出来.</li>
</ul>
<p>你可能注意到,上述三种布局方案中,都是一个 view 对应水平方向两条约束。每个方案中,通过约束完全可以确定 view 的宽度和在水平方向的位置。这就说明这三种布局方案均能够确定 view 水平方向的布局。然而,当 superview 的宽度发生改变时,这三种布局方案产生的效果却不尽相同。</p>
<p>缘距离保持定值。虽然两种方案表现效果相同,但是两个方案并不完全等价。通常情况下,第二种方案更容易让人理解,但是第三种方案更具使用价值,特别是当你想使多个 view 保持居中对齐时。在实际开发过程中,根据具体需求,选择一种最合适的方案。</p>
<p>现在考虑一些稍微复杂一点的情况。假设现在有两个 view,你想使这两个 view 在同一屏幕上并排在一起,四周之间距保持一定距离,宽度始终保持相等。并且在屏幕旋转时,也保持这样的效果。</p>
<p>下面的两幅图中,展示了横屏和竖屏两种情况。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Blocks_Portrait_2x.png" width="240" height="430"><br></div>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Blocks_Landscape_2x.png" width="430" height="240"><br></div>
<p>所以针对这种效果应该怎样设置约束呢?下图展示了一种简单的布局方案:</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/two_view_example_1_2x.png" width="330" height="370"><br></div>
<p>上述布局方案,使用了如下约束条件:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 竖直方向约束条件</span><br><span class="line">Red.top = 1.0 * Superview.top + 20.0</span><br><span class="line">Superview.bottom = 1.0 * Red.bottom + 20.0</span><br><span class="line">Blue.top = 1.0 * Superview.top + 20.0</span><br><span class="line">Superview.bottom = 1.0 * Blue.bottom + 20.0</span><br><span class="line"></span><br><span class="line">// 水平方向约束条件</span><br><span class="line">Red.leading = 1.0 * Superview.leading + 20.0</span><br><span class="line">Blue.leading = 1.0 * Red.trailing + 8.0</span><br><span class="line">Superview.trailing = 1.0 * Blue.trailing + 20.0</span><br><span class="line">Red.width = 1.0 * Blue.width + 0.0</span><br></pre></td></tr></table></figure>
<p>遵循之前说的设计原则,这个布局方案中有两个 view,四条水平方向约束,四条竖直方向约束。这并不是最完美的设计方案,他只是一个参考方案。重要的是,这种方案可以快速地确定两个 view 的大小和位置,实现一个符合需求的布局。如果你现在移除所有的约束,不参考这个方案重新进行布局,你可能会遇到各种约束冲突。</p>
<p>但是,上述只是一个参考方案,并不是唯一的布局方案。例如这里有一种等价效果的布局方案:</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/two_view_example_2_2x.png" width="330" height="370"><br></div>
<p>这里不再是将蓝 view 的底部和顶部边缘直接与 superview 的底部和顶部边缘进行约束,而是将蓝色 view 的顶部与红色 view 的顶部对齐。类似的,将蓝色视图的底部和红色视图的底部对齐。具体约束如下所示。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 竖直方向约束</span><br><span class="line">Red.top = 1.0 * Superview.top + 20.0</span><br><span class="line">Superview.bottom = 1.0 * Red.bottom + 20.0</span><br><span class="line">Red.top = 1.0 * Blue.top + 0.0</span><br><span class="line">Red.bottom = 1.0 * Blue.bottom + 0.0</span><br><span class="line"></span><br><span class="line">// 水平方向约束</span><br><span class="line">Red.leading = 1.0 * Superview.leading + 20.0</span><br><span class="line">Blue.leading = 1.0 * Red.trailing + 8.0</span><br><span class="line">Superview.trailing = 1.0 * Blue.trailing + 20.0</span><br><span class="line">Red.width = 1.0 * Blue.width + 0.0</span><br></pre></td></tr></table></figure>
<p>这种布局方案依然是有两个 view,通过水平方向四条约束,竖直方向有四条约束。定义了一个满足需求的布局。</p>
<blockquote>
<p><strong>但是哪一个方案更好呢</strong><br>这两种方案都能够满足需求。所以那种方案更好一些呢?</p>
<p>实际上,针对这两种方案不能绝对的说哪一种更好,两种方案都有各自的优势。</p>
<p>第一种布局方案更适合有其他 view 被移除的情况。如果一次 view 从视图层级中被移除,那么它的相关的约束也会被移除。所以,在第一种方案中,如果你移除红色视图后,蓝色视图还会保留三条约束。这样你只需要再添加一条约束就能够重新布局。在第二种方案中,如果移除了红色视图,那么蓝色视图只剩一条约束。</p>
<p>另一方面,在第一种布局方案中,如果你想使两个 view 的上下对齐,你需要将每个视图的上下两条约束各自设置相同的常量值。如果你改变了一个的值,你同时必须要修改另一个。</p>
</blockquote>
<h3 id="不等式约束"><a href="#不等式约束" class="headerlink" title="不等式约束"></a>不等式约束</h3><p>到目前为止,所有示例中展示的都是等式约束,这只是约束的一部分。约束也可以通过不等式来表述。具体来说,约束关系可以是等于、大于等于、小于等于。</p>
<p>例如,你可以通过约束设置一个 view 的 size 的最大值和最小值(代码 3-3)。</p>
<p><strong>代码 3-3</strong> 给一个 view 的 size 设置最大值和最小值</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 设置宽度最小值</span><br><span class="line">View.width >= 0.0 * NotAnAttribute + 40.0</span><br><span class="line"></span><br><span class="line">// 设置宽度最大值</span><br><span class="line">View.width <= 0.0 * NotAnAttribute + 280.0</span><br></pre></td></tr></table></figure>
<p>一但你使用的不等式约束,每个 view 上之前的约束将会失效。任何时候你都可以通过使用两条不等式约束去代替等式约束。在代码 3-4 中,上面一个等式关系和下面的两个不等式关系所布局的效果是等价的。</p>
<p><strong>代码 3-4</strong> 使用两个不等式代替一个等式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 一个等式</span><br><span class="line">Blue.leading = 1.0 * Red.trailing + 8.0</span><br><span class="line"></span><br><span class="line">// 可以被下面两个不等式代替</span><br><span class="line">Blue.leading >= 1.0 * Red.trailing + 8.0</span><br><span class="line">Blue.leading <= 1.0 * Red.trailing + 8.0</span><br></pre></td></tr></table></figure>
<p>并不是所有的等式与不等式之间都可以这样转换,有时候两个不等关系并不等价于一个相等关系。例如,在<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW10" target="_blank" rel="external">代码 3-3</a> 中使用两个不等式约束定义了 view 的宽度范围,但是没有具体定义宽度。你如果你想具体定义 view 的位置和大小,仍然需要在这个范围内添加水平方向的约束。</p>
<h3 id="约束的属性"><a href="#约束的属性" class="headerlink" title="约束的属性"></a>约束的属性</h3><p>一般情况下,每条约束都是必须的。Auto Layout 需要根据所有的约束条件计算出一个合理的结果。如果不能计算出来,那说明这里存在错误。Auto Layout 会将一些有问题的约束信息输出到控制台,然后你可以根据控制台信息将一些有冲突的约束干掉。然后 Auto Layout 会重新计算结果。关于更多消息,请查看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ConflictingLayouts.html#//apple_ref/doc/uid/TP40010853-CH19-SW1" target="_blank" rel="external">Unsatisfiable Layouts</a> 章节。</p>
<p>你也可以创建可选约束。所有的约束都有一个权重值,这个值的范围是 1~1000。如果一条约束的权重为 1000,那么这个约束条件是必须的。</p>
<p>当计算约束结果时,Auto Layout 会优先满足权重比较高的约束条件。如果一条可选约束条件不能被满足,这条约束将会被跳过,然后继续处理下一条约束。</p>
<p>尽管一些可选约束条件不能被满足,但是它依然会影响布局。在布局时,如果跳过一些非法约束后,仍然有一些布局不能确定,系统会从跳过的那些约束中,选择一条最接近需求的约束。这些非法约束条件就会被强行加到当前的视图,从而影响布局效果。</p>
<p>通常可选约束和不等式约束会配合使用。例如,在 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW9" target="_blank" rel="external">代码 3-4</a> 中你可以将两个不等式约束的权重设置为不同值。关系为“大于等于”的约束条件设置为最高优先级(权重 1000),关系为”小于等于”的约束条件权重设置为低优先级(权重 250).这意味着蓝色 view 的距离红色 view 不能小于 8.0-point。然而,其他一些约束可能会使这个距离变得更远。所以,通过添加这条可选约束,可以确保蓝色 view 与红色 view 尽可能保持 8.0-point 左右的距离,而不会因为添加其他约束导致距离变得很远。</p>
<blockquote>
<p><strong>备注</strong><br>不要随意将约束的权重设置为 1000。系统默认定义了一个级别的优先级:低优先级(250),中等优先级(500),高优先级(750)和最高优先级(1000)。在为约束设置权重是,你应该围绕着这些值设定,大于或小于 1 或 2。如果你超出这些值很多,你可能需要重新审查一下你的布局逻辑。</p>
<p>关于在 iOS 系统中预定义的一些约束优先级,请查看 <a href="https://developer.apple.com/documentation/uikit/uilayoutpriority" target="_blank" rel="external">UILayoutPriority</a>。对于 OS X 系统,请查看 Layout Priorities constants.</p>
</blockquote>
<h3 id="固有内容大小"><a href="#固有内容大小" class="headerlink" title="固有内容大小"></a>固有内容大小</h3><p>目前为止,所有的样例中都是通过约束来定义 view 的大小和位置。然而,有一些视图会根据内容产生一个固有大小。这就是之前所有的 <strong><em>固有内容大小</em></strong>。例如,一个 button 的固有内容大小就是他的 title 内容大小加上一些边缘的大小。</p>
<p>并不是所有的 view 都有其固有内容大小。如果一个 view 有固有内容大小,那么通过固有内容大小就可以定义这个 view 的宽和高。这里有一些示例在 表 3-1 中。</p>
<p><strong>表 3-1</strong> 控件的固有内容大小情况</p>
<table>
<thead>
<tr>
<th>View</th>
<th>固有内容大小情况</th>
</tr>
</thead>
<tbody>
<tr>
<td>UIView 和 NSView</td>
<td>没有固有内容大小</td>
</tr>
<tr>
<td>Sliders</td>
<td>只有固有宽度(iOS 系统). 根据不同类型,可能有固有宽度,可能有固有高度,或者两者都有(OS X).</td>
</tr>
<tr>
<td>Labels,buttons,switches 和 text fields</td>
<td>同时包含固有宽和高</td>
</tr>
<tr>
<td>Text views 和 image views</td>
<td>固有内容大小为变量</td>
</tr>
</tbody>
</table>
<p>固有大小根据 view 当前展示的内容而定。对于一个 label 或者一个 button,它的固有大小根据控件所展示的文本字数和字体大小而定。对于其他视图,影响控件固有大小的因素会更多。例如,一个空的 image view 没有固有大小。一旦你将一个图片添加到上面,它的固有大小就变成这个图片的大小。</p>
<p>一个 text view 的固有大小根据这些因素而定:内容多少,是否可以滚动,是否有额外约束添加到视图上。例如,如果 view 可以滚动,那么这个 text view 就没有固有大小。如果不可以滚动,默认情况下内容不换行,然后根据内容的大小计算固有大小。例如,如果一个 text view 没有内容,那么会按照一行为本的形式来计算它的宽和高。如果通过约束条件指定了它的宽度,那么它的固有高度就是展示这么宽的文本所需要的高度。</p>
<p>Auto Layout 通过在每个维度设置一组约束条件,以此来表现出固有大小。content hugging 这个约束条件,会尽可能压缩视图,使其紧贴内容;compression resistance 这个约束条件,会尽可能向外扩大视图,是内容尽可能不会被裁剪。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/intrinsic_content_size_2x.png" width="360" height="120"><br></div>
<p>下面代码中,约束条件使用的是代码 3-5 所提及的不等式约束。在这里,<strong>IntrinsicHeight</strong> 和 <strong>IntrinsicWidth</strong> 常量代表 view 的固有内容大小得出的高和宽。</p>
<p><strong>代码 3-5</strong> Compression-Resistance 和 Content-Hugging 方程式.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// Compression Resistance</span><br><span class="line">View.height >= 0.0 * NotAnAttribute + IntrinsicHeight</span><br><span class="line">View.width >= 0.0 * NotAnAttribute + IntrinsicWidth</span><br><span class="line"></span><br><span class="line">// Content Hugging</span><br><span class="line">View.height <= 0.0 * NotAnAttribute + IntrinsicHeight</span><br><span class="line">View.width <= 0.0 * NotAnAttribute + IntrinsicWidth</span><br></pre></td></tr></table></figure>
<p>每条约束都有他自己的权重。默认情况下,Content Hugging 的权重为 250,Compression Resistance 的权重为 750。因此,相对于压缩 view,扩大视图的优先级更高。在多数情况下,这样设计是很有必要的。例如,这样设计后,你可以放心的通过约束去改变 buttton 的大小,使其比固有大小还要大。如果你想压缩这个 button,那么他的内容将会被裁剪,这是不被允许的。需要说的是,通过 Interface Builder 可以改变这些权重。关于更多信息,请查看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html#//apple_ref/doc/uid/TP40010853-CH10-SW2" target="_blank" rel="external">Setting Content-Hugging and Compression-Resistance Priorities</a> 相关内容。</p>
<p>只有可以,尽可能的在布局中利用 view 固有大小这一特性。这样可以让你的 view 随着内容的改变进行动态适配。而且在布局时,可减少约束的数量,避免太多约束冲突。但是你需要记得处理 view 的 content-hugging 和 compression-resistance (CHCR)这两条约束的权重。关于如何处理固有大小,这里提供了一些参考:</p>
<ul>
<li>当你想要通过拉伸视图来填充父视图时,如果每个 view 的 content-hugging 约束的权重相等,那么布局起来会比较混乱。Auto Layout 不知道那个视图需要被拉伸.<br><br>一个比较常见的例子就是:这里有一个 label 和一个 text field,通常情况下,你想拉伸 text field 使其填满空白区域,而使 label 保持固有大小。那么此时你就需要将 text field 水平方向的 content-hugging 约束权重设置的相对低一些.<br><br>实际上,在你使用 Interface Bulider 进行布局时,会自动帮你处理这个问题,直接将 Label 的的 content—hugging 的权重设置为 251。如果你想通编码来进行布局,那么需要你手动去改变 content—hugging 的权重.</li>
<li>如果你强行拉伸了一些有隐藏背景的视图(例如 button 或者 label),使其超过他们个固有大小,会发生一些意想不到的现象。现象可能并不是特别明显,毕竟只是文本出现在错误的位置而已。为了避免这种拉伸,你可以增加 view 的 content-hugging 的权重.</li>
<li>基线对齐约束只作用于 view 的固有高度上。如果一个 view 在竖直方向拉伸或压缩,那么基线对齐约束将不能再使 view 正确的对齐。</li>
<li>一些视图,例如 switch 控件,通常以固有大小展示。你可以通过增加他们的 CHCR 的权重,来避免使它们受到压缩和拉伸。</li>
<li>尽量避免把 view 的 CHCR 的权重设为特定值。通常情况下,展示一个错误的大小比出现约束冲突更好一些。如果一个 view 有必要总以固有大小展示,那么就把他的 CHCR 设置一个非常高(999)的权重。这个方法可以避免使你的 view 被拉伸或者压缩,防止 view 展示的太大或者太小。</li>
</ul>
<h3 id="固有内容大小-VS-合适大小"><a href="#固有内容大小-VS-合适大小" class="headerlink" title="固有内容大小 VS 合适大小"></a>固有内容大小 VS 合适大小</h3><p>对于 Auto Layout 来说,固有大小作为一个输入值。当 view 有一个固有大小,系统会将将这个固有大小转化为对应的约束条件,和其他约束条件放在一起。然后针对这些条件计算布局结果。</p>
<p>另一方面,合适大小相对于 Auto Layout 来说是一个输出值。这个大小是通过 view 的所有约束条件计算出来的。当使用 Auto Layout 布局 view 的子视图时,系统会根据子视图内容大小,为 view 计算出一个合适的展示大小。</p>
<p>Stack view 是一个很好的例子。不添加任何其他约束的情况下,系统会根据 stack view 的内容和属性设置,去计算这个 stack view 的大小。很多时候,stack view 都被视为是有固有大小的 view,在进行布局时,你仅需要在水平方向和竖直方向各添加一条约束来定义它的位置即可,不需要再通过约束去定义它的大小。但是,它的大小是通过 Auto Layout 计算出来的,而不是作位输入值供 Auto Layout 使用。设置 stack view 的 CHCR 的权重不会起任何作用,因为它本质上不是一个具有固有内容大小的 view,只是类似而已。</p>
<h3 id="属性解释"><a href="#属性解释" class="headerlink" title="属性解释"></a>属性解释</h3><p>Auto Layout 的一些值都以 point 为单位。这些属性的含义,需要根据具体布局方向来确定。</p>
<table>
<thead>
<tr>
<th>Auto Layout 属性</th>
<th>含义</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_Height_2x.png" width="30" style="vertical-align:middle;"> Height</div><br><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_Width_2x.png" width="30" style="vertical-align:middle;"> Width</div></td>
<td>view 的大小</td>
<td>这些属性可以直接赋予常量值,或者与其他的 Height 和 Width 属性相关联.这些值不能为负数.</td>
</tr>
<tr>
<td><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_TopToSuper_2x.png" width="30" style="vertical-align:middle;"> Top</div><br><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_BottomToSuper_2x.png" width="30" style="vertical-align:middle;"> Bottom</div><br><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_AlignMiddle_2x.png" width="30" style="vertical-align:middle;"> BaseLine</div></td>
<td>当你上下移动 view 时,这些值会发生变化.</td>
<td>这些属性仅与 Center Y,Top,Bottom,和 BaseLine 这些属性关联.</td>
</tr>
<tr>
<td><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_LeftToSuper_2x.png" width="30" style="vertical-align:middle;"> Leading</div><br><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_RightToSuper_2x.png" width="30" style="vertical-align:middle;"> Trailing</div></td>
<td>这些值会随着你移动 view 远离边缘时而变大.对于从左到右的布局,当你向右移动 view 时值会变大.对于从右到左的布局,当你像左移动 view 时值会变大.</td>
<td>这些属性仅与 Leading,Trailing,或者 Center X 属性关联.</td>
</tr>
<tr>
<td><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_LeftToSuper_2x.png" width="30" style="vertical-align:middle;"> Left</div><br><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_RightToSuper_2x.png" width="30" style="vertical-align:middle;"> Right</div></td>
<td>当你左右移动 view 时,这些属性的值会变化.</td>
<td>这些属性仅可以与 Left,Right,和 Center x 关联使用.<br>在开发过程中,尽量使用 Leading 和 Trailing 代替 Left 和 Right.这样布局方向会随着阅读方向改变而适配.默认的阅读方向是根据用户设置的语言而定的.然而,如果有必要,你可以重写这部分.在 iOS 中,你可以通过设置 view 的 <a href="https://developer.apple.com/documentation/uikit/uiview/1622461-semanticcontentattribute" target="_blank" rel="external">semanticContentAttribute</a> 这一属性来指定当语言方向改变时,是否改变布局方向.</td>
</tr>
<tr>
<td><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_AlignCenter_2x.png" width="30" style="vertical-align:middle;"> Center X</div><br><div><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/ALGuide_AlignMiddle_2x.png" width="30" style="vertical-align:middle;"> Center Y</div></td>
<td>具体含义需要根据约束方程式中其他属性而确定.</td>
<td>Center x 可以与 Center X,Leading,Trailing,Right,和 Left 属性关联.Center Y 可以与 Center Y,Top,Bottom,和 BaseLine 属性关联.</td>
</tr>
</tbody>
</table>
<a id="more"></a>
<p>对视图层级布局,实际上是定义了一系列的线性方程式。每一条约束代表一个方程式。你的目的就是通过声明一些列的方程式,并使其只有一组最优解。</p>
<p>下面展示了一组简单的方程式.</p>
<div align="center"><br><
iOS Auto Layout-不使用约束布局(译)
http://yoursite.com/2018/04/07/iOS Auto Layout-不使用约束布局(译)/
2018-04-07T12:01:23.000Z
2018-06-03T13:14:32.848Z
<a id="more"></a>
<p>Stack view 提供了一种简单的方式进行自动布局,通过这种方式可以不用设置一些复杂的约束,而达到自动布局的效果。一个 stack view 定义了一行或者一列界面元素。stack view 通过以下这些属性去排版它的每个元素。</p>
<ul>
<li><a href="https://developer.apple.com/documentation/uikit/uistackview/1616223-axis" target="_blank" rel="external">axis</a>:(<a href="https://developer.apple.com/documentation/uikit/uistackview" target="_blank" rel="external">UIStackView</a> only) 定义 stack view 的排布方向,是垂直排布还是水平排布.</li>
<li><a href="https://developer.apple.com/documentation/appkit/nsstackview/1488950-orientation" target="_blank" rel="external">orientation</a>:(<a href="https://developer.apple.com/documentation/appkit/nsstackview" target="_blank" rel="external">NSStackView</a> only) 定义 stack view 的排布方向,是垂直排布还是水平排布.</li>
<li><a href="https://developer.apple.com/documentation/uikit/uistackview/1616233-distribution" target="_blank" rel="external">distribution</a>:定义了 stack view 中的 view 的排布方式.</li>
<li><a href="https://developer.apple.com/documentation/uikit/uistackview/1616243-alignment" target="_blank" rel="external">alignment</a>:定义了 stack view 中的 view 的对齐方式.</li>
<li><a href="https://developer.apple.com/documentation/uikit/uistackview/1616225-spacing" target="_blank" rel="external">spacing</a>:定义 stack view 中的 view 之间的间隔.</li>
</ul>
<p>Stack view 使用起来很简单,使用 Interface Builder 拖拽一个垂直或水平排布的 stack view 到画布上.然后拖拽一些你要布局的内容到 stack view 中.</p>
<p>如果某个控件有固有大小,那么拖入 stack 之后它将保持固有大小。如果它没有固有大小,Interface Builder 会提供一个默认大小.你可以通过添加约束去改变它的大小.</p>
<p>为了更进一步地调整布局,你可以通过属性检查器去改变 stack view 的属性。例如,下面这个例子中通过设置属性,使每个 view 等大小排布,并且每个 view 之间保持 8-point 距离。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/IB_StackView_Simple_2x.png" width="215" height="136"><br></div>
<p>同时 stack view 也会根据子视图的排版与大小,去布局自身的大小,使自身紧贴子视图。你可以通过 Size 检查器去改变这些。</p>
<blockquote>
<p><strong>备注</strong><br>如果你想更好的进行布局,你可以直接通过添加约束来排版子视图;而且,如果你想避免任何冲突,你首先要遵循这样一个原则:如果一个 view 的默认尺寸为其根据内容计算出的固有大小,你可以放心的在这个尺寸上面添加约束。关于约束冲突更多的信息,请看 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ConflictingLayouts.html#//apple_ref/doc/uid/TP40010853-CH19-SW1" target="_blank" rel="external">Unsatisfiable Layouts</a> 章节。</p>
</blockquote>
<p>除此之外,你可以在 stack view 中嵌套新的 stack view 作为子视图,进行一些更复杂的布局。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/IB_StackView_NestedStacks_2x.png" width="285" height="232"><br></div>
<p>一般情况下,你可以尽可能地通过 stack view 进行布局。除非当单用 stack view 无法满足你的需求时,在使用约束进行布局。</p>
<p>关于更多使用它 stack view 的信息,请查看 <a href="https://developer.apple.com/documentation/uikit/uistackview" target="_blank" rel="external">UIStackView Class Reference</a> 或者 <a href="https://developer.apple.com/documentation/appkit/nsstackview" target="_blank" rel="external">NSStackView Class Reference</a> 章节。</p>
<blockquote>
<p><strong>备注</strong><br>尽管通过使用嵌套 stack view 可以进行复杂的布局,但是你不能完全避免使用约束。至少你需要通过约束去设置最外层 stack view 的位置(可能还有大小)。</p>
</blockquote>
<a id="more"></a>
<p>Stack view 提供了一种简单的方式进行自动布局,通过这种方式可以不用设置一些复杂的约束,而达到自动布局的效果。一个 stack view 定义了一行或者一列界面元素。stack view 通过以下这些属性去排版它的每个元素。</
iOS Auto Layout-理解自动布局(译)
http://yoursite.com/2018/04/07/iOS Auto Layout-理解自动布局(译)/
2018-04-07T07:03:40.000Z
2018-06-03T13:14:49.037Z
<a id="more"></a>
<p>Auto Layout 可以根据加在 view 上的约束条件,动态计算 view 在视图层级中的大小和位置。例如,你可以给一个 button 和一个 image view 设置这样的约束条件:button 与 image view 保持水平居中,并且使 button 的顶部距离 image view 的底部始终保持 8-point 的距离。这样如果 image view 的大小或者位置发生改变,通过 Auto Layout 会重新计算 button 的大小与位置,更新 button 的布局。</p>
<h3 id="外部变化"><a href="#外部变化" class="headerlink" title="外部变化"></a>外部变化</h3><p>父视图的大小或者形状发生改变时,会引起外部变化。这时你需要根据父视图的大小去更新界面布局,以达到最好的适配效果。以下一些操作会引起这种变化:</p>
<ul>
<li>用户调整 <code>window</code> 大小 (OS X).</li>
<li>用户使用 iPad 的分屏功能时 (iOS).</li>
<li>设备屏幕发生旋转 (iOS).</li>
<li>呼入来电,音量条的显示与消失 (iOS).</li>
<li>你想支持不同的屏幕比例,当屏幕等比缩放时会引起外部变化.</li>
<li>你想支持不同的屏幕尺寸,当应用在不同屏幕大小运行时会引起外部变化.</li>
</ul>
<p>这些操作大部分发生在 APP 运行的时候,因此需要对这些突然的变化做好动态适配。另外,如果你的 APP 进行了屏幕适配,则意味着你的 APP 可以适配各种机型。一个好的屏幕适配方案,可以使你的 APP 在 iPhone 4S,iPhone 6 Plus,甚至 iPad 上做出很好的适配。同时,Auto Layout 也是使 iPad 能够支持多任务和分屏的关键。</p>
<h3 id="内部变化"><a href="#内部变化" class="headerlink" title="内部变化"></a>内部变化</h3><p>当视图本身尺寸发生变化或者界面上一些控件发生变化时,会引起内部变化。</p>
<p>一下一些因素会引起内部变化:</p>
<ul>
<li>APP 展示内容发生改变.</li>
<li>APP 支持国际化.</li>
<li>APP 支持动态字体 (iOS).</li>
</ul>
<p>当 APP 展示内容发生变化时,有时需要更新布局来重新排版内容。这种场景一般发生在文本类或者图片类的 APP 中。例如一个新闻类的 APP,需要根据新闻内容去自动调整布局。类似的,一个图片类 APP 需要根据图片比例做好自动适配。</p>
<p>所谓的国际化过程,就是将 APP 针对不同语言、地区和文化做好适配。对于一个做了国际化处理的 APP,不同的环境下展示内容可能会不同。因此在进行布局适配时,需要将这些因素考虑进去,使 APP 在不同的环境下可以显示正确的内容。</p>
<p>国际化对布局的影响主要有三个方面。第一,当切换不同的语言时,对应的一些文本框需要的空间不同。例如,想对于英语来说,德语需要更多的空间,而日语需要更少的空间。</p>
<p>第二,不同的国家与地区,虽然使用语言相同,但是日期格式可能会有些不同。尽管这些改变不是很明显,但是仍需要对此做好动态适配。</p>
<p>第三,有时候语言的改变,不仅会影响文本的大小,还有可能影响布局方向。例如,使用英语的国家一般是从左到右的布局,而使用阿拉伯语和希伯来语的国家是从右到左的布局。一般来说,交互设计需要根据用户习惯进行合理布局。一个 <code>button</code> ,在语言为英语时如果放在右下角,那么当语言切换为阿拉伯语它应该放在左下角。</p>
<p>最后,如果你的 APP 支持动态字体,那就代表用户可以修改 APP 字体大小,这会影响文本的宽和高。如果用户改变了 APP 的字体大小,那么布局必须做出相应的适配。</p>
<h3 id="自动布局相对于-Frame-布局"><a href="#自动布局相对于-Frame-布局" class="headerlink" title="自动布局相对于 Frame 布局"></a>自动布局相对于 Frame 布局</h3><p>这里有三种主流方式去设计布局。你可以直接通过编码进行布局,也可以使用 autoresizing 布局,或者使用 Auto Layout。</p>
<p>在过去,APP 通过编码设置 view 的 frame 进行布局。以父视图为基础坐标系,frame 定义了 view 的 origin,height 和 width。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/layout_views_2x.png" width="270" height="360"><br></div>
<p>在进行布局时,你需要计算每个子 view 的 size 和 position。当有视图位置或大小发生改变时,你需要对受影响的视图重新布局。</p>
<p>在很多时候,虽然使用 frame 布局可以灵活控制 view 的大小与位置。但是相对的,当视图结构发生改变时,你需要花费大量的精力去重新布局。</p>
<p>你可以使用 autoresizing mask 布局来减少工作量。使用 autoresizing mask 可以给 view 和其 superview 设置一些简单约束,当 superview 发生改变时,view 可以动态适配这种改变。</p>
<p>然而,autoresizing mask 只是布局体系中的一个子集。当布局变得复杂时,需要使用代码与 autoresizing mask 配合才能布局。除此之外,autoresizing mask 只能简单适配的外部变化,不能适配内部变化(例如同一层级的其他 view 发生变化时,使用 autoresizing mask 不能做出动态适配)。</p>
<p>autoresizing mask 只是相对于代码布局简单提升,而 Auto Layout 则是一种全新的布局范式。在使用 Auto Layout 对个 view 布局时,不仅会考虑 view 的 frame,还会考虑与之相关的其他 view 的相互作用。</p>
<p>Auto Layout 是通过一系列的约束进行页面布局的。使用约束设置两个 view 的关系之后,Auto Layout 可以根据约束动态计算 view 的大小和位置,这使 view 既可以响应外部变化,又可以响应内部变化。</p>
<div align="center"><br><img src="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/layout_constraints_2x.png" width="275" height="360"><br></div>
<p>通过约束对视图进行详细的布局,与以往通过代码处理一些事件相比,在逻辑处理上似乎有很大不同。其实,两者并没有太大的区别。这里有两项基本功要做:首先你要理解约束布局背后的逻辑,其次你要学习如何使用 API。在平时编码过程中,你已经将这些基本功掌握的十分熟练。所以 Auto Loyout 对你来说不再是什么难事。</p>
<p>通过阅读剩余的一些篇章,你会很轻松的掌握 Auto Layout.<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutWithoutConstraints.html#//apple_ref/doc/uid/TP40010853-CH8-SW1" target="_blank" rel="external">Auto Layout Without Constraints</a> 章节描述的十分抽象,简化了 Auto Layout 布局过程。<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW1" target="_blank" rel="external">Anatomy of a Constraint</a> 章节讲解了 Auto Layout 背后的原理,如果你想更好的掌握 Auto Layout,你需要自己去了解这些原理。<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html#//apple_ref/doc/uid/TP40010853-CH10-SW1" target="_blank" rel="external">Working with Constraints in Interface Builder</a> 讲解了一些用来设计 Auto Layout 的工具,而 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1" target="_blank" rel="external">Programmatically Creating Constraints</a> 和 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH3-SW1" target="_blank" rel="external">Auto Layout Cookbook</a> 章节详细讲解了的 Auto Layout 的 API。最后,在 <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH3-SW1" target="_blank" rel="external">Auto Layout Cookbook</a> 这一篇中,提供了许多的基于 Auto Layout 的样例,你可以学习这些样例,将其中的一些技巧运用到你自己的项目中,<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/TypesofErrors.html#//apple_ref/doc/uid/TP40010853-CH22-SW1" target="_blank" rel="external">Debugging Auto Layout</a> 提供了一些常见问题解决方案和调试工具,当布局出现问题时,你可以参考这些方案或者使用这些工具进行修复。</p>
<a id="more"></a>
<p>Auto Layout 可以根据加在 view 上的约束条件,动态计算 view 在视图层级中的大小和位置。例如,你可以给一个 button 和一个 image view 设置这样的约束条件:button 与 image view 保持水
我所理解的 iOS 并发编程
http://yoursite.com/2018/04/06/我所理解的 iOS 并发编程/
2018-04-06T15:04:23.000Z
2018-06-03T13:13:21.726Z
<a id="more"></a>
<p>无论在哪个平台,并发编程都是一个让人头疼的问题。庆幸的是,相对于服务端,客户端的并发编程简单了许多。这篇文章主要讲述一些基于 iOS 平台的一些并发编程相关东西,我写博客习惯于先介绍原理,后介绍用法,毕竟对于 API 的使用,官网有更好的文档。</p>
<h3 id="一些原理性的东西"><a href="#一些原理性的东西" class="headerlink" title="一些原理性的东西"></a>一些原理性的东西</h3><p>为了便于理解,这里先解释一些相关概念。如果你对这些概念已经很熟悉,可以直接跳过。</p>
<h4 id="1-进程"><a href="#1-进程" class="headerlink" title="1.进程"></a>1.进程</h4><p>从操作系统定义上来说,进程就是系统进行资源分配和调度的基本单位,系统创建一个线程后,会为其分配对应的资源。在 iOS 系统中,进程可以理解为就是一个 App。iOS 并没有提供可以创建进程的 API,即使你调用 <code>fork()</code> 函数,也不能创建新的进程。所以,<strong>本文所说的并发编程,都是针对线程来说的</strong>。</p>
<h4 id="2-线程"><a href="#2-线程" class="headerlink" title="2.线程"></a>2.线程</h4><p>线程是程序执行流的最小单元。一般情况下,一个进程会有多个线程,或者至少有一个线程。一个线程有创建、就绪、运行、阻塞和死亡五种状态。线程可以共享进程的资源,所有的问题也是因为共享资源引起的。</p>
<h4 id="3-并发"><a href="#3-并发" class="headerlink" title="3.并发"></a>3.并发</h4><p>操作系统引入线程的概念,是为了使过个 CPU 更好的协调运行,充分发挥他们的并行处理能力。例如在 iOS 系统中,你可以在主线程中进行 UI 操作,然后另启一些线程来处理与 UI 操作无关的事情,两件事情并行处理,速度比较快。这就是并发的大致概念。</p>
<h4 id="4-时间片"><a href="#4-时间片" class="headerlink" title="4.时间片"></a>4.时间片</h4><p>按照 <a href="https://zh.wikipedia.org/wiki/%E6%97%B6%E9%97%B4%E7%89%87" target="_blank" rel="external">wiki</a> 上面解释:是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。线程可以被认为是 ”微进程“,因此这个概念也可以用到线程方面。</p>
<p>一般操作系统使用时间片轮转算法进行调度,即每次调度时,总是选择就绪队列的队首进程,让其在CPU上运行一个系统预先设置好的时间片。一个时间片内没有完成运行的进程,返回到绪队列末尾重新排队,等待下一次调度。不同的操作系统,时间片的范围不一致,一般都是毫秒(ms)级别。</p>
<h4 id="4-死锁"><a href="#4-死锁" class="headerlink" title="4.死锁"></a>4.死锁</h4><p>死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:</p>
<ul>
<li>互斥条件 : 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。</li>
<li>请求和保持条件 : 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。</li>
<li>不可剥夺条件 : 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。</li>
<li>环路等待条件 : 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。</li>
</ul>
<p>为了便于理解,这里举一个例子:一座桥,同一时间只允许一辆车经过(<strong>互斥</strong>)。两辆车 A,B 从桥的两端开上桥,走到桥的中间。此时 A 车不肯退(<strong>不可剥夺</strong>),又想占用 B 车所占据的道路;B 车此时也不肯退,又想占用 A 车所占据的道路(<strong>请求和保持</strong>)。此时,A 等待 B 占用的资源,B 等待 A 占用的资源(<strong>环路等待</strong>),两车僵持下去,就形成了<strong>死锁现象</strong>。</p>
<h4 id="5-线程安全"><a href="#5-线程安全" class="headerlink" title="5.线程安全"></a>5.线程安全</h4><p>当多个线程同时访问一块共享资源(例如数据库),因为时序性问题,会导致数据错乱,这就是<strong>线程不安全</strong>。例如数据库中某个整形字段的 value 为 0,此时两个线程同时对其进行写入操作,线程 A 拿到原值为 0,加一后变为 1;线程 B 并不是在 A 加完后拿的,而是和 A 同时拿的,加完后也是 1,加了两次,理想值应该为 2,但是数据库中最终值却是 1。实际开发场景可能要比这个复杂的多。</p>
<p>所谓的线程安全,可以理解为在多个线程操作(例如读写操作)这部分数据时,不会出现问题。</p>
<h3 id="Lock"><a href="#Lock" class="headerlink" title="Lock"></a>Lock</h3><p>因为线程共享进程资源,在并发情况下,就会出现线程安全问题。为了解决此问题,就出现了锁这个概念。在多线程环境下,当你访问一些共享数据时,拿到访问权限,给数据加锁,在这期间其他线程不可访问,直到你操作完之后进行解锁,其他线程才可以对其进行操作。</p>
<p>iOS 提供了多种锁,ibireme 大神的 <a href="https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/" target="_blank" rel="external">这篇文章</a> 对这些锁进行了性能分析,我这里直接把图 cp 过来了:</p>
<p><img src="/uploads/concurrency-programming/lock_benchmark.png" alt="lock_benchmark"></p>
<p>下面针对这些锁,逐一分析。</p>
<h4 id="1-OSSpinLock"><a href="#1-OSSpinLock" class="headerlink" title="1.OSSpinLock"></a>1.OSSpinLock</h4><p>ibireme 大神的文章也说了,虽然这个锁性能最高,但是已经不安全了,建议不再使用,这里简单说一下。</p>
<p>OSSpinLock 是一种自旋锁,主要提供了加锁(<code>OSSpinLockLock</code>)、尝试枷锁(<code>OSSpinLockTry</code>)和解锁(<code>OSSpinLockUnlock</code>)三个方法。对一块资源进行加锁时,如果尝试加锁失败,不会进入睡眠状态,而是一直进行询问(自旋),占用 CPU资源,不适用于较长时间的任务。在自旋期间,因为占用 CPU 导致低优先级线程拿不到 CUP 资源,无法完成任务并释放锁,从而形成了<strong>优先级反转</strong>。</p>
<p>so,虽然性能很高,但是不要用了。而且 Apple 也已经将这个类比较为 deprecate 了。</p>
<blockquote>
<p>自旋锁 & 互斥锁<br>两者大体类似,区别在于:自旋锁属于 busy-waiting 类型锁,尝试加锁失败,会一直处于询问状态,占用 CPU 资源,效率高;互斥锁属于 sleep-waiting 类型锁,在尝试失败之后,会被阻塞,然后进行上下文切换置于等待队列,因为有上下文切换,效率较低。<br>在 iOS 中 NSLock 属于互斥锁。</p>
<p>优先级反转 :当一个高优先级任务访问共享资源时,该资源已经被一个低优先级任务抢占,阻塞了高优先级任务;同时,该低优先级任务被一个次高优先级的任务所抢先,从而无法及时地释放该临界资源。最终使得任务优先级被倒置,发生阻塞。(引用自 <a href="https://zh.wikipedia.org/wiki/%E4%BC%98%E5%85%88%E8%BD%AC%E7%BD%AE" target="_blank" rel="external">wiki</a></p>
</blockquote>
<p>关于自旋锁的原理,bestswifter 的文章 <a href="https://bestswifter.com/ios-lock/" target="_blank" rel="external">深入理解 iOS 开发中的锁</a> 这篇文章讲得很好,我这里大部分锁的知识引用于此,建议读一下原文。</p>
<p>自旋锁是加不上就一直尝试,也就是一个循环,直到尝试加上锁,伪代码如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> lock = <span class="literal">false</span>; <span class="comment">// 一开始没有锁上,任何线程都可以申请锁 </span></span><br><span class="line"><span class="keyword">do</span> { </span><br><span class="line"> <span class="keyword">while</span>(test_and_set(&lock); <span class="comment">// test_and_set 是一个原子操作,尝试加锁</span></span><br><span class="line"> Critical section <span class="comment">// 临界区</span></span><br><span class="line"> lock = <span class="literal">false</span>; <span class="comment">// 相当于释放锁,这样别的线程可以进入临界区</span></span><br><span class="line"> Reminder section <span class="comment">// 不需要锁保护的代码 </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用 :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">OSSpinLock spinLock = OS_SPINLOCK_INIT;</span><br><span class="line">OSSpinLockLock(&spinLock);</span><br><span class="line"><span class="comment">// 被锁住的资源</span></span><br><span class="line">OSSpinLockUnlock(&spinLock);</span><br></pre></td></tr></table></figure>
<h4 id="2-dispatch-semaphore"><a href="#2-dispatch-semaphore" class="headerlink" title="2.dispatch_semaphore"></a>2.dispatch_semaphore</h4><p>dispatch_semaphore 并不属于锁,而是信号量。两者的区别如下:</p>
<ul>
<li>锁是用于线程互斥操作,一个线程锁住了某个资源,其他线程都无法访问,直到整个线程解锁;信号量用于线程同步,一个线程完成了某个动作通过信号量告诉别的线程,别的线程再进行操作。</li>
<li>锁的作用域是线程之间;信号量的作用域是线程和进程之间。</li>
<li>信号量有时候可以充当锁的作用,初次之前还有其他作用。</li>
<li>如果转化为数值,锁可以认为只有 0 和 1;信号量可以大于零和小于零,有多个值。</li>
</ul>
<p>dispatch_semaphore 使用分为三步:create、wait 和 signal。如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create</span></span><br><span class="line">dispatch_semaphore_t semaphore = dispatch_semaphore_create(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// thread A</span></span><br><span class="line"><span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);</span><br><span class="line"> <span class="comment">// execute task A</span></span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"task A"</span>);</span><br><span class="line"> sleep(<span class="number">10</span>);</span><br><span class="line"> dispatch_semaphore_signal(semaphore);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// thread B</span></span><br><span class="line"><span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);</span><br><span class="line"> <span class="comment">// execute task B</span></span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"task B"</span>);</span><br><span class="line"> dispatch_semaphore_signal(semaphore);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>执行结果:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-03</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">09.068586</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">44084</span>:<span class="number">1384262</span>] task A</span><br><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-03</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">19.072951</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">44084</span>:<span class="number">1384265</span>] task B</span><br></pre></td></tr></table></figure>
<p>thread A,B 是两个异步线程,一般情况下,各自执行自己的事件,互不干涉。但是根据 console 输出,B 是在 A 执行完了 10s 执行之后才执行的,显然受到阻塞。使用 dispatch_semaphore 大致执行过程这样:创建 semaphore 时,信号量值为 1;执行到线程 A 的 <code>dispatch_semaphore_wait</code> 时,信号量值减 1,变为 0;然后执行任务 A,执行完毕后 <code>sleep</code> 方法阻塞当前线程 10s;与此同时,线程 B 执行到了 <code>dispatch_semaphore_wait</code>,由于信号量此时为 0,且线程 A 中设置的为 <code>DISPATCH_TIME_FOREVER</code>,因此需要等到线程 A sleep 10s 之后,执行 <code>dispatch_semaphore_signal</code> 将信号量置为 1,线程 B 的任务才开始执行。</p>
<p>根据上面的描述,dispatch_semaphore 的原理大致也就了解了。<a href="https://opensource.apple.com/tarballs/libdispatch/" target="_blank" rel="external">GCD 源码</a> 对这些方法定义如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">long</span></span><br><span class="line"><span class="title">dispatch_semaphore_wait</span><span class="params">(dispatch_semaphore_t dsema, dispatch_time_t timeout)</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">long</span> value = dispatch_atomic_dec2o(dsema, dsema_value);</span><br><span class="line"> dispatch_atomic_acquire_barrier();</span><br><span class="line"> <span class="keyword">if</span> (fastpath(value >= <span class="number">0</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> _dispatch_semaphore_wait_slow(dsema, timeout);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">long</span></span><br><span class="line">_dispatch_semaphore_wait_slow(<span class="keyword">dispatch_semaphore_t</span> dsema,</span><br><span class="line"> <span class="keyword">dispatch_time_t</span> timeout)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">long</span> orig;</span><br><span class="line"></span><br><span class="line">again:</span><br><span class="line"> <span class="comment">// Mach semaphores appear to sometimes spuriously wake up. Therefore,</span></span><br><span class="line"> <span class="comment">// we keep a parallel count of the number of times a Mach semaphore is</span></span><br><span class="line"> <span class="comment">// signaled (6880961).</span></span><br><span class="line"> <span class="keyword">while</span> ((orig = dsema->dsema_sent_ksignals)) {</span><br><span class="line"> <span class="keyword">if</span> (dispatch_atomic_cmpxchg2o(dsema, dsema_sent_ksignals, orig,</span><br><span class="line"> orig - <span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">struct</span> timespec _timeout;</span><br><span class="line"> <span class="keyword">int</span> ret;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span> (timeout) {</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">uint64_t</span> nsec = _dispatch_timeout(timeout);</span><br><span class="line"> _timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);</span><br><span class="line"> _timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);</span><br><span class="line"> ret = slowpath(sem_timedwait(&dsema->dsema_sem, &_timeout));</span><br><span class="line"> } <span class="keyword">while</span> (ret == <span class="number">-1</span> && errno == EINTR);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (ret == <span class="number">-1</span> && errno != ETIMEDOUT) {</span><br><span class="line"> DISPATCH_SEMAPHORE_VERIFY_RET(ret);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Fall through and try to undo what the fast path did to</span></span><br><span class="line"> <span class="comment">// dsema->dsema_value</span></span><br><span class="line"> <span class="keyword">case</span> DISPATCH_TIME_NOW:</span><br><span class="line"> <span class="keyword">while</span> ((orig = dsema->dsema_value) < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (dispatch_atomic_cmpxchg2o(dsema, dsema_value, orig, orig + <span class="number">1</span>)) {</span><br><span class="line"> errno = ETIMEDOUT;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Another thread called semaphore_signal().</span></span><br><span class="line"> <span class="comment">// Fall through and drain the wakeup.</span></span><br><span class="line"> <span class="keyword">case</span> DISPATCH_TIME_FOREVER:</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> ret = sem_wait(&dsema->dsema_sem);</span><br><span class="line"> } <span class="keyword">while</span> (ret != <span class="number">0</span>);</span><br><span class="line"> DISPATCH_SEMAPHORE_VERIFY_RET(ret);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">goto</span> again;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以上时对 wait 方法的定义,如果你不想看代码,可以直接听我说:</p>
<ul>
<li>调用 <code>dispatch_semaphore_wait</code> 方法时,如果信号量大于 0,直接返回;否则进入后续步骤。</li>
<li><code>_dispatch_semaphore_wait_slow</code> 方法根据传入 <code>timeout</code> 参数不同,使用 switch-case 处理。</li>
<li>如果传入的是 DISPATCH_TIME_NOW 参数,将信号量加 1 并立即返回。</li>
<li>如果传入的是一个超时时间,调用系统的 <code>semaphore_timedwait</code> 方法进行等待,直至超时。</li>
<li>如果传入的是 DISPATCH_TIME_FOREVER 参数,调用系统的 <code>semaphore_wait</code> 进行等待,直到收到 <code>singal</code> 信号。</li>
</ul>
<p>至于 <code>dispatch_semaphore_signal</code> 就比较简单了,源码如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">long</span></span><br><span class="line"><span class="title">dispatch_semaphore_signal</span><span class="params">(dispatch_semaphore_t dsema)</span></span><br><span class="line"></span>{</span><br><span class="line"> dispatch_atomic_release_barrier();</span><br><span class="line"> <span class="keyword">long</span> value = dispatch_atomic_inc2o(dsema, dsema_value);</span><br><span class="line"> <span class="keyword">if</span> (fastpath(value > <span class="number">0</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (slowpath(value == LONG_MIN)) {</span><br><span class="line"> DISPATCH_CLIENT_CRASH(<span class="string">"Unbalanced call to dispatch_semaphore_signal()"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> _dispatch_semaphore_signal_slow(dsema);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>现将信号量加 1,大于 0 直接返回。</li>
<li>小于 0 返回 <code>_dispatch_semaphore_signal_slow</code>,这个方法的作用是调用内核的 semaphore_signal 函数唤醒信号量,然后返回 1。</li>
</ul>
<h4 id="3-pthread-mutex"><a href="#3-pthread-mutex" class="headerlink" title="3.pthread_mutex"></a>3.pthread_mutex</h4><p>Pthreads 是 POSIX Threads 的缩写。<code>pthread_mutex</code> 属于互斥锁,即尝试加锁失败后悔阻塞线程并睡眠,会进行上下文切换。锁的类型主要有三种:<code>PTHREAD_MUTEX_NORMAL</code>、<code>PTHREAD_MUTEX_ERRORCHECK</code>、<code>PTHREAD_MUTEX_RECURSIVE</code>。</p>
<ul>
<li>PTHREAD_MUTEX_NORMAL,普通锁,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。</li>
<li>PTHREAD_MUTEX_ERRORCHECK,检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK。否则和 PTHREAD_MUTEX_NORMAL 相同。</li>
<li>PTHREAD_MUTEX_RECURSIVE,递归锁,允许一个线程进行递归申请锁。</li>
</ul>
<p>使用如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pthread_mutex_t</span> mutex; <span class="comment">// 定义锁</span></span><br><span class="line"><span class="keyword">pthread_mutexattr_t</span> attr; <span class="comment">// 定义 mutexattr_t 变量</span></span><br><span class="line">pthread_mutexattr_init(&attr); <span class="comment">// 初始化attr为默认属性</span></span><br><span class="line">pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); <span class="comment">// 设置锁的属性</span></span><br><span class="line">pthread_mutex_init(&mutex, &attr); <span class="comment">// 创建锁</span></span><br><span class="line"></span><br><span class="line">pthread_mutex_lock(&mutex); <span class="comment">// 申请锁</span></span><br><span class="line"><span class="comment">// 临界区</span></span><br><span class="line">pthread_mutex_unlock(&mutex); <span class="comment">// 释放锁</span></span><br></pre></td></tr></table></figure>
<h4 id="4-NSLock"><a href="#4-NSLock" class="headerlink" title="4.NSLock"></a>4.NSLock</h4><p>NSLock 属于互斥锁,是 Objective-C 封装的一个对象。虽然我们不知道 Objective-C 是如何实现的,但是我们可以在 <a href="https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSLock.swift" target="_blank" rel="external">swift 源码</a> 中找到他的实现 :</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">var</span> mutex = _PthreadMutexPointer.allocate(capacity: <span class="number">1</span>)</span><br><span class="line">...</span><br><span class="line">open <span class="function"><span class="keyword">func</span> <span class="title">lock</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_mutex_lock(mutex)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">open <span class="function"><span class="keyword">func</span> <span class="title">unlock</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_mutex_unlock(mutex)</span><br><span class="line">#<span class="keyword">if</span> os(macOS) || os(iOS)</span><br><span class="line"> <span class="comment">// Wakeup any threads waiting in lock(before:)</span></span><br><span class="line"> pthread_mutex_lock(timeoutMutex)</span><br><span class="line"> pthread_cond_broadcast(timeoutCond)</span><br><span class="line"> pthread_mutex_unlock(timeoutMutex)</span><br><span class="line">#endif</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看出他只是将 <code>pthread_mutex</code> 封装了一下。只因为比 <code>pthread_mutex</code> 慢一些,难道是因为方法层级之间的调用,多了几次压栈操作???</p>
<p>常规使用:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSLock</span> *mutexLock = [<span class="built_in">NSLock</span> new];</span><br><span class="line">[mutexLock lock];</span><br><span class="line"><span class="comment">// 临界区</span></span><br><span class="line">[muteLock unlock];</span><br></pre></td></tr></table></figure>
<h4 id="4-NSCondition-amp-NSConditionLock"><a href="#4-NSCondition-amp-NSConditionLock" class="headerlink" title="4.NSCondition & NSConditionLock"></a>4.NSCondition & NSConditionLock</h4><p>NSCondition 可以同时起到 lock 和条件变量的作用。同样你可以在 <a href="https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSLock.swift" target="_blank" rel="external">swift 源码</a> 中找到他的实现 :</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">open <span class="class"><span class="keyword">class</span> <span class="title">NSCondition</span>: <span class="title">NSObject</span>, <span class="title">NSLocking</span> </span>{</span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> mutex = _PthreadMutexPointer.allocate(capacity: <span class="number">1</span>)</span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> cond = _PthreadCondPointer.allocate(capacity: <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">init</span>() {</span><br><span class="line"> pthread_mutex_init(mutex, <span class="literal">nil</span>)</span><br><span class="line"> pthread_cond_init(cond, <span class="literal">nil</span>)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">deinit</span> {</span><br><span class="line"> pthread_mutex_destroy(mutex)</span><br><span class="line"> pthread_cond_destroy(cond)</span><br><span class="line"> mutex.deinitialize(<span class="built_in">count</span>: <span class="number">1</span>)</span><br><span class="line"> cond.deinitialize(<span class="built_in">count</span>: <span class="number">1</span>)</span><br><span class="line"> mutex.deallocate()</span><br><span class="line"> cond.deallocate()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">lock</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_mutex_lock(mutex)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">unlock</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_mutex_unlock(mutex)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">wait</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_cond_wait(cond, mutex)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">wait</span><span class="params">(until limit: Date)</span></span> -> <span class="type">Bool</span> {</span><br><span class="line"> <span class="keyword">guard</span> <span class="keyword">var</span> timeout = timeSpecFrom(date: limit) <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> pthread_cond_timedwait(cond, mutex, &timeout) == <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">signal</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_cond_signal(cond)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">broadcast</span><span class="params">()</span></span> {</span><br><span class="line"> pthread_cond_broadcast(cond)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="keyword">var</span> name: <span class="type">String</span>?</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看出,它还是遵循 NSLocking 协议,lock 方法同样还是使用的 <code>pthread_mutex</code>,wait 和 signal 使用的是 <code>pthread_cond_wait</code> 和 <code>pthread_cond_signal</code>。</p>
<p>使用 NSCondition 是,先对要操作的临界区加锁,然后因为条件不满足,使用 wait 方法阻塞线程;待条件满足之后,使用 signal 方法进行通知。下面是一个 生产者-消费者的例子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSCondition</span> *condition = [<span class="built_in">NSCondition</span> new];</span><br><span class="line"><span class="built_in">NSMutableArray</span> *products = [<span class="built_in">NSMutableArray</span> array];</span><br><span class="line"></span><br><span class="line"><span class="comment">// consume</span></span><br><span class="line"><span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> [condition lock];</span><br><span class="line"> <span class="keyword">while</span> (products.count == <span class="number">0</span>) {</span><br><span class="line"> [condition wait];</span><br><span class="line"> }</span><br><span class="line"> [products removeObjectAtIndex:<span class="number">0</span>];</span><br><span class="line"> [condition unlock];</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// product</span></span><br><span class="line"><span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> [condition lock];</span><br><span class="line"> [products addObject:[<span class="built_in">NSObject</span> new]];</span><br><span class="line"> [condition signal];</span><br><span class="line"> [condition unlock];</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>NSConditionLock 是通过使用 NSCondition 来实现的,遵循 NSLocking 协议,然后这是 <a href="https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSLock.swift" target="_blank" rel="external">swift 源码</a> (源码比较占篇幅,我这里简化一下):</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">open <span class="class"><span class="keyword">class</span> <span class="title">NSConditionLock</span> : <span class="title">NSObject</span>, <span class="title">NSLocking</span> </span>{</span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _cond = <span class="type">NSCondition</span>()</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">lock</span><span class="params">(whenCondition condition: Int)</span></span> {</span><br><span class="line"> <span class="keyword">let</span> <span class="number">_</span> = lock(whenCondition: condition, before: <span class="type">Date</span>.distantFuture)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> `<span class="title">try</span>`<span class="params">()</span></span> -> <span class="type">Bool</span> {</span><br><span class="line"> <span class="keyword">return</span> lock(before: <span class="type">Date</span>.distantPast)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">tryLock</span><span class="params">(whenCondition condition: Int)</span></span> -> <span class="type">Bool</span> {</span><br><span class="line"> <span class="keyword">return</span> lock(whenCondition: condition, before: <span class="type">Date</span>.distantPast)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">unlock</span><span class="params">(withCondition condition: Int)</span></span> {</span><br><span class="line"> _cond.lock()</span><br><span class="line"> _thread = <span class="literal">nil</span></span><br><span class="line"> _value = condition</span><br><span class="line"> _cond.broadcast()</span><br><span class="line"> _cond.unlock()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">lock</span><span class="params">(before limit: Date)</span></span> -> <span class="type">Bool</span> {</span><br><span class="line"> _cond.lock()</span><br><span class="line"> <span class="keyword">while</span> _thread != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">if</span> !_cond.wait(until: limit) {</span><br><span class="line"> _cond.unlock()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> _thread = pthread_self()</span><br><span class="line"> _cond.unlock()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">lock</span><span class="params">(whenCondition condition: Int, before limit: Date)</span></span> -> <span class="type">Bool</span> {</span><br><span class="line"> _cond.lock()</span><br><span class="line"> <span class="keyword">while</span> _thread != <span class="literal">nil</span> || _value != condition {</span><br><span class="line"> <span class="keyword">if</span> !_cond.wait(until: limit) {</span><br><span class="line"> _cond.unlock()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> _thread = pthread_self()</span><br><span class="line"> _cond.unlock()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看出它使用了一个 NSCondition 全局变量来实现 lock 和 unlock 方法,都是一些简单的代码逻辑,就不详细说了。</p>
<p>使用 NSConditionLock 注意:</p>
<ul>
<li>初始化 NSConditionLock 会设置一个 condition,只有满足这个 condition 才能加锁。</li>
<li><code>-[unlockWithCondition:]</code> <strong>并不是满足条件时解锁,而是解锁后,修改 condition 值</strong>。</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_ENUM</span>(<span class="built_in">NSInteger</span>, CTLockCondition) {</span><br><span class="line"> CTLockConditionNone = <span class="number">0</span>,</span><br><span class="line"> CTLockConditionPlay,</span><br><span class="line"> CTLockConditionShow</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)testConditionLock {</span><br><span class="line"> <span class="built_in">NSConditionLock</span> *conditionLock = [[<span class="built_in">NSConditionLock</span> alloc] initWithCondition:CTLockConditionPlay];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// thread one</span></span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> [conditionLock lockWhenCondition:CTLockConditionNone];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread one"</span>);</span><br><span class="line"> sleep(<span class="number">2</span>);</span><br><span class="line"> [conditionLock unlock];</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// thread two</span></span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">if</span> ([conditionLock tryLockWhenCondition:CTLockConditionPlay]) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread two"</span>);</span><br><span class="line"> [conditionLock unlockWithCondition:CTLockConditionShow];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread two unlocked"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread two try lock failed"</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// thread three</span></span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> sleep(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">if</span> ([conditionLock tryLockWhenCondition:CTLockConditionPlay]) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread three"</span>);</span><br><span class="line"> [conditionLock unlock];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread three locked success"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread three try lock failed"</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// thread four</span></span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</span><br><span class="line"> sleep(<span class="number">4</span>);</span><br><span class="line"> <span class="keyword">if</span> ([conditionLock tryLockWhenCondition:CTLockConditionShow]) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread four"</span>);</span><br><span class="line"> [conditionLock unlock];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread four unlocked success"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"thread four try lock failed"</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后看输出结果 :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-05</span> <span class="number">16</span>:<span class="number">34</span>:<span class="number">33.801855</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">97128</span>:<span class="number">3100768</span>] thread two</span><br><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-05</span> <span class="number">16</span>:<span class="number">34</span>:<span class="number">33.802312</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">97128</span>:<span class="number">3100768</span>] thread two unlocked</span><br><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-05</span> <span class="number">16</span>:<span class="number">34</span>:<span class="number">34.804384</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">97128</span>:<span class="number">3100776</span>] thread three try lock failed</span><br><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-05</span> <span class="number">16</span>:<span class="number">34</span>:<span class="number">35.806634</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">97128</span>:<span class="number">3100778</span>] thread four</span><br><span class="line"><span class="number">2018</span><span class="number">-05</span><span class="number">-05</span> <span class="number">16</span>:<span class="number">34</span>:<span class="number">35.806883</span>+<span class="number">0800</span> ConcurrencyTest[<span class="number">97128</span>:<span class="number">3100778</span>] thread four unlocked success</span><br></pre></td></tr></table></figure>
<p>可以看出,thread one 因为条件和初始化不符,加锁失败,未输出 log; thread two 条件相符,解锁成功,并修改加锁条件;thread three 使用原来的加锁条件,显然无法加锁,尝试加锁失败; thread four 使用修改后的条件,加锁成功。</p>
<h4 id="5-NSRecursiveLock"><a href="#5-NSRecursiveLock" class="headerlink" title="5. NSRecursiveLock"></a>5. NSRecursiveLock</h4><p>NSRecursiveLock 属于递归锁。然后这是 <a href="https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSLock.swift" target="_blank" rel="external">swift 源码</a>,只贴一下关键部分:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">open class NSRecursiveLock: NSObject, NSLocking {</span><br><span class="line"> ...</span><br><span class="line"> public override init() {</span><br><span class="line"> super.init()</span><br><span class="line">#if CYGWIN</span><br><span class="line"> var attrib : pthread_mutexattr_t? = nil</span><br><span class="line">#else</span><br><span class="line"> var attrib = pthread_mutexattr_t()</span><br><span class="line">#endif</span><br><span class="line"> withUnsafeMutablePointer(to: &attrib) { attrs in</span><br><span class="line"> pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))</span><br><span class="line"> pthread_mutex_init(mutex, attrs)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>它是使用 <code>PTHREAD_MUTEX_RECURSIVE</code> 类型的 <code>pthread_mutex_t</code> 初始化的。递归所可以在一个线程中重复调用,然后底层会记录加锁和解锁次数,当二者次数相同时,才能正确解锁,释放这块临界区。</p>
<p>使用例子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)testRecursiveLock {</span><br><span class="line"> <span class="built_in">NSRecursiveLock</span> *recursiveLock = [<span class="built_in">NSRecursiveLock</span> new];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> (^__block fibBlock)(<span class="keyword">int</span>) = ^(<span class="keyword">int</span> num) {</span><br><span class="line"> [recursiveLock lock];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (num < <span class="number">0</span>) {</span><br><span class="line"> [recursiveLock unlock];</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (num == <span class="number">1</span> || num == <span class="number">2</span>) {</span><br><span class="line"> [recursiveLock unlock];</span><br><span class="line"> <span class="keyword">return</span> num;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">int</span> newValue = fibBlock(num - <span class="number">1</span>) + fibBlock(num - <span class="number">2</span>);</span><br><span class="line"> [recursiveLock unlock];</span><br><span class="line"> <span class="keyword">return</span> newValue;</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> value = fibBlock(<span class="number">10</span>);</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"value is %d"</span>, value);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="6-synchronized"><a href="#6-synchronized" class="headerlink" title="6. @synchronized"></a>6. @synchronized</h4><p>@synchronized 是牺牲性能来换取语法上的简洁。如果你想深入了解,建议你去读 <a href="http://yulingtianxia.com/blog/2015/11/01/More-than-you-want-to-know-about-synchronized/" target="_blank" rel="external">这篇文章</a>。这里说一下他的大概原理:</p>
<p>@synchronized 的加锁过程,大概是这个样子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@try</span> {</span><br><span class="line"> objc_sync_enter(obj); <span class="comment">// lock</span></span><br><span class="line"> <span class="comment">// 临界区</span></span><br><span class="line">} <span class="keyword">@finally</span> {</span><br><span class="line"> objc_sync_exit(obj); <span class="comment">// unlock</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>@synchronized 的存储结构,是使用哈希表来实现的。当你传入一个对象后,会为这个对象分配一个锁。锁和对象打包成一个对象,然后和一个锁在进行二次打包成一个对象,可以理解为 value;通过一个算法,根据对象的地址得到一个值,作为 key。然后以 key-value 的形式写入哈希表。结构大概是这个样子:</p>
<p><img src="/uploads/concurrency-programming/synchronized结构.png" alt="synchronized结构"></p>
<blockquote>
<p>存储的时候,是以哈希表结构存储,不是我上面画的顺序存储,上面只是一个节点而已。</p>
</blockquote>
<p>@synchronized 的使用就很简单了 :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSMutableArray</span> *elementArray = [<span class="built_in">NSMutableArray</span> array];</span><br><span class="line"> </span><br><span class="line"><span class="keyword">@synchronized</span>(elementArray) {</span><br><span class="line"> [elementArray addObject:[<span class="built_in">NSObject</span> new]];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="Pthreads"><a href="#Pthreads" class="headerlink" title="Pthreads"></a>Pthreads</h3><p>前面也说了,pthreads 是 POSIX Threads 的缩写。这个东西一般我们用不到,这里简单介绍一下。Pthreads 是POSIX的线程标准,定义了创建和操纵线程的一套API。实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-like POSIX 系统,如Linux、 Solaris。</p>
<h3 id="NSThread"><a href="#NSThread" class="headerlink" title="NSThread"></a>NSThread</h3><p><code>NSThread</code> 是对内核 mach kernel 中的 mach thread 的封装,一个 <code>NSThread</code> 对象就是一个线程。使用频率比较低,除了 API 的使用,没什么可讲的。如果你已经熟悉这些 API,可以跳过这一节了。</p>
<h4 id="1-初始化线程执行一个-task"><a href="#1-初始化线程执行一个-task" class="headerlink" title="1.初始化线程执行一个 task"></a>1.初始化线程执行一个 task</h4><p>使用初始化方法初始化一个 <code>NSTherad</code> 对象,调用 <code>-[cancel]</code>、<code>-[start</code>、<code>-[main]</code> 方法对线程进行操作,一般线程执行完即销毁,或者因为某种异常退出。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 使用 target 对象的中的方法作为执行主体,可以通过 argument 传递一些参数。</span><br><span class="line">- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;</span><br><span class="line"></span><br><span class="line">/** 使用 block 对象作为执行主体 */</span></span><br><span class="line">- (instancetype)initWithBlock:(<span class="keyword">void</span> (^)(<span class="keyword">void</span>))block;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 类方法,上面对象方法需要调用 -[start] 方法启动线程,下面两个方法不需要手动启动 */</span></span><br><span class="line">+ (<span class="keyword">void</span>)detachNewThreadWithBlock:(<span class="keyword">void</span> (^)(<span class="keyword">void</span>))block;</span><br><span class="line">+ (<span class="keyword">void</span>)detachNewThreadSelector:(SEL)selector toTarget:(<span class="keyword">id</span>)target withObject:(nullable <span class="keyword">id</span>)argument;</span><br></pre></td></tr></table></figure>
<h4 id="2-在主线程执行一个-task"><a href="#2-在主线程执行一个-task" class="headerlink" title="2.在主线程执行一个 task"></a>2.在主线程执行一个 task</h4><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 说一下最后一个参数,这里你至少指定一个 mode 执行 selector,如果你传 nil 或者空数组,selector 不会执行,虽然方法定义写了 nullable */</span></span><br><span class="line">- (<span class="keyword">void</span>)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable <span class="keyword">id</span>)arg waitUntilDone:(<span class="built_in">BOOL</span>)wait modes:(nullable <span class="built_in">NSArray</span><<span class="built_in">NSString</span> *> *)array;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable <span class="keyword">id</span>)arg waitUntilDone:(<span class="built_in">BOOL</span>)wait;</span><br></pre></td></tr></table></figure>
<h4 id="3-在其他线程执行一个-task"><a href="#3-在其他线程执行一个-task" class="headerlink" title="3.在其他线程执行一个 task"></a>3.在其他线程执行一个 task</h4><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** modes 参数同上一个 */</span></span><br><span class="line">- (<span class="keyword">void</span>)performSelector:(SEL)aSelector onThread:(<span class="built_in">NSThread</span> *)thr withObject:(nullable <span class="keyword">id</span>)arg waitUntilDone:(<span class="built_in">BOOL</span>)wait modes:(nullable <span class="built_in">NSArray</span><<span class="built_in">NSString</span> *> *)array;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)performSelector:(SEL)aSelector onThread:(<span class="built_in">NSThread</span> *)thr withObject:(nullable <span class="keyword">id</span>)arg waitUntilDone:(<span class="built_in">BOOL</span>)wait</span><br></pre></td></tr></table></figure>
<h4 id="4-在后台线程执行一个-task"><a href="#4-在后台线程执行一个-task" class="headerlink" title="4.在后台线程执行一个 task"></a>4.在后台线程执行一个 task</h4><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)performSelectorInBackground:(SEL)aSelector withObject:(nullable <span class="keyword">id</span>)arg;</span><br></pre></td></tr></table></figure>
<h4 id="5-获取当前线程"><a href="#5-获取当前线程" class="headerlink" title="5.获取当前线程"></a>5.获取当前线程</h4><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@property</span> (class, <span class="keyword">readonly</span>, <span class="keyword">strong</span>) <span class="built_in">NSThread</span> *currentThread;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>使用线程相关方法时,记得设置好 name,方便后面调试。同时也设置好优先级等其他参数。</p>
<p>performSelector: 系列方法已经不太安全,慎用。</p>
</blockquote>
<h3 id="Grand-Central-Dispatch-GCD"><a href="#Grand-Central-Dispatch-GCD" class="headerlink" title="Grand Central Dispatch (GCD)"></a>Grand Central Dispatch (GCD)</h3><p>GCD 是基于 C 实现的一套 API,而且是开源的,如果有兴趣,可以在 <a href="https://opensource.apple.com/tarballs/libdispatch/" target="_blank" rel="external">这里</a> down 一份源码研究一下。GCD 是由系统帮我们处理多线程调度,很是方便,也是使用频率最高的。这一章节主要讲解一下 GCD 的原理和使用。</p>
<p>在讲解之前,我们先有个概览,看一下 GCD 为我们提供了那些东西:</p>
<p><img src="/uploads/concurrency-programming/GCD总体结构.png" alt="GCD总体结构"></p>
<p>系统所提供的 API,完全可以满足我们日常开发需求了。下面就根据这些模块分别讲解一下。</p>
<h4 id="1-Dispatch-Queue"><a href="#1-Dispatch-Queue" class="headerlink" title="1. Dispatch Queue"></a>1. Dispatch Queue</h4><p>GCD 为我们提供了两类队列,<strong>串行队列</strong> 和 <strong>并行队列</strong>。两者的区别是:</p>
<ul>
<li>串行队列中,按照 FIFO 的顺序执行任务,前面一个任务执行完,后面一个才开始执行。</li>
<li>并行队列中,也是按照 FIFO 的顺序执行任务,只要前一个被拿去执行,继而后面一个就开始执行,后面的任务无需等到前面的任务执行完再开始执行。</li>
</ul>
<p>除此之外,还要解释一个容易混淆的概念,<strong>并发</strong>和<strong>并行</strong>:</p>
<ul>
<li>并发:是指单独部分可以同时执行,但是需要系统决定怎样发生。</li>
<li>并行:两个任务互不干扰,同时执行。单核设备,系统需要通过切换上下文来实现并发;多核设备,系统可以通过并行来执行并发任务。</li>
</ul>
<p>最后,还有一个概念,<strong>同步</strong>和<strong>异步</strong>:</p>
<ul>
<li>同步 : 同步执行的任务会阻塞当前线程。</li>
<li>异步 : 异步执行的任务不会阻塞当前线程。是否开启新的线程,由系统管理。如果当前有空闲的线程,使用当前线程执行这个异步任务;如果没有空闲的线程,而且线程数量没有达到系统最大,则开启新的线程;如果线程数量已经达到系统最大,则需要等待其他线程中任务执行完毕。</li>
</ul>
<h5 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h5><p>我们使用时,一般使用这几个队列:</p>
<ul>
<li><p>主队列 - dispatch_get_main_queue :一个特殊的串行队列。在 GCD 中,方法主队列中的任务都是在主线程执行。当我们更新 UI 时想 dispatch 到主线程,可以使用这个队列。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> - (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{</span><br><span class="line"> <span class="comment">// UI 相关操作</span></span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>全局并行队列 - dispatch_get_global_queue : 系统提供的一个全局并行队列,我们可以通过指定参数,来获取不同优先级的队列。系统提供了四个优先级,所以也可以认为系统为我们提供了四个并行队列,分别为 :</p>
<ul>
<li>DISPATCH_QUEUE_PRIORITY_HIGH</li>
<li>DISPATCH_QUEUE_PRIORITY_DEFAULT</li>
<li>DISPATCH_QUEUE_PRIORITY_LOW</li>
<li><p>DISPATCH_QUEUE_PRIORITY_BACKGROUND</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, <span class="number">0</span>);</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, ^{</span><br><span class="line"> <span class="comment">// 相关操作</span></span><br><span class="line"> });</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>自定义队列 :你可以自己定义串行或者并行队列,来执行一些相关的任务,平时开发中也建议用自定义队列。创建自定义队列时,需要两个参数。一个是队列的名字,方便我们再调试时查找队列使用,命名方式采用的是<strong>反向 DNS 命名规则</strong>;一个是队列类型,传 NULL 或者 DISPATCH_QUEUE_SERIAL 代表串行队列,传 DISPATCH_QUEUE_CONCURRENT 代表并行队列,通常情况下,不要传 NULL,会降低可读性。<br> DISPATCH_QUEUE_SERIAL_INACTIVE 代表串行不活跃队列,DISPATCH_QUEUE_CONCURRENT_INACTIVE 代表并行不活跃队列,在执行 block 任务时,需要被激活。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.dispatch"</span>,DISPATCH_QUEUE_SERIAL);</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li>你可以使用 <code>dispatch_queue_set_specific</code>、<code>dispatch_queue_get_specific</code> 和 <code>dispatch_get_specific</code> 方法,为 queue 设置关联的 key 或者根据 key 找到关联对象等操作。</li>
</ul>
<p>可以说,系统为我们提供了 5 中不同的队列,运行在主线程中的 main queue;3 个不同优先级的 global queue; 一个优先级更低的 background queue。除此之外,开发者可以自定义一些串行和并行队列,这些自定义队列中被调度的所有 block 最终都会被放到系统全局队列和线程池中,后面会讲这部分原理。盗用一张经典图:</p>
<p><img src="/uploads/concurrency-programming/gcd-queues.png" alt="gcd-queues"></p>
<h5 id="同步-VS-异步"><a href="#同步-VS-异步" class="headerlink" title="同步 VS 异步"></a>同步 VS 异步</h5><p>我们大多数情况下,都是使用 <code>dispatch_asyn()</code> 做异步操作,因为程序本来就是顺序执行,很少用到同步操作。有时候我们会把 <code>dispatch_syn()</code> 当做锁来用,以达到保护的作用。</p>
<p>系统维护的是一个队列,根据 FIFO 的规则,将 dispatch 到队列中的任务一一执行。有时候我们想把一些任务延后执行以下,例如 App 启动时,我想让主线程中一个耗时的工作放在后,可以尝试用一下 <code>dispatch_asyn()</code>,相当于把任务重新追加到了队尾。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{</span><br><span class="line"> <span class="comment">// 想要延后的任务</span></span><br><span class="line"> });</span><br></pre></td></tr></table></figure>
<p>通常情况下,我们使用 <code>dispatch_asyn()</code> 是不会造成死锁的。死锁一般出现在使用 <code>dispatch_syn()</code> 的时候。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_sync</span>(dispatch_get_main_queue(), ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"dead lock"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>想上面这样写,启动就会报错误。以下情况也如此:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.dispatch"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"dispatch asyn"</span>);</span><br><span class="line"> <span class="built_in">dispatch_sync</span>(queue, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"dispatch asyn -> dispatch syn"</span>);</span><br><span class="line"> });</span><br><span class="line"> });</span><br></pre></td></tr></table></figure>
<p>在上面的代码中,<code>dispatch_asyn()</code> 整个 block(称作 blcok_asyn) 当做一个任务追加到串行队列队尾,然后开始执行。在 block_asyn 内部中,又进行了 <code>dispatch_syn()</code>,想想要执行 block_syn。因为是串行队列,需要前一个执行完(block_asyn),再执行后面一个(block_syn);但是要执行完 block_asyn,需要执行内部的 block_syn。互相等待,形成死锁。</p>
<p>现实开发中,还有更复杂的死锁场景。不过现在编译器很友好,我们能在编译执行时就检测到了。</p>
<h5 id="基本原理"><a href="#基本原理" class="headerlink" title="基本原理"></a>基本原理</h5><p>针对下面这几行代码,我们分析一下它的底层过程:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.dispatch"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"dispatch asyn test"</span>);</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>创建队列</strong></p>
<p>源码很长,但实际只有一个方法,逻辑比较清晰,如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 开发者调用的方法 */</span></span><br><span class="line"><span class="keyword">dispatch_queue_t</span></span><br><span class="line">dispatch_queue_create(<span class="keyword">const</span> <span class="keyword">char</span> *label, <span class="keyword">dispatch_queue_attr_t</span> attr)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> _dispatch_queue_create_with_target(label, attr,</span><br><span class="line"> DISPATCH_TARGET_QUEUE_DEFAULT, <span class="literal">true</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 内部实际调用方法 */</span></span><br><span class="line">DISPATCH_NOINLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">dispatch_queue_t</span></span><br><span class="line">_dispatch_queue_create_with_target(<span class="keyword">const</span> <span class="keyword">char</span> *label, <span class="keyword">dispatch_queue_attr_t</span> dqa,</span><br><span class="line"> <span class="keyword">dispatch_queue_t</span> tq, <span class="keyword">bool</span> legacy)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 1.初步判断</span></span><br><span class="line"> <span class="keyword">if</span> (!slowpath(dqa)) {</span><br><span class="line"> dqa = _dispatch_get_default_queue_attr();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (dqa->do_vtable != DISPATCH_VTABLE(queue_attr)) {</span><br><span class="line"> DISPATCH_CLIENT_CRASH(dqa->do_vtable, <span class="string">"Invalid queue attribute"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2.配置队列参数</span></span><br><span class="line"> <span class="keyword">dispatch_qos_t</span> qos = _dispatch_priority_qos(dqa->dqa_qos_and_relpri);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> !HAVE_PTHREAD_WORKQUEUE_QOS</span></span><br><span class="line"> <span class="keyword">if</span> (qos == DISPATCH_QOS_USER_INTERACTIVE) {</span><br><span class="line"> qos = DISPATCH_QOS_USER_INITIATED;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (qos == DISPATCH_QOS_MAINTENANCE) {</span><br><span class="line"> qos = DISPATCH_QOS_BACKGROUND;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">// !HAVE_PTHREAD_WORKQUEUE_QOS</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">_dispatch_queue_attr_overcommit_t</span> overcommit = dqa->dqa_overcommit;</span><br><span class="line"> <span class="keyword">if</span> (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {</span><br><span class="line"> <span class="keyword">if</span> (tq->do_targetq) {</span><br><span class="line"> DISPATCH_CLIENT_CRASH(tq, <span class="string">"Cannot specify both overcommit and "</span></span><br><span class="line"> <span class="string">"a non-global target queue"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tq && !tq->do_targetq &&</span><br><span class="line"> tq->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT) {</span><br><span class="line"> <span class="comment">// Handle discrepancies between attr and target queue, attributes win</span></span><br><span class="line"> <span class="keyword">if</span> (overcommit == _dispatch_queue_attr_overcommit_unspecified) {</span><br><span class="line"> <span class="keyword">if</span> (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {</span><br><span class="line"> overcommit = _dispatch_queue_attr_overcommit_enabled;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> overcommit = _dispatch_queue_attr_overcommit_disabled;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (qos == DISPATCH_QOS_UNSPECIFIED) {</span><br><span class="line"> <span class="keyword">dispatch_qos_t</span> tq_qos = _dispatch_priority_qos(tq->dq_priority);</span><br><span class="line"> tq = _dispatch_get_root_queue(tq_qos,</span><br><span class="line"> overcommit == _dispatch_queue_attr_overcommit_enabled);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> tq = <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (tq && !tq->do_targetq) {</span><br><span class="line"> <span class="comment">// target is a pthread or runloop root queue, setting QoS or overcommit</span></span><br><span class="line"> <span class="comment">// is disallowed</span></span><br><span class="line"> <span class="keyword">if</span> (overcommit != _dispatch_queue_attr_overcommit_unspecified) {</span><br><span class="line"> DISPATCH_CLIENT_CRASH(tq, <span class="string">"Cannot specify an overcommit attribute "</span></span><br><span class="line"> <span class="string">"and use this kind of target queue"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (qos != DISPATCH_QOS_UNSPECIFIED) {</span><br><span class="line"> DISPATCH_CLIENT_CRASH(tq, <span class="string">"Cannot specify a QoS attribute "</span></span><br><span class="line"> <span class="string">"and use this kind of target queue"</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (overcommit == _dispatch_queue_attr_overcommit_unspecified) {</span><br><span class="line"> <span class="comment">// Serial queues default to overcommit!</span></span><br><span class="line"> overcommit = dqa->dqa_concurrent ?</span><br><span class="line"> _dispatch_queue_attr_overcommit_disabled :</span><br><span class="line"> _dispatch_queue_attr_overcommit_enabled;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!tq) {</span><br><span class="line"> tq = _dispatch_get_root_queue(</span><br><span class="line"> qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,</span><br><span class="line"> overcommit == _dispatch_queue_attr_overcommit_enabled);</span><br><span class="line"> <span class="keyword">if</span> (slowpath(!tq)) {</span><br><span class="line"> DISPATCH_CLIENT_CRASH(qos, <span class="string">"Invalid queue attribute"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 初始化队列</span></span><br><span class="line"> <span class="keyword">if</span> (legacy) {</span><br><span class="line"> <span class="comment">// if any of these attributes is specified, use non legacy classes</span></span><br><span class="line"> <span class="keyword">if</span> (dqa->dqa_inactive || dqa->dqa_autorelease_frequency) {</span><br><span class="line"> legacy = <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">void</span> *vtable;</span><br><span class="line"> <span class="keyword">dispatch_queue_flags_t</span> dqf = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (legacy) {</span><br><span class="line"> vtable = DISPATCH_VTABLE(<span class="built_in">queue</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (dqa->dqa_concurrent) {</span><br><span class="line"> vtable = DISPATCH_VTABLE(queue_concurrent);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> vtable = DISPATCH_VTABLE(queue_serial);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">switch</span> (dqa->dqa_autorelease_frequency) {</span><br><span class="line"> <span class="keyword">case</span> DISPATCH_AUTORELEASE_FREQUENCY_NEVER:</span><br><span class="line"> dqf |= DQF_AUTORELEASE_NEVER;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:</span><br><span class="line"> dqf |= DQF_AUTORELEASE_ALWAYS;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (legacy) {</span><br><span class="line"> dqf |= DQF_LEGACY;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (label) {</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">char</span> *tmp = _dispatch_strdup_if_mutable(label);</span><br><span class="line"> <span class="keyword">if</span> (tmp != label) {</span><br><span class="line"> dqf |= DQF_LABEL_NEEDS_FREE;</span><br><span class="line"> label = tmp;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">dispatch_queue_t</span> dq = _dispatch_object_alloc(vtable,</span><br><span class="line"> <span class="keyword">sizeof</span>(<span class="keyword">struct</span> dispatch_queue_s) - DISPATCH_QUEUE_CACHELINE_PAD);</span><br><span class="line"> _dispatch_queue_init(dq, dqf, dqa->dqa_concurrent ?</span><br><span class="line"> DISPATCH_QUEUE_WIDTH_MAX : <span class="number">1</span>, DISPATCH_QUEUE_ROLE_INNER |</span><br><span class="line"> (dqa->dqa_inactive ? DISPATCH_QUEUE_INACTIVE : <span class="number">0</span>));</span><br><span class="line"></span><br><span class="line"> dq->dq_label = label;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> HAVE_PTHREAD_WORKQUEUE_QOS</span></span><br><span class="line"> dq->dq_priority = dqa->dqa_qos_and_relpri;</span><br><span class="line"> <span class="keyword">if</span> (overcommit == _dispatch_queue_attr_overcommit_enabled) {</span><br><span class="line"> dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> _dispatch_retain(tq);</span><br><span class="line"> <span class="keyword">if</span> (qos == QOS_CLASS_UNSPECIFIED) {</span><br><span class="line"> <span class="comment">// legacy way of inherithing the QoS from the target</span></span><br><span class="line"> _dispatch_queue_priority_inherit_from_target(dq, tq);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!dqa->dqa_inactive) {</span><br><span class="line"> _dispatch_queue_inherit_wlh_from_target(dq, tq);</span><br><span class="line"> }</span><br><span class="line"> dq->do_targetq = tq;</span><br><span class="line"> _dispatch_object_debug(dq, <span class="string">"%s"</span>, __func__);</span><br><span class="line"> <span class="keyword">return</span> _dispatch_introspection_queue_create(dq);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>根据代码生成的流程图,不想看代码直接看图,下同:</p>
<p><img src="/uploads/concurrency-programming/Create_Queue.png" alt="Create_Queue"></p>
<p>根据流程图,这个方法的步骤如下:</p>
<ul>
<li>开发者调用 <code>dispatch_queue_create()</code> 方法之后,内部会调用 <code>_dispatch_queue_create_with_target()</code> 方法。</li>
<li>然后进行初步判断,多数情况下,我们是不会传队列类型的,都是穿 NULL,所以这里是个 slowpath。如果传了参数,但是不是规定的队列类型,系统会认为你是个智障,并抛出错误。</li>
<li>然后初始化一些配置项。主要是 target_queue,overcommit 项和 qos。target_queue 是依赖的目标队列,像任何队列提交的任务(block),最终都会放到目标队列中执行;支持 overcommit 时,每当想队列提交一个任务时,都会开一个新的线程处理,这样是为了避免单一线程任务太多而过载;qos 是队列优先级,之前已经说过。</li>
<li>然后进入判断分支。普通的串行队列的目标队列,就是一个支持 overcommit 的全局队列(对应 else 分支);当前 tq 对象的引用计数为 DISPATCH_OBJECT_GLOBAL_REFCNT (永远不会释放)时,且还没有目标队列时,才可以设置 overcommit 项,而且当优先级为 DISPATCH_QOS_UNSPECIFIED 时,需要重置 tq (对应 if 分支);其他情况(else if 分支)。</li>
<li>然后配置队列的标识,以方便在调试时找到自己的那个队列。</li>
<li>使用 <code>_dispatch_object_alloc</code> 方法申请一个 dispatch_queue_t 对象空间,dq。</li>
<li>根据传入的信息(并行 or 串行;活跃 or 非活跃)来初始化这个队列。并行队列的 width 会设置为 <code>DISPATCH_QUEUE_WIDTH_MAX</code> 即最大,不设限;串行的会设置为 1。</li>
<li>将上面获得配置项,目标队列,是否支持 overcommit,优先级和 dq 绑定。</li>
<li>返回这个队列。返回去还输出了一句信息,便于调试。</li>
</ul>
<p><strong>异步执行</strong></p>
<p>这个版本异步执行的代码,因为方法拆分很多,所以显得很乱。源码如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 开发者调用 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_async</span><span class="params">(dispatch_queue_t dq, dispatch_block_t work)</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">dispatch_continuation_t</span> dc = _dispatch_continuation_alloc();</span><br><span class="line"> <span class="keyword">uintptr_t</span> dc_flags = DISPATCH_OBJ_CONSUME_BIT;</span><br><span class="line"></span><br><span class="line"> _dispatch_continuation_init(dc, dq, work, <span class="number">0</span>, <span class="number">0</span>, dc_flags);</span><br><span class="line"> _dispatch_continuation_async(dq, dc);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 内部调用,包一层,再深入调用 */</span></span><br><span class="line">DISPATCH_NOINLINE</span><br><span class="line"><span class="keyword">void</span></span><br><span class="line">_dispatch_continuation_async(<span class="keyword">dispatch_queue_t</span> dq, <span class="keyword">dispatch_continuation_t</span> dc)</span><br><span class="line">{</span><br><span class="line"> _dispatch_continuation_async2(dq, dc,</span><br><span class="line"> dc->dc_flags & DISPATCH_OBJ_BARRIER_BIT);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 根据 barrier 关键字区别串行还是并行,分两支 */</span></span><br><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span></span><br><span class="line">_dispatch_continuation_async2(<span class="keyword">dispatch_queue_t</span> dq, <span class="keyword">dispatch_continuation_t</span> dc,</span><br><span class="line"> <span class="keyword">bool</span> barrier)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (fastpath(barrier || !DISPATCH_QUEUE_USES_REDIRECTION(dq->dq_width))) {</span><br><span class="line"> <span class="comment">// 串行</span></span><br><span class="line"> <span class="keyword">return</span> _dispatch_continuation_push(dq, dc);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 并行</span></span><br><span class="line"> <span class="keyword">return</span> _dispatch_async_f2(dq, dc);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 并行又多了一层调用,就是这个方法 */</span></span><br><span class="line">DISPATCH_NOINLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span></span><br><span class="line">_dispatch_async_f2(<span class="keyword">dispatch_queue_t</span> dq, <span class="keyword">dispatch_continuation_t</span> dc)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (slowpath(dq->dq_items_tail)) {<span class="comment">// 少路径</span></span><br><span class="line"> <span class="keyword">return</span> _dispatch_continuation_push(dq, dc);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (slowpath(!_dispatch_queue_try_acquire_async(dq))) {<span class="comment">// 少路径</span></span><br><span class="line"> <span class="keyword">return</span> _dispatch_continuation_push(dq, dc);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 多路径</span></span><br><span class="line"> <span class="keyword">return</span> _dispatch_async_f_redirect(dq, dc,</span><br><span class="line"> _dispatch_continuation_override_qos(dq, dc));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 主要用来重定向 */</span></span><br><span class="line">DISPATCH_NOINLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span></span><br><span class="line">_dispatch_async_f_redirect(<span class="keyword">dispatch_queue_t</span> dq,</span><br><span class="line"> <span class="keyword">dispatch_object_t</span> dou, <span class="keyword">dispatch_qos_t</span> qos)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (!slowpath(_dispatch_object_is_redirection(dou))) {</span><br><span class="line"> dou._dc = _dispatch_async_redirect_wrap(dq, dou);</span><br><span class="line"> }</span><br><span class="line"> dq = dq->do_targetq;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Find the queue to redirect to</span></span><br><span class="line"> <span class="keyword">while</span> (slowpath(DISPATCH_QUEUE_USES_REDIRECTION(dq->dq_width))) {</span><br><span class="line"> <span class="keyword">if</span> (!fastpath(_dispatch_queue_try_acquire_async(dq))) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!dou._dc->dc_ctxt) {</span><br><span class="line"> dou._dc->dc_ctxt = (<span class="keyword">void</span> *)</span><br><span class="line"> (<span class="keyword">uintptr_t</span>)_dispatch_queue_autorelease_frequency(dq);</span><br><span class="line"> }</span><br><span class="line"> dq = dq->do_targetq;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 同步异步最终都是调用的这个方法,将任务追加到队列中</span></span><br><span class="line"> dx_push(dq, dou, qos);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">... 省略一些调用层级,</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 核心方法,通过 dc_flags 参数区分了是 group,还是串行,还是并行 */</span></span><br><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span></span><br><span class="line">_dispatch_continuation_invoke_inline(<span class="keyword">dispatch_object_t</span> dou, <span class="keyword">voucher_t</span> ov,</span><br><span class="line"> <span class="keyword">dispatch_invoke_flags_t</span> flags)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">dispatch_continuation_t</span> dc = dou._dc, dc1;</span><br><span class="line"> dispatch_invoke_with_autoreleasepool(flags, {</span><br><span class="line"> <span class="keyword">uintptr_t</span> dc_flags = dc->dc_flags;</span><br><span class="line"> _dispatch_continuation_voucher_adopt(dc, ov, dc_flags);</span><br><span class="line"> <span class="keyword">if</span> (dc_flags & DISPATCH_OBJ_CONSUME_BIT) { <span class="comment">// 并行</span></span><br><span class="line"> dc1 = _dispatch_continuation_free_cacheonly(dc);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> dc1 = <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (unlikely(dc_flags & DISPATCH_OBJ_GROUP_BIT)) { <span class="comment">// group</span></span><br><span class="line"> _dispatch_continuation_with_group_invoke(dc);</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// 串行</span></span><br><span class="line"> _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);</span><br><span class="line"> _dispatch_introspection_queue_item_complete(dou);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (unlikely(dc1)) {</span><br><span class="line"> _dispatch_continuation_free_to_cache_limit(dc1);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> _dispatch_perfmon_workitem_inc();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>不想看代码,直接看图:</p>
<p><img src="/uploads/concurrency-programming/Dispatch_Asyn.png" alt="Dispatch_Asyn"></p>
<p>根据流程图描述一下过程:</p>
<ul>
<li>首先开发者调用 <code>dispatch_async()</code> 方法,然后内部创建了一个 <code>_dispatch_continuation_init</code> 队列,将 queue、block 这些信息和这个 dc 绑定起来。这过程中 copy 了 block。</li>
<li>然后经过了几个层次的调用,主要为了区分并行还是串行。</li>
<li>如果是串行(这种情况比较常见,所以是 fastpath),直接就 dx_push 了,其实就是讲任务追加到一个链表里面。</li>
<li>如果是并行,需要做重定向。之前我们说过,放到队列中的任务,最终都会以各种形式追加到目标队列里面。在 <code>_dispatch_async_f_redirect</code> 方法中,重新寻找依赖目标队列,然后追加过去。</li>
<li>经过一系列调用,我们会在 <code>_dispatch_continuation_invoke_inline</code> 方法里区分串行还是并行。因为这个方法会被频繁调用,所以定义成了内联函数。对于串行队列,我们使用信号量控制,执行前信号量置为 wait,执行完毕后发送 singal;对于调度组,我们会在执行完之后调用 <code>dispatch_group_leave</code>。</li>
<li>底层的线程池,是使用 pthread 维护的,所以最终都会使用 pthread 来处理这些任务。</li>
</ul>
<p><strong>同步执行</strong></p>
<p>同步执行,相对来说比较简单,源码如下 :</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 开发者调用 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_sync</span><span class="params">(dispatch_queue_t dq, dispatch_block_t work)</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">if</span> (unlikely(_dispatch_block_has_private_data(work))) {</span><br><span class="line"> <span class="keyword">return</span> _dispatch_sync_block_with_private_data(dq, work, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> dispatch_sync_f(dq, work, _dispatch_Block_invoke(work));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 内部调用 */</span></span><br><span class="line"><span class="function">DISPATCH_NOINLINE</span><br><span class="line"><span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_sync_f</span><span class="params">(dispatch_queue_t dq, <span class="keyword">void</span> *ctxt, dispatch_function_t func)</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">if</span> (likely(dq->dq_width == <span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">return</span> dispatch_barrier_sync_f(dq, ctxt, func);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Global concurrent queues and queues bound to non-dispatch threads</span></span><br><span class="line"> <span class="comment">// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE</span></span><br><span class="line"> <span class="keyword">if</span> (unlikely(!_dispatch_queue_try_reserve_sync_width(dq))) {</span><br><span class="line"> <span class="keyword">return</span> _dispatch_sync_f_slow(dq, ctxt, func, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> _dispatch_introspection_sync_begin(dq);</span><br><span class="line"> <span class="keyword">if</span> (unlikely(dq->do_targetq->do_targetq)) {</span><br><span class="line"> <span class="keyword">return</span> _dispatch_sync_recurse(dq, ctxt, func, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> _dispatch_sync_invoke_and_complete(dq, ctxt, func);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>同步执行,相对来说简单些,大体逻辑差不多。偷懒一下,就不画图了,直接描述:</p>
<ul>
<li>开发者使用 <code>dispatch_sync()</code> 方法,大多数路径,都会调用 <code>dispatch_sync_f()</code> 方法。</li>
<li>如果是串行队列,则通过 <code>dispatch_barrier_sync_f()</code> 方法来保证原子操作。</li>
<li>如果不是串行的(一般很少),我们使用 <code>_dispatch_introspection_sync_begin</code> 和 <code>_dispatch_sync_invoke_and_complete</code> 来保证同步。</li>
</ul>
<h5 id="dispatch-after"><a href="#dispatch-after" class="headerlink" title="dispatch_after"></a>dispatch_after</h5><p>dispatch_after 一般用于延后执行一些任务,可以用来代替 NSTimer,因为有时候 NSTimer 问题太多了。在后面的一章里,我会总体讲一下多线程中的问题,这里就不详细说了。一般我们这样来使用 dispatch_after :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.dispatch"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<span class="built_in">NSEC_PER_SEC</span> * <span class="number">2.0</span>f)),queue, ^{</span><br><span class="line"> <span class="comment">// 2.0 second execute</span></span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在做页面过渡时,刚进入到新的页面我们并不会立即更新一些 view,为了引起用户注意,我们会过会儿再进行更新,可以中此 API 来完成。</p>
<p>源码如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span></span><br><span class="line">_dispatch_after(<span class="keyword">dispatch_time_t</span> when, <span class="keyword">dispatch_queue_t</span> <span class="built_in">queue</span>,</span><br><span class="line"> <span class="keyword">void</span> *ctxt, <span class="keyword">void</span> *handler, <span class="keyword">bool</span> block)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">dispatch_timer_source_refs_t</span> dt;</span><br><span class="line"> <span class="keyword">dispatch_source_t</span> ds;</span><br><span class="line"> <span class="keyword">uint64_t</span> leeway, delta;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (when == DISPATCH_TIME_FOREVER) {</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> DISPATCH_DEBUG</span></span><br><span class="line"> DISPATCH_CLIENT_CRASH(<span class="number">0</span>, <span class="string">"dispatch_after called with 'when' == infinity"</span>);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> delta = _dispatch_timeout(when);</span><br><span class="line"> <span class="keyword">if</span> (delta == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (block) {</span><br><span class="line"> <span class="keyword">return</span> dispatch_async(<span class="built_in">queue</span>, handler);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> dispatch_async_f(<span class="built_in">queue</span>, ctxt, handler);</span><br><span class="line"> }</span><br><span class="line"> leeway = delta / <span class="number">10</span>; <span class="comment">// <rdar://problem/13447496></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (leeway < NSEC_PER_MSEC) leeway = NSEC_PER_MSEC;</span><br><span class="line"> <span class="keyword">if</span> (leeway > <span class="number">60</span> * NSEC_PER_SEC) leeway = <span class="number">60</span> * NSEC_PER_SEC;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// this function can and should be optimized to not use a dispatch source</span></span><br><span class="line"> ds = dispatch_source_create(&_dispatch_source_type_after, <span class="number">0</span>, <span class="number">0</span>, <span class="built_in">queue</span>);</span><br><span class="line"> dt = ds->ds_timer_refs;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">dispatch_continuation_t</span> dc = _dispatch_continuation_alloc();</span><br><span class="line"> <span class="keyword">if</span> (block) {</span><br><span class="line"> _dispatch_continuation_init(dc, ds, handler, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> _dispatch_continuation_init_f(dc, ds, ctxt, handler, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// reference `ds` so that it doesn't show up as a leak</span></span><br><span class="line"> dc->dc_data = ds;</span><br><span class="line"> _dispatch_trace_continuation_push(ds->_as_dq, dc);</span><br><span class="line"> os_atomic_store2o(dt, ds_handler[DS_EVENT_HANDLER], dc, relaxed);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ((<span class="keyword">int64_t</span>)when < <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// wall clock</span></span><br><span class="line"> when = (<span class="keyword">dispatch_time_t</span>)-((<span class="keyword">int64_t</span>)when);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// absolute clock</span></span><br><span class="line"> dt->du_fflags |= DISPATCH_TIMER_CLOCK_MACH;</span><br><span class="line"> leeway = _dispatch_time_nano2mach(leeway);</span><br><span class="line"> }</span><br><span class="line"> dt->dt_timer.target = when;</span><br><span class="line"> dt->dt_timer.interval = UINT64_MAX;</span><br><span class="line"> dt->dt_timer.deadline = when + leeway;</span><br><span class="line"> dispatch_activate(ds);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>dispatch_after()</code> 内部会调用 <code>_dispatch_after()</code> 方法,然后先判断延迟时间。如果为 <code>DISPATCH_TIME_FOREVER</code>(永远不执行),则会出现异常;如果为 0 则立即执行;否则的话会创建一个 dispatch_timer_source_refs_t 结构体指针,将上下文相关信息与之关联。然后使用 dispatch_source 相关方法,将定时器和 block 任务关联起来。定时器时间到时,取出 block 任务开始执行。</p>
<h5 id="dispatch-once"><a href="#dispatch-once" class="headerlink" title="dispatch_once"></a>dispatch_once</h5><p>如果我们有一段代码,在 App 生命周期内最好只初始化一次,这时候使用 dispatch_once 最好不过了。例如我们单例中经常这样用:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">+ (instancetype)sharedManager {</span><br><span class="line"> <span class="keyword">static</span> BLDispatchManager *sharedInstance = <span class="literal">nil</span>;</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line"> <span class="built_in">dispatch_once</span>(&onceToken, ^{</span><br><span class="line"> sharedInstance = [[BLDispatchManager alloc] initPrivate];</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> sharedInstance;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>还有在定义 <code>NSDateFormatter</code> 时使用:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">NSString</span> *)todayDateString {</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">NSDateFormatter</span> *formatter = <span class="literal">nil</span>;</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line"> <span class="built_in">dispatch_once</span>(&onceToken, ^{</span><br><span class="line"> formatter = [<span class="built_in">NSDateFormatter</span> new];</span><br><span class="line"> formatter.locale = [<span class="built_in">NSLocale</span> localeWithLocaleIdentifier:<span class="string">@"en_US_POSIX"</span>];</span><br><span class="line"> formatter.timeZone = [<span class="built_in">NSTimeZone</span> timeZoneForSecondsFromGMT:<span class="number">8</span> * <span class="number">3600</span>];</span><br><span class="line"> formatter.dateFormat = <span class="string">@"yyyyMMdd"</span>;</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> [formatter stringFromDate:[<span class="built_in">NSDate</span> date]];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>因为这是很常用的一个代码片段,所以被加在了 Xcode 的 <a href="https://help.apple.com/xcode/mac/current/#/dev2b24f6f93" target="_blank" rel="external">code snippet</a> 中。</p>
<p>它的源代码如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 一个结构体,里面为当前的信号量、线程端口和指向下一个节点的指针 */</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> _dispatch_once_waiter_s {</span><br><span class="line"> <span class="keyword">volatile</span> <span class="keyword">struct</span> _dispatch_once_waiter_s *<span class="keyword">volatile</span> dow_next;</span><br><span class="line"> dispatch_thread_event_s dow_event;</span><br><span class="line"> <span class="keyword">mach_port_t</span> dow_thread;</span><br><span class="line">} *<span class="keyword">_dispatch_once_waiter_t</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 我们调用的方法 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_once</span><span class="params">(dispatch_once_t *val, dispatch_block_t block)</span></span><br><span class="line"></span>{</span><br><span class="line"> dispatch_once_f(val, block, _dispatch_Block_invoke(block));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 实际执行的方法 */</span></span><br><span class="line"><span class="function">DISPATCH_NOINLINE</span><br><span class="line"><span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_once_f</span><span class="params">(dispatch_once_t *val, <span class="keyword">void</span> *ctxt, dispatch_function_t func)</span></span><br><span class="line"></span>{</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> !DISPATCH_ONCE_INLINE_FASTPATH</span></span><br><span class="line"> <span class="keyword">if</span> (likely(os_atomic_load(val, acquire) == DLOCK_ONCE_DONE)) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">// !DISPATCH_ONCE_INLINE_FASTPATH</span></span></span><br><span class="line"> <span class="keyword">return</span> dispatch_once_f_slow(val, ctxt, func);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">DISPATCH_ONCE_SLOW_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_once_f_slow</span><span class="params">(dispatch_once_t *val, <span class="keyword">void</span> *ctxt, dispatch_function_t func)</span></span><br><span class="line"></span>{</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> DISPATCH_GATE_USE_FOR_DISPATCH_ONCE</span></span><br><span class="line"> <span class="keyword">dispatch_once_gate_t</span> l = (<span class="keyword">dispatch_once_gate_t</span>)val;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (_dispatch_once_gate_tryenter(l)) {</span><br><span class="line"> _dispatch_client_callout(ctxt, func);</span><br><span class="line"> _dispatch_once_gate_broadcast(l);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> _dispatch_once_gate_wait(l);</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"> <span class="keyword">_dispatch_once_waiter_t</span> <span class="keyword">volatile</span> *vval = (<span class="keyword">_dispatch_once_waiter_t</span>*)val;</span><br><span class="line"> <span class="keyword">struct</span> _dispatch_once_waiter_s dow = { };</span><br><span class="line"> <span class="keyword">_dispatch_once_waiter_t</span> tail = &dow, next, tmp;</span><br><span class="line"> <span class="keyword">dispatch_thread_event_t</span> event;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (os_atomic_cmpxchg(vval, <span class="literal">NULL</span>, tail, acquire)) {</span><br><span class="line"> dow.dow_thread = _dispatch_tid_self();</span><br><span class="line"> _dispatch_client_callout(ctxt, func);</span><br><span class="line"></span><br><span class="line"> next = (<span class="keyword">_dispatch_once_waiter_t</span>)_dispatch_once_xchg_done(val);</span><br><span class="line"> <span class="keyword">while</span> (next != tail) {</span><br><span class="line"> tmp = (<span class="keyword">_dispatch_once_waiter_t</span>)_dispatch_wait_until(next->dow_next);</span><br><span class="line"> event = &next->dow_event;</span><br><span class="line"> next = tmp;</span><br><span class="line"> _dispatch_thread_event_signal(event);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> _dispatch_thread_event_init(&dow.dow_event);</span><br><span class="line"> next = *vval;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">if</span> (next == DISPATCH_ONCE_DONE) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (os_atomic_cmpxchgv(vval, next, tail, &next, release)) {</span><br><span class="line"> dow.dow_thread = next->dow_thread;</span><br><span class="line"> dow.dow_next = next;</span><br><span class="line"> <span class="keyword">if</span> (dow.dow_thread) {</span><br><span class="line"> <span class="keyword">pthread_priority_t</span> pp = _dispatch_get_priority();</span><br><span class="line"> _dispatch_thread_override_start(dow.dow_thread, pp, val);</span><br><span class="line"> }</span><br><span class="line"> _dispatch_thread_event_wait(&dow.dow_event);</span><br><span class="line"> <span class="keyword">if</span> (dow.dow_thread) {</span><br><span class="line"> _dispatch_thread_override_end(dow.dow_thread, val);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> _dispatch_thread_event_destroy(&dow.dow_event);</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>不想看代码直接看图 (emmm… 根据逻辑画完图才发现,其实这个图也挺乱的,所以我将两个主分支用不同颜色标记处理):</p>
<p><img src="/uploads/concurrency-programming/Dispatch_Once.png" alt="Dispatch_Once"></p>
<p>根据这个图,我来表述一下主要过程:</p>
<ul>
<li>我们调用 <code>dispatch_once()</code> 方法之后,内部多数情况下会调用 <code>dispatch_once_f_slow()</code> 方法,这个方法才是真正的执行方法。</li>
<li><p><code>os_atomic_cmpxchg(vval, NULL, tail, acquire)</code> 这个方法,执行过程实际是这个样子</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (*vval == <span class="literal">NULL</span>) {</span><br><span class="line"> *vval = tail = &dow;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<pre><code>我们初始化的 once_token,也就是 *vval 实际是 0,所以第一次执行时是返回 true 的。if() 中的这个方法是原子操作,也就是说,如果多个线程同时调用这个方法,只有一个线程会进入 true 的分支,其他都进入 else 分支。
</code></pre><ul>
<li><p>这里先说进入 true 分支。进入之后,会执行对应的 block,也就是对应的任务。然后 next 指向 <em>vval, </em>vval 标记为 <code>DISPATCH_ONCE_DONE</code>,即执行的是这样一个过程:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">next = (<span class="keyword">_dispatch_once_waiter_t</span>)_dispatch_once_xchg_done(val);</span><br><span class="line"><span class="comment">// 实际执行时这样的</span></span><br><span class="line">next = *vval;</span><br><span class="line">*vval = DISPATCH_ONCE_DONE;</span><br></pre></td></tr></table></figure>
</li>
<li><p>然后 <code>tail = &dow</code>。此时我们发现,原来的 <code>*vval = &dow -> next = *vval</code>,实际则是 <code>next = &dow</code>,<strong>如果没有其他线程(或者调用)进入 else 分支,&dow 实际没有改变,即 <code>tail == tmp</code></strong>。此时 <code>while (tail != tmp)</code> 是不会执行的,分支结束。</p>
</li>
<li>如果有其他线程(或者调用)进入了 else 分支,那么就已经生成了一个等待响应的链表。此时进入 &dow 已经改变,成为了链表尾部,*vval 是链表头部。进入 while 循环后,开始遍历链表,依次发送信号进行唤起。</li>
<li>然后说进入 else 分支的这些调用。进入分支后,随即进入一个死循环,直到发现 *vval 已经标记为了 <code>DISPATCH_ONCE_DONE</code> 才跳出循环。</li>
<li>发现 *vval 不是 <code>DISPATCH_ONCE_DONE</code> 之后,会将这个节点追加到链表尾部,并调用信号量的 wait 方法,等待被唤起。</li>
</ul>
<p>以上为全部的执行过程。通过源码可以看出,使用的是 原子操作 + 信号量来保证 block 只会被执行多次,哪怕是在多线程情况下。</p>
<p>这样一个关于 <code>dispatch_once</code> 递归调用会产生死锁的现象,也就很好解释了。看下面代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)dispatchOnceTest {</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line"> <span class="built_in">dispatch_once</span>(&onceToken, ^{</span><br><span class="line"> [<span class="keyword">self</span> dispatchOnceTest];</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>通过上面分析,在 block 执行完,并将 *vval 置为 <code>DISPATCH_ONCE_DONE</code> 之前,其他的调用都会进入 else 分支。第二次递归调用,信号量处于等待状态,需要等到第一个 block 执行完才能被唤起;但是第一个 block 所执行的内容就是进行第二次调用,这个任务被 wait 了,也即是说 block 永远执行不完。死锁就这样发生了。</p>
<h5 id="dispatch-apply"><a href="#dispatch-apply" class="headerlink" title="dispatch_apply"></a>dispatch_apply</h5><p>有时候没有时序性依赖的时候,我们会用 <code>dispatch_apply</code> 来代替 <code>for loop</code>。例如我们下载一组图片:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** 使用 for loop */</span></span><br><span class="line">- (<span class="keyword">void</span>)downloadImages:(<span class="built_in">NSArray</span> <<span class="built_in">NSURL</span> *> *)imageURLs {</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">NSURL</span> *imageURL <span class="keyword">in</span> imageURLs) {</span><br><span class="line"> [<span class="keyword">self</span> downloadImageWithURL:imageURL];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** dispatch_apply */</span></span><br><span class="line">- (<span class="keyword">void</span>)downloadImages:(<span class="built_in">NSArray</span> <<span class="built_in">NSURL</span> *> *)imageURLs {</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> downloadQueue = dispatch_queue_create(<span class="string">"com.bool.download"</span>, DISPATCH_QUEUE_CONCURRENT);</span><br><span class="line"> dispatch_apply(imageURLs.count, downloadQueue, ^(size_t index) {</span><br><span class="line"> <span class="built_in">NSURL</span> *imageURL = imageURLs[index];</span><br><span class="line"> [<span class="keyword">self</span> downloadImageWithURL:imageURL];</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>进行替换是需要注意几个问题:</p>
<ul>
<li>任务之间没有时序性依赖,谁先执行都可以。</li>
<li>一般在并发队列,并发执行任务时,才替换。串行队列替换没有意义。</li>
<li>如果数组中数据很少,或者每个任务执行时间很短,替换也没有意义。强行进行并发的消耗,可能比使用 for loop 还要多,并不能得到优化。</li>
</ul>
<p>至于原理,就不大篇幅讲了。大概是这个样子:这个方法是同步的,会阻塞当前线程,直到所有的 block 任务都完成。如果提交到并发队列,每个任务执行顺序是不一定的。</p>
<p>更多时候,我们执行下载任务,并不希望阻塞当前线程,这时我们可以使用 <code>dispatch_group</code>。</p>
<h5 id="dispatch-group"><a href="#dispatch-group" class="headerlink" title="dispatch_group"></a>dispatch_group</h5><p>当处理批量异步任务时,<code>dispatch_group</code> 是一个很好的选择。针对上面说的下载图片的例子,我们可以这样做:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)downloadImages:(<span class="built_in">NSArray</span> <<span class="built_in">NSURL</span> *> *)imageURLs {</span><br><span class="line"> dispatch_group_t taskGroup = dispatch_group_create();</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.group"</span>, DISPATCH_QUEUE_CONCURRENT);</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">NSURL</span> *imageURL <span class="keyword">in</span> imageURLs) {</span><br><span class="line"> dispatch_group_enter(taskGroup);</span><br><span class="line"> <span class="comment">// 下载方法是异步的</span></span><br><span class="line"> [<span class="keyword">self</span> downloadImageWithURL:imageURL withQueue:queue completeHandler:^{</span><br><span class="line"> dispatch_group_leave(taskGroup);</span><br><span class="line"> }];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> dispatch_group_notify(taskGroup, queue, ^{</span><br><span class="line"> <span class="comment">// all task finish</span></span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/** 如果使用这个方法,内部执行异步任务,会立即到 dispatch_group_notify 方法中,因为是异步,系统认为已经执行完了。所以这个方法使用不多。</span><br><span class="line"> */</span></span><br><span class="line"> dispatch_group_async(taskGroup, queue, ^{</span><br><span class="line"> </span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>关于原理方面,和 <code>dispatch_async()</code> 方法类似,前面也提到。这里只说一段代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span></span><br><span class="line">_dispatch_continuation_group_async(dispatch_group_t dg, <span class="built_in">dispatch_queue_t</span> dq,</span><br><span class="line"> dispatch_continuation_t dc)</span><br><span class="line">{</span><br><span class="line"> dispatch_group_enter(dg);</span><br><span class="line"> dc->dc_data = dg;</span><br><span class="line"> _dispatch_continuation_async(dq, dc);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码中,调用了 <code>dispatch_group_enter(dg)</code> 方法进行标记,最终都会和 <code>dispatch_async()</code> 走到同样的方法里 <code>_dispatch_continuation_invoke_inline()</code>。在里面判断类型为 group,执行 task,执行结束后调用 <code>dispatch_group_leave((dispatch_group_t)dou)</code>,和之前的 enter 对应。</p>
<p>以上是 Dispatch Queues 内容的介绍,我们平时使用 GCD 的过程中,60% 都是使用的以上内容。</p>
<h4 id="2-Dispatch-Block"><a href="#2-Dispatch-Block" class="headerlink" title="2. Dispatch Block"></a>2. Dispatch Block</h4><p>在 iOS 8 中,Apple 为我们提供了新的 API,<code>Dispatch Block</code> 相关。虽然之前我们可以向 dispatch 传递 block 参数,作为任务,但是这里和之前的不一样。之前经常说,使用 <code>NSOperation</code> 创建的任务可以 cancel,使用 GCD 不可以。但是在 iOS 8 之后,可以 cancel 任务了。</p>
<h5 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h5><ul>
<li><p>创建一个 block 并执行。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> - (<span class="keyword">void</span>)dispatchBlockTest {</span><br><span class="line"> <span class="comment">// 不指定优先级</span></span><br><span class="line"> dispatch_block_t dsBlock = dispatch_block_create(<span class="number">0</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block"</span>);</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 指定优先级</span></span><br><span class="line"> dispatch_block_t dsQosBlock = dispatch_block_create_with_qos_class(<span class="number">0</span>, QOS_CLASS_USER_INITIATED, <span class="number">-1</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block"</span>);</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), dsBlock);</span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), dsQosBlock);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 直接创建并执行</span></span><br><span class="line"> dispatch_block_perform(<span class="number">0</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block"</span>);</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>阻塞当前任务,等 block 执行完在继续执行。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> - (<span class="keyword">void</span>)dispatchBlockTest {</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.block"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> dispatch_block_t dsBlock = dispatch_block_create(<span class="number">0</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block"</span>);</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, dsBlock);</span><br><span class="line"> <span class="comment">// 等到 block 执行完</span></span><br><span class="line"> dispatch_block_wait(dsBlock, DISPATCH_TIME_FOREVER);</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"block was finished"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>block 执行完后,收到通知,执行其他任务</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> - (<span class="keyword">void</span>)dispatchBlockTest {</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.block"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> dispatch_block_t dsBlock = dispatch_block_create(<span class="number">0</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block"</span>);</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, dsBlock);</span><br><span class="line"> <span class="comment">// block 执行完收到通知</span></span><br><span class="line"> dispatch_block_notify(dsBlock, queue, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"block was finished,do other thing"</span>);</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"execute first"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>对 block 进行 cancel 操作</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"> - (<span class="keyword">void</span>)dispatchBlockTest {</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.block"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> dispatch_block_t dsBlock1 = dispatch_block_create(<span class="number">0</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block1"</span>);</span><br><span class="line"> });</span><br><span class="line"> dispatch_block_t dsBlock2 = dispatch_block_create(<span class="number">0</span>, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test block2"</span>);</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, dsBlock1);</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, dsBlock2);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 第二个 block 将会被 cancel,不执行</span></span><br><span class="line"> dispatch_block_cancel(dsBlock2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h4 id="3-Dispatch-Barriers"><a href="#3-Dispatch-Barriers" class="headerlink" title="3. Dispatch Barriers"></a>3. Dispatch Barriers</h4><p>Dispatch Barriers 可以理解为调度屏障,常用于多线程并发读写操作。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ViewController</span> ()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">dispatch_queue_t</span> imageQueue;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableArray</span> *imageArray;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">self</span>.imageQueue = dispatch_queue_create(<span class="string">"com.bool.image"</span>, DISPATCH_QUEUE_CONCURRENT);</span><br><span class="line"> <span class="keyword">self</span>.imageArray = [<span class="built_in">NSMutableArray</span> array];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 保证写入时不会有其他操作,写完之后到主线程更新 UI */</span></span><br><span class="line">- (<span class="keyword">void</span>)addImage:(<span class="built_in">UIImage</span> *)image {</span><br><span class="line"> dispatch_barrier_async(<span class="keyword">self</span>.imageQueue, ^{</span><br><span class="line"> [<span class="keyword">self</span>.imageArray addObject:image];</span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{</span><br><span class="line"> <span class="comment">// update UI</span></span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 这里的 dispatch_sync 起到了 lock 的作用 */</span></span><br><span class="line">- (<span class="built_in">NSArray</span> <<span class="built_in">UIImage</span> *> *)images {</span><br><span class="line"> __block <span class="built_in">NSArray</span> *imagesArray = <span class="literal">nil</span>;</span><br><span class="line"> <span class="built_in">dispatch_sync</span>(<span class="keyword">self</span>.imageQueue, ^{</span><br><span class="line"> imagesArray = [<span class="keyword">self</span>.imageArray mutableCopy];</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> imagesArray;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>转化成图可能好理解一些:</p>
<p><img src="/uploads/concurrency-programming/Dispatch_Barrier.png" alt="Dispatch_Barrier"></p>
<p><code>dispatch_barrier_async()</code> 的原理和 <code>dispatch_async()</code> 差不多,只不过设置的 flags 不一样:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span></span><br><span class="line">dispatch_barrier_async(<span class="built_in">dispatch_queue_t</span> dq, dispatch_block_t work)</span><br><span class="line">{</span><br><span class="line"> dispatch_continuation_t dc = _dispatch_continuation_alloc();</span><br><span class="line"> <span class="comment">// 在 dispatch_async() 中只设置了 DISPATCH_OBJ_CONSUME_BIT</span></span><br><span class="line"> uintptr_t dc_flags = DISPATCH_OBJ_CO<span class="built_in">NSUME_BIT</span> | DISPATCH_OBJ_BARRIER_BIT;</span><br><span class="line"></span><br><span class="line"> _dispatch_continuation_init(dc, dq, work, <span class="number">0</span>, <span class="number">0</span>, dc_flags);</span><br><span class="line"> _dispatch_continuation_push(dq, dc);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>后面都是 push 到队列中,然后,获取任务时一个死循环,在从队列中获取任务一个一个执行,如果判断 flag 为 barrier,终止循环,则单独执行这个任务。它后面的任务放入一个队列,等它执行完了再开始执行。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">DISPATCH_ALWAYS_INLINE</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">dispatch_queue_wakeup_target_t</span></span><br><span class="line">_dispatch_queue_drain(<span class="keyword">dispatch_queue_t</span> dq, <span class="keyword">dispatch_invoke_context_t</span> dic,</span><br><span class="line"> <span class="keyword">dispatch_invoke_flags_t</span> flags, <span class="keyword">uint64_t</span> *owned_ptr, <span class="keyword">bool</span> serial_drain)</span><br><span class="line">{</span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> ...</span><br><span class="line">first_iteration:</span><br><span class="line"> dq_state = os_atomic_load(&dq->dq_state, relaxed);</span><br><span class="line"> <span class="keyword">if</span> (unlikely(_dq_state_is_suspended(dq_state))) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (unlikely(orig_tq != dq->do_targetq)) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (serial_drain || _dispatch_object_is_barrier(dc)) {</span><br><span class="line"> <span class="keyword">if</span> (!serial_drain && owned != DISPATCH_QUEUE_IN_BARRIER) {</span><br><span class="line"> <span class="keyword">if</span> (!_dispatch_queue_try_upgrade_full_width(dq, owned)) {</span><br><span class="line"> <span class="keyword">goto</span> out_with_no_width;</span><br><span class="line"> }</span><br><span class="line"> owned = DISPATCH_QUEUE_IN_BARRIER;</span><br><span class="line"> }</span><br><span class="line"> next_dc = _dispatch_queue_next(dq, dc);</span><br><span class="line"> <span class="keyword">if</span> (_dispatch_object_is_sync_waiter(dc)) {</span><br><span class="line"> owned = <span class="number">0</span>;</span><br><span class="line"> dic->dic_deferred = dc;</span><br><span class="line"> <span class="keyword">goto</span> out_with_deferred;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (owned == DISPATCH_QUEUE_IN_BARRIER) {</span><br><span class="line"> <span class="comment">// we just ran barrier work items, we have to make their</span></span><br><span class="line"> <span class="comment">// effect visible to other sync work items on other threads</span></span><br><span class="line"> <span class="comment">// that may start coming in after this point, hence the</span></span><br><span class="line"> <span class="comment">// release barrier</span></span><br><span class="line"> os_atomic_xor2o(dq, dq_state, owned, release);</span><br><span class="line"> owned = dq->dq_width * DISPATCH_QUEUE_WIDTH_INTERVAL;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (unlikely(owned == <span class="number">0</span>)) {</span><br><span class="line"> <span class="keyword">if</span> (_dispatch_object_is_sync_waiter(dc)) {</span><br><span class="line"> <span class="comment">// sync "readers" don't observe the limit</span></span><br><span class="line"> _dispatch_queue_reserve_sync_width(dq);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (!_dispatch_queue_try_acquire_async(dq)) {</span><br><span class="line"> <span class="keyword">goto</span> out_with_no_width;</span><br><span class="line"> }</span><br><span class="line"> owned = DISPATCH_QUEUE_WIDTH_INTERVAL;</span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> next_dc = _dispatch_queue_next(dq, dc);</span><br><span class="line"> <span class="keyword">if</span> (_dispatch_object_is_sync_waiter(dc)) {</span><br><span class="line"> owned -= DISPATCH_QUEUE_WIDTH_INTERVAL;</span><br><span class="line"> _dispatch_sync_waiter_redirect_or_wake(dq,</span><br><span class="line"> DISPATCH_SYNC_WAITER_NO_UNLOCK, dc);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="4-Dispatch-Source"><a href="#4-Dispatch-Source" class="headerlink" title="4. Dispatch Source"></a>4. Dispatch Source</h4><p>关于 <code>dispatch_source</code> 我们使用的少之又少,他是 BSD 系统内核功能的包装,经常用来监测某些事件发生。例如监测断点的使用和取消。[这里][<a href="https://developer.apple.com/documentation/dispatch/dispatch_source_type_constants?language=objc" target="_blank" rel="external">https://developer.apple.com/documentation/dispatch/dispatch_source_type_constants?language=objc</a>] 介绍了可以监测的事件:</p>
<ul>
<li>DISPATCH_SOURCE_TYPE_DATA_ADD : 自定义事件</li>
<li>DISPATCH_SOURCE_TYPE_DATA_OR : 自定义事件</li>
<li>DISPATCH_SOURCE_TYPE_MACH_RECV : MACH 端口接收事件</li>
<li>DISPATCH_SOURCE_TYPE_MACH_SEND : MACH 端口发送事件</li>
<li>DISPATCH_SOURCE_TYPE_PROC : 进程相关事件</li>
<li>DISPATCH_SOURCE_TYPE_READ : 文件读取事件</li>
<li>DISPATCH_SOURCE_TYPE_SIGNAL : 信号相关事件</li>
<li>DISPATCH_SOURCE_TYPE_TIMER : 定时器相关事件 </li>
<li>DISPATCH_SOURCE_TYPE_VNODE : 文件属性修改事件</li>
<li>DISPATCH_SOURCE_TYPE_WRITE : 文件写入事件</li>
<li>DISPATCH_SOURCE_TYPE_MEMORYPRESSURE : 内存压力事件</li>
</ul>
<p>例如我们可以通过下面代码,来监测断点的使用和取消:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ViewController</span> ()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) dispatch_source_t signalSource;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">assign</span>) <span class="built_in">dispatch_once_t</span> signalOnceToken;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> <span class="built_in">dispatch_once</span>(&_signalOnceToken, ^{</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_get_main_queue();</span><br><span class="line"> <span class="keyword">self</span>.signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, <span class="number">0</span>, queue);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>.signalSource) {</span><br><span class="line"> dispatch_source_set_event_handler(<span class="keyword">self</span>.signalSource, ^{</span><br><span class="line"> <span class="comment">// 点击一下断点,再取消断点,便会执行这里。</span></span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"debug test"</span>);</span><br><span class="line"> });</span><br><span class="line"> dispatch_resume(<span class="keyword">self</span>.signalSource);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>还有 <code>diapatch_after()</code> 就是依赖 <code>dispatch_source()</code> 来实现的。我们可以自己实现一个类似的定时器:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)customTimer {</span><br><span class="line"> dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, <span class="number">0</span>, <span class="number">0</span>, DISPATCH_TARGET_QUEUE_DEFAULT);</span><br><span class="line"> dispatch_source_set_timer(timerSource, dispatch_time(DISPATCH_TIME_NOW, <span class="number">5.0</span> * <span class="built_in">NSEC_PER_SEC</span>), <span class="number">2.0</span> * <span class="built_in">NSEC_PER_SEC</span>, <span class="number">5</span>);</span><br><span class="line"> dispatch_source_set_event_handler(timerSource, ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"dispatch source timer"</span>);</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">self</span>.signalSource = timerSource;</span><br><span class="line"> dispatch_resume(<span class="keyword">self</span>.signalSource);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="基本原理-1"><a href="#基本原理-1" class="headerlink" title="基本原理"></a>基本原理</h5><p>使用 <code>dispatch_source</code> 时,大致过程是这样的:我们创建一个 source,然后加到队列中,并调用 <code>dispatch_resume()</code> 方法,便会从队列中唤起 source,执行对应的 block。下面是一个详细的流程图,我们结合这张图来说一下:</p>
<p><img src="/uploads/concurrency-programming/Dispatch_Source.png" alt="Dispatch_Source"></p>
<ul>
<li><p>创建一个 source 对象,过程和创建 queue 类似,所以后面一些操作,和操作 queue 很类似。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">dispatch_source_t</span><br><span class="line">dispatch_source_create(dispatch_source_type_t dst, uintptr_t handle,</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> mask, <span class="built_in">dispatch_queue_t</span> dq)</span><br><span class="line">{</span><br><span class="line"> dispatch_source_refs_t dr;</span><br><span class="line"> dispatch_source_t ds;</span><br><span class="line"></span><br><span class="line"> dr = dux_create(dst, handle, mask)._dr;</span><br><span class="line"> <span class="keyword">if</span> (unlikely(!dr)) {</span><br><span class="line"> <span class="keyword">return</span> DISPATCH_BAD_INPUT;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 申请内存空间</span></span><br><span class="line"> ds = _dispatch_object_alloc(DISPATCH_VTABLE(source),</span><br><span class="line"> <span class="keyword">sizeof</span>(<span class="keyword">struct</span> dispatch_source_s));</span><br><span class="line"> <span class="comment">// 初始化一个队列,然后配置参数,完全被当做一个 queue 来处理</span></span><br><span class="line"> _dispatch_queue_init(ds->_as_dq, DQF_LEGACY, <span class="number">1</span>,</span><br><span class="line"> DISPATCH_QUEUE_INACTIVE | DISPATCH_QUEUE_ROLE_INNER);</span><br><span class="line"> ds->dq_label = <span class="string">"source"</span>;</span><br><span class="line"> ds->do_ref_cnt++; <span class="comment">// the reference the manager queue holds</span></span><br><span class="line"> ds->ds_refs = dr;</span><br><span class="line"> dr->du_owner_wref = _dispatch_ptr2wref(ds);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (slowpath(!dq)) {</span><br><span class="line"> dq = _dispatch_get_root_queue(DISPATCH_QOS_DEFAULT, <span class="literal">true</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> _dispatch_retain((<span class="built_in">dispatch_queue_t</span> _Nonnull)dq);</span><br><span class="line"> }</span><br><span class="line"> ds->do_targetq = dq;</span><br><span class="line"> <span class="keyword">if</span> (dr->du_is_timer && (dr->du_fflags & DISPATCH_TIMER_INTERVAL)) {</span><br><span class="line"> _dispatch_source_set_interval(ds, handle);</span><br><span class="line"> }</span><br><span class="line"> _dispatch_object_debug(ds, <span class="string">"%s"</span>, __func__);</span><br><span class="line"> <span class="keyword">return</span> ds;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>设置 event_handler。从源码中看出,用的是 <code>dispatch_continuation_t</code> 进行绑定,和之前绑定 queue 一样,将 block copy 了一份。后面执行的时候,拿出来用。然后将这个任务 push 到队列里。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">void</span></span><br><span class="line">dispatch_source_set_event_handler(dispatch_source_t ds,</span><br><span class="line"> dispatch_block_t handler)</span><br><span class="line">{</span><br><span class="line"> dispatch_continuation_t dc;</span><br><span class="line"> <span class="comment">// 这里实际就是在初始化 dispatch_continuation_t</span></span><br><span class="line"> dc = _dispatch_source_handler_alloc(ds, handler, DS_EVENT_HANDLER, <span class="literal">true</span>);</span><br><span class="line"> <span class="comment">// 经过一顿操作,将任务 push 到队列中。</span></span><br><span class="line"> _dispatch_source_set_handler(ds, DS_EVENT_HANDLER, dc);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>调用 resume 方法,执行 source。一般新创建的都是暂停状态,这里判断是暂停状态,就开始唤起。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">void</span></span><br><span class="line"><span class="title">dispatch_resume</span><span class="params">(dispatch_object_t dou)</span></span><br><span class="line"></span>{</span><br><span class="line"> DISPATCH_OBJECT_TFB(_dispatch_objc_resume, dou);</span><br><span class="line"> <span class="keyword">if</span> (dx_vtable(dou._do)->do_suspend) {</span><br><span class="line"> dx_vtable(dou._do)->do_resume(dou._do, <span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>最后一步,是最核心的异步,唤起任务开始执行。之前的 queue 最终也是走到这样类似的一步,可以看返回类型都是 <code>dispatch_queue_wakeup_target_t</code>,基本是沿着 queue 的逻辑一路 copy 过来。这个方法,经过一系列判断,保证所有的 source 都会在正确的队列上面执行;如果队列和任务不对应,那么就返回正确的队列,重新派发让任务在正确的队列上执行。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br></pre></td><td class="code"><pre><span class="line"> DISPATCH_ALWAYS_INLINE</span><br><span class="line">static inline dispatch_queue_wakeup_target_t</span><br><span class="line">_dispatch_source_invoke2(dispatch_object_t dou, dispatch_invoke_context_t dic,</span><br><span class="line"> dispatch_invoke_flags_t flags, uint64_t *owned)</span><br><span class="line">{</span><br><span class="line"> dispatch_source_t ds = dou._ds;</span><br><span class="line"> dispatch_queue_wakeup_target_t retq = DISPATCH_QUEUE_WAKEUP_NONE;</span><br><span class="line"> // 获取当前 queue</span><br><span class="line"> dispatch_queue_t dq = _dispatch_queue_get_current();</span><br><span class="line"> dispatch_source_refs_t dr = ds->ds_refs;</span><br><span class="line"> dispatch_queue_flags_t dqf;</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> // timer 事件处理</span><br><span class="line"> if (dr->du_is_timer &&</span><br><span class="line"> os_atomic_load2o(ds, ds_timer_refs->dt_pending_config, relaxed)) {</span><br><span class="line"> dqf = _dispatch_queue_atomic_flags(ds->_as_dq);</span><br><span class="line"> if (!(dqf & (DSF_CANCELED | DQF_RELEASED))) {</span><br><span class="line"> // timer has to be configured on the kevent queue</span><br><span class="line"> if (dq != dkq) {</span><br><span class="line"> return dkq;</span><br><span class="line"> }</span><br><span class="line"> _dispatch_source_timer_configure(ds);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 是否安装 source</span><br><span class="line"> if (!ds->ds_is_installed) {</span><br><span class="line"> // The source needs to be installed on the kevent queue.</span><br><span class="line"> if (dq != dkq) {</span><br><span class="line"> return dkq;</span><br><span class="line"> }</span><br><span class="line"> _dispatch_source_install(ds, _dispatch_get_wlh(),</span><br><span class="line"> _dispatch_get_basepri());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 是否暂停,因为之前判断过,一般不可能走到这里</span><br><span class="line"> if (unlikely(DISPATCH_QUEUE_IS_SUSPENDED(ds))) {</span><br><span class="line"> // Source suspended by an item drained from the source queue.</span><br><span class="line"> return ds->do_targetq;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 是否在</span><br><span class="line"> if (_dispatch_source_get_registration_handler(dr)) {</span><br><span class="line"> // The source has been registered and the registration handler needs</span><br><span class="line"> // to be delivered on the target queue.</span><br><span class="line"> if (dq != ds->do_targetq) {</span><br><span class="line"> return ds->do_targetq;</span><br><span class="line"> }</span><br><span class="line"> // clears ds_registration_handler</span><br><span class="line"> _dispatch_source_registration_callout(ds, dq, flags);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> if (!(dqf & (DSF_CANCELED | DQF_RELEASED)) &&</span><br><span class="line"> os_atomic_load2o(ds, ds_pending_data, relaxed)) {</span><br><span class="line"> // 有些 source 还有未完成的数据,需要通过目标队列上的回调进行传送;有些 source 则需要切换到管理队列上去。</span><br><span class="line"> if (dq == ds->do_targetq) {</span><br><span class="line"> _dispatch_source_latch_and_call(ds, dq, flags);</span><br><span class="line"> dqf = _dispatch_queue_atomic_flags(ds->_as_dq);</span><br><span class="line"> prevent_starvation = dq->do_targetq ||</span><br><span class="line"> !(dq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT);</span><br><span class="line"> if (prevent_starvation &&</span><br><span class="line"> os_atomic_load2o(ds, ds_pending_data, relaxed)) {</span><br><span class="line"> retq = ds->do_targetq;</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> return ds->do_targetq;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if ((dqf & (DSF_CANCELED | DQF_RELEASED)) && !(dqf & DSF_DEFERRED_DELETE)) {</span><br><span class="line"> // 已经被取消的 source 需要从管理队列中卸载。卸载完成后,取消的 handler 需要交付到目标队列。</span><br><span class="line"> if (!(dqf & DSF_DELETED)) {</span><br><span class="line"> if (dr->du_is_timer && !(dqf & DSF_ARMED)) {</span><br><span class="line"> // timers can cheat if not armed because there's nothing left</span><br><span class="line"> // to do on the manager queue and unregistration can happen</span><br><span class="line"> // on the regular target queue</span><br><span class="line"> } else if (dq != dkq) {</span><br><span class="line"> return dkq;</span><br><span class="line"> }</span><br><span class="line"> _dispatch_source_refs_unregister(ds, 0);</span><br><span class="line"> dqf = _dispatch_queue_atomic_flags(ds->_as_dq);</span><br><span class="line"> if (unlikely(dqf & DSF_DEFERRED_DELETE)) {</span><br><span class="line"> if (!(dqf & DSF_ARMED)) {</span><br><span class="line"> goto unregister_event;</span><br><span class="line"> }</span><br><span class="line"> // we need to wait for the EV_DELETE</span><br><span class="line"> return retq ? retq : DISPATCH_QUEUE_WAKEUP_WAIT_FOR_EVENT;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if (dq != ds->do_targetq && (_dispatch_source_get_event_handler(dr) ||</span><br><span class="line"> _dispatch_source_get_cancel_handler(dr) ||</span><br><span class="line"> _dispatch_source_get_registration_handler(dr))) {</span><br><span class="line"> retq = ds->do_targetq;</span><br><span class="line"> } else {</span><br><span class="line"> _dispatch_source_cancel_callout(ds, dq, flags);</span><br><span class="line"> dqf = _dispatch_queue_atomic_flags(ds->_as_dq);</span><br><span class="line"> }</span><br><span class="line"> prevent_starvation = false;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (_dispatch_unote_needs_rearm(dr) &&</span><br><span class="line"> !(dqf & (DSF_ARMED|DSF_DELETED|DSF_CANCELED|DQF_RELEASED))) {</span><br><span class="line"> // 需要在管理队列进行 rearm 的</span><br><span class="line"> if (dq != dkq) {</span><br><span class="line"> return dkq;</span><br><span class="line"> }</span><br><span class="line"> if (unlikely(dqf & DSF_DEFERRED_DELETE)) {</span><br><span class="line"> // 如果我们可以直接注销,不需要 resume</span><br><span class="line"> goto unregister_event;</span><br><span class="line"> }</span><br><span class="line"> if (unlikely(DISPATCH_QUEUE_IS_SUSPENDED(ds))) {</span><br><span class="line"> // 如果 source 已经暂停,不需要在管理队列 rearm</span><br><span class="line"> return ds->do_targetq;</span><br><span class="line"> }</span><br><span class="line"> if (prevent_starvation && dr->du_wlh == DISPATCH_WLH_ANON) {</span><br><span class="line"> return ds->do_targetq;</span><br><span class="line"> }</span><br><span class="line"> if (unlikely(!_dispatch_source_refs_resume(ds))) {</span><br><span class="line"> goto unregister_event;</span><br><span class="line"> }</span><br><span class="line"> if (!prevent_starvation && _dispatch_wlh_should_poll_unote(dr)) {</span><br><span class="line"> _dispatch_event_loop_drain(KEVENT_FLAG_IMMEDIATE);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return retq;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<pre><code>还有一些其他的方法,这里就不介绍了。有兴趣的可以看源码,太多了。
</code></pre><h4 id="5-Dispatch-I-O"><a href="#5-Dispatch-I-O" class="headerlink" title="5. Dispatch I/O"></a>5. Dispatch I/O</h4><p>我们可以使用 Dispatch I/O 快速读取一些文件,例如这样 :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)readFile {</span><br><span class="line"> <span class="built_in">NSString</span> *filePath = <span class="string">@"/.../青花瓷.m"</span>;</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.readfile"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> dispatch_fd_t fd = open(filePath.UTF8String, O_RDONLY,<span class="number">0</span>);</span><br><span class="line"> dispatch_io_t fileChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(<span class="keyword">int</span> error) {</span><br><span class="line"> close(fd);</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSMutableData</span> *fileData = [<span class="built_in">NSMutableData</span> new];</span><br><span class="line"> dispatch_io_set_low_water(fileChannel, SIZE_MAX);</span><br><span class="line"> dispatch_io_read(fileChannel, <span class="number">0</span>, SIZE_MAX, queue, ^(<span class="keyword">bool</span> done, dispatch_data_t _Nullable data, <span class="keyword">int</span> error) {</span><br><span class="line"> <span class="keyword">if</span> (error == <span class="number">0</span> && dispatch_data_get_size(data) > <span class="number">0</span>) {</span><br><span class="line"> [fileData appendData:(<span class="built_in">NSData</span> *)data];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (done) {</span><br><span class="line"> <span class="built_in">NSString</span> *str = [[<span class="built_in">NSString</span> alloc] initWithData:fileData encoding:<span class="built_in">NSUTF8StringEncoding</span>];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"read file completed, string is :\n %@"</span>,str);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出结果:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ConcurrencyTest[<span class="number">41479</span>:<span class="number">5357296</span>] read file completed, string is :</span><br><span class="line"> 天青色等烟雨 而我在等你</span><br><span class="line">月色被打捞起 晕开了结局</span><br></pre></td></tr></table></figure>
<p>如果读取大文件,我们可以进行切片读取,将文件分割多个片,放在异步线程中并发执行,这样会比较快一些。</p>
<p>关于源码,简单看了一下,调度逻辑和之前的任务类似。然后读写操作,是调用的一些底层接口实现,这里就偷懒一下不详细说了。使用 Dispatch I/O,多数情况下是为了并发读取一个大文件,提高读取速度。</p>
<h4 id="6-Other"><a href="#6-Other" class="headerlink" title="6. Other"></a>6. Other</h4><p>上面已经讲了概览图中的大部分东西,还有一些未讲述,这里简单描述一下:</p>
<ul>
<li><p>dispatch_object。GCD 用 C 函数实现的对象,不能通过集成 dispatch 类实现,也不能用 alloc 方法初始化。GCD 针对 dispatch_object 提供了一些接口,我们使用这些接口可以处理一些内存事件、取消和暂停操作、定义上下文和处理日志相关工作。dispatch_object 必须要手动管理内存,不遵循垃圾回收机制。</p>
</li>
<li><p>dispatch_time。在 GCD 中使用的时间对象,可以创建自定义时间,也可以使用 <code>DISPATCH_TIME_NOW</code>、<code>DISPATCH_TIME_FOREVER</code> 这两个系统给出的时间。</p>
</li>
</ul>
<p>以上为 GCD 相关知识,这次使用的源码版本为最新版本 —— <strong>912.30.4.tar.gz</strong>,和之前看的版本代码差距很大,因为代码量的增加,新版本代码比较乱,不过基本原理还是差不多的。曾经我一度认为,最上面的是最新版本…</p>
<h3 id="Operations"><a href="#Operations" class="headerlink" title="Operations"></a>Operations</h3><p>Operations 也是我们在并发编程中常用的一套 API,根据 <a href="https://developer.apple.com/documentation/foundation/nsinvocationoperation?language=objc" target="_blank" rel="external">官方文档</a> 划分的结构如下图:</p>
<p><img src="/uploads/concurrency-programming/Operations 结构图.png" alt="Operations 结构图"></p>
<p>其中 <code>NSBlockOperation</code> 和 <code>NSInvocationOperation</code> 是基于 <code>NSOperation</code> 的子类化实现。相对于 GCD,Operations 的原理要稍微好理解一些,下面就将用法和原理介绍一下。</p>
<h4 id="1-NSOperation"><a href="#1-NSOperation" class="headerlink" title="1. NSOperation"></a>1. NSOperation</h4><h5 id="基本使用-1"><a href="#基本使用-1" class="headerlink" title="基本使用"></a>基本使用</h5><p>每一个 operation 可以认为是一个 task。<code>NSOperation</code> 本事是一个抽象类,使用前需子类化。幸运的是,Apple 为我们实现了两个子类:<code>NSInvocationOperation</code>、<code>NSBlockOperation</code>。我们也可以自己去定义一个 operation。下面介绍一下基本使用:</p>
<ul>
<li><p>创建一个 <code>NSInvocationOperation</code> 对象并在当前线程执行.</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSInvocationOperation</span> *invocationOperation = [[<span class="built_in">NSInvocationOperation</span> alloc] initWithTarget:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(log) object:<span class="literal">nil</span>];</span><br><span class="line"> [invocationOperation start];</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建一个 <code>NSBlockOperation</code> 对象并执行 (每个 block 不一定会在当前线程,也不一定在同一线程执行).</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSBlockOperation</span> *blockOpeartion = [<span class="built_in">NSBlockOperation</span> blockOperationWithBlock:^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"block operation"</span>);</span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 可以添加多个 block</span></span><br><span class="line"> [blockOpeartion addExecutionBlock:^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"other block opeartion"</span>);</span><br><span class="line"> }];</span><br><span class="line"> [blockOpeartion start];</span><br></pre></td></tr></table></figure>
</li>
<li><p>自定义一个 Operation。当我们不需要操作状态的时候,只需要实现 <code>main()</code> 方法即可。需要操作状态的后面再说.</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">BLOpeartion</span> : <span class="title">NSOperation</span></span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">BLOpeartion</span></span></span><br><span class="line">- (<span class="keyword">void</span>)main {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"BLOperation main method"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> BLOperation *blOperation = [BLOperation new];</span><br><span class="line"> [blOperation start];</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>每个 operation 之间设置依赖.</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSBlockOperation</span> *blockOpeartion1 = [<span class="built_in">NSBlockOperation</span> blockOperationWithBlock:^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"block operation1"</span>);</span><br><span class="line"> }];</span><br><span class="line"> <span class="built_in">NSBlockOperation</span> *blockOpeartion2 = [<span class="built_in">NSBlockOperation</span> blockOperationWithBlock:^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"block operation2"</span>);</span><br><span class="line"> }];</span><br><span class="line"> <span class="comment">// 2 需要在 1 执行完之后再执行。</span></span><br><span class="line"> [blockOpeartion2 addDependency:blockOpeartion1];</span><br></pre></td></tr></table></figure>
</li>
<li><p>与队列相关的使用,后面再说.</p>
</li>
</ul>
<h5 id="基本原理-2"><a href="#基本原理-2" class="headerlink" title="基本原理"></a>基本原理</h5><p><code>NSOperation</code> 内置了一个强大的状态机,一个 operation 从初始化到执行完毕这一生命周期,对应了各种状态。下面是在 <a href="https://developer.apple.com/videos/play/wwdc2015/226/" target="_blank" rel="external">WWDC 2015 Advanced NSOperations</a> 出现的一张图:</p>
<p><img src="/uploads/concurrency-programming/opeartion_status.png" alt="opeartion_status"></p>
<p>operation 一开始是 Pending 状态,代表即将进入 Ready;进入 Ready 之后,代表任务可以执行;然后进入 Executing 状态;最后执行完成,进入 Finished 状态。过程中,除了 Finished 状态,在其他几个状态中都可以进行 Cancelled。</p>
<p><code>NSOperation</code> 并没有开源。但是 swift 开源了,在 swift 中它叫 <code>Opeartion</code>,我们可以在 <a href="https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/Operation.swift" target="_blank" rel="external">这里</a> 找到他的源码。我这里 copy 了一份:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br></pre></td><td class="code"><pre><span class="line">open <span class="class"><span class="keyword">class</span> <span class="title">Operation</span> : <span class="title">NSObject</span> </span>{</span><br><span class="line"> <span class="keyword">let</span> lock = <span class="type">NSLock</span>()</span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">weak</span> <span class="keyword">var</span> _queue: <span class="type">OperationQueue</span>?</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 默认几个状态都是 false</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _cancelled = <span class="literal">false</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _executing = <span class="literal">false</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _finished = <span class="literal">false</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _ready = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 用一个集合来保存依赖它的对象</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _dependencies = <span class="type">Set</span><<span class="type">Operation</span>>()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 初始化一些 dispatch_group 对象,来管理 operation 以及其依赖对象的 执行。</span></span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _group = <span class="type">DispatchGroup</span>()</span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _depGroup = <span class="type">DispatchGroup</span>()</span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _groups = [<span class="type">DispatchGroup</span>]()</span><br><span class="line">#endif</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">init</span>() {</span><br><span class="line"> <span class="keyword">super</span>.<span class="keyword">init</span>()</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> _group.enter()</span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">internal</span> <span class="function"><span class="keyword">func</span> _leaveGroups<span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// assumes lock is taken</span></span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> _groups.forEach() { $<span class="number">0</span>.leave() }</span><br><span class="line"> _groups.removeAll()</span><br><span class="line"> _group.leave()</span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 默认实现的 start 方法中,执行 main 方法,线程安全,下同。执行前后设置 _executing。</span></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">start</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">if</span> !isCancelled {</span><br><span class="line"> lock.lock()</span><br><span class="line"> _executing = <span class="literal">true</span></span><br><span class="line"> lock.unlock()</span><br><span class="line"> main()</span><br><span class="line"> lock.lock()</span><br><span class="line"> _executing = <span class="literal">false</span></span><br><span class="line"> lock.unlock()</span><br><span class="line"> }</span><br><span class="line"> finish()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 默认实现的 finish 方法中,标记 _finished 状态。</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="function"><span class="keyword">func</span> <span class="title">finish</span><span class="params">()</span></span> {</span><br><span class="line"> lock.lock()</span><br><span class="line"> _finished = <span class="literal">true</span></span><br><span class="line"> _leaveGroups()</span><br><span class="line"> lock.unlock()</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> queue = _queue {</span><br><span class="line"> queue._operationFinished(<span class="keyword">self</span>)</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// main 方法默认空,需要子类去实现。</span></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> { }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 调用 cancel 方法后,只是标记状态,具体操作在 main 中,调用 cancel 后也被认为是 finish。</span></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">cancel</span><span class="params">()</span></span> {</span><br><span class="line"> lock.lock()</span><br><span class="line"> _cancelled = <span class="literal">true</span></span><br><span class="line"> lock.unlock()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/** 几个状态的 get 方法,省略 */</span> </span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否为异步任务,默认为 false。这个方法在 OC 中永远不会去实现</span></span><br><span class="line"> open <span class="keyword">var</span> isAsynchronous: <span class="type">Bool</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 设置依赖,即将 operation 放到集合中</span></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">addDependency</span><span class="params">(<span class="number">_</span> op: Operation)</span></span> {</span><br><span class="line"> lock.lock()</span><br><span class="line"> _dependencies.insert(op)</span><br><span class="line"> op.lock.lock()</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> _depGroup.enter()</span><br><span class="line"> op._groups.append(_depGroup)</span><br><span class="line">#endif</span><br><span class="line"> op.lock.unlock()</span><br><span class="line"> lock.unlock()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 默认队列优先级为 normal</span></span><br><span class="line"> open <span class="keyword">var</span> queuePriority: <span class="type">QueuePriority</span> = .normal</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">var</span> completionBlock: (() -> <span class="type">Void</span>)?</span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">waitUntilFinished</span><span class="params">()</span></span> {</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> _group.wait()</span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 线程优先级</span></span><br><span class="line"> open <span class="keyword">var</span> threadPriority: <span class="type">Double</span> = <span class="number">0.5</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">/// - Note: Quality of service is not directly supported here since there are not qos class promotions available outside of darwin targets.</span></span><br><span class="line"> open <span class="keyword">var</span> qualityOfService: <span class="type">QualityOfService</span> = .<span class="keyword">default</span></span><br><span class="line"> </span><br><span class="line"> open <span class="keyword">var</span> name: <span class="type">String</span>?</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">internal</span> <span class="function"><span class="keyword">func</span> _waitUntilReady<span class="params">()</span></span> {</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> _depGroup.wait()</span><br><span class="line">#endif</span><br><span class="line"> _ready = <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>代码很简单,具体过程可以直接看注释,就不另说了。除此之外,我们可以看出,<code>Operation</code> 总很多方法造作都加了锁,说明这个类是线程安全的,当我们对 <code>NSOperation</code> 进行子类化时,重写方法要注意线程暗转问题。</p>
<h4 id="2-NSOperationQueue"><a href="#2-NSOperationQueue" class="headerlink" title="2. NSOperationQueue"></a>2. NSOperationQueue</h4><p><code>NSOperation</code> 的很多花式操作,都是结合着 <code>NSOperationQueue</code> 进行的。我们在使用的时候,也是两者结合着使用。下面对其进行详细分析。</p>
<h5 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h5><ul>
<li>operation 放到 queue 中不用在手动调用 <code>start</code> 方法去执行,operation 会自动执行。</li>
<li>queue 可以设置最大并发数,当并发数量设置为 1 时,为串行队列;默认并发数量为无限大。</li>
<li>queue 可以通过设置 <code>suspended</code> 属性来<strong>暂停或者启动还未执行的 operation</strong>。</li>
<li>queue 可以通过调用 <code>-[cancelAllOperations]</code> 方法来取消队列中的任务。</li>
<li>queue 可以通过 <code>mainQueue</code> 方法来回到主队列(主线程);可以通过 <code>currentQueue</code> 方法来获取当前队列。</li>
<li>更多方法,请参考 <a href="https://developer.apple.com/documentation/foundation/nsoperationqueue?language=objc" target="_blank" rel="external">官方文档</a></li>
</ul>
<p>使用例子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)testOperationQueue {</span><br><span class="line"> <span class="built_in">NSOperationQueue</span> *operationQueue = [<span class="built_in">NSOperationQueue</span> new];</span><br><span class="line"> <span class="comment">// 设置最大并发数量为 3</span></span><br><span class="line"> [operationQueue setMaxConcurrentOperationCount:<span class="number">3</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSInvocationOperation</span> *invocationOpeartion = [[<span class="built_in">NSInvocationOperation</span> alloc] initWithTarget:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(log) object:<span class="literal">nil</span>];</span><br><span class="line"> [operationQueue addOperation:invocationOpeartion];</span><br><span class="line"> </span><br><span class="line"> [operationQueue addOperationWithBlock:^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"block operation"</span>);</span><br><span class="line"> <span class="comment">// 回到主线程执行任务</span></span><br><span class="line"> [[<span class="built_in">NSOperationQueue</span> mainQueue] addOperationWithBlock:^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"execute in main thread"</span>);</span><br><span class="line"> }];</span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂停还未开始执行的任务</span></span><br><span class="line"> operationQueue.suspended = <span class="literal">YES</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 取消所有任务</span></span><br><span class="line"> [operationQueue cancelAllOperations];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>有一个问题要特别说明一下:</p>
<p><strong><code>NSOperationQueue</code> 和 GCD 中的队列不同。GCD 中的队列是遵循 FIFO 原则,先加入队列的先执行;<code>NSOperationQueue</code> 中的任务,根据谁先进入到 <code>Ready</code> 状态,谁先执行。如果有多个任务同时达到 <code>Ready</code> 状态,那么根据优先级来执行。</strong></p>
<p>例如下面的任务中,4 先到达了 <code>Ready</code> 状态,4 先执行。并不是按照 1,2,3… 顺序执行。</p>
<p><img src="/uploads/concurrency-programming/Operation_Queue.png" alt="Operation_Queue"></p>
<h5 id="基本原理-3"><a href="#基本原理-3" class="headerlink" title="基本原理"></a>基本原理</h5><p>我们依然是在 swift 中找到相关源码,然后来进行分析:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 默认最大并发数量为 int 最大值</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">extension</span> <span class="title">OperationQueue</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">let</span> defaultMaxConcurrentOperationCount: <span class="type">Int</span> = <span class="type">Int</span>.<span class="built_in">max</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用一个 list 来保存各个优先级的 operation。调用其中的方法对 operation 进行增删等操作。</span></span><br><span class="line"><span class="keyword">internal</span> <span class="class"><span class="keyword">struct</span> _OperationList </span>{</span><br><span class="line"> <span class="keyword">var</span> veryLow = [<span class="type">Operation</span>]()</span><br><span class="line"> <span class="keyword">var</span> low = [<span class="type">Operation</span>]()</span><br><span class="line"> <span class="keyword">var</span> normal = [<span class="type">Operation</span>]()</span><br><span class="line"> <span class="keyword">var</span> high = [<span class="type">Operation</span>]()</span><br><span class="line"> <span class="keyword">var</span> veryHigh = [<span class="type">Operation</span>]()</span><br><span class="line"> <span class="keyword">var</span> all = [<span class="type">Operation</span>]()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">mutating</span> <span class="function"><span class="keyword">func</span> <span class="title">insert</span><span class="params">(<span class="number">_</span> operation: Operation)</span></span> { ... }</span><br><span class="line"> <span class="keyword">mutating</span> <span class="function"><span class="keyword">func</span> <span class="title">remove</span><span class="params">(<span class="number">_</span> operation: Operation)</span></span> { ... }</span><br><span class="line"> <span class="keyword">mutating</span> <span class="function"><span class="keyword">func</span> <span class="title">dequeue</span><span class="params">()</span></span> -> <span class="type">Operation</span>? { ... }</span><br><span class="line"> <span class="keyword">var</span> <span class="built_in">count</span>: <span class="type">Int</span> {</span><br><span class="line"> <span class="keyword">return</span> all.<span class="built_in">count</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">func</span> <span class="title">map</span><T><span class="params">(<span class="number">_</span> transform: <span class="params">(Operation)</span></span></span> <span class="keyword">throws</span> -> <span class="type">T</span>) <span class="keyword">rethrows</span> -> [<span class="type">T</span>] {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">try</span> all.<span class="built_in">map</span>(transform)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">open <span class="class"><span class="keyword">class</span> <span class="title">OperationQueue</span>: <span class="title">NSObject</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment">// 使用一个信号量的来控制并发数量</span></span><br><span class="line"> <span class="keyword">var</span> __concurrencyGate: <span class="type">DispatchSemaphore</span>?</span><br><span class="line"> <span class="keyword">var</span> __underlyingQueue: <span class="type">DispatchQueue</span>? {</span><br><span class="line"> <span class="keyword">didSet</span> {</span><br><span class="line"> <span class="keyword">let</span> key = <span class="type">OperationQueue</span>.<span class="type">OperationQueueKey</span></span><br><span class="line"> oldValue?.setSpecific(key: key, value: <span class="literal">nil</span>)</span><br><span class="line"> __underlyingQueue?.setSpecific(key: key, value: <span class="type">Unmanaged</span>.passUnretained(<span class="keyword">self</span>))</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _underlyingQueue: <span class="type">DispatchQueue</span> {</span><br><span class="line"> lock.lock()</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> queue = __underlyingQueue {</span><br><span class="line"> lock.unlock()</span><br><span class="line"> <span class="keyword">return</span> queue</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 信号量的值根据最大并发数量来确定。每当执行一个任务,wait 信号量减一,signal 信号量加一,当信号量为0时,一直等待,直接大于 0 才会正常执行。</span></span><br><span class="line"> <span class="keyword">if</span> maxConcurrentOperationCount == <span class="number">1</span> {</span><br><span class="line"> attr = []</span><br><span class="line"> __concurrencyGate = <span class="type">DispatchSemaphore</span>(value: <span class="number">1</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> attr = .concurrent</span><br><span class="line"> <span class="keyword">if</span> maxConcurrentOperationCount != <span class="type">OperationQueue</span>.defaultMaxConcurrentOperationCount {</span><br><span class="line"> __concurrencyGate = <span class="type">DispatchSemaphore</span>(value:maxConcurrentOperationCount)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">let</span> queue = <span class="type">DispatchQueue</span>(label: effectiveName, attributes: attr)</span><br><span class="line"> <span class="keyword">if</span> _suspended {</span><br><span class="line"> queue.suspend()</span><br><span class="line"> }</span><br><span class="line"> __underlyingQueue = queue</span><br><span class="line"> lock.unlock()</span><br><span class="line"> <span class="keyword">return</span> queue</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 出队列,每个任务执行时拿出队列执行</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="function"><span class="keyword">func</span> _dequeueOperation<span class="params">()</span></span> -> <span class="type">Operation</span>? {</span><br><span class="line"> lock.lock()</span><br><span class="line"> <span class="keyword">let</span> op = _operations.dequeue()</span><br><span class="line"> lock.unlock()</span><br><span class="line"> <span class="keyword">return</span> op</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">addOperation</span><span class="params">(<span class="number">_</span> op: Operation)</span></span> {</span><br><span class="line"> addOperations([op], waitUntilFinished: <span class="literal">false</span>)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 主要执行方法。先判断 operation 是否 ready,处于 ready 后判断是否 cancel。没有 cancel 则执行。</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="function"><span class="keyword">func</span> _runOperation<span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> op = _dequeueOperation() {</span><br><span class="line"> <span class="keyword">if</span> !op.isCancelled {</span><br><span class="line"> op._waitUntilReady()</span><br><span class="line"> <span class="keyword">if</span> !op.isCancelled {</span><br><span class="line"> op.start()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 将任务加到队列中。如果不指定任务优先级,执行的还快一些。否则需要对不同优先级进行划分,然后执行</span></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">addOperations</span><span class="params">(<span class="number">_</span> ops: [Operation], waitUntilFinished wait: Bool)</span></span> {</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> <span class="keyword">var</span> waitGroup: <span class="type">DispatchGroup</span>?</span><br><span class="line"> <span class="keyword">if</span> wait {</span><br><span class="line"> waitGroup = <span class="type">DispatchGroup</span>()</span><br><span class="line"> }</span><br><span class="line">#endif</span><br><span class="line"> lock.lock()</span><br><span class="line"> <span class="comment">// 将 operation 依依加入 list,根据优先级保存到不同数组中</span></span><br><span class="line"> ops.forEach { (operation: <span class="type">Operation</span>) -> <span class="type">Void</span> <span class="keyword">in</span></span><br><span class="line"> operation._queue = <span class="keyword">self</span></span><br><span class="line"> _operations.insert(operation)</span><br><span class="line"> }</span><br><span class="line"> lock.unlock()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 遍历执行,使用了 diapatch group,控制 enter 和 leave</span></span><br><span class="line"> ops.forEach { (operation: <span class="type">Operation</span>) -> <span class="type">Void</span> <span class="keyword">in</span></span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> group = waitGroup {</span><br><span class="line"> group.enter()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 通过信号量来控制并发数量</span></span><br><span class="line"> <span class="keyword">let</span> block = <span class="type">DispatchWorkItem</span>(flags: .enforceQoS) { () -> <span class="type">Void</span> <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> sema = <span class="keyword">self</span>._concurrencyGate {</span><br><span class="line"> sema.wait()</span><br><span class="line"> <span class="keyword">self</span>._runOperation()</span><br><span class="line"> sema.signal()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">self</span>._runOperation()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> group = waitGroup {</span><br><span class="line"> group.leave()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> _underlyingQueue.async(group: queueGroup, execute: block)</span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> group = waitGroup {</span><br><span class="line"> group.wait()</span><br><span class="line"> }</span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">internal</span> <span class="function"><span class="keyword">func</span> _operationFinished<span class="params">(<span class="number">_</span> operation: Operation)</span></span> { ... }</span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">addOperation</span><span class="params">(<span class="number">_</span> block: @escaping <span class="params">()</span></span></span> -> <span class="type">Swift</span>.<span class="type">Void</span>) { ... }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回值不一定准确</span></span><br><span class="line"> open <span class="keyword">var</span> operations: [<span class="type">Operation</span>] { ... }</span><br><span class="line"> <span class="comment">// 返回值不一定准确</span></span><br><span class="line"> open <span class="keyword">var</span> operationCount: <span class="type">Int</span> { ... }</span><br><span class="line"> open <span class="keyword">var</span> maxConcurrentOperationCount: <span class="type">Int</span> = <span class="type">OperationQueue</span>.defaultMaxConcurrentOperationCount</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// suppend 属性的 get & set 方法。默认不暂停</span></span><br><span class="line"> <span class="keyword">internal</span> <span class="keyword">var</span> _suspended = <span class="literal">false</span></span><br><span class="line"> open <span class="keyword">var</span> isSuspended: <span class="type">Bool</span> { ... }</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// operation 在获取系统资源时的优先级</span></span><br><span class="line"> open <span class="keyword">var</span> qualityOfService: <span class="type">QualityOfService</span> = .<span class="keyword">default</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 依次调用每个 operation 的 cancel 方法</span></span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">cancelAllOperations</span><span class="params">()</span></span> { ... }</span><br><span class="line"> </span><br><span class="line"> open <span class="function"><span class="keyword">func</span> <span class="title">waitUntilAllOperationsAreFinished</span><span class="params">()</span></span> {</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> queueGroup.wait()</span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">let</span> <span class="type">OperationQueueKey</span> = <span class="type">DispatchSpecificKey</span><<span class="type">Unmanaged</span><<span class="type">OperationQueue</span>>>()</span><br><span class="line"> <span class="comment">// 通过使用 GCD 中的 getSpecific 方法获取当前队列</span></span><br><span class="line"> open <span class="class"><span class="keyword">class</span> <span class="title">var</span> <span class="title">current</span>: <span class="title">OperationQueue</span>? </span>{</span><br><span class="line">#<span class="keyword">if</span> <span class="type">DEPLOYMENT_ENABLE_LIBDISPATCH</span></span><br><span class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> specific = <span class="type">DispatchQueue</span>.getSpecific(key: <span class="type">OperationQueue</span>.<span class="type">OperationQueueKey</span>) <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> _CFIsMainThread() {</span><br><span class="line"> <span class="keyword">return</span> <span class="type">OperationQueue</span>.main</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> specific.takeUnretainedValue()</span><br><span class="line">#<span class="keyword">else</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">#endif</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 定义主队列,最大并发数量为 1,获取主队列时将这个值返回 </span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">let</span> _main = <span class="type">OperationQueue</span>(_queue: .main, maxConcurrentOperations: <span class="number">1</span>) </span><br><span class="line"> open <span class="class"><span class="keyword">class</span> <span class="title">var</span> <span class="title">main</span>: <span class="title">OperationQueue</span> </span>{ ... }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>代码很长,但是简单,可以直接通过注释来理解了。这里屡一下:</p>
<ul>
<li>将每个 operation 加入到队列时,会根据优先级将 operation 分类存入 list 中,根据优先级执行。如果都不设置优先级,执行起来比较快一些。</li>
<li>加入到队列,会遍历每个 operation,取出进入 <code>Ready</code> 状态且没被 <code>Cancel</code> 的依次执行。</li>
<li>通过 <code>concurrencyGate</code> 这个信号量来控制并发数量。每当执行一个任务,wait 信号量减一,signal 信号量加一,当信号量为0时,一直等待,直接大于 0 才会正常执行。</li>
<li>每个方法中基本都加了锁,来保证线程安全。</li>
</ul>
<h5 id="自定义-NSOperation"><a href="#自定义-NSOperation" class="headerlink" title="自定义 NSOperation"></a>自定义 NSOperation</h5><p>之前说了自定义普通的 <code>NSOperation</code>,只需要重写 <code>main</code> 方法就可以了,但是因为我们没有处理并发情况,线程执行结束操作,KVO 机制,所以这种普通的不建议用来做并发任务。下面讲一下如何自定义并行的 <code>NSOperation</code>。</p>
<p>必须要实现的一些方法:</p>
<ul>
<li><code>start</code> 方法,在你想要执行的线程中调用此方法。<strong>不需要调用 super 方法</strong>。</li>
<li><code>main</code> 方法,在 <code>start</code> 方法中调用,任务主体。</li>
<li><code>isExecuting</code> 方法,是否正在执行,要实现 KVO 机制。</li>
<li><code>isConcurrent</code> 方法,已经弃用,由 <code>isAsynchronous</code> 来代替。</li>
<li><code>isAsynchronous</code> 方法,在并发任务中,需要返回 YES。</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">BLOperation</span> ()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">assign</span>) <span class="built_in">BOOL</span> executing;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">assign</span>) <span class="built_in">BOOL</span> finished;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">BLOperation</span></span></span><br><span class="line"><span class="keyword">@synthesize</span> executing;</span><br><span class="line"><span class="keyword">@synthesize</span> finished;</span><br><span class="line"></span><br><span class="line">- (instancetype)init {</span><br><span class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> init];</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>) {</span><br><span class="line"> executing = <span class="literal">NO</span>;</span><br><span class="line"> finished = <span class="literal">NO</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)start {</span><br><span class="line"> <span class="keyword">if</span> ([<span class="keyword">self</span> isCancelled]) {</span><br><span class="line"> [<span class="keyword">self</span> willChangeValueForKey:<span class="string">@"isFinished"</span>];</span><br><span class="line"> finished = <span class="literal">YES</span>;</span><br><span class="line"> [<span class="keyword">self</span> didChangeValueForKey:<span class="string">@"isFinished"</span>];</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span> willChangeValueForKey:<span class="string">@"isExecuting"</span>];</span><br><span class="line"> [<span class="built_in">NSThread</span> detachNewThreadSelector:<span class="keyword">@selector</span>(main) toTarget:<span class="keyword">self</span> withObject:<span class="literal">nil</span>];</span><br><span class="line"> executing = <span class="literal">YES</span>;</span><br><span class="line"> [<span class="keyword">self</span> didChangeValueForKey:<span class="string">@"isExecuting"</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)main {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"main begin"</span>);</span><br><span class="line"> <span class="keyword">@try</span> {</span><br><span class="line"> <span class="keyword">@autoreleasepool</span> {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"custom operation"</span>);</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"currentThread = %@"</span>, [<span class="built_in">NSThread</span> currentThread]);</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"mainThread = %@"</span>, [<span class="built_in">NSThread</span> mainThread]);</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span> willChangeValueForKey:<span class="string">@"isFinished"</span>];</span><br><span class="line"> [<span class="keyword">self</span> willChangeValueForKey:<span class="string">@"isExecuting"</span>];</span><br><span class="line"> executing = <span class="literal">NO</span>;</span><br><span class="line"> finished = <span class="literal">YES</span>;</span><br><span class="line"> [<span class="keyword">self</span> didChangeValueForKey:<span class="string">@"isExecuting"</span>];</span><br><span class="line"> [<span class="keyword">self</span> didChangeValueForKey:<span class="string">@"isFinished"</span>];</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">@catch</span> (<span class="built_in">NSException</span> *exception) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"exception is %@"</span>, exception);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"main end"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">BOOL</span>)isExecuting {</span><br><span class="line"> <span class="keyword">return</span> executing;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">BOOL</span>)isFinished {</span><br><span class="line"> <span class="keyword">return</span> finished;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">BOOL</span>)isAsynchronous {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>关于 <code>NSBlockOpeartion</code>,主要实现了 <code>main</code> 方法,然后用一个数组保存加进来的其他 block,源码如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">open class BlockOperation: Operation {</span><br><span class="line"> typealias ExecutionBlock = () -> Void</span><br><span class="line"> internal var _block: () -> Void</span><br><span class="line"> internal var _executionBlocks = [ExecutionBlock]()</span><br><span class="line"> </span><br><span class="line"> public init(block: @escaping () -> Void) {</span><br><span class="line"> _block = block</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> override open func main() {</span><br><span class="line"> lock.lock()</span><br><span class="line"> let block = _block</span><br><span class="line"> let executionBlocks = _executionBlocks</span><br><span class="line"> lock.unlock()</span><br><span class="line"> block()</span><br><span class="line"> executionBlocks.forEach { $<span class="number">0</span>() }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open func addExecutionBlock(_ block: @escaping () -> Void) {</span><br><span class="line"> lock.lock()</span><br><span class="line"> _executionBlocks.append(block)</span><br><span class="line"> lock.unlock()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> open var executionBlocks: [() -> Void] {</span><br><span class="line"> lock.lock()</span><br><span class="line"> let blocks = _executionBlocks</span><br><span class="line"> lock.unlock()</span><br><span class="line"> <span class="keyword">return</span> blocks</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>关于 <code>NSOperation</code> 的相关东西,到此结束。</p>
<h3 id="在开发中的一些问题"><a href="#在开发中的一些问题" class="headerlink" title="在开发中的一些问题"></a>在开发中的一些问题</h3><p>相对于 API 的使用和基本原理的了解,我认为最重要的还是这一部分。毕竟我们还是要拿这些东西来开发的。并发编程中有很多坑,这里简单介绍一些。</p>
<h4 id="1-NSNotification-与多线程问题"><a href="#1-NSNotification-与多线程问题" class="headerlink" title="1. NSNotification 与多线程问题"></a>1. NSNotification 与多线程问题</h4><p>我们都知道,<code>NSNotification</code> 在哪个线程 post,最终就会在哪个线程执行。如果我们不是在主线程 post 的,但是却在主线程接收的,而且我们期望 selector 在主线程执行。这时候我们需要注意下,在 selector 需要 dispatch 到主线程才可以。当然你也可以使用 <code>addObserverForName:object:queue:usingBlock:</code> 来指定执行 block 的 queue。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">BLPostNotification</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)postNotification {</span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.bool.post.notification"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> <span class="built_in">dispatch_async</span>(queue, ^{</span><br><span class="line"> <span class="comment">// 从非主线程发送通知 (通知名字最好定义成一个常量)</span></span><br><span class="line"> [[<span class="built_in">NSNotificationCenter</span> defaultCenter] postNotificationName:<span class="string">@"downloadImage"</span> object:<span class="literal">nil</span>];</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ImageViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserver:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(show) name:<span class="string">@"downloadImage"</span> object:<span class="literal">nil</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)showImage {</span><br><span class="line"> <span class="comment">// 需要 dispatch 到主线程更新 UI</span></span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{</span><br><span class="line"> <span class="comment">// update UI</span></span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<h4 id="2-NSTimer-与多线程问题"><a href="#2-NSTimer-与多线程问题" class="headerlink" title="2. NSTimer 与多线程问题"></a>2. NSTimer 与多线程问题</h4><p>使用 <code>NSTimer</code> 时,在哪个线程生成的 timer,就在哪个线程销毁,否则会有意想不到的结果。官方这样描述的:</p>
<blockquote>
<p>However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate method. Calling this method requests the removal of the timer from the current run loop; as a result, <strong>you should always call the invalidate method from the same thread on which the timer was installed</strong>. </p>
</blockquote>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">BLTimerTest</span> ()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">dispatch_queue_t</span> queue;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSTimer</span> *timer;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">BLTimerTest</span></span></span><br><span class="line">- (instancetype)init {</span><br><span class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> init];</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>) {</span><br><span class="line"> _queue = dispatch_queue_create(<span class="string">"com.bool.timer.test"</span>, DISPATCH_QUEUE_SERIAL);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)installTimer {</span><br><span class="line"> <span class="built_in">dispatch_async</span>(<span class="keyword">self</span>.queue, ^{</span><br><span class="line"> <span class="keyword">self</span>.timer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:<span class="number">3.0</span>f repeats:<span class="literal">YES</span> block:^(<span class="built_in">NSTimer</span> * _Nonnull timer) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"test timer"</span>);</span><br><span class="line"> }];</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)clearTimer {</span><br><span class="line"> <span class="built_in">dispatch_async</span>(<span class="keyword">self</span>.queue, ^{</span><br><span class="line"> <span class="keyword">if</span> ([<span class="keyword">self</span>.timer isValid]) {</span><br><span class="line"> [<span class="keyword">self</span>.timer invalidate];</span><br><span class="line"> <span class="keyword">self</span>.timer = <span class="literal">nil</span>;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<h4 id="3-Dispatch-Once-死锁问题"><a href="#3-Dispatch-Once-死锁问题" class="headerlink" title="3. Dispatch Once 死锁问题"></a>3. Dispatch Once 死锁问题</h4><p>在开发中,我们经常使用 <code>dispatch_once</code>,但是递归调用会造成死锁。例如下面这样:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)dispatchOnceTest {</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line"> <span class="built_in">dispatch_once</span>(&onceToken, ^{</span><br><span class="line"> [<span class="keyword">self</span> dispatchOnceTest];</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>至于为什么会死锁,上文介绍 Dispatch Once 的时候已经说明了,这里就不多做介绍了。提醒一下使用的时候要注意,不要造成递归调用。</p>
<h4 id="4-Dispatch-Group-问题"><a href="#4-Dispatch-Group-问题" class="headerlink" title="4. Dispatch Group 问题"></a>4. Dispatch Group 问题</h4><p>在使用 <code>dispatch_group</code> 的时候,<code>dispatch_group_enter(taskGroup)</code> 和 <code>dispatch_group_leave(taskGroup)</code> 一定要成对,否则也会出现崩溃。大多数情况下我们都会注意,但是有时候可能会疏忽。例如多层 for loop 时 :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)testDispatchGroup {</span><br><span class="line"> <span class="built_in">NSString</span> *path = <span class="string">@""</span>;</span><br><span class="line"> <span class="built_in">NSFileManager</span> *fileManager = [<span class="built_in">NSFileManager</span> defaultManager];</span><br><span class="line"> <span class="built_in">NSArray</span> *folderList = [fileManager contentsOfDirectoryAtPath:path error:<span class="literal">nil</span>];</span><br><span class="line"> dispatch_group_t taskGroup = dispatch_group_create();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">NSString</span> *folderName <span class="keyword">in</span> folderList) {</span><br><span class="line"> dispatch_group_enter(taskGroup);</span><br><span class="line"> <span class="built_in">NSString</span> *folderPath = [<span class="string">@"path"</span> stringByAppendingPathComponent:folderName];</span><br><span class="line"> <span class="built_in">NSArray</span> *fileList = [fileManager contentsOfDirectoryAtPath:folderPath error:<span class="literal">nil</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">NSString</span> *fileName <span class="keyword">in</span> fileList) {</span><br><span class="line"> <span class="built_in">dispatch_async</span>(_queue, ^{</span><br><span class="line"> <span class="comment">// 异步任务</span></span><br><span class="line"> dispatch_group_leave(taskGroup);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的 <code>dispatch_group_enter(taskGroup)</code> 在第一层 for loop 中,而 <code>dispatch_group_leave(taskGroup)</code> 在第二层 for loop 中,两者的关系是一对多,很容造成崩溃。有时候嵌套层级太多,很容易忽略这个问题。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>关于 iOS 并发编程,就总结到这里。后面如果有一些 best practices 我会更新进来。另外,因为文章比较长,可能会出现一个错误,欢迎指正,我会对此加以修改。</p>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><ol>
<li><a href="https://bestswifter.com/ios-lock/" target="_blank" rel="external">深入理解 iOS 开发中的锁</a></li>
<li><a href="http://yulingtianxia.com/blog/2015/11/01/More-than-you-want-to-know-about-synchronized/" target="_blank" rel="external">关于 @synchronized,这儿比你想知道的还要多</a></li>
<li><a href="https://bestswifter.com/deep-gcd/" target="_blank" rel="external">深入理解 GCD</a></li>
<li><a href="http://blog.jimmyis.in/dispatch_once/" target="_blank" rel="external">GCD源码分析2 —— dispatch_once篇</a></li>
<li><a href="http://lingyuncxb.com/2018/02/10/GCD%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%906%20%E2%80%94%E2%80%94%20dispatch-source%E7%AF%87/" target="_blank" rel="external">GCD源码分析6 —— dispatch_source篇</a></li>
<li><a href="https://developer.apple.com/documentation/dispatch?language=objc" target="_blank" rel="external">Dispatch</a></li>
<li><a href="https://developer.apple.com/documentation/foundation/task_management?language=objc" target="_blank" rel="external">Task Management - Operation</a></li>
<li><a href="https://github.com/apple/swift-corelibs-foundation/tree/master/Foundation" target="_blank" rel="external">swift-corelibs-foundation</a></li>
<li><a href="https://developer.apple.com/videos/play/wwdc2015/226/" target="_blank" rel="external">Advanced NSOperations</a></li>
</ol>
<a id="more"></a>
<p>无论在哪个平台,并发编程都是一个让人头疼的问题。庆幸的是,相对于服务端,客户端的并发编程简单了许多。这篇文章主要讲述一些基于 iOS 平台的一些并发编程相关东西,我写博客习惯于先介绍原理,后介绍用法,毕竟对于 API 的使用,官网有更好的
iOS 中的事件响应与处理
http://yoursite.com/2018/03/25/iOS 中的事件响应与处理/
2018-03-25T13:00:45.000Z
2018-06-03T13:14:18.611Z
<a id="more"></a>
<p>在使用 iPhone 过程中,会产生很多交互事件,例如点击、长按、摇晃、3D Touch 等。这些事件都需要 iOS 系统去响应并作出处理。这篇文章主要讲解一下系统如何去响应并处理这些事件。</p>
<h3 id="事件种类"><a href="#事件种类" class="headerlink" title="事件种类"></a>事件种类</h3><p>为满足用户需求,iOS 提供了多种事件,这里先说一下具体有哪些事件,现在脑中有一个清晰的轮廓。iOS 中的事件大致可以分为如下几类:</p>
<h4 id="1-触摸事件"><a href="#1-触摸事件" class="headerlink" title="1.触摸事件"></a>1.触摸事件</h4><p>触摸事件主要来源于人体触摸和通过 Apple Pencil (iPad) 触摸。触摸事件也分为以下几类:</p>
<ul>
<li>手势事件<ul>
<li>长按手势 (UILongPressGestureRecognizer)</li>
<li>拖动手势 (UIPanGestureRecognizer)</li>
<li>捏合手势 (UIPinchGestureRecognizer)</li>
<li>响应屏幕边缘手势 (UIScreenEdgePanGestureRecognizer)</li>
<li>轻扫手势 (UISwipeGestureRecognizer)</li>
<li>旋转手势 (UIRotationGestureRecognizer)</li>
<li>点击手势 (UITapGestureRecognizer)</li>
</ul>
</li>
<li>自定义手势</li>
<li>点击 button 相关</li>
</ul>
<h4 id="2-运动事件"><a href="#2-运动事件" class="headerlink" title="2.运动事件"></a>2.运动事件</h4><p>iPhone 内置陀螺仪、加速器和磁力仪,可以感知手机的运动情况。iOS 提供了 <a href="https://developer.apple.com/documentation/coremotion?language=objc" target="_blank" rel="external">Core Motion</a> 框架来处理这些运动事件。根据这些内置硬件,运动事件大致分为三类:</p>
<ul>
<li>陀螺仪相关:陀螺仪会测量设备绕 X-Y-Z 轴的自转速率,倾斜角度等。通过 <a href="https://developer.apple.com/documentation/coremotion?language=objc" target="_blank" rel="external">Core Motion</a> 提供的一些 API 可以获取到这些数据,并进行处理;通过系统可以通过内置陀螺仪获取设备的朝向,以此对 App UI 做出调整。</li>
<li>加速器相关:设备可以通过内置加速器测量设备在 X-Y-Z 轴速度的改变; <a href="https://developer.apple.com/documentation/coremotion?language=objc" target="_blank" rel="external">Core Motion</a> 提供了高度计(CMAltimeter)、计步器(CMPedometer) 等对象,来获取并处理这些产生的数据。</li>
<li>磁力仪相关:使用磁力仪可以获取当前设备的磁极、方向、经纬度等数据,这些数据多用于地图导航开发。</li>
</ul>
<h4 id="3-远程控制事件"><a href="#3-远程控制事件" class="headerlink" title="3.远程控制事件"></a>3.远程控制事件</h4><p>远程控制事件指通过耳机去控制手机上的一些操作。目前 iOS 仅提供我们远程控制音频和视频的权限。即对音频实现暂停/播放、上一曲/下一曲、快进/快退操作。可以在 <a href="https://developer.apple.com/documentation/uikit/uieventsubtype?language=objc" target="_blank" rel="external">UIEventSubtype</a> 中看到这些事件,一般用于开发播放器相关。</p>
<h4 id="4-按压事件"><a href="#4-按压事件" class="headerlink" title="4.按压事件"></a>4.按压事件</h4><p>iOS 9 提供了 3D Touch 事件,通过使用这个功能我们可以做如下操作:</p>
<ul>
<li>Quick Actions,重压 App icon 可以进行很多快捷操作。</li>
<li>Peek and Pop,使用这个功能对文件进行预览和其他操作,可以在手机自带 “信息” 里面试验。</li>
<li>Pressure Sensitivity,压力响应敏感,可以在备忘录中选择画笔,按压不同力度画出来的颜色深浅不一样。</li>
</ul>
<h3 id="事件响应"><a href="#事件响应" class="headerlink" title="事件响应"></a>事件响应</h3><p>当 iPhone 接收到一个事件时,处理过程大体如下:</p>
<p><img src="/uploads/iOS-event-response/iOS 事件响应.png" alt="iOS 事件响应"></p>
<ol>
<li><p>当你通过一个动作(触摸/摇晃/线控)等触发一个事件,这时候会唤起处于休眠状态的 cup。</p>
</li>
<li><p>事件会通过使用 <code>IOKit.framework</code> 来封装成 <code>IOHIDEvent</code> 对象。</p>
<blockquote>
<p><code>IOKit.framework</code> 是一个系统框架的集合,用来驱动一些系统事件。<code>IOHIDEvent</code> 中的 HID 代表 <strong>Human Interface Device,即人机交互驱动</strong>。</p>
</blockquote>
</li>
<li><p>然后系统通过 mach port(IPC 进程间通信) 将 <code>IOHIDEvent</code> 对象转发给 SpringBoard.app。</p>
</li>
<li><p>SpringBoard.app 是 iOS 系统桌面 App,它只接收按键、触摸、加速、接近传感器等几种 Event。SpringBoard.app 会找到可以响应这个事件的 App,并通过 mach port(IPC 进程间通信) 将 <code>IOHIDEvent</code> 对象转发给这个 App。</p>
</li>
<li><p>前台 App 主线程 Runloop 接收到 SpringBoard.app 转发过来的消息之后,触发对应的 mach port 的 Source1 回调 <code>__IOHIDEventSystemClientQueueCallback()</code>。</p>
</li>
<li><p>Source1 回调内部触发了 Source0 回调 <code>__UIApplicationHandleEventQueue()</code>。</p>
</li>
<li>Source0 回掉内部,将 <code>IOHIDEvent</code> 对象转化为 <code>UIEvent</code>。</li>
<li>Soucre0 回调内部调用 <code>UIApplication</code> 的 <code>+[sendEvent:]</code> 方法,将 <code>UIEvent</code> 传给<code>UIWindow</code>。</li>
</ol>
<p><code>UIWindow</code> 接收到这个事件后,开始传递事件,就是下一节要说的问题了。</p>
<h3 id="事件传递"><a href="#事件传递" class="headerlink" title="事件传递"></a>事件传递</h3><p><code>UIWindow</code> 的收到的事件,有的是通过响应链传递,找到合适的 view 进行处理的;有的是不用传递,直接用 first responder 来处理的。这里先介绍使用响应链传递的过程,之后再说不通过响应链传递的一些事件。</p>
<p>事件传递大致可以分为三个阶段:Hit-Testing(寻找合适的 view)、Recognize Gesture(响应应手势)、Response Chain(touch 事件传递)。通过手去触摸屏幕所产生的事件,都是通过这三步去传递的,例如上文所说的<strong>触摸事件</strong>和<strong>按压事件</strong>。</p>
<h4 id="1-Hit-Testing"><a href="#1-Hit-Testing" class="headerlink" title="1. Hit-Testing"></a>1. Hit-Testing</h4><p><strong>这一过程主要来确定由哪个视图来首先处理 UITouch 事件</strong>。当你点击一个 view,事件传到 UIWindow 这一步之后,会去遍历 view 层级,直至找到那个合适的 view 来处理这个事件,这一过程也叫做 <code>Hit-Testing</code>。</p>
<h5 id="遍历方式"><a href="#遍历方式" class="headerlink" title="遍历方式"></a>遍历方式</h5><p>既然遍历,就会有一定的顺序。系统会根据添加 view 的前后顺序,确定 view 在 subviews 数组中的顺序。然后根据这个顺序将视图层级转化为图层树,针对这个树,使用<strong>倒着进行前序深度遍历</strong>的算法,进行遍历。</p>
<blockquote>
<p>如果使用 storyboard 添加视图,添加顺序等同于使用 addSubview() 的方式添加视图。即先拖入的属于 subviews 数组中第 0 个元素。</p>
</blockquote>
<p>例如下面一个图层,我点击了红色箭头标注的地方:</p>
<p><img src="/uploads/iOS-event-response/view层级图.png" alt="view层级图"></p>
<p>这个图层,转化为图层树如下,同时我也将遍历顺序标记出来了:</p>
<p><img src="/uploads/iOS-event-response/View 层级树状图.png" alt="view层级树状图"></p>
<p>在上面图层树中,View A,B,C 平级,以 A,B,C 先后顺序加入。所以当我点击一个 point 的时候,会从 View C 开始遍历;判断点不在 View C 上,转向 View B;判断点在 View B 上,转向右子树 View b2;判断点不在 View b2 上,转向 View b1; 点在 View b1 上,且其没有子视图,那么 View b1 为最合适的点。</p>
<blockquote>
<p>有时候你点击一次,会发现 <code>[hitTest:withEvent:]</code> 被调用了多次,我也不清楚为什么,但是这并不影响事件传递。可能你的手指点击时有轻微移动产生了多个事件。</p>
</blockquote>
<h5 id="hitTest-withEvent-方法实现原理"><a href="#hitTest-withEvent-方法实现原理" class="headerlink" title="[hitTest:withEvent:] 方法实现原理"></a>[hitTest:withEvent:] 方法实现原理</h5><p>UIWindow 拿到事件之后,会先将事件传递给图层树中距离最靠近 UIWindow 那一层最后一个 view,然后调用其 <code>[hitTest:withEvent:]</code>。注意这里是**先将视图传递给 view,再调用其 <code>[hitTest:withEvent:]</code> 方法。并遵循这样的原则:</p>
<ul>
<li>如果点不在这个视图内,则去遍历其他视图。</li>
<li>如果点击在这个视图内,但是其还有自视图,那么将事件传递给自视图,并且调用自视图的 <code>[hitTest:withEvent:]</code>.</li>
<li>如果点击在这个视图内,并且这个视图没有子视图,那么 return self,即它就是那个最合适的视图。</li>
<li>如果点击在这个视图内,并且这个视图没有子视图,但是不想作为处理事件的 view,可以 return nil,事件由父视图处理。</li>
</ul>
<p>有几种方式,设置了之后<strong>视图和其自视图</strong>不会再接收 touch 事件。分别为:</p>
<ul>
<li>视图被隐藏:self.hidden = YES.</li>
<li>视图不允许响应交互事件:self.userInteractionEnabled = NO.</li>
<li>视图的 alpha 在 0~0.01 之间。几乎透明。</li>
</ul>
<p>综上,我们可以得出 <code>[hitTest:withEvent:]</code> 方法实现大致如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">UIView</span> *)hitTest:(<span class="built_in">CGPoint</span>)point withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="comment">// 是否响应 touch 事件</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">self</span>.isUserInteractionEnabled || <span class="keyword">self</span>.isHidden || <span class="keyword">self</span>.alpha <= <span class="number">0.01</span>) <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 点是否在 view 内</span></span><br><span class="line"> <span class="keyword">if</span> (![<span class="keyword">self</span> pointInside:point withEvent:event]) <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">UIView</span> *subview <span class="keyword">in</span> [<span class="keyword">self</span>.subviews reverseObjectEnumerator]) {</span><br><span class="line"> <span class="built_in">CGPoint</span> convertedPoint = [subview convertPoint:point fromView:<span class="keyword">self</span>];</span><br><span class="line"> <span class="comment">// point 进行坐标转化,递归调用,寻找自视图,直到返回 nil 或者 self</span></span><br><span class="line"> <span class="built_in">UIView</span> *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line"> <span class="keyword">if</span> (hitTestView) {</span><br><span class="line"> <span class="keyword">return</span> hitTestView;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="重写-hitTest-withEvent"><a href="#重写-hitTest-withEvent" class="headerlink" title="重写 [hitTest:withEvent:]"></a>重写 [hitTest:withEvent:]</h5><h6 id="当你想中断传递时"><a href="#当你想中断传递时" class="headerlink" title="当你想中断传递时"></a>当你想中断传递时</h6><p>当时想在当前 view 处理事件,不想在对 subview 进行遍历,可以直接重写 <code>[hitTest:withEvent:]</code> 方法并 return self 即可。不过一般没有这样做的,这样会影响事件传递,产生一些 bug。</p>
<blockquote>
<p>因为遍历顺序在层级树中是从上向下,但是反应到视图上面,是从里向外传,所以这种情况也可以理解为 “透传”,即你点击了 View b2,但是最终响应的是 View B。</p>
</blockquote>
<h6 id="当你想增加视图的-touch-区域"><a href="#当你想增加视图的-touch-区域" class="headerlink" title="当你想增加视图的 touch 区域"></a>当你想增加视图的 touch 区域</h6><p>在实际开发中,有些 button 面积很小,不容易点击上。这时候你想扩大 touch 响应区域。可以通过重写 <code>[hitTest:withEvent:]</code> 方法实现。例如下图中的情况:</p>
<p><img src="/uploads/iOS-event-response/扩大touch区域.png" alt="扩大touch区域"></p>
<p>实现代码如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">UIView</span> *)hitTest:(<span class="built_in">CGPoint</span>)point withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">self</span>.isUserInteractionEnabled || <span class="keyword">self</span>.isHidden || <span class="keyword">self</span>.alpha <= <span class="number">0.01</span>) <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CGFloat</span> inset = <span class="number">45.0</span>f - <span class="number">78.0</span>f;</span><br><span class="line"> <span class="built_in">CGRect</span> touchRect = <span class="built_in">CGRectInset</span>(<span class="keyword">self</span>.bounds, inset, inset);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">CGRectContainsPoint</span>(touchRect, point)) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">UIView</span> *subview <span class="keyword">in</span> [<span class="keyword">self</span>.subviews reverseObjectEnumerator]) {</span><br><span class="line"> <span class="built_in">CGPoint</span> convertedPoint = [subview convertPoint:point fromView:<span class="keyword">self</span>];</span><br><span class="line"> <span class="built_in">UIView</span> *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line"> <span class="keyword">if</span> (hitTestView) {</span><br><span class="line"> <span class="keyword">return</span> hitTestView;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>当然,你也可以通过<strong>重写父视图的 <code>[hitTest:withEvent:]</code> 方法实现</strong>。很多 App 都有这样的需求,例如自定义 UITabbar 时,中间的那个按钮一般比较大,超出了 UITabbar 高度,有时需要重写 <code>[hitTest:withEvent:]</code> 来处理响应范围。</p>
<h6 id="当你想指定某个-view-响应事件"><a href="#当你想指定某个-view-响应事件" class="headerlink" title="当你想指定某个 view 响应事件"></a>当你想指定某个 view 响应事件</h6><p>有时候在一个父视图中有多个子视图 A,B,C,无论点击 B 还是 C,你都想让 A 响应。例如 App Store 中的预览 App 页面就属于这种类型:</p>
<div align="center"><br><img src="/uploads/iOS-event-response/AppStore.PNG" width="375" height="670"><br></div>
<p>当你点击两侧边缘的时候,你想让中间的 UIScrollView 去响应,这时候可以通过重写 <code>[hitTest:withEvent:]</code> 方法实现。</p>
<p>转化为模型如下图:</p>
<p><img src="/uploads/iOS-event-response/ScorllView.png" alt="ScrollView"></p>
<p>当我点击边缘视图 B 和 C 时,我希望能够响应到 UIScrollView 上面,即可以正常滚动,这时候可以重写<strong>父视图</strong> 的 <code>[hitTest:withEvent:]</code>,指定响应 View。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">UIView</span> *)hitTest:(<span class="built_in">CGPoint</span>)point withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="built_in">UIView</span> *hitTestView = [<span class="keyword">super</span> hitTest:point withEvent:event];</span><br><span class="line"> <span class="keyword">if</span> (hitTestView) {</span><br><span class="line"> hitTestView = <span class="keyword">self</span>.scrollView;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> hitTestView;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以上即 Hit-Testing 过程相关知识,如果这一过程最终都没有找到合适的 View,那么本次事件将被丢弃。当你想改变遍历路径时,你可以考虑重写 <code>[hitTest:withEvent:]</code> 以达到你想要的结果。</p>
<h4 id="2-Gesture-Recognizer"><a href="#2-Gesture-Recognizer" class="headerlink" title="2. Gesture Recognizer"></a>2. Gesture Recognizer</h4><p>Gesture Recognizer(手势识别器)是系统封装的一些类,用来识别一系列的常见手势,例如点击、长按等。在上一步中确定了合适的 View 之后,<strong>UIWindow 会首先将 touches 事件先传递给 Gesture Recognizer,再传递给视图</strong>,这一点你可以通过自定义一个手势,并将手势添加到 View 上来验证。你会发现会先调用自定义手势中的一系列 touches 方法,再调用视图自己的一系列 touches 方法。</p>
<p>Gesture Recognizer 有一套自己的 touches 方法和状态转换机制。一个手势的响应到结束,流程如下:</p>
<p><img src="/uploads/iOS-event-response/Gesture_Recognizer状态转换.png" alt="Gesture_Recognizer状态转换"></p>
<p>系统为 Gesture Recognizer 提供了如下几种状态:</p>
<ul>
<li>UIGestureRecognizerStatePossible : 未确定状态。</li>
<li>UIGestureRecognizerStateBegan : 接收到 touches,手势开始。</li>
<li>UIGestureRecognizerStateChanged : 接收到 touches,手势改变。</li>
<li>UIGestureRecognizerStateEnded : 手势识别结束,在下个 run loop 前调用对应的 action 方法。</li>
<li>UIGestureRecognizerStateCancelled : 手势取消,恢复到 possible 状态。</li>
<li>UIGestureRecognizerStateFailed : 手势识别失败,恢复到 possible 状态。</li>
<li>UIGestureRecognizerStateRecognized : 等同于 UIGestureRecognizerStateEnded。</li>
</ul>
<p>当接收到一个系统定义的手势,首先会调用 recognizer 的 <code>[touchesBegan:withEvent:]</code> 方法,这时候 recognizer 的状态是未确定的;然后调用 <code>[touchesMoved:withEvent:]</code> 方法,依然没有识别成功;接下来要么调用 <code>[touchesEnded:withEvent:]</code> 方法,手势识别成功,调用对应的 action;要么调用 <code>[touchesCancelled:withEvent:]</code> 方法,手势识别失败。</p>
<p>官方也给出了一张比较明晰的图:</p>
<p><img src="https://docs-assets.developer.apple.com/published/7c21d852b9/08b952fe-6f46-41eb-8b8a-4830c1d48842.png" alt=""></p>
<p>大致过程如此,但是细节上还有些不同。关于状态转换过程,官方给了几篇不错的文档:</p>
<ul>
<li><a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/implementing_a_custom_gesture_recognizer/about_the_gesture_recognizer_state_machine" target="_blank" rel="external">About the Gesture Recognizer State Machine</a></li>
<li><a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/implementing_a_custom_gesture_recognizer/implementing_a_discrete_gesture_recognizer" target="_blank" rel="external">Implementing a Discrete Gesture Recognizer</a></li>
<li><a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/implementing_a_custom_gesture_recognizer/implementing_a_continuous_gesture_recognizer" target="_blank" rel="external">Implementing a Continuous Gesture Recognizer</a></li>
</ul>
<h4 id="3-Response-Chain"><a href="#3-Response-Chain" class="headerlink" title="3. Response Chain"></a>3. Response Chain</h4><p>上面也涉及到了,对于 touch 事件,系统提供了四个方法来处理:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)touchesBegan:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event;</span><br><span class="line">- (<span class="keyword">void</span>)touchesMoved:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event;</span><br><span class="line">- (<span class="keyword">void</span>)touchesEnded:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event;</span><br><span class="line">- (<span class="keyword">void</span>)touchesCancelled:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span><br><span class="line"> iOS 9.1 增加的 API,当无法获取真实的 touches 时,UIKit 会提供一个预估值,并设置到 UITouch 对应的 estimatedProperties 中监测更新。当收到新的属性更新时,会通过调用此方法来传递这些更新值。</span><br><span class="line"> </span><br><span class="line"> eg: 当使用 Apple Pencil 靠近屏幕边缘时,传感器无法感应到准确的值,此时会获取一个预估值赋给 estimatedProperties 属性。不断去更新数据,直到获取到准确的值</span><br><span class="line"> */</span></span><br><span class="line">- (<span class="keyword">void</span>)touchesEstimatedPropertiesUpdated:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">9</span>_1);</span><br></pre></td></tr></table></figure>
<p>上面的前四个方法,是由系统自动调用的。</p>
<ul>
<li>默认情况下,当发生一个事件时,view 只接收到一个 <code>UITouch</code> 对象。当你使用多个手指<strong>同时</strong>触摸是,会接收多个 <code>UITouch</code> 对象,每个手指对应一个。多个手指分开触摸,会调用多次 touches 系列方法,每个 touches 里面有一个 <code>UITouch</code> 对象。</li>
<li>如果你想处理一些额外的事件,可以重写以上四个方法,处理你想要处理的事件。之后不要忘记调用 <code>[super touchexxxx]</code> 方法,否则事件处理就中断于此 view 了,不会传递上去了。</li>
</ul>
<p><code>UITouch</code> 对象保存了事件的相关信息:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>) <span class="built_in">NSTimeInterval</span> timestamp; <span class="comment">///< 事件产生或变化时间</span></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>) <span class="built_in">UITouchPhase</span> phase; <span class="comment">///< 所处阶段</span></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>) <span class="built_in">NSUInteger</span> tapCount; <span class="comment">///< 短时间内点击屏幕次数</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/** 点击类型,直接点击、间接点击还是笔触*/</span></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>) <span class="built_in">UITouchType</span> type <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">9</span>_0);</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 使用硬件设备点击时,以点为圆心的 touch 半径,以此确定 touch 范围大小 */</span></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>) <span class="built_in">CGFloat</span> majorRadius <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">8</span>_0);</span><br><span class="line"><span class="comment">/** 半径公差 */</span></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>) <span class="built_in">CGFloat</span> majorRadiusTolerance <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">8</span>_0);</span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span>(nullable,<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>,<span class="keyword">strong</span>) <span class="built_in">UIWindow</span> *window; <span class="comment">///< 事件所属 window</span></span><br><span class="line"><span class="keyword">@property</span>(nullable,<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>,<span class="keyword">strong</span>) <span class="built_in">UIView</span> *view; <span class="comment">///< 事件所属 view</span></span><br><span class="line"><span class="comment">/** 所包含的手势识别器 */</span></span><br><span class="line"><span class="keyword">@property</span>(nullable,<span class="keyword">nonatomic</span>,<span class="keyword">readonly</span>,<span class="keyword">copy</span>) <span class="built_in">NSArray</span> <<span class="built_in">UIGestureRecognizer</span> *> *gestureRecognizers <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">3</span>_2);</span><br></pre></td></tr></table></figure>
<p>touch 事件处理的传递过程与 Hit-Testing 过程正好相反。Hit-Tesing 过程是从上向下(从父视图到子视图)遍历;touch 事件处理传递是从下向上(从子视图到父视图)传递。这也就是传说中的 <strong>Response Chain</strong>。最有机会处理事件的对象就是通过 Hit-Testing 找到的视图或者第一响应者,如果两者都能处理,则传递给下一个响应者,之后依次传递。官方给出了一个传递过程图,我就懒得画了:</p>
<p><img src="https://docs-assets.developer.apple.com/published/7c21d852b9/f17df5bc-d80b-4e17-81cf-4277b1e0f6e4.png" alt="Responder chains in an app"></p>
<blockquote>
<p>如果你不重写这几个 touches 方法,系统会通过响应链找到视图响应。如果你想做自己的事件处理操作,可以重写这几个方法。就是说,你不重写,事件处理正常传递;你重写了,处理完之后不要忘记调用 super 方法,使处理过程继续传递。</p>
</blockquote>
<h4 id="4-UIResponder"><a href="#4-UIResponder" class="headerlink" title="4.UIResponder"></a>4.UIResponder</h4><p>App 可以接收并处理很多事件,这过程中使用的是 <code>UIResponder</code> 对象来接收和处理的。<code>UIResponder</code> 类为那些需要响应比处理事件的对象定义了一组接口,使用这些接口可以处理各种花式事件。在 <code>UIKit</code> 中,<code>UIView</code>、<code>UIViewController</code> 和 <code>UIApplication</code> 这些类都是继承自 <code>UIResponder</code> 类。下面根据提供的这些接口,讲解一下这个类相关的东西。</p>
<h5 id="确定第一响应者"><a href="#确定第一响应者" class="headerlink" title="确定第一响应者"></a>确定第一响应者</h5><p>对于每个事件发生之后,系统会去找能给处理这个事件的第一响应者。根据不同的事件类型,第一响应者也不同:</p>
<ul>
<li>触摸事件:被触摸的那个 view。</li>
<li>按压事件:被聚焦按压的那个对象。</li>
<li>摇晃事件:用户或者 <code>UIKit</code> 指定的那个对象。</li>
<li>远程事件:用户或者 <code>UIKit</code> 指定的那个对象。</li>
<li>菜单编辑事件:用户或者 <code>UIKit</code> 指定的那个对象。</li>
</ul>
<blockquote>
<p>与加速计、陀螺仪、磁力仪相关的运动事件,是不遵循响应链机制传递的。Core Motion 会将事件直接传递给你所指定的第一响应者。更多信息可以查看 <a href="https://developer.apple.com/documentation/#//apple_ref/doc/uid/TP40007898-CH10-SW27" target="_blank" rel="external">Core Motion Framework</a>。</p>
</blockquote>
<p><code>UIResponder</code> 提供了几个方法(属性)来管理响应链 :</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#if UIKIT_DEFINE_AS_PROPERTIES</span><br><span class="line">@property(nonatomic, readonly, nullable) UIResponder *nextResponder;</span><br><span class="line">#else</span><br><span class="line">- (nullable UIResponder*)nextResponder;</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#if UIKIT_DEFINE_AS_PROPERTIES</span><br><span class="line">@property(nonatomic, readonly) BOOL canBecomeFirstResponder; // default is NO</span><br><span class="line">#else</span><br><span class="line">- (BOOL)canBecomeFirstResponder; // default is NO</span><br><span class="line">#endif</span><br><span class="line">- (BOOL)becomeFirstResponder;</span><br><span class="line"></span><br><span class="line">#if UIKIT_DEFINE_AS_PROPERTIES</span><br><span class="line">@property(nonatomic, readonly) BOOL canResignFirstResponder; // default is YES</span><br><span class="line">#else</span><br><span class="line">- (BOOL)canResignFirstResponder; // default is YES</span><br><span class="line">#endif</span><br><span class="line">- (BOOL)resignFirstResponder;</span><br><span class="line"></span><br><span class="line">#if UIKIT_DEFINE_AS_PROPERTIES</span><br><span class="line">@property(nonatomic, readonly) BOOL isFirstResponder;</span><br><span class="line">#else</span><br><span class="line">- (BOOL)isFirstResponder;</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure>
<ul>
<li><code>-[nextResponder]</code> 方法负责事件传递,默认返回 nil。子类必须实现此方法。例如 <code>UIView</code> 返回的是管理他的 <code>UIViewController</code> 对象或者其父视图;<code>UIViewController</code> 返回的是他的视图的父视图;<code>UIWindow</code> 返回的是 App 对象;<code>UIApplication</code> 返回的是 nil。这些在构建视图层次结构的时候就形成了。</li>
<li>使用 <code>-[isFirstResponder]</code> 来判断响应对象是否为第一响应者。</li>
<li>使用 <code>-[canBecomeFirstResponder]</code> 方法判断是否可以成为第一响应者。</li>
<li>使用 <code>-[becomeFirstResponder]</code> 方法将响应对象设置为第一响应者。</li>
</ul>
<p>对应的 <code>Resignxxxx</code> 系列方法使用场景类似。</p>
<h5 id="处理各种事件的方法"><a href="#处理各种事件的方法" class="headerlink" title="处理各种事件的方法"></a>处理各种事件的方法</h5><p><code>UIResponder</code> 定义了 touches 系列方法用来处理手势触摸事件;定义了 press 系列方法处理按压事件;定义了 motion 系列方法处理运动事件;定义了 remote 系列方法处理远程事件。可以说大部分事件都是通过这个类来处理的。这里就不详细说了。</p>
<h5 id="输入视图相关"><a href="#输入视图相关" class="headerlink" title="输入视图相关"></a>输入视图相关</h5><p>当我们使用 <code>UITextView</code> 或者 <code>UITextField</code> 时,点击视图会让其成为 fist responder,然后弹出一个视图(系统键盘 or 自定义键盘)让用户进行文本输入。在 <code>UIResponder + UIResponderInputViewAdditions</code> 这个分类中,定义了 <code>inputView</code> 和 <code>inputAccessoryView</code> 两个输入视图,样式分别如下:</p>
<p><img src="/uploads/iOS-event-response/inputView.png" alt="inputView"></p>
<p>设置了 <code>UITextView</code> 的 <code>inputView</code> 属性之后,将不再弹出键盘,弹出的是自定义的 view;设置了 <code>inputAccessoryView</code> 属性之后,将会在键盘上面显示一个自定义图,这个属性默认为 nil。</p>
<p>还有一些其他属性,与输入视图相关,这里不再详细说。</p>
<h5 id="复制粘贴相关"><a href="#复制粘贴相关" class="headerlink" title="复制粘贴相关"></a>复制粘贴相关</h5><p>在文本中选中一些文字后,会弹出一个编辑菜单,我们可以通过这些菜单进行复制、粘贴等操作。如下图是微信读书的自定义菜单:</p>
<p><img src="/uploads/iOS-event-response/复制粘贴菜单.png" alt="复制黏贴菜单"></p>
<p><code>UIResponder</code> 这个类中定义了 <code>UIResponderStandardEditActions</code> protocol,来处理复制粘贴相关事件。你可以通过重写 <code>UIResponder</code> 提供的 <code>-[canPerformAction:withSender]</code> 方法,判断 action 是否是你想要的,如果是的话,你便可以为所欲为:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">BOOL</span>)canPerformAction:(SEL)action withSender:(<span class="keyword">id</span>)sender {</span><br><span class="line"> <span class="keyword">if</span> (action == <span class="keyword">@selector</span>(<span class="keyword">copy</span>:)) {</span><br><span class="line"> <span class="comment">// 为你所欲为</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们还可以重写 <code>UIResponder</code> 提供的 <code>-[targetForAction:withSender:]</code> 方法来处理某个 action 的接收者。和上面类似:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">id</span>)targetForAction:(SEL)action withSender:(<span class="keyword">id</span>)sender {</span><br><span class="line"> <span class="keyword">if</span> (action == <span class="keyword">@selector</span>(cut:)) {</span><br><span class="line"> <span class="comment">// 为你所欲为</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> [<span class="keyword">super</span> targetForAction:action withSender:sender];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="响应键盘快捷键"><a href="#响应键盘快捷键" class="headerlink" title="响应键盘快捷键"></a>响应键盘快捷键</h5><p>iOS 7 新增加了 <code>UIResponder + UIResponderKeyCommands</code> 分类,添加了一个 <code>keyCommands</code> 属性,同时还定义了 <code>UIKeyCommands</code> 类和一系列方法。使用这些方法,我们可以处理一些键盘快捷键。没用过,不多说,了解即可。</p>
<h5 id="支持-User-Activities"><a href="#支持-User-Activities" class="headerlink" title="支持 User Activities"></a>支持 User Activities</h5><p>iOS 8 Apple 提供了 Handoff 功能,通过这个功能,用户可以在多个 Apple 设备中共同处理一件事。例如我们使用 Mac 的 Safari 浏览一些东西,因为某些事情离开,这时候我们可以使用移动设备(iPad)上的的 Safari 继续浏览。</p>
<p>Handoff 的基本思想是用户在一个应用里所做的任何操作都可以看作是一个 Activity,一个 Activity 可以和一个特定 iCloud 用户的多台设备关联起来。设备和设备之间使用 Activity 传递信息,达到共享操作。</p>
<p>为了支持这个功能,iOS 8 后新增加了 <code>UIResponder + ActivityContinuation</code> 分类,提供了一些方法来处理这些事件。对于继承自 <code>UIResponder</code> 的对象,已经为我们提供了一个 <code>userActivity</code> 属性,多个响应者可以共享这个 <code>NSUserActivity</code> 类型的属性。另外我们可以使用 <code>-[updateUserActivityState:]</code> 方法来更新这个属性;使用 <code>-[restoreUserActivityState:]</code> 方法重置这个属性的状态。</p>
<p>更秀的操作,请看 <a href="http://www.cocoachina.com/ios/20150115/10926.html" target="_blank" rel="external">iOS 8 Handoff 开发指南</a>。</p>
<p>如你所见,<code>UIResponder</code> 类提供了处理大部分事件的接口,熟练了这些接口的使用,你便可以为所欲为。</p>
<h4 id="5-不遵循-Responder-Chain-的事件"><a href="#5-不遵循-Responder-Chain-的事件" class="headerlink" title="5.不遵循 Responder Chain 的事件"></a>5.不遵循 Responder Chain 的事件</h4><p>上面也说了,与加速计、陀螺仪、磁力仪相关的运动事件,是不遵循响应链机制传递的。而是直接传递给用户指定的 frist responder。所以要将运动事件传递给一个对象,需要遵循:</p>
<ul>
<li>对象的 <code>-[canBecomeFirstResponder]</code> 方法必须返回 YES。</li>
<li>在 view controller 控制器中,在合适的地方调用对象的 <code>-[becomeFirstResponder]</code> 和 <code>-[resignFirstResponder]</code> 方法。</li>
</ul>
<p>下面是一个处理摇一摇事件的例子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义视图</span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">CustomShakeView</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - Overrid Method</span></span><br><span class="line"></span><br><span class="line">- (<span class="built_in">BOOL</span>)canBecomeFirstResponder {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)motionBegan:(<span class="built_in">UIEventSubtype</span>)motion withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="keyword">if</span> (motion == <span class="built_in">UIEventSubtypeMotionShake</span>) {</span><br><span class="line"> <span class="built_in">CGFloat</span> width = <span class="keyword">self</span>.frame.size.width;</span><br><span class="line"> <span class="built_in">CGFloat</span> height = <span class="keyword">self</span>.frame.size.height;</span><br><span class="line"> <span class="built_in">UILabel</span> *label = [[<span class="built_in">UILabel</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">0</span>, width, height)];</span><br><span class="line"> label.text = <span class="string">@"phone was shaked"</span>;</span><br><span class="line"> label.textAlignment = <span class="built_in">NSTextAlignmentCenter</span>;</span><br><span class="line"> [<span class="keyword">self</span> addSubview:label];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)motionEnded:(<span class="built_in">UIEventSubtype</span>)motion withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="comment">// nothing</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)motionCancelled:(<span class="built_in">UIEventSubtype</span>)motion withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="comment">// nothing</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 视图控制器</span></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ViewController</span> ()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) CustomShakeView *shakeView;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> <span class="keyword">self</span>.shakeView = [[CustomShakeView alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">250</span>, viewWidth, <span class="number">60</span>)];</span><br><span class="line"> <span class="keyword">self</span>.shakeView.backgroundColor = [<span class="built_in">UIColor</span> grayColor];</span><br><span class="line"> [<span class="keyword">self</span>.view addSubview:_shakeView];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewWillAppear:(<span class="built_in">BOOL</span>)animated {</span><br><span class="line"> [<span class="keyword">super</span> viewWillAppear:animated];</span><br><span class="line"> [<span class="keyword">self</span>.shakeView becomeFirstResponder];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewWillDisappear:(<span class="built_in">BOOL</span>)animated {</span><br><span class="line"> [<span class="keyword">super</span> viewWillDisappear:animated];</span><br><span class="line"> [<span class="keyword">self</span>.shakeView resignFirstResponder];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>远程控制事件与此类似,不在多说。</p>
<h3 id="各种事件的使用"><a href="#各种事件的使用" class="headerlink" title="各种事件的使用"></a>各种事件的使用</h3><p>这一章节主要是一些事件的使用 demo,基本 API 的调用,已经熟练使用的同学可以略过了。</p>
<h4 id="1-手势类使用"><a href="#1-手势类使用" class="headerlink" title="1.手势类使用"></a>1.手势类使用</h4><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 创建一个系统手势或者自定义手势,添加到一个 view 上即可。</span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> <span class="built_in">UIView</span> *customView = [<span class="built_in">UIView</span> new];</span><br><span class="line"> <span class="built_in">UITapGestureRecognizer</span> *gesture = [[<span class="built_in">UITapGestureRecognizer</span> alloc] initWithTarget:<span class="keyword">self</span> action:<span class="keyword">@selector</span>(tapAcation:)];</span><br><span class="line"> [customView addGestureRecognizer:gesture];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)tapAcation:(<span class="built_in">UIGestureRecognizer</span> *)gestureRecognizer {</span><br><span class="line"> <span class="comment">// 为所欲为</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<h4 id="2-touches-系列方法使用"><a href="#2-touches-系列方法使用" class="headerlink" title="2.touches 系列方法使用"></a>2.touches 系列方法使用</h4><p>这里是一个可以被拖动的 imageView 的例子。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">DragView</span> : <span class="title">UIImageView</span></span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">DragView</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)touchesMoved:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="comment">// 获取前后两个点,计算偏移量,然后做平移转换</span></span><br><span class="line"> <span class="built_in">UITouch</span> *touch = [touches anyObject];</span><br><span class="line"> <span class="built_in">CGPoint</span> currentPoint = [touch locationInView:<span class="keyword">self</span>];</span><br><span class="line"> <span class="built_in">CGPoint</span> previousPoint = [touch previousLocationInView:<span class="keyword">self</span>];</span><br><span class="line"> <span class="built_in">CGFloat</span> offsetX = currentPoint.x - previousPoint.x;</span><br><span class="line"> <span class="built_in">CGFloat</span> offsetY = currentPoint.y - previousPoint.y;</span><br><span class="line"> <span class="keyword">self</span>.transform = <span class="built_in">CGAffineTransformTranslate</span>(<span class="keyword">self</span>.transform, offsetX, offsetY);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> DragView *dragView = [[DragView alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">100</span>, <span class="number">100</span>, <span class="number">200</span>, <span class="number">200</span>)];</span><br><span class="line"> dragView.userInteractionEnabled = <span class="literal">YES</span>;</span><br><span class="line"> dragView.image = [<span class="built_in">UIImage</span> imageNamed:<span class="string">@"picture.jpg"</span>];</span><br><span class="line"> [<span class="keyword">self</span>.view addSubview:dragView];</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<h4 id="3-摇一摇事件(运动事件)"><a href="#3-摇一摇事件(运动事件)" class="headerlink" title="3.摇一摇事件(运动事件)"></a>3.摇一摇事件(运动事件)</h4><p>请参见上一章的最后一小节。</p>
<h4 id="4-远程控制事件"><a href="#4-远程控制事件" class="headerlink" title="4.远程控制事件"></a>4.远程控制事件</h4><p>一个可以通过耳机控制音乐播放的 view controller,主要做的几件事情我已经用注释标出。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">PlayVideoViewController</span> ()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">assign</span>, <span class="keyword">nonatomic</span>) <span class="built_in">BOOL</span> isPlaying;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>) <span class="built_in">AVAudioPlayer</span> *avAudioPlayer;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">PlayVideoViewController</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - Override Method</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="keyword">self</span>.view.backgroundColor = [<span class="built_in">UIColor</span> whiteColor];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 接收线控事件,并设置 VC 为第一响应者</span></span><br><span class="line"> [[<span class="built_in">UIApplication</span> sharedApplication] beginReceivingRemoteControlEvents];</span><br><span class="line"> [<span class="keyword">self</span> becomeFirstResponder];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 读取一个音频文件到 player 中</span></span><br><span class="line"> <span class="built_in">NSString</span> *filePath = [[<span class="built_in">NSBundle</span> mainBundle] pathForResource:<span class="string">@"周杰伦-我的地盘"</span> ofType:<span class="string">@"mp3"</span>];</span><br><span class="line"> <span class="built_in">NSURL</span> *url = [<span class="built_in">NSURL</span> fileURLWithPath:filePath];</span><br><span class="line"> <span class="keyword">self</span>.avAudioPlayer = [[<span class="built_in">AVAudioPlayer</span> alloc] initWithContentsOfURL:url error:<span class="literal">nil</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewWillDisappear:(<span class="built_in">BOOL</span>)animated {</span><br><span class="line"> <span class="comment">// 取消接收线控事件</span></span><br><span class="line"> [[<span class="built_in">UIApplication</span> sharedApplication] endReceivingRemoteControlEvents];</span><br><span class="line"> [<span class="keyword">self</span> resignFirstResponder];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 重写方法,返回 YES */</span></span><br><span class="line">- (<span class="built_in">BOOL</span>)canBecomeFirstResponder {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 实现这个方法,处理各种事件 */</span></span><br><span class="line">- (<span class="keyword">void</span>)remoteControlReceivedWithEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> <span class="keyword">switch</span> (event.subtype) {</span><br><span class="line"> <span class="keyword">case</span> <span class="built_in">UIEventSubtypeRemoteControlTogglePlayPause</span>:</span><br><span class="line"> <span class="comment">// 同时控制播放和暂停</span></span><br><span class="line"> <span class="keyword">if</span> (!_isPlaying) {</span><br><span class="line"> [_avAudioPlayer play];</span><br><span class="line"> _isPlaying = <span class="literal">YES</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> [_avAudioPlayer pause];</span><br><span class="line"> _isPlaying = <span class="literal">NO</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="built_in">UIEventSubtypeRemoteControlPlay</span>:</span><br><span class="line"> <span class="comment">// 播放</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="built_in">UIEventSubtypeRemoteControlPause</span>:</span><br><span class="line"> <span class="comment">// 暂停</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="built_in">UIEventSubtypeRemoteControlStop</span>:</span><br><span class="line"> <span class="comment">// 停止</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="built_in">UIEventSubtypeRemoteControlNextTrack</span>:</span><br><span class="line"> <span class="comment">// 下一曲</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="built_in">UIEventSubtypeRemoteControlPreviousTrack</span>:</span><br><span class="line"> <span class="comment">// 上一曲</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>初次建立这个工程,发现无论如何都不响应 <code>[remoteControlReceivedWithEvent:]</code> 方法,这时候你想工程中加入一段音频,并想办法使用代码播放一下这段音频(点击 button,调用 AVAudioPlayer 的 play) 方法,然后再重新编译应该就好了。属于玄学领域,我也不清楚为什么。</p>
</blockquote>
<h4 id="5-3D-Touch-事件"><a href="#5-3D-Touch-事件" class="headerlink" title="5.3D Touch 事件"></a>5.3D Touch 事件</h4><h5 id="Home-Screen-Quick-Actions"><a href="#Home-Screen-Quick-Actions" class="headerlink" title="Home Screen Quick Actions"></a>Home Screen Quick Actions</h5><p>使用这个功能,点击 icon 可以快速预览某些功能,并以此为入口点击进入。有两种方式来配置这个功能,一是直接使用 pilst 文件进行静态配置;另外一种是使用代码来动态配置。</p>
<h6 id="(1)使用-plist-文件配置"><a href="#(1)使用-plist-文件配置" class="headerlink" title="(1)使用 plist 文件配置"></a>(1)使用 plist 文件配置</h6><p>所有事件的数组叫做 UIApplicationShortcutItems,每个事件叫做 UIApplicationShortcutItem,每个 UIApplicationShortcutItem 中包含的信息如下:</p>
<blockquote>
<p>系统默认最多只能添加 4 个 item(不算“分享”这个 item),即使你添加了很多,最多也只显示四个。如果你想添加更多,可以效仿一下支付宝的做法,即在预览 view 中添加对应功能,这里就不贴图了。</p>
</blockquote>
<table>
<thead>
<tr>
<th>Key</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody>
<tr>
<td>UIApplicationShortcutItemType</td>
<td>事件的标识</td>
<td>YES</td>
</tr>
<tr>
<td>UIApplicationShortcutItemTitle</td>
<td>事件标题</td>
<td>YES</td>
</tr>
<tr>
<td>UIApplicationShortcutItemSubtitle</td>
<td>事件子标题</td>
<td>NO</td>
</tr>
<tr>
<td>UIApplicationShortcutItemIconType</td>
<td>系统定义的 icon 类型</td>
<td>NO</td>
</tr>
<tr>
<td>UIApplicationShortcutItemIconFile</td>
<td>icon 图片,以单一颜色,35*35 大小展示,如果设置了这个属性,UIApplicationShortcutItemIconType 属性将不起作用</td>
<td>NO</td>
</tr>
<tr>
<td>UIApplicationShortcutItemUserInfo</td>
<td>传递信息的 dictionary</td>
<td>NO</td>
</tr>
</tbody>
</table>
<p>你可以通过使用 plist 文件配置这些东西,例如下面这样:</p>
<p><img src="/uploads/iOS-event-response/plist 配置 3D Touch.png" alt="plist 配置 3D touch"></p>
<h6 id="2-使用代码动态配置"><a href="#2-使用代码动态配置" class="headerlink" title="(2) 使用代码动态配置"></a>(2) 使用代码动态配置</h6><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application didFinishLaunchingWithOptions:(<span class="built_in">NSDictionary</span> *)launchOptions {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 创建 item</span></span><br><span class="line"> <span class="built_in">UIApplicationShortcutIcon</span> *cameraIcon = [<span class="built_in">UIApplicationShortcutIcon</span> iconWithTemplateImageName:<span class="string">@"camera"</span>];</span><br><span class="line"> <span class="built_in">UIApplicationShortcutIcon</span> *mosaicIcon = [<span class="built_in">UIApplicationShortcutIcon</span> iconWithTemplateImageName:<span class="string">@"mosaic"</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">UIMutableApplicationShortcutItem</span> *cameraItem = [[<span class="built_in">UIMutableApplicationShortcutItem</span> alloc] initWithType:<span class="string">@"event://camera"</span> localizedTitle:<span class="string">@"Camera"</span> localizedSubtitle:<span class="literal">nil</span> icon:cameraIcon userInfo:<span class="literal">nil</span>];</span><br><span class="line"> <span class="built_in">UIMutableApplicationShortcutItem</span> *mosaicItem = [[<span class="built_in">UIMutableApplicationShortcutItem</span> alloc] initWithType:<span class="string">@"event://mosaic"</span> localizedTitle:<span class="string">@"Mosaic"</span> localizedSubtitle:<span class="literal">nil</span> icon:mosaicIcon userInfo:<span class="literal">nil</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 放到应用中</span></span><br><span class="line"> [<span class="built_in">UIApplication</span> sharedApplication].shortcutItems = @[cameraItem,mosaicItem];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>用上述任何一种方式添加了 item 之后,效果大概是这个样子:</p>
<p><img src="/uploads/iOS-event-response/3d_touch.PNG" alt="3dtouch"></p>
<h6 id="3-处理对应的事件"><a href="#3-处理对应的事件" class="headerlink" title="(3) 处理对应的事件"></a>(3) 处理对应的事件</h6><p>上述两种方式是配置事件入口,这里是响应对应事件。在 <code>AppDelegate</code> 中系统提供了一个代理方法:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)application:(<span class="built_in">UIApplication</span> *)application performActionForShortcutItem:(<span class="built_in">UIApplicationShortcutItem</span> *)shortcutItem completionHandler:(<span class="keyword">void</span> (^)(<span class="built_in">BOOL</span>))completionHandler {</span><br><span class="line"> <span class="keyword">if</span> (shortcutItem) {</span><br><span class="line"> <span class="keyword">if</span> ([shortcutItem.type isEqualToString:<span class="string">@"event.responser.test://camera"</span>]) {</span><br><span class="line"> <span class="comment">// 跳转到照相页面</span></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> ([shortcutItem.type isEqualToString:<span class="string">@"event.responser.test://mosaic"</span>]) {</span><br><span class="line"> <span class="comment">// 跳转到马赛克页面</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (completionHandler) {</span><br><span class="line"> completionHandler(<span class="literal">YES</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="Peek-and-Pop"><a href="#Peek-and-Pop" class="headerlink" title="Peek and Pop"></a>Peek and Pop</h5><p>只需要两步,第一步是在当前的 View Controller 中实现 <code>UIViewControllerPreviewingDelegate</code> delegate;第二部是在预览 view controller 实现 <code>previewActionItems</code> delegate。具体代码如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/** 当前 View Controller */</span></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">TableViewController</span> () <<span class="title">UIViewControllerPreviewingDelegate</span>></span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSArray</span> *dataArray;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TableViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="keyword">self</span>.dataArray = @[<span class="string">@"依然范特西"</span>,<span class="string">@"十一月的肖邦"</span>,<span class="string">@"七里香"</span>,<span class="string">@"叶惠美"</span>,<span class="string">@"八度空间"</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - Table view data source</span></span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSInteger</span>)numberOfSectionsInTableView:(<span class="built_in">UITableView</span> *)tableView {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSInteger</span>)tableView:(<span class="built_in">UITableView</span> *)tableView numberOfRowsInSection:(<span class="built_in">NSInteger</span>)section {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">5</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- (<span class="built_in">UITableViewCell</span> *)tableView:(<span class="built_in">UITableView</span> *)tableView cellForRowAtIndexPath:(<span class="built_in">NSIndexPath</span> *)indexPath {</span><br><span class="line"> <span class="built_in">UITableViewCell</span> *cell = [tableView dequeueReusableCellWithIdentifier:<span class="string">@"CellIdentifier"</span> forIndexPath:indexPath];</span><br><span class="line"> cell.textLabel.text = <span class="keyword">self</span>.dataArray[indexPath.row];</span><br><span class="line"> <span class="keyword">return</span> cell;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - UIViewControllerPreviewingDelegate</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/** peek 操作,预览模式 */</span></span><br><span class="line">- (<span class="built_in">UIViewController</span> *)previewingContext:(<span class="keyword">id</span><<span class="built_in">UIViewControllerPreviewing</span>>)previewingContext viewControllerForLocation:(<span class="built_in">CGPoint</span>)location {</span><br><span class="line"> <span class="comment">// 这里没有使用 indexPath,实际项目中,需要根据 indexPath 选择对应的 VC</span></span><br><span class="line"> <span class="built_in">NSIndexPath</span> *indexPath = [<span class="keyword">self</span>.tableView indexPathForCell:(<span class="built_in">UITableViewCell</span> *)[previewingContext sourceView]];</span><br><span class="line"> PreViewController *preViewController = [[<span class="built_in">UIStoryboard</span> storyboardWithName:<span class="string">@"Main"</span> bundle:<span class="literal">nil</span>]</span><br><span class="line"> instantiateViewControllerWithIdentifier:<span class="string">@"PreViewController"</span>];</span><br><span class="line"> preViewController.preferredContentSize = <span class="built_in">CGSizeMake</span>(<span class="number">0.0</span>f, <span class="number">400.0</span>f);</span><br><span class="line"> <span class="built_in">CGRect</span> rect = <span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">375.0</span>f, <span class="number">40</span>);</span><br><span class="line"> previewingContext.sourceRect = rect;</span><br><span class="line"> <span class="keyword">return</span> preViewController;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** pop 操作,继续按压 */</span></span><br><span class="line">- (<span class="keyword">void</span>)previewingContext:(<span class="keyword">id</span><<span class="built_in">UIViewControllerPreviewing</span>>)previewingContext commitViewController:(<span class="built_in">UIViewController</span> *)viewControllerToCommit {</span><br><span class="line"> PreViewController *preViewController = [[<span class="built_in">UIStoryboard</span> storyboardWithName:<span class="string">@"Main"</span> bundle:<span class="literal">nil</span>]</span><br><span class="line"> instantiateViewControllerWithIdentifier:<span class="string">@"PreViewController"</span>];</span><br><span class="line"> [<span class="keyword">self</span>.navigationController pushViewController:preViewController animated:<span class="literal">YES</span>];</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/** 预览 view controller */</span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">PreViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="keyword">self</span>.view.backgroundColor = [<span class="built_in">UIColor</span> clearColor];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSArray</span><<span class="keyword">id</span><<span class="built_in">UIPreviewActionItem</span>>> *)previewActionItems {</span><br><span class="line"> <span class="built_in">UIPreviewAction</span> *shareAction = [<span class="built_in">UIPreviewAction</span> actionWithTitle:<span class="string">@"分享"</span> style:<span class="built_in">UIPreviewActionStyleDefault</span> handler:^(<span class="built_in">UIPreviewAction</span> * _Nonnull action, <span class="built_in">UIViewController</span> * _Nonnull previewViewController) {</span><br><span class="line"> <span class="comment">// 分享</span></span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">UIPreviewAction</span> *markAction = [<span class="built_in">UIPreviewAction</span> actionWithTitle:<span class="string">@"标记"</span> style:<span class="built_in">UIPreviewActionStyleDefault</span> handler:^(<span class="built_in">UIPreviewAction</span> * _Nonnull action, <span class="built_in">UIViewController</span> * _Nonnull previewViewController) {</span><br><span class="line"> <span class="comment">// 标记</span></span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> @[shareAction, markAction];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>实现之后效果大概是这个样子:</p>
<p><img src="/uploads/iOS-event-response/peek_and_pop.PNG" alt="peek_and_pop"></p>
<h5 id="Force-Properties"><a href="#Force-Properties" class="headerlink" title="Force Properties"></a>Force Properties</h5><p>3D Touch 所提供的最后一个功能,就是可以感应按压力度,转化到实际应用中,就是下面这张图:</p>
<div align="center"><br><img src="https://devimages-cdn.apple.com/ios/3d-touch/images/pressure-sensitivity_2x.jpg" width="375" height="670"><br></div>
<p>根据按压程度不同,颜色有深有浅。我们可以通过 <code>UITouch</code> 对象获取到这个值,使用这个值做一些其他操作:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">-(<span class="keyword">void</span>)touchesMoved:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">NSArray</span> *arrayTouch = [touches allObjects];</span><br><span class="line"> <span class="built_in">UITouch</span> *touch = (<span class="built_in">UITouch</span> *)[arrayTouch lastObject];</span><br><span class="line"> <span class="built_in">CGFloat</span> force = touch.force;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"压力值为 %f"</span>,force);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="6-自定义手势"><a href="#6-自定义手势" class="headerlink" title="6.自定义手势"></a>6.自定义手势</h4><p>有些时候,系统提供的手势已经不能满足我们的需求了,这时候我们可以根据需要,自定义一个手势。自定义手势的一个思路就是:继承 <code>UIGestureRecognizer</code> 类,然后重写那几个 touches 方法,在里面处理手势识别器的状态,即从 began -> end 的状态。</p>
<p>下面是效仿大神,写的一个“点击对角线两个点”才能响应的手势:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_OPTIONS</span>(<span class="built_in">NSInteger</span>, TouchArea) {</span><br><span class="line"> other = <span class="number">0</span>,</span><br><span class="line"> topLeft = <span class="number">1</span>,</span><br><span class="line"> topRight = <span class="number">1</span> << <span class="number">1</span>,</span><br><span class="line"> bottomLeft = <span class="number">1</span> << <span class="number">2</span>,</span><br><span class="line"> bottomRight = <span class="number">1</span> << <span class="number">3</span>,</span><br><span class="line"> </span><br><span class="line"> bingoOne = topLeft | bottomRight,</span><br><span class="line"> bingoTwo = topRight | bottomLeft,</span><br><span class="line"> none = other,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">TapDiagonalGesture</span>()</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">assign</span>) TouchArea alreadyTouched;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableSet</span><<span class="built_in">UITouch</span> *> *trackingTouches;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableDictionary</span> <<span class="built_in">NSValue</span> *, <span class="built_in">NSNumber</span> *> *allTouchedArea;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TapDiagonalGesture</span></span></span><br><span class="line"></span><br><span class="line">- (instancetype)initWithTarget:(<span class="keyword">id</span>)target action:(SEL)action {</span><br><span class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> initWithTarget:target action:action];</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>) {</span><br><span class="line"> _trackingTouches = [<span class="built_in">NSMutableSet</span> set];</span><br><span class="line"> _allTouchedArea = [<span class="built_in">NSMutableDictionary</span> dictionary];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)touchesBegan:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> [<span class="keyword">super</span> touchesBegan:touches withEvent:event];</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">UITouch</span> *touch <span class="keyword">in</span> touches) {</span><br><span class="line"> TouchArea touchArea = [<span class="keyword">self</span> toucheAreaForPosition:[touch locationInView:<span class="keyword">self</span>.view] inView:<span class="keyword">self</span>.view];</span><br><span class="line"> <span class="keyword">if</span> (touchArea == other) {</span><br><span class="line"> <span class="keyword">self</span>.state = <span class="built_in">UIGestureRecognizerStateFailed</span>;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span>.trackingTouches addObject:touch];</span><br><span class="line"> <span class="built_in">NSValue</span> *value = [<span class="built_in">NSValue</span> valueWithNonretainedObject:touch];</span><br><span class="line"> <span class="keyword">self</span>.allTouchedArea[value] = @(touchArea);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)touchesMoved:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> [<span class="keyword">super</span> touchesMoved:touches withEvent:event];</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">UITouch</span> *touch <span class="keyword">in</span> touches) {</span><br><span class="line"> <span class="keyword">if</span> (![_trackingTouches containsObject:touch]) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSValue</span> *value = [<span class="built_in">NSValue</span> valueWithNonretainedObject:touch];</span><br><span class="line"> TouchArea touchArea = <span class="keyword">self</span>.allTouchedArea[value].integerValue;</span><br><span class="line"> TouchArea currentArea = [<span class="keyword">self</span> toucheAreaForPosition:[touch locationInView:<span class="keyword">self</span>.view] inView:<span class="keyword">self</span>.view];</span><br><span class="line"> <span class="keyword">if</span> (currentArea == other || touchArea != currentArea) {</span><br><span class="line"> <span class="keyword">self</span>.state = <span class="built_in">UIGestureRecognizerStateFailed</span>;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)touchesEnded:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> [<span class="keyword">super</span> touchesEnded:touches withEvent:event];</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">UITouch</span> *touch <span class="keyword">in</span> touches) {</span><br><span class="line"> <span class="keyword">if</span> (![_trackingTouches containsObject:touch]) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSValue</span> *value = [<span class="built_in">NSValue</span> valueWithNonretainedObject:touch];</span><br><span class="line"> TouchArea touchArea = <span class="keyword">self</span>.allTouchedArea[value].integerValue;</span><br><span class="line"> TouchArea currentArea = [<span class="keyword">self</span> toucheAreaForPosition:[touch locationInView:<span class="keyword">self</span>.view] inView:<span class="keyword">self</span>.view];</span><br><span class="line"> <span class="keyword">if</span> (currentArea == other || touchArea != currentArea) {</span><br><span class="line"> <span class="keyword">self</span>.state = <span class="built_in">UIGestureRecognizerStateFailed</span>;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span>.trackingTouches removeObject:touch];</span><br><span class="line"> <span class="keyword">self</span>.allTouchedArea[value] = <span class="literal">nil</span>;</span><br><span class="line"> <span class="keyword">self</span>.alreadyTouched |= currentArea;</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>.alreadyTouched == bingoOne ||</span><br><span class="line"> <span class="keyword">self</span>.alreadyTouched == bingoTwo) {</span><br><span class="line"> <span class="keyword">self</span>.state = <span class="built_in">UIGestureRecognizerStateRecognized</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)touchesCancelled:(<span class="built_in">NSSet</span><<span class="built_in">UITouch</span> *> *)touches withEvent:(<span class="built_in">UIEvent</span> *)event {</span><br><span class="line"> [<span class="keyword">super</span> touchesCancelled:touches withEvent:event];</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">UITouch</span> *touch <span class="keyword">in</span> touches) {</span><br><span class="line"> <span class="keyword">if</span> (![_trackingTouches containsObject:touch]) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">self</span>.state = <span class="built_in">UIGestureRecognizerStateCancelled</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)reset {</span><br><span class="line"> [<span class="keyword">super</span> reset];</span><br><span class="line"> [<span class="keyword">self</span>.trackingTouches removeAllObjects];</span><br><span class="line"> [<span class="keyword">self</span>.allTouchedArea removeAllObjects];</span><br><span class="line"> <span class="keyword">self</span>.alreadyTouched = none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">BOOL</span>)shouldBeRequiredToFailByGestureRecognizer:(<span class="built_in">UIGestureRecognizer</span> *)otherGestureRecognizer {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - Private Method</span></span><br><span class="line"></span><br><span class="line">- (TouchArea)toucheAreaForPosition:(<span class="built_in">CGPoint</span>)point inView:(<span class="built_in">UIView</span> *)view {</span><br><span class="line"> <span class="built_in">CGPoint</span> origin = view.bounds.origin;</span><br><span class="line"> <span class="built_in">CGSize</span> size = view.frame.size;</span><br><span class="line"> <span class="keyword">int</span> horizontoalArea = [<span class="keyword">self</span> areaForValue:point.x rangeBegin:origin.x rangeLength:size.width];</span><br><span class="line"> <span class="keyword">int</span> verticalArea = [<span class="keyword">self</span> areaForValue:point.y rangeBegin:origin.y rangeLength:size.height];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (horizontoalArea == <span class="number">0</span> || verticalArea == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> other;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> shifts = (horizontoalArea > <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>) + (verticalArea > <span class="number">0</span> ? <span class="number">2</span> : <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> << shifts;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">int</span>)areaForValue:(<span class="built_in">CGFloat</span>)value</span><br><span class="line"> rangeBegin:(<span class="built_in">CGFloat</span>)rangeBegin</span><br><span class="line"> rangeLength:(<span class="built_in">CGFloat</span>)rangeLength {</span><br><span class="line"> <span class="built_in">CGFloat</span> threadShold = MAX(<span class="number">40</span>, rangeLength / <span class="number">3</span>);</span><br><span class="line"> <span class="keyword">if</span> (rangeLength < threadShold * <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (value <= rangeBegin + threadShold) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (value >= rangeBegin + rangeLength - threadShold) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>在一个 view 上面添加这个手势之后,同时点击这个 view 对角线两个点(左上 & 右下;左下 & 右上),便会响应对应的 action。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>上面讲述了大部分事件以及其原理,了解了之后,对我们的开发很有帮助。当然,iOS 11 新增了 Drag and Drop 功能,这个功能大多在 Mac 或者 iPad 上面用,在 iPhone 上也可以使用,但使用的功能有限,这里就不多说了。</p>
<p>针对上面的内容,有问题可以提出,我会尽快修改。</p>
<h4 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h4><ol>
<li><a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures?language=objc" target="_blank" rel="external">Touches, Presses, and Gestures</a></li>
<li><a href="https://zhongwuzw.github.io/2016/09/12/iOS%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E4%B9%8BHit-Testing/" target="_blank" rel="external">iOS事件处理之Hit-Testing</a></li>
<li><a href="http://southpeak.github.io/2015/03/07/cocoa-uikit-uiresponder/" target="_blank" rel="external">UIKit: UIResponder</a></li>
</ol>
<a id="more"></a>
<p>在使用 iPhone 过程中,会产生很多交互事件,例如点击、长按、摇晃、3D Touch 等。这些事件都需要 iOS 系统去响应并作出处理。这篇文章主要讲解一下系统如何去响应并处理这些事件。</p>
<h3 id="事件种类"><a hr
按下 ⌘ + R 后发生的事情
http://yoursite.com/2018/03/10/当你按下 ⌘ + R 后发生的事情/
2018-03-10T14:03:50.000Z
2018-06-03T13:13:03.934Z
<a id="more"></a>
<p>作为一名 coder,每天的工作不是解 bug,就是写 bug。有些东西,了解了并不一定有利于写 bug,但是有利于解 bug。</p>
<p>对于一个工程,当你按下 <code>⌘ + R</code> 到主界面显示出来,你可曾想过这一过程发生了哪些事情?这些原理性的东西,对我们 coding 并没有直接帮助,了解与否都可以 coding。但是一个 coder 的工作不只是 coding,还有 debug。了解这些东西,对我们排查一些问题很有帮助。</p>
<p>按照阶段划分,这一过程大致可以划为三个阶段:<strong>编译阶段</strong>、<strong>APP 启动阶段</strong>、<strong>图层渲染阶段</strong>。下面针对这三个过程进行详细描述。</p>
<h3 id="编译阶段"><a href="#编译阶段" class="headerlink" title="编译阶段"></a>编译阶段</h3><p>学过编译原理的同学都应该知道,编译主要分为四个过程:预处理、编译、汇编、链接。下面大致也是按照这个路子来。iOS 编译过程,使用的 clang 做前端,LLVM 作为后端进行完成的。使用 clang 处理前几个阶段,LLVM 处理后面几个阶段。</p>
<h4 id="1-预处理"><a href="#1-预处理" class="headerlink" title="1.预处理"></a>1.预处理</h4><p>又称为预编译,主要做一些文本替换工作。处理 <code>#</code> 开头的指令,例如:</p>
<ul>
<li>宏定义的展开 (#define)</li>
<li>头文件展开 (#include,#import)</li>
<li>处理条件编译指令 (#if,#else,#endif)</li>
</ul>
<p>例如我们在代码中定义了如下宏:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#define APP_VERSION <span class="string">"V1.0.0"</span></span><br><span class="line"></span><br><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> char *version <span class="built_in">=</span> APP_VERSION;</span><br><span class="line"> printf(<span class="string">"app version is %s"</span>,version);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用 <code>clang -E main.m</code> 进行宏展开的预处理结果如下:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> char *version <span class="built_in">=</span> <span class="string">"V1.0.0"</span>;</span><br><span class="line"> printf(<span class="string">"version is %s"</span>,version);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>宏的使用有很多坑,尽量用其他方式代替。</p>
<h4 id="2-词法分析"><a href="#2-词法分析" class="headerlink" title="2.词法分析"></a>2.词法分析</h4><p>完成预处理后,词法分析器(也叫扫描器)会对 .m 中的源代码进行从左到右扫描,按照语言的词法规则识别各类单词、关键字,并生成对应的单词的属性字。例如下面一段代码:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#define APP_VERSION <span class="string">"V1.0.0"</span></span><br><span class="line"></span><br><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> char *version <span class="built_in">=</span> APP_VERSION;</span><br><span class="line"> printf(<span class="string">"version is %s"</span>,version);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>经过预处理阶段,然后使用 clang 命令 <code>clang -Xclang -dump-tokens main.m</code> 进行扫描分析,导出结果如下:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">int <span class="string">'int'</span> [StartOfLine] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">1</span>></span><br><span class="line">identifier <span class="string">'main'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">5</span>></span><br><span class="line">l_paren <span class="string">'('</span> Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">9</span>></span><br><span class="line">int <span class="string">'int'</span> Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">10</span>></span><br><span class="line">identifier <span class="string">'argc'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">14</span>></span><br><span class="line">comma <span class="string">','</span> Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">18</span>></span><br><span class="line">char <span class="string">'char'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">20</span>></span><br><span class="line">star <span class="string">'*'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">25</span>></span><br><span class="line">identifier <span class="string">'argv'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">27</span>></span><br><span class="line">l_square <span class="string">'['</span> Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">31</span>></span><br><span class="line">r_square <span class="string">']'</span> Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">32</span>></span><br><span class="line">r_paren <span class="string">')'</span> Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">33</span>></span><br><span class="line">l_brace <span class="string">'{'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">14</span>:<span class="number">35</span>></span><br><span class="line">char <span class="string">'char'</span> [StartOfLine] [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">18</span>:<span class="number">5</span>></span><br><span class="line">star <span class="string">'*'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">18</span>:<span class="number">10</span>></span><br><span class="line">identifier <span class="string">'version'</span> Loc<span class="built_in">=</span><main.m:<span class="number">18</span>:<span class="number">11</span>></span><br><span class="line">equal <span class="string">'='</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">18</span>:<span class="number">19</span>></span><br><span class="line">string_literal <span class="string">'"V1.0.0"'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">18</span>:<span class="number">21</span> <Spelling<span class="built_in">=</span>main.m:<span class="number">12</span>:<span class="number">21</span>>></span><br><span class="line">semi <span class="string">';'</span> Loc<span class="built_in">=</span><main.m:<span class="number">18</span>:<span class="number">32</span>></span><br><span class="line">identifier <span class="string">'printf'</span> [StartOfLine] [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">5</span>></span><br><span class="line">l_paren <span class="string">'('</span> Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">11</span>></span><br><span class="line">string_literal <span class="string">'"version is %s"'</span> Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">12</span>></span><br><span class="line">comma <span class="string">','</span> Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">27</span>></span><br><span class="line">identifier <span class="string">'version'</span> Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">28</span>></span><br><span class="line">r_paren <span class="string">')'</span> Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">35</span>></span><br><span class="line">semi <span class="string">';'</span> Loc<span class="built_in">=</span><main.m:<span class="number">19</span>:<span class="number">36</span>></span><br><span class="line">return <span class="string">'return'</span> [StartOfLine] [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">20</span>:<span class="number">5</span>></span><br><span class="line">numeric_constant <span class="string">'0'</span> [LeadingSpace] Loc<span class="built_in">=</span><main.m:<span class="number">20</span>:<span class="number">12</span>></span><br><span class="line">semi <span class="string">';'</span> Loc<span class="built_in">=</span><main.m:<span class="number">20</span>:<span class="number">13</span>></span><br><span class="line">r_brace <span class="string">'}'</span> [StartOfLine] Loc<span class="built_in">=</span><main.m:<span class="number">21</span>:<span class="number">1</span>></span><br><span class="line">eof <span class="string">''</span> Loc<span class="built_in">=</span><main.m:<span class="number">21</span>:<span class="number">2</span>></span><br></pre></td></tr></table></figure>
<p>从上面可以看出每个单词或者字符,都标记出了具体列数和行数,这样如果在编译过程中遇到什么问题,clang 可以快速定位错误在代码中的位置。</p>
<h4 id="3-语法分析"><a href="#3-语法分析" class="headerlink" title="3.语法分析"></a>3.语法分析</h4><p>接下来是进行语法分析。通过这一阶段,会将上一阶段的导出的结果解析成一棵抽象语法树(<a href="http://clang.llvm.org/docs/IntroductionToTheClangAST.html" target="_blank" rel="external">abstract syntax tree – AST</a>)。假设我们的源代码如下,并且已经经过了预处理:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#define APP_VERSION <span class="string">"V1.0.0"</span></span><br><span class="line"></span><br><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> char *version <span class="built_in">=</span> APP_VERSION;</span><br><span class="line"> printf(<span class="string">"version is %s"</span>,version);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用 clang 命令 <code>clang -Xclang -ast-dump -fsyntax-only mian.m</code> 处理过后,输入的语法树如下:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">FunctionDecl 0x7ffe55884228 <main.m:14:1, line:21:1> line:14:5 main 'int (int, char **)'</span><br><span class="line"> |-ParmVarDecl 0x7ffe55884028 <col:10, col:14> col:14 argc 'int'</span><br><span class="line"> |-ParmVarDecl 0x7ffe55884110 <col:20, col:32> col:27 argv 'char **':'char **'</span><br><span class="line"> `-CompoundStmt 0x7ffe55884568 <col:35, line:21:1></span><br><span class="line"> |-DeclStmt 0x7ffe55884390 <line:18:5, col:32></span><br><span class="line"> | `-VarDecl 0x7ffe558842e8 <col:5, line:12:21> line:18:11 used version 'char *' cinit</span><br><span class="line"> | `-ImplicitCastExpr 0x7ffe55884378 <line:12:21> 'char *' <ArrayToPointerDecay></span><br><span class="line"> | `-StringLiteral 0x7ffe55884348 <col:21> 'char [7]' lvalue "V1.0.0"</span><br><span class="line"> |-CallExpr 0x7ffe558844b0 <line:19:5, col:35> 'int'</span><br><span class="line"> | |-ImplicitCastExpr 0x7ffe55884498 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay></span><br><span class="line"> | | `-DeclRefExpr 0x7ffe558843a8 <col:5> 'int (const char *, ...)' Function 0x7ffe55088570 'printf' 'int (const char *, ...)'</span><br><span class="line"> | |-ImplicitCastExpr 0x7ffe55884500 <col:12> 'const char *' <BitCast></span><br><span class="line"> | | `-ImplicitCastExpr 0x7ffe558844e8 <col:12> 'char *' <ArrayToPointerDecay></span><br><span class="line"> | | `-StringLiteral 0x7ffe55884408 <col:12> 'char [14]' lvalue "version is %s"</span><br><span class="line"> | `-ImplicitCastExpr 0x7ffe55884518 <col:28> 'char *' <LValueToRValue></span><br><span class="line"> | `-DeclRefExpr 0x7ffe55884440 <col:28> 'char *' lvalue Var 0x7ffe558842e8 'version' 'char *'</span><br><span class="line"> `-ReturnStmt 0x7ffe55884550 <line:20:5, col:12></span><br><span class="line"> `-IntegerLiteral 0x7ffe55884530 <col:12> 'int' 0</span><br></pre></td></tr></table></figure>
<p>抽象语法树中每一个节点也标记出了在源码中的具体位置,便于问题定位。抽象语法树的相关知识有很多,这里就不详细解释了。</p>
<h4 id="4-静态分析"><a href="#4-静态分析" class="headerlink" title="4.静态分析"></a>4.静态分析</h4><p>把源码转化为抽象语法树之后,编译器就可以对这个树进行分析处理。静态分析会对代码进行错误检查,如出现方法被调用但是未定义、定义但是未使用的变量等,以此提高代码质量。当然,还可以通过使用 Xcode 自带的静态分析工具(Product -> Analyze)或者一些第三方的静态分析工具(例如 Facebook 的 <a href="https://infer.liaohuqiu.net/" target="_blank" rel="external">infer</a>进行深度分析。</p>
<p>有时候编译器自带的静态分析,并不能满足我们的日常开发需求。因此我们可以通过使用脚本定制一套分析方案,放到集成环境中。每次提交代码时,会触发脚本进行静态分析,如果出现错误边报出警告,并且提交代码失败。依次太高开发质量。</p>
<p>如果有兴趣,可以看一下 <a href="https://github.com/llvm-mirror/clang/tree/master/lib/StaticAnalyzer" target="_blank" rel="external">clang 静态分析源码</a>,看其中对哪些语法做了静态分析。</p>
<h4 id="5-生成代码和优化"><a href="#5-生成代码和优化" class="headerlink" title="5.生成代码和优化"></a>5.生成代码和优化</h4><p>使用 clang 完成预处理和分析之后,接着会生成 LLVM 代码。还是之前那段代码:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#define APP_VERSION <span class="string">"V1.0.0"</span></span><br><span class="line"></span><br><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> char *version <span class="built_in">=</span> APP_VERSION;</span><br><span class="line"> printf(<span class="string">"version is %s"</span>,version);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们可以用 clang 命令 <code>clang -O3 -S -emit-llvm main.m -o main.ll</code> 进行转化,然后打开之后看到内容如下:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">; ModuleID <span class="built_in">=</span> <span class="string">'main.m'</span></span><br><span class="line">source_filename <span class="built_in">=</span> <span class="string">"main.m"</span></span><br><span class="line">target datalayout <span class="built_in">=</span> <span class="string">"e-m:o-i64:64-f80:128-n8:16:32:64-S128"</span></span><br><span class="line">target triple <span class="built_in">=</span> <span class="string">"x86_64-apple-macosx10.13.0"</span></span><br><span class="line"></span><br><span class="line">@.str <span class="built_in">=</span> private unnamed_addr constant [<span class="number">7</span> x i8] c<span class="string">"V1.0.0\00"</span>, align <span class="number">1</span></span><br><span class="line">@.str.<span class="number">1</span> <span class="built_in">=</span> private unnamed_addr constant [<span class="number">14</span> x i8] c<span class="string">"version is %s\00"</span>, align <span class="number">1</span></span><br><span class="line"></span><br><span class="line">; Function Attrs: nounwind ssp uwtable</span><br><span class="line"></span><br><span class="line">// main 方法</span><br><span class="line">define i32 @main(i32, i8** nocapture readnone) local_unnamed_addr #<span class="number">0</span> {</span><br><span class="line"> <span class="comment">%3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.1, i64 0, i64 0), i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0))</span></span><br><span class="line"> ret i32 <span class="number">0</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">; Function Attrs: nounwind</span><br><span class="line">declare i32 @printf(i8* nocapture readonly, ...) local_unnamed_addr #<span class="number">1</span></span><br><span class="line"></span><br><span class="line">attributes #<span class="number">0</span> <span class="built_in">=</span> { nounwind ssp uwtable <span class="string">"correctly-rounded-divide-sqrt-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"disable-tail-calls"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"less-precise-fpmad"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-frame-pointer-elim"</span><span class="built_in">=</span><span class="string">"true"</span> <span class="string">"no-frame-pointer-elim-non-leaf"</span> <span class="string">"no-infs-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-jump-tables"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-nans-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-signed-zeros-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-trapping-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"stack-protector-buffer-size"</span><span class="built_in">=</span><span class="string">"8"</span> <span class="string">"target-cpu"</span><span class="built_in">=</span><span class="string">"penryn"</span> <span class="string">"target-features"</span><span class="built_in">=</span><span class="string">"+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87"</span> <span class="string">"unsafe-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"use-soft-float"</span><span class="built_in">=</span><span class="string">"false"</span> }</span><br><span class="line">attributes #<span class="number">1</span> <span class="built_in">=</span> { nounwind <span class="string">"correctly-rounded-divide-sqrt-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"disable-tail-calls"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"less-precise-fpmad"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-frame-pointer-elim"</span><span class="built_in">=</span><span class="string">"true"</span> <span class="string">"no-frame-pointer-elim-non-leaf"</span> <span class="string">"no-infs-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-nans-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-signed-zeros-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"no-trapping-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"stack-protector-buffer-size"</span><span class="built_in">=</span><span class="string">"8"</span> <span class="string">"target-cpu"</span><span class="built_in">=</span><span class="string">"penryn"</span> <span class="string">"target-features"</span><span class="built_in">=</span><span class="string">"+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87"</span> <span class="string">"unsafe-fp-math"</span><span class="built_in">=</span><span class="string">"false"</span> <span class="string">"use-soft-float"</span><span class="built_in">=</span><span class="string">"false"</span> }</span><br><span class="line"></span><br><span class="line">!llvm.<span class="keyword">module</span>.flags <span class="built_in">=</span> !{!<span class="number">0</span>, !<span class="number">1</span>, !<span class="number">2</span>, !<span class="number">3</span>, !<span class="number">4</span>, !<span class="number">5</span>}</span><br><span class="line">!llvm.ident <span class="built_in">=</span> !{!<span class="number">6</span>}</span><br><span class="line"></span><br><span class="line">!<span class="number">0</span> <span class="built_in">=</span> !{i32 <span class="number">1</span>, !<span class="string">"Objective-C Version"</span>, i32 <span class="number">2</span>}</span><br><span class="line">!<span class="number">1</span> <span class="built_in">=</span> !{i32 <span class="number">1</span>, !<span class="string">"Objective-C Image Info Version"</span>, i32 <span class="number">0</span>}</span><br><span class="line">!<span class="number">2</span> <span class="built_in">=</span> !{i32 <span class="number">1</span>, !<span class="string">"Objective-C Image Info Section"</span>, !<span class="string">"__DATA, __objc_imageinfo, regular, no_dead_strip"</span>}</span><br><span class="line">!<span class="number">3</span> <span class="built_in">=</span> !{i32 <span class="number">4</span>, !<span class="string">"Objective-C Garbage Collection"</span>, i32 <span class="number">0</span>}</span><br><span class="line">!<span class="number">4</span> <span class="built_in">=</span> !{i32 <span class="number">1</span>, !<span class="string">"Objective-C Class Properties"</span>, i32 <span class="number">64</span>}</span><br><span class="line">!<span class="number">5</span> <span class="built_in">=</span> !{i32 <span class="number">1</span>, !<span class="string">"PIC Level"</span>, i32 <span class="number">2</span>}</span><br><span class="line">!<span class="number">6</span> <span class="built_in">=</span> !{!<span class="string">"Apple LLVM version 9.0.0 (clang-900.0.39.2)"</span>}</span><br></pre></td></tr></table></figure>
<p>可以简单看一下 main 方法,看不懂无所谓,我也看不懂。只是了解这个过程就可以了。</p>
<p>接下来 LLVM 会对代码进行编译优化,例如针对全局变量优化、循环优化、尾递归优化等,这些我了解的不是太多,所以不能乱说。想要了解的同学,可以看一下这篇文章:<a href="http://blog.csdn.net/dashuniuniu/article/details/50385528" target="_blank" rel="external">《LLVM 全时优化》</a>。</p>
<p>最后就是输出汇编代码。</p>
<h4 id="6-汇编"><a href="#6-汇编" class="headerlink" title="6.汇编"></a>6.汇编</h4><p>在这一阶段,汇编器将可读的汇编代码转化为机器代码。最终产物就是 <strong>以 .o 结尾的目标文件</strong>。</p>
<p>针对下部分代码:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#define APP_VERSION <span class="string">"V1.0.0"</span></span><br><span class="line"></span><br><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> char *version <span class="built_in">=</span> APP_VERSION;</span><br><span class="line"> printf(<span class="string">"version is %s"</span>,version);</span><br><span class="line"> return <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们可以使用 clang 命令 <code>clang -c main.m</code> 生成目标文件 mian.o。我就不写打开后的内容了,都是二进制,也看不懂。</p>
<h4 id="7-链接"><a href="#7-链接" class="headerlink" title="7.链接"></a>7.链接</h4><p>这一阶段是将上个阶段生成的<strong>目标文件</strong>和引用的<strong>静态库</strong>链接起来,最终生成可执行文件。</p>
<p>我们可以用 clang 命令 <code>clang main.m</code> 生成可执行文件 a.out (不指定名字默认命名为 a.out)。然后使用 <code>file a.out</code> 命令查看其类型:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a.out: Mach-O <span class="number">64</span>-bit executable x86_64</span><br></pre></td></tr></table></figure>
<p>可以看出可执行文件类型为 <code>Mach-O</code> 类型,在 MAC OS 和 iOS 平台的可执行文件都是这种类型。因为我使用的是模拟器,所以处理器指令集为 <code>x86_64</code>。</p>
<p>至此编译阶段完成。</p>
<h4 id="8-Xcode-中一次完整的-build"><a href="#8-Xcode-中一次完整的-build" class="headerlink" title="8.Xcode 中一次完整的 build"></a>8.Xcode 中一次完整的 build</h4><p>最后我们先来看一下 Xcode 中的 build 日志,完整的看一遍这个过程。打开 Xcode 的 Log Navigator,选中 Build 这一项我们可以看到这次 build 的日志:</p>
<p><img src="/uploads/compile-iOS/build_log.png" alt="build_log"></p>
<p>日志是按照 target 进行分段的。当前工程中,通过 Pod 引入了 <code>YYCache</code>、<code>YYImage</code>、<code>AFNetworking</code> 三个库,除此之外还有一个 <code>Pods-Test</code> 和项目本身的 target。每个 target 之间的日志格式都是一样的,因此我们只针对一个 target 进行分析。这里只针对项目本身 target,也就是 <code>Test</code> 进行分析。也就是下面这个样子:</p>
<p><img src="/uploads/compile-iOS/test_build_log.png" alt="test_build_log"></p>
<p>看着很乱套,整理完之后,屡一下大概是这个流程:</p>
<ol>
<li>编译信息写入辅助文件,创建编译后的文件架构 (test.app)。</li>
<li>处理打包信息。</li>
<li>执行 CocoaPods 编译前脚本。例如这里的 <code>Check Pods Manifest.lock</code>。</li>
<li>编译各种 .m 文件(.h 文件不参与编译)。</li>
<li>链接所需要的 framework。</li>
<li>编译 ImageAssets。</li>
<li>编译 Storyboard 等相关文件。</li>
<li>处理 info.plist 文件。</li>
<li>链接 Storyboards。</li>
<li>执行 CocoaPods 相关脚本,可以在 Build Phases 中查看这些脚本。</li>
<li>创建 .app 文件。</li>
<li>对 .app 文件进行签名</li>
</ol>
<p>这里我们针对第 4 步详细说一下。我们选取其中一个文件 <code>ViewController.m</code> 的日志进行分析:</p>
<p><img src="/uploads/compile-iOS/viewcontroller_build_log.png" alt="viewcontroller_build_log"></p>
<p>将 log 信息整理一下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">1. CompileC /.../Test.build/Objects-normal/x86_64/ViewController.o Test/ViewController.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler</span><br><span class="line"></span><br><span class="line">2. cd /Users/zhoubo/Test</span><br><span class="line">3. export LANG=en_US.US-ASCII</span><br><span class="line"> export PATH="/Applications/Xcode.app/Contents/Developer/../sbin"</span><br><span class="line">4. clang -x objective-c </span><br><span class="line"> -arch x86_64 -fmessage-length=0...</span><br><span class="line"> -fobjc-arc...</span><br><span class="line"> -Wno-missing-field-initializers...</span><br><span class="line"> -DDEBUG=1...</span><br><span class="line"> -isysroot .../iPhoneSimulator11.2.sdk</span><br><span class="line"> -I ONE PATH</span><br><span class="line"> -F ONE PATH</span><br><span class="line"> -c /../ViewController.m</span><br><span class="line"> -o /../ViewController.o</span><br></pre></td></tr></table></figure>
<p>对应解释如下:</p>
<ol>
<li>通过 log 表述任务起点。</li>
<li>进入对应工作目录。</li>
<li>对 LANG 和 PATH 环境变量执行设置。</li>
<li><p>clang 命令开始:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">-x : 所使用语言,此处为 Objective-C</span><br><span class="line">-arch x86_64 : 处理器指令集为 x86_64</span><br><span class="line">-fobjc-arc : 一系列以 -f 开头,指定此文件使用 ARC 环境。你可以通过 Build Phases 设置对每个文件是否支持 ARC。</span><br><span class="line">-Wno-missing-field-initializers : 一系列以 -w 开头指令,编译警告选项,可以通过这个指令定制编译选项</span><br><span class="line">-DDEBUG<span class="built_in">=</span><span class="number">1</span> : 一些以 -D 开头的,指的是预编译宏。</span><br><span class="line">-isysroot .../iPhoneSimulator11.<span class="number">2</span>.sdk : 编译时采用的 iOS SDK 版本。</span><br><span class="line">-I : 把编译信息写入文件</span><br><span class="line">-F : 链接过程中所需要的 framework</span><br><span class="line">-c : 编译文件</span><br><span class="line">-o : 编译中间产物</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h4 id="9-关于-dSYM-文件"><a href="#9-关于-dSYM-文件" class="headerlink" title="9.关于 dSYM 文件"></a>9.关于 dSYM 文件</h4><p>每次我们编译过后,都会生成一个 dSYM 文件。这个文件中,存储了 16 进制的函数地址映射表。在 APP 执行的二进制文件中,是通过地址来调用方法的。当发生了 crash,可以通过 dSYM 文件进行地址映射,找到具体的函数调用栈。</p>
<h3 id="App-启动阶段"><a href="#App-启动阶段" class="headerlink" title="App 启动阶段"></a>App 启动阶段</h3><p>上个阶段,最终产物为<strong>可执行文件</strong>,文件格式为 <a href="https://en.wikipedia.org/wiki/Mach-o" target="_blank" rel="external">Mach-o</a>。这一阶段,就以这个文件开始,详细描述一下 APP 启动过程。</p>
<h4 id="1-过程概览"><a href="#1-过程概览" class="headerlink" title="1.过程概览"></a>1.过程概览</h4><p>这一过程分为多个阶段,简单梳理一下,可以使大脑有一个清晰的脑回路,不至于越看越懵逼。</p>
<ul>
<li>系统准备阶段。</li>
<li>将 dyld 加载到 App 进程中 (Dyld)。</li>
<li>加载 App 所需要的动态库 (Load Dylibs)。</li>
<li>Rebase & Bind。</li>
<li>Objc setup。</li>
<li>Initializers。</li>
<li>mian()。</li>
</ul>
<p>官方的一张流程图:</p>
<p><img src="/uploads/compile-iOS/加载过程.png" alt="加载过程"></p>
<h4 id="2-概念解释"><a href="#2-概念解释" class="headerlink" title="2.概念解释"></a>2.概念解释</h4><p>在讲述整个过程之前,先解释两个概念:<strong>Mach-O 文件</strong> 和 <strong>dyld</strong>。</p>
<h5 id="Mach-O"><a href="#Mach-O" class="headerlink" title=".Mach-O"></a>.Mach-O</h5><p>Mach-O 是一种文件格式,主要用于 iOS、MacOS、WatchOS 等 Apple 操作系统。这种文件格式可用于一下几种文件:</p>
<ul>
<li>可以行文件 (Mach-O Executable)</li>
<li>Dylib 动态库</li>
<li>Bundle 无法被连接的动态库,只能通过 dlopen() 加载</li>
<li>Image,这里指的是 Executable,Dylib 或者 Bundle 的一种,下文中会提到。</li>
<li>Framework 动态库和对应的头文件和资源文件的集合。</li>
</ul>
<p>Mach-O 文件的格式如下:</p>
<p><img src="/uploads/compile-iOS/mach-o.jpg" alt="Mach-O文件格式"></p>
<ul>
<li>Header,包含文件的 CPU 架构,例如 x86,arm7,arm64 等。</li>
<li>Load commands,包含文件的组织架构和在虚拟内存布局方式。</li>
<li>Data,包含 Load commands 中需要的各个 segment,每个 segment 中又包含多个 section。当运行一个可执行文件时,虚拟内存 (virtual memory) 系统将 segment 映射到进程的地址空间上。</li>
</ul>
<p>上个阶段中我们知道如何产生可执行文件(a.out),这里我们可以用 size 工具来查看这个可执行文件的 segment 内容,执行如下命令:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xcrun size -x -l -m a.out</span><br></pre></td></tr></table></figure>
<p>可以得到如下结果:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Segment __PAGEZERO: <span class="number">0</span>x100000000 (vmaddr <span class="number">0</span>x0 fileoff <span class="number">0</span>)</span><br><span class="line">Segment __TEXT: <span class="number">0</span>x1000 (vmaddr <span class="number">0</span>x100000000 fileoff <span class="number">0</span>)</span><br><span class="line"> Section __text: <span class="number">0</span>x43 (addr <span class="number">0</span>x100000f30 offset <span class="number">3888</span>)</span><br><span class="line"> Section __stubs: <span class="number">0</span>x6 (addr <span class="number">0</span>x100000f74 offset <span class="number">3956</span>)</span><br><span class="line"> Section __stub_helper: <span class="number">0</span>x1a (addr <span class="number">0</span>x100000f7c offset <span class="number">3964</span>)</span><br><span class="line"> Section __cstring: <span class="number">0</span>x15 (addr <span class="number">0</span>x100000f96 offset <span class="number">3990</span>)</span><br><span class="line"> Section __unwind_info: <span class="number">0</span>x48 (addr <span class="number">0</span>x100000fac offset <span class="number">4012</span>)</span><br><span class="line"> total <span class="number">0</span>xc0</span><br><span class="line">Segment __DATA: <span class="number">0</span>x1000 (vmaddr <span class="number">0</span>x100001000 fileoff <span class="number">4096</span>)</span><br><span class="line"> Section __nl_symbol_ptr: <span class="number">0</span>x10 (addr <span class="number">0</span>x100001000 offset <span class="number">4096</span>)</span><br><span class="line"> Section __la_symbol_ptr: <span class="number">0</span>x8 (addr <span class="number">0</span>x100001010 offset <span class="number">4112</span>)</span><br><span class="line"> Section __objc_imageinfo: <span class="number">0</span>x8 (addr <span class="number">0</span>x100001018 offset <span class="number">4120</span>)</span><br><span class="line"> total <span class="number">0</span>x20</span><br><span class="line">Segment __LINKEDIT: <span class="number">0</span>x1000 (vmaddr <span class="number">0</span>x100002000 fileoff <span class="number">8192</span>)</span><br><span class="line">total <span class="number">0</span>x100003000</span><br></pre></td></tr></table></figure>
<p>长话短说:</p>
<ul>
<li><code>Segment __PAGEZERO</code>。大小为 4GB,规定进程地址空间的前 4GB 被映射为不可读不可写不可执行。</li>
<li><code>Segment __TEXT</code>。包含可执行的代码,以只读和可执行方式映射。</li>
<li><code>Segment __DATA</code>。包含了将会被更改的数据,以可读写和不可执行方式映射。</li>
<li><code>Segment __LINKEDIT</code>。包含了方法和变量的元数据,代码签名等信息。</li>
</ul>
<h5 id="dyld"><a href="#dyld" class="headerlink" title="dyld"></a>dyld</h5><p>动态加载器(dynamic loader)。它是开源的,如果有兴趣,你可以阅读它的<a href="https://opensource.apple.com/source/dyld/" target="_blank" rel="external">源码</a>。dyld1 已经过时,不用去理解。目前大多用的是 dyld2。在 WWDC2017 上 Apple 新推出了 dyld3,目前只在 iOS 系统 App 上使用,后面应该会普及。这一阶段最后会详细介绍一下 dyld3,这里就不描述了。</p>
<p>下面开始正式讲解启动过程。</p>
<h4 id="3-系统准备阶段"><a href="#3-系统准备阶段" class="headerlink" title="3.系统准备阶段"></a>3.系统准备阶段</h4><p>点击 APP 之后,到加载 dyld 动态加载器这一过程中,系统做了很多事情,大体分为如下图几个阶段:</p>
<p><img src="/uploads/compile-iOS/dyld 之前准备工作.png" alt="dyld 加载之前准备工作"></p>
<p>大部分同学没有深入研究过这部分内容,我也没有深入研究过。所以我尽量复杂问题简单化,以最简单的方式将这些过程讲述明白。</p>
<ul>
<li>点击 APP 之后,系统会创建一个进程。然后使用 <code>load_init_program</code> 函数加载系统初始化的进程。然后再方法内调用 <code>load_init_program_at_path</code>。通过 <code>load_init_program_at_path</code> 方法调用 <code>__mac_execve</code>。</li>
<li><code>__mac_execve</code> 函数会启动新的进程和 task,调用 <code>exec_activate_image</code>。</li>
<li><code>exec_activate_image</code> 函数会按照二进制的格式分发映射内存的函数。<code>Mach-O</code> 文件会由 <code>exec_mach_imgact</code> 处理。</li>
<li>在 <code>exec_mach_imgact</code> 函数中,会检测 <code>Mach-O</code> header,解析其架构等信息,文件是否合法等;先拷贝 <code>Mach-O</code> 文件到内存中;然后拷贝 <code>Mach-O</code> 文件到内存中;之后是 dyld 相关处理工作;最后释放资源。</li>
<li><code>load_machfile</code> 函数负责 <code>Mach-O</code> 文件加载相关工作。为当前 task 分配可执行内存;加载 <code>Mach-O</code> 中 load command 部分的命令;进制数据段执行,防止溢出漏洞攻击,设置 ASLR 等;最后为 <code>exec_mach_imgact</code> 回传结果。</li>
<li><code>parse_machfile</code> 根据 <code>load_command</code> 的信息选择不同函数加载数据。其中使用的是 <code>switch-case</code> 语句,处理的类型有 <code>LC_LOAD_DYLINKER</code>、<code>LC_ENCRYPTION_INFO_64</code> 等。</li>
<li>上一步处理中,有一个 case 为 <code>LC_LOAD_DYLINKER</code>。进入这个 case 三次,并存在 <code>dylinker_command</code> 命令,之后会<strong>执行 <code>load_dylinker()</code> 加载 dyld</strong>。</li>
</ul>
<h4 id="4-将-dyld-加载到-App-进程中"><a href="#4-将-dyld-加载到-App-进程中" class="headerlink" title="4.将 dyld 加载到 App 进程中"></a>4.将 dyld 加载到 App 进程中</h4><p>在 dyld 的源码中,有一个 <a href="https://opensource.apple.com/source/dyld/dyld-132.13/src/dyldStartup.s.auto.html" target="_blank" rel="external">dyldStartup.s</a> 文件。这个文件针对不同的 CPU 架构,定义了不同的启动方法,大同小异。这里会执行到 <code>__dyld_start</code> 方法,然后调用 <code>dyldbootstrap::start()</code> 方法,最终调用到 <a href="https://opensource.apple.com/source/dyld/dyld-132.13/src/dyld.cpp.auto.html" target="_blank" rel="external">dyld.cppp</a> 中的 <code>dyld::_main()</code> 方法。部分代码如下:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">__dyld_start:</span><br><span class="line"> pushq $<span class="number">0</span> <span class="meta"># push a zero for debugger end of frames marker</span></span><br><span class="line"> movq %rsp,%rbp <span class="meta"># pointer to base of kernel frame</span></span><br><span class="line"> andq $<span class="number">-16</span>,%rsp <span class="meta"># force SSE alignment</span></span><br><span class="line"> </span><br><span class="line"> <span class="meta"># call dyldbootstrap::start(app_mh, argc, argv, slide)</span></span><br><span class="line"> movq <span class="number">8</span>(%rbp),%rdi <span class="meta"># param1 = mh into %rdi</span></span><br><span class="line"> movl <span class="number">16</span>(%rbp),%esi <span class="meta"># param2 = argc into %esi</span></span><br><span class="line"> leaq <span class="number">24</span>(%rbp),%rdx <span class="meta"># param3 = &argv[0] into %rdx</span></span><br><span class="line"> movq __dyld_start_static(%rip), %r8</span><br><span class="line"> leaq __dyld_start(%rip), %rcx</span><br><span class="line"> subq %r8, %rcx <span class="meta"># param4 = slide into %rcx</span></span><br><span class="line"> call __ZN13dyldbootstrap5startEPK12macho_headeriPPKcl </span><br><span class="line"></span><br><span class="line"> <span class="meta"># clean up stack and jump to result</span></span><br><span class="line"> movq %rbp,%rsp <span class="meta"># restore the unaligned stack pointer</span></span><br><span class="line"> addq $<span class="number">16</span>,%rsp <span class="meta"># remove the mh argument, and debugger end frame marker</span></span><br><span class="line"> movq $<span class="number">0</span>,%rbp <span class="meta"># restore ebp back to zero</span></span><br><span class="line"> jmp *%rax <span class="meta"># jump to the entry point</span></span><br></pre></td></tr></table></figure>
<p><code>_main()</code> 方法包含了 App 的启动流程,最终返回应用程序 <code>main</code> 方法的地址,这里省略代码,只标注流程:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">uintptr_t</span></span><br><span class="line">_main(<span class="keyword">const</span> macho_header* mainExecutableMH, <span class="keyword">uintptr_t</span> mainExecutableSlide, <span class="keyword">int</span> argc, <span class="keyword">const</span> <span class="keyword">char</span>* argv[], <span class="keyword">const</span> <span class="keyword">char</span>* envp[], <span class="keyword">const</span> <span class="keyword">char</span>* apple[])</span><br><span class="line">{ </span><br><span class="line"> <span class="comment">// 上下文建立,初始化必要参数,解析环境变量等</span></span><br><span class="line"> ...... </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// instantiate ImageLoader for main executable</span></span><br><span class="line"> sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);</span><br><span class="line"> sMainExecutable->setNeverUnload();</span><br><span class="line"> gLinkContext.mainExecutable = sMainExecutable;</span><br><span class="line"> gLinkContext.processIsRestricted = sProcessIsRestricted;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// load shared cache</span></span><br><span class="line"> checkSharedRegionDisable();</span><br><span class="line"> <span class="meta">#<span class="meta-keyword">if</span> DYLD_SHARED_CACHE_SUPPORT</span></span><br><span class="line"> <span class="keyword">if</span> ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion )</span><br><span class="line"> mapSharedCache();</span><br><span class="line"> <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// load any inserted libraries</span></span><br><span class="line"> <span class="keyword">if</span> ( sEnv.DYLD_INSERT_LIBRARIES != <span class="literal">NULL</span> ) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">char</span>* <span class="keyword">const</span>* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != <span class="literal">NULL</span>; ++lib) </span><br><span class="line"> loadInsertedDylib(*lib);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ......</span><br><span class="line"></span><br><span class="line"> <span class="comment">// link main executable</span></span><br><span class="line"> gLinkContext.linkingMainExecutable = <span class="literal">true</span>;</span><br><span class="line"> link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, ImageLoader::RPathChain(<span class="literal">NULL</span>, <span class="literal">NULL</span>));</span><br><span class="line"> gLinkContext.linkingMainExecutable = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span> ( sMainExecutable->forceFlat() ) {</span><br><span class="line"> gLinkContext.bindFlat = <span class="literal">true</span>;</span><br><span class="line"> gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// get main address</span></span><br><span class="line"> result = (<span class="keyword">uintptr_t</span>)sMainExecutable->getMain();</span><br><span class="line"></span><br><span class="line"> ......</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="5-加载-App-所需要的动态库"><a href="#5-加载-App-所需要的动态库" class="headerlink" title="5.加载 App 所需要的动态库"></a>5.加载 App 所需要的动态库</h4><p>上文提到过,image 实际是 <code>Mach-O</code> 文件的一种,包括 Executable,Dylib 或者 Bundle。在上节的 <code>dyld::_main()</code> 函数中可以看出,dyld 会通过调用 <code>instantiateFromLoadedImage</code> 选择<code>imageLoader</code> 加载对应可执行文件。</p>
<p>然后通过 <code>mapSharedCache()</code> 函数将 <code>/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64</code> 共享的动态库加载到内存,<strong>这也是不同的 App 实现动态库共享机制,不同的 App 的虚拟内存中共享动态库会通过系统的 vm_map 来映射同一块物理内存,从而实现共享动态库。</strong></p>
<p>之后会调用 <code>loadInsertedDylib()</code> 函数加载环境变量 <code>DYLD_INSERT_LIBRARIES</code> 中的动态库。<code>loadInsertedDylib</code> 动态库并未做太多工作,主要工作都是调用 <code>load</code> 函数来处理,<code>dlopen</code> 也会调用 <code>load</code> 函数来进行动态库加载。</p>
<p>再后面调用 <code>link()</code> 函数递归链接程序所依赖的库。一般一个 App 所依赖的动态库在 100-400 个左右。使用命令 <code>otool -L Test</code> 可以查看 Test 工程所需要的动态库如下:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/usr/lib/libsqlite3.dylib (compatibility version <span class="number">9.0</span>.<span class="number">0</span>, current version <span class="number">274.6</span>.<span class="number">0</span>)</span><br><span class="line"> /usr/lib/libz.<span class="number">1</span>.dylib (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">1.2</span>.<span class="number">11</span>)</span><br><span class="line"> /System/Library/Frameworks/Accelerate.framework/Accelerate (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">4.0</span>.<span class="number">0</span>)</span><br><span class="line"> /System/Library/Frameworks/AssetsLibrary.framework/AssetsLibrary (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">1.0</span>.<span class="number">0</span>)</span><br><span class="line"> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version <span class="number">150.0</span>.<span class="number">0</span>, current version <span class="number">1450.14</span>.<span class="number">0</span>)</span><br><span class="line"> /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version <span class="number">64.0</span>.<span class="number">0</span>, current version <span class="number">1129.2</span>.<span class="number">1</span>)</span><br><span class="line"> /System/Library/Frameworks/ImageIO.framework/ImageIO (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">0.0</span>.<span class="number">0</span>)</span><br><span class="line"> /System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">822.19</span>.<span class="number">0</span>)</span><br><span class="line"> /System/Library/Frameworks/QuartzCore.framework/QuartzCore (compatibility version <span class="number">1.2</span>.<span class="number">0</span>, current version <span class="number">1.11</span>.<span class="number">0</span>)</span><br><span class="line"> /System/Library/Frameworks/Security.framework/Security (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">58286.32</span>.<span class="number">2</span>)</span><br><span class="line"> /System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">963.30</span>.<span class="number">1</span>)</span><br><span class="line"> /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">3698.33</span>.<span class="number">6</span>)</span><br><span class="line"> /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version <span class="number">300.0</span>.<span class="number">0</span>, current version <span class="number">1450.14</span>.<span class="number">0</span>)</span><br><span class="line"> /usr/lib/libobjc.A.dylib (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">228.0</span>.<span class="number">0</span>)</span><br><span class="line"> /usr/lib/libSystem.dylib (compatibility version <span class="number">1.0</span>.<span class="number">0</span>, current version <span class="number">1252.0</span>.<span class="number">0</span>)</span><br></pre></td></tr></table></figure>
<blockquote>
<p>对于 CocoaPods 中的第三方库,一般是以静态库的方式加载,所以使用 <code>otool -L [文件名]</code> 并不会看到 Pod 中的库。但是如果 Podfile 中加入了 <code>use_frameworks!</code>,即以动态库方式加载,才会看到,也就是上面所示。</p>
</blockquote>
<p>最后,获取到应用程序 main 函数地址,返回。</p>
<h4 id="6-Rebase-amp-Bind"><a href="#6-Rebase-amp-Bind" class="headerlink" title="6.Rebase & Bind"></a>6.Rebase & Bind</h4><p>这两个过程,并不是在上面 <code>_main()</code> 方法返回之后进行的,而是在上一节中 “link main executable” 这一步进行的。</p>
<p>Apple 为了保证应用安全,应用了两种技术:ASLR (Address space layout randomization) 和 Code sign。</p>
<p>ASLR 是指 “地址空间布局随机化”。App 启动的时候,程序会被映射到一个逻辑地址空间。如果这个地址固定,很容易根据地址+偏移量计算出函数地址,被攻击。 ASLR 使得这个地址是随机的,防止攻击者直接定位攻击代码位置。</p>
<p>Code sign 是指代码签名。Apple 使用两层非对称加密,以保证 App 的安全安装。在进行 Code sign 时,是针对每个 page 进行加密,这样在 dyld 加载时,可以针对每个 page 进行独立验证。</p>
<p>因为使用 ASLR 导致的地址随机,需要加上偏移量才是真正方法地址。调用的一个方法,这个方法的地址可能属于 Mach-O 文件内部,也可能属于其他 Mach-O 文件。</p>
<p>Rebase 是修复内部符号地址,即修复的是指向当前 Mach-O 文件内部的资源指针,修复过程只是加一个偏移量就可以。</p>
<p>Bind 是修复外部符号地址,即修复的是指向外部 Mach-O 文件指针。这一过程需要查询符号表,指向其他 Mach-O 文件,比较耗费时间。</p>
<p>官方给出的一张图如下:</p>
<p><img src="/uploads/compile-iOS/rebase_bind.png" alt="rebase_bind"></p>
<p>简言之就是,前面步骤加载动态库时地址指偏了,这里进行 fix-up,否则调不到。</p>
<p>至此,Mach-O 的加载就完事儿了,下面就是 iOS 系统的事情了。</p>
<h4 id="7-Objc-Setup"><a href="#7-Objc-Setup" class="headerlink" title="7.Objc Setup"></a>7.Objc Setup</h4><p>Objc 是一门动态语言,这一步主要来加载 Runtime 相关的东西。主要做一下几件事情:</p>
<ul>
<li>把相关的类注册到全局 table 中。</li>
<li>将 Category 和 Protocol 中的方法注册到对应的类中。</li>
<li>确保 Selector 的唯一性。</li>
</ul>
<p>这一步主要处理自定义的一些类和方法。大部分系统类的 Runtime 初始化已经在 Rebase 和 Bind 中完成了。</p>
<h4 id="8-Initializers"><a href="#8-Initializers" class="headerlink" title="8.Initializers"></a>8.Initializers</h4><p>这一步进行一些类的初始化。这是一个递归过程,先将依赖的动态库初始化,再对自己自定义的类初始化。主要做的事情有:</p>
<ul>
<li>调用 Objc 类中的 <code>+[load]</code> 方法。</li>
<li>调用 C/C++ 标记为 <code>__attribute__(constructor)</code> 的方法。</li>
<li>非基本类型的 C++ 静态全局比变量的创建。</li>
</ul>
<blockquote>
<p>Swift 用已经干掉了 <code>+load</code> 方法,官方建议使用 <code>initialize</code> 方法,减少 App 启动时间。</p>
</blockquote>
<h4 id="9-Main"><a href="#9-Main" class="headerlink" title="9.Main"></a>9.Main</h4><p>千辛万苦,我们终于来到了 <code>main()</code> 方法。</p>
<p>基于 C 的程序一般都以 <code>main()</code> 方法为入口,iOS 系统会为你自动创建 <code>main()</code> 方法。代码很简单:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[])</span><br><span class="line">{</span><br><span class="line"> @autoreleasepool {</span><br><span class="line"> return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里用的 <code>UIApplicationMain</code> 方法声明如下:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nonnull * _Null_unspecified argv, NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);</span><br></pre></td></tr></table></figure>
<ul>
<li>argc、argv 直接传递给 UIApplicationMain 进行相关处理。</li>
<li>principalClassName 指定应用程序的类名。这个类必须为 <code>UIApplication</code> 类型或者其子类。如果为 nil,则使用 <code>UIApplication</code> 类。</li>
<li>delegateClassName,指定应用程序代理类。这个类必须遵循 <code>UIApplicationDelegate</code> 协议。</li>
<li>UIApplicationMain 会根据 principalClassName 创建 <code>UIApplication</code> 对象,并根据 delegateClassName 创建 delegate 对象,将这个对象赋值给 <code>UIApplication</code> 对象的 delegate 属性。</li>
<li>然后将 App 放入 Main Run Loop 环境中来响应和处理用户交互事件。</li>
</ul>
<p>关于 <code>AppDelegate</code> 中的一些方法:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">@<span class="keyword">implementation</span> AppDelegate</span><br><span class="line"></span><br><span class="line">- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {</span><br><span class="line"> // 通知进程已启动,但是还未完成显示。</span><br><span class="line"> return YES;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {</span><br><span class="line"> // 启动完成,程序准备开始运行。页面显示前最后一次操作机会。</span><br><span class="line"> return YES;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- (void)applicationWillResignActive:(UIApplication *)application {</span><br><span class="line"> // App 失去焦点,进入非活动状态。主要实例有:来电话,某些系统弹窗,双击 home 键,下拉显示系统通知栏,上拉显示系统控制中心等。</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- (void)applicationDidEnterBackground:(UIApplication *)application {</span><br><span class="line"> // App 进入后台。</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- (void)applicationWillEnterForeground:(UIApplication *)application {</span><br><span class="line"> // App 进入前台。冷启动不会收到这个通知。</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- (void)applicationDidBecomeActive:(UIApplication *)application {</span><br><span class="line"> // App 获得焦点,处于活动状态。冷热启动都会收到这个通知。</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- (void)applicationWillTerminate:(UIApplication *)application {</span><br><span class="line"> // 应用将要退出时,可以在这个方法中保存数据和一些退出前清理工作。</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {</span><br><span class="line"> // 收到内存警告,释放一些内存。</span><br><span class="line">}</span><br><span class="line">@end</span><br></pre></td></tr></table></figure>
<h4 id="10-One-more-thing"><a href="#10-One-more-thing" class="headerlink" title="10.One more thing"></a>10.One more thing</h4><p>上文说有详细讲一下 dyld3,放到这里了。dyld3 是 WWDC 2017 介绍的新的动态加载器。与 dyld2 对比如下图:</p>
<p><img src="/uploads/compile-iOS/dyld_2_3.png" alt="dyld_2_3"></p>
<p>两者的区别,通俗一点说就是:dyld2 所有的过程都是在启动时进行的,每次启动都会讲所有过程走一遍;dyld3 分成了两部分,虚线上面的部分在 App 下载安装和版本更新时执行并将结果写入缓存,虚线下面的部分在每次 App 启动执行。</p>
<p>这样减少了 dyld 加载步骤,也就加快了 APP 启动时间。不过目前 dyld3 只在 Apple 系统 App 才会使用,开发者不能使用。后面应该会普及。</p>
<p>根据上面的分析过程,我们可以大体总结出,如果要针对 App 做启动优化,可以从哪些方面入手:</p>
<ul>
<li>减少动态库的引入。如果是公司内部自定义组件,可以将某些同类的组件合并为一个。</li>
<li>为了减少 Rebase & Bind 时间,减少 <code>__DATA</code> 中的指针数量。</li>
<li>为了减少 Runtime 注册时间,减少 Category,减少无用的 Class 和 Selector。</li>
<li>尽量不要在 <code>+[load]</code> 方法中写东西,减少 <code>__atribute__((constructor))</code>,减少非基本类型 C++ 静态常量创建。</li>
<li>将一些第三方库在使用的时候再初始化,lazy load,不要都放在 AppDelegate 中。</li>
<li>使用 Swift。</li>
</ul>
<h3 id="图层渲染阶段"><a href="#图层渲染阶段" class="headerlink" title="图层渲染阶段"></a>图层渲染阶段</h3><p>做了一堆准备工作,可算是到了渲染展示界面了。</p>
<p>图层的布局过程(这里指自动布局),主要分为三步:<strong>设置约束、更新布局、渲染视图</strong>。这里会结合 view controller 的生命周期来讲解。</p>
<h4 id="1-视图布局过程"><a href="#1-视图布局过程" class="headerlink" title="1.视图布局过程"></a>1.视图布局过程</h4><h5 id="Update-Cycle"><a href="#Update-Cycle" class="headerlink" title="Update Cycle"></a>Update Cycle</h5><p>在程序启动时,会将 App 放到 Main Run Loop 中来响应和处理用户交互事件。关于 RunLoop,简单说来就是一个循环,只要 App 未被杀死,这个循环就一直存在。每一次循环可以认为是一个迭代周期,这个周期中会相应和处理用户交互事件。<strong>当完成了各种事件处理之后控制流回到 Main Run Loop 那个时间点,开始更新视图</strong>,更新完进入下一个循环。整个过程如下图所示:</p>
<p><img src="/uploads/compile-iOS/update_cycle.png" alt="update_cycle"></p>
<p>在 update cycle 这个阶段,系统会根据计算出来的新的 frame 对视图进行重绘。这个过程很快,所以用户感觉不到延迟卡顿。因为视图的更新是按照周期来的,所以有时候修改了约束、添加了视图或者修改了 frame 并不会立即重绘视图。接下来就详细介绍这一过程。</p>
<h5 id="约束"><a href="#约束" class="headerlink" title="约束"></a>约束</h5><p>一个视图的 frame 包含了视图的位置和大小,通过这个 frame(和当前坐标系) 可以确定视图的具体位置。约束的本质就是设置一系列的关系,计算布局时会将这些关系转化为一系列线性方程式,通过线性方程式求解得出 x,y,width,height,从而确定视图位置。这一阶段是从下向上(from subview to super view),为下一步布局准备消息。</p>
<p><strong>updateConstraints()</strong></p>
<p>这个方法用来在自动布局中动态改变视图约束。一般情况下,这个方法<strong>只应该被重载,不应该手动调用</strong>。在开发过程中,一些静态约束,可以在视图初始化方法或者 <code>viewDidLoad()</code> 方法中设置;对于一些动态约束,例如 <code>UILabel</code> 有时需要随着文案字数改变大小,需要动态修改约束,这时候可以重载此方法,将动态修改约束代码写在次方法里。</p>
<p>还有一些操作会将视图标记,在下一个 update cycle 中自动触发这个方法:</p>
<ul>
<li>激活/禁用约束。</li>
<li>改变约束的大小或者优先级。</li>
<li>改变视图层级。</li>
</ul>
<p><strong>setNeedsUpdateConstraints()</strong></p>
<p>如果你希望视图在下一个 update cycle 中一定要调用 <code>updateConstraints()</code> 方法,你可以调用此方法,这样就给视图打上一个标记,<strong>如果有必要</strong>在下一个 update cycle 便会调用 <code>updateConstraints()</code> 方法。</p>
<blockquote>
<p>这里说“如果有必要“,是因为如果系统检测视图没有任何变化,即使标记了,也不会调用此方法,避免耗费性能。所以标记了,只是告诉系统到时候 check 一下,是否要更新约束。下面一些方法同理。</p>
</blockquote>
<p><strong>updateConstraintsIfNeeded()</strong></p>
<p>如果你不想等到 run loop 末尾,进入 update cycle 的时候,再去检查标记并更新约束。你想立刻检查被打上标记的视图,更新约束,可以调用此方法。同样的,调用此方法只会检查那些被标记的视图,<strong>如果有必要</strong>,才会调用 <code>updateConstraints()</code> 方法。</p>
<p><strong>invalidateIntrinsicContentSize()</strong></p>
<p>有些视图(例如 UILabel)有 <code>intrinsicContentSize</code> 属性,这是根据视图内容得到的固有大小。你也可以通过重载来自定义这个大小,重载之后,你需要调用 <code>invalidateIntrinsicContentSize()</code> 方法来标记 <code>intrinsicContentSize</code> 已经过期,需要再下一个 update cycle 中重新计算。</p>
<h5 id="布局"><a href="#布局" class="headerlink" title="布局"></a>布局</h5><p>根据约束计算出视图大小和位置,下一步就是布局。这一部分是从上向下(from super view to subview),使用上一步计算出来的大小和位置去设置视图的 center 和 bounds。</p>
<p><strong>layoutSubviews()</strong></p>
<p>这个方法会对<strong>视图</strong>和其<strong>子视图</strong>进行重新定位和大小调整。这个方法很昂贵,因为它会处理当前视图和其自视图的布局情况,还会调用自视图的 <code>layoutSubviews()</code>,层层调用。同样,这个方法<strong>只应该被重载,不应该手动调用</strong>。当你需要更新视图 frame 时,可以重载这个方法。</p>
<p>一些操作可能会触发这个方法,间接触发比手动调用资源消耗要小得多。有以下几种情况会触发此方法:</p>
<ul>
<li>修改视图大小。</li>
<li>添加视图 (addSubview)。</li>
<li>UIScrollView 滚动。</li>
<li>设备旋转。</li>
<li>更新视图约束</li>
</ul>
<p>这些情况有的会告诉系统视图 frame 需要重新计算,从而调用 <code>layoutSubviews()</code>,也有的会直接触发 <code>layoutSubviews()</code> 方法。</p>
<p><strong>setNeedsLayout()</strong></p>
<p>此方法会将视图标记,告诉系统视图的布局需要重新计算。然后再下一个 update cycle 中,系统就会调用视图的 <code>layoutSubviews()</code> 方法。同样的,<strong>如果有必要,系统才会去调用</strong>。</p>
<p><strong>layoutIfNeeded()</strong></p>
<p><code>setNeedsLayout</code> 是标记视图,在下个 update cycle 中可能会调用 <code>layoutSubviews()</code> 方法。而 <code>layoutIfNeeded()</code> 是告诉系统立即调用 <code>layoutSubviews()</code> 方法。当然,调用了 <code>layoutIfNeeded()</code> 方法只会,系统会 check 视图是否有必要刷新,<strong>如果有必要</strong>,系统才会调用 <code>layoutSubviews()</code> 方法。如果你再同一个 run loop 中调用了两次 <code>layoutIfNeeded()</code>,两次之间没有视图更新,那么第二次则不会触发 <code>layoutSubviews()</code>。</p>
<p>在做约束动画时,这个方法很有用。在动画之前,调用此方法以确保其他视图已经更新。然后在 animation block 中设置新的约束后,调用此方法来动画到新的状态。例如:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[self.view layoutIfNeeded];</span><br><span class="line"> [UIView animateWithDuration:<span class="number">1.0</span> animations:^{</span><br><span class="line"> [self changeConstraints];</span><br><span class="line"> [self.view layoutIfNeeded];</span><br><span class="line"> }];</span><br></pre></td></tr></table></figure>
<h5 id="渲染"><a href="#渲染" class="headerlink" title="渲染"></a>渲染</h5><p>视图的显示包含了颜色、文本、图片和 Core Graphics 绘制等。与约束、布局两个步骤类似,这里也有一些方法用来刷新渲染。这一过程是从上向下(from super view to subview)。</p>
<p><strong>draw(_:)</strong></p>
<p>UIView 的 <code>draw</code> 方法(OC 中的 drawRect)用来绘制视图显示的内容,只作用于当前视图,不会影响子视图。依然,这个方法应该通过其他方法触发,而不应该手动调用。</p>
<p><strong>setNeedsDisplay()</strong></p>
<p>这个方法类似于布局中的 <code>setNeedsLayout()</code>。调用此方法会将视图标记,然后在下一个 update cycle 系统遍历被标记的视图,调用其 <code>draw()</code> 方法进行重绘。大部分 UI 组件如果有更新,都会进行标记,在下个 update cycle 进行重绘。一般不需要显式调用此方法。</p>
<p>这一步骤没有类似于 <code>layoutIfNeeded()</code> 这样的方法来立即刷新。通常等到下一个 update cycle 再刷新也没影响。</p>
<h5 id="三者联系"><a href="#三者联系" class="headerlink" title="三者联系"></a>三者联系</h5><p>布局过程并不是单向的,而是一个 <strong>约束-布局</strong> 的迭代过程。布局过程有可能会影响约束,从而触发 <code>updateConstraints()</code>。只要确定好布局,判断是否需要重绘,然后展示。这一轮完毕后进入下一个 runloop。它们的大体流程如下:</p>
<p><img src="/uploads/compile-iOS/布局过程.png" alt="布局过程"></p>
<p>上面说的这三个过程的方法,有些类似,记起来比较乱,可以通过下面的表格对比记忆:</p>
<table>
<thead>
<tr>
<th>方法作用</th>
<th>约束</th>
<th>布局</th>
<th>渲染</th>
</tr>
</thead>
<tbody>
<tr>
<td>刷新方法,可以重载,不可直接调用</td>
<td>updateConstraints</td>
<td>layoutSubviews</td>
<td>draw</td>
<td></td>
</tr>
<tr>
<td>标记刷新方法,使视图在下一个 update cycle 调用刷新方法</td>
<td>setNeedsUpdateConstraints <br> invalidateIntrinsicContentSize</td>
<td>setNeedsLayout</td>
<td>setNeedsDisplay</td>
<td></td>
</tr>
<tr>
<td>updateConstraintsIfNeeded</td>
<td>layoutIfNeeded</td>
<td></td>
<td></td>
</tr>
<tr>
<td>触发刷新方法的操作</td>
<td>激活/禁用约束 <br> 改变约束的大小或者优先级 <br> 改变视图层级</td>
<td>修改视图大小 <br> 添加视图 (addSubview) <br> UIScrollView 滚动 <br> 设备旋转 <br> 更新视图约束</td>
<td>修改视图 bounds</td>
<td></td>
</tr>
</tbody>
</table>
<h4 id="2-View-Controller-生命周期"><a href="#2-View-Controller-生命周期" class="headerlink" title="2.View Controller 生命周期"></a>2.View Controller 生命周期</h4><p>校招找工作时,经常被问到 VC 的生命周期。最近面试其他人,也经常问这个问题。无论是校招时候的我,还是我面试的其他人,哪怕是工作三五年的,都回答不好这个问题。</p>
<p>这是一个基础问题,没有太多技术难度,应该掌握。</p>
<h5 id="单个-View-Controller-生命周期"><a href="#单个-View-Controller-生命周期" class="headerlink" title="单个 View Controller 生命周期"></a>单个 View Controller 生命周期</h5><p>以方法调用顺序描述单个 View Controller 生命周期,依次为:</p>
<ul>
<li><p><strong>load</strong><br> 类加载时调用,在 main 函数之前。</p>
</li>
<li><p><strong>initialize</strong><br> 类第一次初始化时调用,在main 函数之后。</p>
</li>
<li><p><strong>类初始化相关方法</strong><br> <code>[initWithCoder:]</code> 在使用 storeboard 调用。<code>[initWithNibName: bundle:]</code> 在使用自定义 nib 文件时调用。还有其他 init 方法则是普通初始化类时调用。</p>
</li>
<li><p><strong>loadView</strong><br> 开始加载视图,在这之前都没有视图。除非手动调用,否则在 View Controller 生命周期只会调用一次。在</p>
</li>
<li><p><strong>viewDidLoad</strong><br> View Controller 生命周期中只会调用一次。类中成员变量、子视图等一些数据的初始化都放在这个方法里。</p>
</li>
<li><p><strong>viewWillAppear</strong><br> 视图将要展示前调用。</p>
</li>
<li><p><strong>viewWillLayoutSubviews</strong><br> 将要对子视图进行布局。</p>
</li>
<li><p><strong>viewDidLayoutSubviews</strong><br> 已完成子视图布局,第一时间拿到 view 的具体 frame。一些依赖布局或者大小的代码都应该放在这个方法。放在之前的方法中,视图还没有布局,frame 都是 0;放在后面的方法中,可能因为一些改动,布局或者位置变量发生改变。</p>
</li>
<li><p><strong>viewDidAppear</strong><br> 视图显示完成调用。</p>
</li>
<li><p><strong>viewWillDisappear</strong><br> 视图即将消失时调用。</p>
</li>
<li><p><strong>viewDidDisappear</strong><br> 视图已经消失时调用。</p>
</li>
<li><p><strong>dealloc</strong><br> View Controller 被释放时调用。</p>
</li>
</ul>
<h5 id="两个-View-Controller-进行转场时各自方法调用时机"><a href="#两个-View-Controller-进行转场时各自方法调用时机" class="headerlink" title="两个 View Controller 进行转场时各自方法调用时机"></a>两个 View Controller 进行转场时各自方法调用时机</h5><p>不同的转场方式,两个 VC 之间方法调用顺序不同。常见的有以下几种方式:</p>
<ul>
<li><p><strong>Navigation</strong></p>
<p> push 操作</p>
<ul>
<li>New viewDidLoad</li>
<li>Current viewWillDisappear</li>
<li>New viewWillAppear</li>
<li>New viewWillLayoutSubviews</li>
<li>New viewDidLayoutSubviews</li>
<li>Current viewDidDisappear</li>
<li><p>New viewDidAppear</p>
<p>Pop 操作(上一步的 New 在这里变为 Current,下同)</p>
</li>
<li><p>Current viewWillDisappear</p>
</li>
<li>New viewWillAppear</li>
<li>Current viewDidDisappear</li>
<li>New viewDidappear</li>
</ul>
</li>
<li><p><strong>Page Curling (UIPageViewControllerTransitionStylePageCurl)</strong></p>
<p> Normal 正常翻页操作</p>
<ul>
<li>New viewDidLoad</li>
<li>Current viewWillDisappear</li>
<li>New viewWillAppear</li>
<li>New viewWillLayoutSubviews</li>
<li>New viewDidLayoutSubviews</li>
<li>Current viewDidDisappear</li>
<li><p>New viewDidAppear</p>
<p>Canceled 翻到一半取消</p>
</li>
<li><p>New viewWillAppear</p>
</li>
<li>New viewWillAppear</li>
<li>Current viewWillDisappear</li>
<li>New viewWillLayoutSubviews</li>
<li>New viewDidLayoutSubviews</li>
<li>New viewWillDisappear</li>
<li>Current viewWillAppear</li>
<li>New viewDidDisappear</li>
<li>Current viewDidAppear</li>
</ul>
</li>
<li><p><strong>Page Scrolling (UIPageViewControllerTransitionStyleScroll)</strong></p>
<p> Normal 正常滑动翻页操作</p>
<ul>
<li>New viewDidLoad</li>
<li>New viewWillAppear</li>
<li>Current viewWillDisappear</li>
<li>New viewWillLayoutSubviews</li>
<li>New viewDidLayoutSubviews</li>
<li>New viewDidAppear</li>
<li><p>Current viewDidDisappear</p>
<p>Canceled 滑到一半取消</p>
</li>
<li><p>New viewWillAppear</p>
</li>
<li>Current viewWillDisappear</li>
<li>Current viewWillAppear</li>
<li>Current viewDidAppear</li>
<li>New viewWillDisappear</li>
<li>New viewDidDisappear</li>
</ul>
</li>
</ul>
<p>可以看出,不同的专场方式,两个 View Cotroller 之间的生命周期方法调用顺序是不一样的。很混乱是吧,不用强记,只需要知道这个 case,在开发是注意就好了。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>以上基本就是一个工程从编译到启动的所有过程。深入理解这一过程,可以帮助我们更好的开发。因为文章比较长,中间难免有一些纰漏。如果发现请指出,我会尽快修改。</p>
<h4 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h4><ol>
<li><a href="https://www.objc.io/issues/6-build-tools/" target="_blank" rel="external">objc-Issues-Build-Tools</a></li>
<li><a href="https://blog.csdn.net/Hello_Hwc/article/details/78317863" target="_blank" rel="external">深入理解iOS App的启动过程</a></li>
<li><a href="https://mp.weixin.qq.com/s/I60p2M-IHDmeUanDUkFdVw" target="_blank" rel="external">XNU、dyld源码分析Mach-O和动态库的加载过程(上)</a></li>
<li><a href="https://mp.weixin.qq.com/s/fdDPyjRkVf9AdWiikBagHg" target="_blank" rel="external">XNU、dyld 源码分析,Mach-O 和动态库的加载过程 (下)</a></li>
<li><a href="http://tech.gc.com/demystifying-ios-layout/" target="_blank" rel="external">Demystifying iOS Layout</a></li>
<li><a href="http://wangling.me/2014/02/the-inconsistent-order-of-view-transition-events.html" target="_blank" rel="external">The Inconsistent Order of View Transition Events</a></li>
</ol>
<a id="more"></a>
<p>作为一名 coder,每天的工作不是解 bug,就是写 bug。有些东西,了解了并不一定有利于写 bug,但是有利于解 bug。</p>
<p>对于一个工程,当你按下 <code>⌘ + R</code> 到主界面显示出来,你可曾想过这一
我所理解的 Block
http://yoursite.com/2018/02/28/我所理解的Block/
2018-02-28T12:10:27.000Z
2018-06-03T13:13:29.074Z
<a id="more"></a>
<p>关于 block 的文章,网上已经有很多了。我这里只是将这个知识点再梳理一下,做一下记录。毕竟年纪大了,容易忘事。</p>
<h4 id="抛砖引玉"><a href="#抛砖引玉" class="headerlink" title="抛砖引玉"></a>抛砖引玉</h4><p>围绕 block 所产生的问题,太多太多。这里我将这些问题罗列出来,如果你对某些问题感到懵逼,可以在下文中找到答案。找不到,私信我。</p>
<ul>
<li>为什么要用 block?毕竟它的语法难记,还容易产生内存泄漏。</li>
<li>block 的各种书写格式,你是否了解?</li>
<li>按内存区这一维度划分,block 可以分为哪几种类型,如何定义的?</li>
<li>block 是 Objective-C 对象吗?</li>
<li>block 内部实现原理是怎样的?</li>
<li>怎样写会造成循环引用,又是如何避免循环引用?</li>
<li>如果以上问题你都了解,可以不用往下看了。</li>
</ul>
<h4 id="为什么使用-Block"><a href="#为什么使用-Block" class="headerlink" title="为什么使用 Block"></a>为什么使用 Block</h4><p>block 的唯一好处就是:<strong>使代码变得更简洁</strong>。</p>
<p>我们可以向一个方法以参数的形式传递一个 block,作为方法的 callback 函数。类似于向方法传递一个函数指针。这样就不必再声明一个新的方法,并调用,在一定程度上简化了代码。下面有一个例子:</p>
<p>使用 notification 时,常规方式是注册一个 selector 并实现对应的方法,像这样:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserver:<span class="keyword">self</span></span><br><span class="line"> selector:<span class="keyword">@selector</span>(keyboardWillShow:)</span><br><span class="line"> name:<span class="built_in">UIKeyboardWillShowNotification</span> object:<span class="literal">nil</span>];</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">- (<span class="keyword">void</span>)keyboardWillShow:(<span class="built_in">NSNotification</span> *)notification {</span><br><span class="line"> <span class="comment">// Notification-handling code goes here.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果使用 block,可以写成这样:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserverForName:<span class="built_in">UIKeyboardWillShowNotification</span></span><br><span class="line"> object:<span class="literal">nil</span> queue:[<span class="built_in">NSOperationQueue</span> mainQueue] usingBlock:^(<span class="built_in">NSNotification</span> *notif) {</span><br><span class="line"> <span class="comment">// Notification-handling code goes here. </span></span><br><span class="line"> }];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>另外一个简化代码的特性就是,block 可以捕获外部变量。这样就不必再以参数的形式传递,简化的方法的定义和调用。</p>
<h4 id="Block-长什么样"><a href="#Block-长什么样" class="headerlink" title="Block 长什么样"></a>Block 长什么样</h4><p>在最初接触 block 时,我经常写不对,它的语法太另类。<a href="http://fuckingblocksyntax.com/" target="_blank" rel="external">fucking block syntax</a> 提供了各种 block 的写法,我这里就直接照搬过来了。</p>
<ul>
<li>作为局部变量</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};</span><br></pre></td></tr></table></figure>
<ul>
<li>作为属性(property)</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>, nullability) returnType (^blockName)(parameterTypes);</span><br></pre></td></tr></table></figure>
<ul>
<li>定义方法时,作为方法参数</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;</span><br></pre></td></tr></table></figure>
<ul>
<li>调用方法时,作为参数传递</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];</span><br></pre></td></tr></table></figure>
<ul>
<li>作为类型别名 (typedef),增加代码可读性</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> returnType (^TypeName)(parameterTypes);</span><br><span class="line">TypeName blockName = ^returnType(parameters) {...};</span><br></pre></td></tr></table></figure>
<h4 id="Block-内部原理是怎样的"><a href="#Block-内部原理是怎样的" class="headerlink" title="Block 内部原理是怎样的"></a>Block 内部原理是怎样的</h4><p>在编译时,编译器会将 block 语法转化成 C 的源代码,再将这部分 C 的源代码编译为编译器处理的代码。我们可以使用 clange (LLVM 编译器) 来完成 “将 block 语法转化为 C++ 源代码 (本质还是 C)” 这一阶段。具体命令如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clang -rewrite-objc 源代码文件名</span><br></pre></td></tr></table></figure>
<h5 id="1-一个简单-Block-的结构"><a href="#1-一个简单-Block-的结构" class="headerlink" title="1.一个简单 Block 的结构"></a>1.一个简单 Block 的结构</h5><p>下面我们转化一段 OC 代码来分析 block。</p>
<p>使用 <code>clang -rewrite-objc main.m</code> 转化如下代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[]) {</span><br><span class="line"> <span class="keyword">void</span> (^myBlock) (<span class="keyword">void</span>) = ^{printf(<span class="string">"test block"</span>);};</span><br><span class="line"> myBlock();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>转化接入后是下面这个样子(主要代码)。因为语法和命名的关系,代码看着很乱,但是逻辑很清晰。为了方便理解,我加了部分注释。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// block 结构体。可以理解为 'block' 这种类型的基本结构</span></span><br><span class="line"><span class="keyword">struct</span> __block_impl {</span><br><span class="line"> <span class="keyword">void</span> *isa;</span><br><span class="line"> <span class="keyword">int</span> Flags;</span><br><span class="line"> <span class="keyword">int</span> Reserved;</span><br><span class="line"> <span class="keyword">void</span> *FuncPtr;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 整个 block 的结构,命名有点歧义,理解即可</span></span><br><span class="line"><span class="keyword">struct</span> __main_block_impl_0 {</span><br><span class="line"> <span class="keyword">struct</span> __block_impl impl; <span class="comment">// __block_impl 类型的成员变量</span></span><br><span class="line"> <span class="keyword">struct</span> __main_block_desc_0* Desc; <span class="comment">// Desc 指针</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造函数主要是为两个成员变量赋值</span></span><br><span class="line"> __main_block_impl_0(<span class="keyword">void</span> *fp, <span class="keyword">struct</span> __main_block_desc_0 *desc, <span class="keyword">int</span> flags=<span class="number">0</span>) {</span><br><span class="line"> impl.isa = &_NSConcreteStackBlock;</span><br><span class="line"> impl.Flags = flags;</span><br><span class="line"> impl.FuncPtr = fp;</span><br><span class="line"> Desc = desc;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// block 的代码块,实际执行部分</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> __main_block_func_0(<span class="keyword">struct</span> __main_block_impl_0 *__cself) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"test block"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 版本升级所需的区域和 block 大小。不懂也没关系</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">struct</span> __main_block_desc_0 {</span><br><span class="line"> <span class="keyword">size_t</span> reserved;</span><br><span class="line"> <span class="keyword">size_t</span> Block_size;</span><br><span class="line">} __main_block_desc_0_DATA = { <span class="number">0</span>, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> __main_block_impl_0)};</span><br><span class="line"></span><br><span class="line"><span class="comment">// main 方法</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[])</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 定义 block</span></span><br><span class="line"> <span class="keyword">void</span> (*myBlock) (<span class="keyword">void</span>) = ((<span class="keyword">void</span> (*)())&__main_block_impl_0((<span class="keyword">void</span> *)__main_block_func_0, &__main_block_desc_0_DATA));</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 执行 block</span></span><br><span class="line"> ((<span class="keyword">void</span> (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上述代码中,定义了三个结构体:block 基本结构 <code>__block_impl</code>、Desc 指针 <code>__main_block_desc_0</code>、整个 block 的结构 <code>__main_block_impl_0</code>。其中 <code>__main_block_impl_0</code> 包含两个成员变量,分别为 <code>__block_impl</code> 结构体实例和 <code>__main_block_desc_0</code> 指针。</p>
<p>上述还定义了两个方法:block 实际执行方法 <code>__main_block_func_0</code> 和 <code>main()</code> 方法。</p>
<p><code>__main_block_func_0</code> 方法为输出对应的字符串(”test block”)。</p>
<p><code>main</code> 方法主要分为两步:</p>
<p>定义 block。将 block 实际执行方法,也就是 <code>__main_block_func_0</code> 的函数指针和 <code>__main_block_desc_0_DATA</code> 的地址传入 <code>__main_block_desc_0</code> 的构造方法,构造成一个完整的 block。根据定义可以看出 <code>__main_block_desc_0</code> 初始化时所有的大小为 <code>__main_block_impl_0</code> 结构体大小。</p>
<p>执行 block。实际可以简化为 <code>*myBlock->impl.FuncPtr</code>,就是调用对应的方法。</p>
<p>了解了这个基本结构,后面的都是在这基础上追加部分代码,很容易理解。</p>
<h5 id="2-Block-结构与-isa-指针"><a href="#2-Block-结构与-isa-指针" class="headerlink" title="2.Block 结构与 isa 指针"></a>2.Block 结构与 isa 指针</h5><p>在上述代码中,我们可以看出 block 结构体,也就是 <code>__block_impl</code> 中有一个 <code>isa</code> 指针。我们先来看看这个 <code>isa</code> 指针。</p>
<p>“id” 这一变量类型用于存储 OC 对象。在 <code>runtime.h</code> 中,它的定义如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> objc_objct {</span><br><span class="line"> Class isa;</span><br><span class="line">} *<span class="keyword">id</span>;</span><br></pre></td></tr></table></figure>
<p><code>Class</code> 类型属于一个结构体指针类型,定义为:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> objc_class *Class</span><br></pre></td></tr></table></figure>
<p><code>objc_class</code> 结构体定义如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> objc_class {</span><br><span class="line"> Class isa;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>综上可知,OC 中每个类的结构体就是基于 <code>objc_class</code> 结构体。</p>
<p>在上面可以看到这样一段代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">impl.isa = &_<span class="built_in">NSConcreteStackBlock</span>;</span><br></pre></td></tr></table></figure>
<p><code>isa</code> 被赋值为 <code>_NSConcreteStackBlock</code> 类型的指针。那么 <code>_NSConcreteStackBlock</code> 又是什么?通过 debug 界面我们可以看到如下情况 :</p>
<p><img src="/uploads/block/block debug 结构.png" alt="block debug 结构"></p>
<blockquote>
<p>block 一供有三种类型,分别为 <code>__NSGlobalBlock__</code>、<code>__NSStackBlock__</code>、<code>__NSMallocBlock__</code>,这三种类型后面会详细解释。这里转化的代码和 debug 界面显示的类型不一样,但是基本类型以信仰,都是 <code>Class</code> 类型,不必纠结。</p>
</blockquote>
<p>可以看出 <code>_NSConcreteStackBlock</code> 实际是 <code>Class</code> 类型。那么,<strong>block 本质就是 Objective-c 对象</strong>。</p>
<h5 id="3-捕获自动变量"><a href="#3-捕获自动变量" class="headerlink" title="3.捕获自动变量"></a>3.捕获自动变量</h5><p>我们将源代码改为如下情况:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[]) {</span><br><span class="line"> <span class="keyword">int</span> val = <span class="number">10</span>;</span><br><span class="line"> <span class="keyword">void</span> (^myBlock) (<span class="keyword">void</span>) = ^{printf(<span class="string">"value is %i"</span>, val);};</span><br><span class="line"> myBlock();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用 clang 进行转化。我们只看转化后的关键部分。即整个 block 结构:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> __main_block_impl_0 {</span><br><span class="line"> <span class="keyword">struct</span> __block_impl impl;</span><br><span class="line"> <span class="keyword">struct</span> __main_block_desc_0* Desc;</span><br><span class="line"> <span class="keyword">int</span> val;</span><br><span class="line"> __main_block_impl_0(<span class="keyword">void</span> *fp, <span class="keyword">struct</span> __main_block_desc_0 *desc, <span class="keyword">int</span> _val, <span class="keyword">int</span> flags=<span class="number">0</span>) : val(_val) {</span><br><span class="line"> impl.isa = &_<span class="built_in">NSConcreteStackBlock</span>;</span><br><span class="line"> impl.Flags = flags;</span><br><span class="line"> impl.FuncPtr = fp;</span><br><span class="line"> Desc = desc;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> __main_block_func_0(<span class="keyword">struct</span> __main_block_impl_0 *__cself) {</span><br><span class="line"> <span class="keyword">int</span> val = __cself->val; </span><br><span class="line"> printf(<span class="string">"value is %i"</span>, val);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看到局部变量 <code>val</code> 被自动追加到了 <code>__main_block_impl_0</code> 结构体中,并在构造函数中添加了参数。通过构造函数初始化 block 时,会将外部变量捕获进来。<strong>这里捕获的是引用,所以在 block 内部改变局部变量的值之后,并不会传出去</strong>。</p>
<h5 id="4-关于-block"><a href="#4-关于-block" class="headerlink" title="4.关于 __block"></a>4.关于 __block</h5><p>正常情况下,block 捕获的变量是不可以修改的。但是有两种方式可以让其修改:</p>
<ul>
<li>使用<strong>静态变量、静态全局变量、全局变量</strong>。因为前两个生成在静态数据区,最后一个生成在堆区。它们都不会随着 block 栈的消失而被释放。出了 block 作用域依然有效。但是平时使用这种变量诸多不变。</li>
<li>使用 <code>__block</code> 关键字修饰。它类似于 <code>static</code>、<code>auto</code> 和 <code>register</code> 这些关键字,主要来指定变量存储在哪个区域。</li>
</ul>
<p>为什么使用 <code>__block</code> 关键字修饰之后就可以修改。我们使用 clang 转化如下一段代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[]) {</span><br><span class="line"> __block <span class="keyword">int</span> val = <span class="number">10</span>;</span><br><span class="line"> <span class="keyword">void</span> (^myBlock) (<span class="keyword">void</span>) = ^{val = <span class="number">20</span>;};</span><br><span class="line"> myBlock();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>转换后如下,可以看出加了一句 <code>__block</code> 多了很多代码,依然是代码很乱,但是逻辑很清晰,我们只看主要部分 :</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> __Block_byref_val_0 {</span><br><span class="line"> <span class="keyword">void</span> *__isa;</span><br><span class="line">__Block_byref_val_0 *__forwarding;</span><br><span class="line"> <span class="keyword">int</span> __flags;</span><br><span class="line"> <span class="keyword">int</span> __size;</span><br><span class="line"> <span class="keyword">int</span> val;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> __main_block_impl_0 {</span><br><span class="line"> <span class="keyword">struct</span> __block_impl impl;</span><br><span class="line"> <span class="keyword">struct</span> __main_block_desc_0* Desc;</span><br><span class="line"> __Block_byref_val_0 *val; <span class="comment">// by ref</span></span><br><span class="line"> __main_block_impl_0(<span class="keyword">void</span> *fp, <span class="keyword">struct</span> __main_block_desc_0 *desc, __Block_byref_val_0 *_val, <span class="keyword">int</span> flags=<span class="number">0</span>) : val(_val->__forwarding) {</span><br><span class="line"> impl.isa = &_<span class="built_in">NSConcreteStackBlock</span>;</span><br><span class="line"> impl.Flags = flags;</span><br><span class="line"> impl.FuncPtr = fp;</span><br><span class="line"> Desc = desc;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> __main_block_func_0(<span class="keyword">struct</span> __main_block_impl_0 *__cself) {</span><br><span class="line"> __Block_byref_val_0 *val = __cself->val; <span class="comment">// bound by ref</span></span><br><span class="line"> (val->__forwarding->val) = <span class="number">20</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> __main_block_copy_0(<span class="keyword">struct</span> __main_block_impl_0*dst, <span class="keyword">struct</span> __main_block_impl_0*src) {</span><br><span class="line"> _Block_object_assign((<span class="keyword">void</span>*)&dst->val, (<span class="keyword">void</span>*)src->val, <span class="number">8</span><span class="comment">/*BLOCK_FIELD_IS_BYREF*/</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> __main_block_dispose_0(<span class="keyword">struct</span> __main_block_impl_0*src) {</span><br><span class="line"> _Block_object_dispose((<span class="keyword">void</span>*)src->val, <span class="number">8</span><span class="comment">/*BLOCK_FIELD_IS_BYREF*/</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">struct</span> __main_block_desc_0 {</span><br><span class="line"> size_t reserved;</span><br><span class="line"> size_t Block_size;</span><br><span class="line"> <span class="keyword">void</span> (*<span class="keyword">copy</span>)(<span class="keyword">struct</span> __main_block_impl_0*, <span class="keyword">struct</span> __main_block_impl_0*);</span><br><span class="line"> <span class="keyword">void</span> (*dispose)(<span class="keyword">struct</span> __main_block_impl_0*);</span><br><span class="line">} __main_block_desc_0_DATA = { <span class="number">0</span>, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[]) {</span><br><span class="line"> __attribute__((__blocks__(<span class="keyword">byref</span>))) __Block_byref_val_0 val = {(<span class="keyword">void</span>*)<span class="number">0</span>,(__Block_byref_val_0 *)&val, <span class="number">0</span>, <span class="keyword">sizeof</span>(__Block_byref_val_0), <span class="number">10</span>};</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">void</span> (*myBlock) (<span class="keyword">void</span>) = ((<span class="keyword">void</span> (*)())&__main_block_impl_0((<span class="keyword">void</span> *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, <span class="number">570425344</span>));</span><br><span class="line"> </span><br><span class="line"> ((<span class="keyword">void</span> (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们可以看出局部变量转化为一个结构体:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> __Block_byref_val_0 {</span><br><span class="line"> <span class="keyword">void</span> *__isa;</span><br><span class="line">__Block_byref_val_0 *__forwarding;</span><br><span class="line"> <span class="keyword">int</span> __flags;</span><br><span class="line"> <span class="keyword">int</span> __size;</span><br><span class="line"> <span class="keyword">int</span> val;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>在 <code>__main_block_impl_0</code> 中追加了一个 <code>__Block_byref_val_0</code> 结构体指针,后续的初始化和修改 val 的值也是<strong>通过指针来操作</strong>。所以修改后的值就可以传出去了。</p>
<h5 id="5-block-的存储类型"><a href="#5-block-的存储类型" class="headerlink" title="5.block 的存储类型"></a>5.block 的存储类型</h5><p>前面有提到过,block 按照存储类型划分,可以分为三种:</p>
<ul>
<li>_NSConcreteGlobalBlock</li>
<li>_NSConcreteStackBlock</li>
<li>_NSConcreteMallocBlock</li>
</ul>
<p>他们在内存中的存储结构如下图所示,对号入座:</p>
<p><img src="/uploads/block/block 存储结构.png" alt="block 存储结构"></p>
<p>我们分别来解释一下。</p>
<p><strong>_NSConcreteGlobalBlock,也叫全局 block。</strong>有两种生成方式:<br>一种是在全局的地方生成,不存在捕获局部变量的情况。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span>(^globalBlock)(<span class="keyword">void</span>) = ^{printf(<span class="string">"this is global block"</span>);};</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[]) {</span><br><span class="line"> globalBlock();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>另一种是,block 中不截获局部变量。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">int</span> (^TestBlock) (<span class="keyword">int</span>);</span><br><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> * argv[]) {</span><br><span class="line"> TestBlock block = ^(<span class="keyword">int</span> num) {printf(<span class="string">"num is %d"</span>,num);}</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>_NSConcreteStackBlock,也叫栈 block。</strong>除了上述的初始化方式,通过其他方式初始化为的 block 都是栈 block。</p>
<p><strong>_NSConcreteMallocBlock,也叫堆 block。</strong><br>堆 block 不是由代码初始化来的,而是由栈 block 调用 copy 方法时从栈内存拷贝到堆内存而得来的。</p>
<p>至于什么时候会发生 copy 操作,可以总结为一下几点 (ARC 环境):</p>
<ul>
<li>Cocoa 框架的方法且方法名中含有 usingBlock。</li>
<li>GCD 中的方法。</li>
<li>block 赋值给强引用对象时。</li>
<li>作为返回值时。</li>
<li>显示调用 copy 方法。</li>
</ul>
<p>下面是一些例子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="built_in">BOOL</span> (^TestBlock)(<span class="built_in">NSString</span> *);</span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">void</span> (^paramBlock)(<span class="built_in">NSString</span> *);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> val = <span class="number">10</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// block1 is global block</span></span><br><span class="line"> <span class="keyword">void</span> (^block1)(<span class="built_in">NSString</span> *) = ^(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"this is global block"</span>);</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// block2 is malloc block</span></span><br><span class="line"> <span class="keyword">void</span> (^block2)(<span class="built_in">NSString</span> *) = ^(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="keyword">int</span> value = <span class="number">10</span> * val;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"this is malloc block"</span>);</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// block3 is stack block</span></span><br><span class="line"> __<span class="keyword">weak</span> <span class="keyword">void</span> (^block3)(<span class="built_in">NSString</span> *) = ^(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="keyword">int</span> value = <span class="number">10</span> * val;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"this is stack block"</span>);</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// block4 is malloc block</span></span><br><span class="line"> TestBlock block4 = [<span class="keyword">self</span> testWithBlock:^(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"noting"</span>);</span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// block5 is global block</span></span><br><span class="line"> TestBlock block5 = [<span class="keyword">self</span> getGlobalBlock];</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (TestBlock)testWithBlock:(paramBlock)block {</span><br><span class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"capture block is %@"</span>,block); <span class="comment">// malloc block</span></span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> val = <span class="number">10</span>;</span><br><span class="line"> <span class="keyword">return</span> ^<span class="built_in">BOOL</span>(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="keyword">int</span> value = val * <span class="number">10</span>;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"noting"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (TestBlock)getGlobalBlock {</span><br><span class="line"> <span class="keyword">return</span> ^<span class="built_in">BOOL</span>(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"nothing"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="6-block-变量结构中的-forwarding"><a href="#6-block-变量结构中的-forwarding" class="headerlink" title="6. block 变量结构中的 forwarding"></a>6. <strong>block 变量结构中的 </strong>forwarding</h5><p>在前面的代码中,我们发现 <code>__block</code> 代码中有一个 <code>__forwarding</code>,如下面的代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> __Block_byref_val_0 {</span><br><span class="line"> <span class="keyword">void</span> *__isa;</span><br><span class="line">__Block_byref_val_0 *__forwarding;</span><br><span class="line"> <span class="keyword">int</span> __flags;</span><br><span class="line"> <span class="keyword">int</span> __size;</span><br><span class="line"> <span class="keyword">int</span> val;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>长话短说。当一个栈 block 捕获了一个在栈上生成的 <code>__block</code> 变量,那么随着 block 从栈上 copy 到堆上,这个 <code>__block</code> 变量也从栈上 copy 到堆上。因为有一个 <code>__forwarding</code> 指针,使得无论从从栈上还是堆上,访问的都是一个变量。如果没有明白看下面的图和代码。</p>
<p><img src="/uploads/block/block 拷贝.png" alt="block 拷贝"></p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">__block <span class="keyword">int</span> val = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> (^block)(<span class="keyword">int</span>) = [^(<span class="keyword">int</span> count) { val++;} <span class="keyword">copy</span>];</span><br><span class="line"></span><br><span class="line">val++;</span><br><span class="line">block();</span><br><span class="line"></span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"val is %d"</span>,val); <span class="comment">// val is 12;</span></span><br></pre></td></tr></table></figure>
<p>无论是操作栈上的 val 变量还是堆上的 val 变量,最终修改的是同一个值。</p>
<h5 id="7-block-与循环引用"><a href="#7-block-与循环引用" class="headerlink" title="7.block 与循环引用"></a>7.block 与循环引用</h5><p>发生循环引用说明出现了互相持有的现象,例如下面这样:</p>
<p><img src="/uploads/block/循环引用.png" alt="循环引用"></p>
<p>上图中 self 持有 blk 属性,blk 持有 block,block 持有 self,这就形成了一个环。现如今的 Xcode 已经很智能,这种简单的循环引用,会出现警告。</p>
<p>为避免循环引用,可以使用 <code>__weak</code> 关键字。例如下面这样:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">__<span class="keyword">weak</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) weakSelf = <span class="keyword">self</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">self</span>.blk = ^<span class="built_in">BOOL</span>(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> [weakSelf log];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>为了避免在 block 内使用 self 期间,self 被释放。可以在 block 内部对 self 进行强引用。因为这个强引用生成在 block 栈内,会随着 block 的作用域消失而消失。不会产生循环引用。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">__<span class="keyword">weak</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) weakSelf = <span class="keyword">self</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">self</span>.blk = ^<span class="built_in">BOOL</span>(<span class="built_in">NSString</span> *name) {</span><br><span class="line"> <span class="keyword">typeof</span>(<span class="keyword">self</span>) <span class="keyword">self</span> = weakSelf;</span><br><span class="line"> [<span class="keyword">self</span> log];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h4 id="如何使用-Block"><a href="#如何使用-Block" class="headerlink" title="如何使用 Block"></a>如何使用 Block</h4><p> 前面讲了很多原理,过程中也讲了很多使用。这里只总结几点,使用 block 一定要注意:</p>
<ul>
<li>block 的命名方式,牢记。</li>
<li>对于要再 block 内修改的变量,加 <code>__block</code> 修饰符。对于 OC 中的一些对象,例如 NSMutableArray,如果只修改数组内的元素,不需要加 <code>__block</code>;如果要修改数组的指针,需要加 <code>__block</code>。</li>
<li>使用自定义 block 时,注意循环引用的问题。尤其是各种间接关系产生的循环引用。</li>
<li><p>对于捕获到 block 中的弱引用,如果怕使用期间被释放,需要再 block 内部再次强引用一下。</p>
<p>综上,block 总结完毕,祝好运。</p>
</li>
</ul>
<h4 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h4><p>1.<a href="https://developer.apple.com/library/content/featuredarticles/Short_Practical_Guide_Blocks/index.html" target="_blank" rel="external">A Short Practical Guide to Blocks</a></p>
<p>2.<a href="http://fuckingblocksyntax.com/" target="_blank" rel="external">How Do I Declare A Block in Objective-C?</a></p>
<p>3.<a href="https://book.douban.com/subject/24720270/" target="_blank" rel="external">Objective-C高级编程</a></p>
<a id="more"></a>
<p>关于 block 的文章,网上已经有很多了。我这里只是将这个知识点再梳理一下,做一下记录。毕竟年纪大了,容易忘事。</p>
<h4 id="抛砖引玉"><a href="#抛砖引玉" class="headerlink" title="抛
我所理解的 CocoaPods
http://yoursite.com/2018/02/16/我所理解的CocoaPods/
2018-02-16T09:09:23.000Z
2018-06-03T13:13:36.986Z
<a id="more"></a>
<p>很久之前读了一遍 <a href="https://guides.cocoapods.org/" target="_blank" rel="external">Cocoa Pods 官方文档</a>,对 Cocoa Pods 有了一个简单的了解。时隔多日,全忘了。</p>
<p>所以再回顾一下,顺便写一篇总结。文章分为<strong>原理</strong>和<strong>使用</strong>两部分,比较长,可以根据自己的需求选择性阅读。</p>
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>CocoaPods 是开发 OS X 和 iOS 应用程序的一个第三方库的依赖管理工具,使用这个工具可以简化对组件依、更新的过程。新添加一些第三方组件可以直接修改 podfile 然后进行 <code>pod install</code>;更新已有第三方组件,可以修改 podfile 然后进行 <code>pod update</code>;自己开发的组件也可以上传到 CocoaPods 或者私有仓库,供其他人使用。</p>
<p>CocoaPods 是用 ruby 写的,由若干个 gems 组成。也就是说,iOS project 使用 CocoaPods 来进行组件管理,CocoaPods 本身也是一个 project,它使用 gem 进行组件管理。</p>
<p>开始写这篇文章的时候,我想先写使用,再写原理。因为我担心很多人感觉原理晦涩难懂,就放弃看后面了。但构思的时候发现,明白了原理之后,对一些命令的使用会有更深刻的了解。所以还是决定将原理放在前面讲。</p>
<h3 id="基本原理"><a href="#基本原理" class="headerlink" title="基本原理"></a>基本原理</h3><h4 id="1-CocoaPods-结构"><a href="#1-CocoaPods-结构" class="headerlink" title="1.CocoaPods 结构"></a>1.CocoaPods 结构</h4><p>CocoaPods 是用 Ruby 写的,并由若干个 Ruby 包 (gems) 构成的,源码托管在 <a href="https://github.com/CocoaPods" target="_blank" rel="external">GitHub</a> 上。其中主要的几个组件为:</p>
<ul>
<li><p><strong><a href="https://github.com/CocoaPods/Specs" target="_blank" rel="external">CocoaPods/Specs</a></strong><br>这个是一个保存第三方组件 podspec 文件的仓库。第三方组件开发完成之后,会传一份 podspec 文件传到 CocoaPods,这个 Specs 包含了每个第三方组件所有版本的 podspec 文件。当使用某个第三方组件时,如果这些组件支持 CocoaPods,会在 Podfile 中指定 source,例如下面这样:</p>
<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">source <span class="string">'https://github.com/CocoaPods/Specs.git'</span></span><br></pre></td></tr></table></figure>
<p>当执行 <code>pod install</code> 或 <code>pod update</code> 等一些命令时,便会从这个仓库找到组件指定版本的 podspec 文件,然后根据这个 podspec 配置信息去获取组件。</p>
</li>
<li><p><strong><a href="https://github.com/CocoaPods/CocoaPods" target="_blank" rel="external">CocoaPods/CocoaPods</a></strong><br>这是是一个面向用户的组件,每当执行一个 pod 命令时,这个组件都将被激活。该组件包括了所有使用 CocoaPods 涉及到的功能,并且还能通过调用所有其它的 gems 来执行任务。</p>
</li>
<li><p><strong><a href="https://github.com/CocoaPods/Core" target="_blank" rel="external">CocoaPods/Core</a></strong><br>这个 gem 组件提供支持与 CocoaPods 相关文件的处理,例如 <a href="https://guides.cocoapods.org/syntax/podspec.html" target="_blank" rel="external">Specification</a>、<a href="https://guides.cocoapods.org/syntax/podfile.html" target="_blank" rel="external">Podfile</a> 和 <a href="https://github.com/CocoaPods/Specs" target="_blank" rel="external">Source</a>。当执行 <code>pod install</code> 等一些命令时。Core 组件会解析第三方组件开发者上传的 podspec 文件和使用者的 podfile,以此确定需要为 project 引入哪些文件。除此之外,当执行与这些文件一些相关的命令时,也由这部分组件处理,例如使用 <code>pod spec lint</code> 来检测 podspec 文件的有效性。</p>
</li>
<li><p><strong><a href="https://github.com/CocoaPods/Xcodeproj" target="_blank" rel="external">CocoaPods/Xcodeproj</a></strong><br>使用这个 gem 组件,你可以用 ruby 来创建并修改 Xcode projects。在 CocoaPods 中它负责所有工程文件的整合。如果你想要写一个脚本来方便的修改工程文件,那么可以单独下载这个 gem 并使用。更多信息可以查看工程的 <a href="https://github.com/CocoaPods/Xcodeproj" target="_blank" rel="external">readme</a>。</p>
</li>
</ul>
<h4 id="2-几个相关文件"><a href="#2-几个相关文件" class="headerlink" title="2.几个相关文件"></a>2.几个相关文件</h4><ul>
<li><p><strong>Specification</strong></p>
<p>这个文件用来描述第三方组件某个版本的信息。主要包含了组件拉取地址、应该拉取那些文件和项目配置信息。除此之外,还包含一些组件信息,例如组件的名字、版本等。后面章节会详细讲解字段含义和文件书写规范。</p>
</li>
<li><p><strong>Podfile</strong> </p>
<p>这个文件用来指定工程中依赖了那些组件。主要包含了依赖的组件名、组件版本、组件地址等。后面章节会详细讲解字段含义和文件书写规范。</p>
</li>
<li><p><strong>Podfile.lock</strong></p>
<p>在第一次执行 <code>pod install</code> 时,执行完毕后会生成一个 podfile.lock 文件。这个文件主要标注了项目当前依赖的具体版本。看下面这个文件信息:</p>
<p><img src="/uploads/Cocoa-Pods/PodfileLock.png" alt="PodfileLock.png"></p>
</li>
</ul>
<p> 有个问题需要牢记:<strong>CocoaPods 强烈建议将 Podfile.lock 文件加入版本管理,这样其他人同步了你的 podfile.lock 文件之后,执行 <code>pod install</code> 时会将按照里面指定给的版本加载,避免多人协作时发生冲突</strong>。后面的 pod install vs pod update 会详细讲解 podfile.lock 变更时机。</p>
<ul>
<li><strong>Manifest.lock</strong></li>
</ul>
<p>Manifest.lock 是由 Podfile.lock 生成的一个副本,每次生成或者更新 Podfile.lock,都会更下 Pods 文件夹下面的 Manifest.lock 文件。如果你遇见过这样的错误 沙盒文件与 Podfile.lock 文件不同步 (The sandbox is not in sync with the Podfile.lock),这是因为 Manifest.lock 文件和 Podfile.lock 文件不一致所引起。</p>
<h4 id="3-相互关系"><a href="#3-相互关系" class="headerlink" title="3.相互关系"></a>3.相互关系</h4><p> <img src="/uploads/Cocoa-Pods/三者关系.png" alt="三者关系"></p>
<p>上图为组件开发者、CocoaPods、组件使用者三者的关系。</p>
<p>组件开发者开发完组件之后,会将组件上传到仓库 (Github or other)。然后创建一个 podspec 文件,文件中包含了使用组件时需要加载哪些文件以及从哪里加载。然后会将这个文件上传到 CocoaPods(也可以上传至私人构建的 spec 管理仓库)。</p>
<p>组件使用者想要使用某个组件,会在 Podfile 中指定组件的名字、版本、加载源以及更加详细的信息(例如想要加载某个 commit)。然后执行相关 Pod 命令。</p>
<p>CocoaPods 执行 Pod 命令,然后解析对应的 podspec 文件,确定需要加载的文件信息并将文件加载到项目工程里。并创建 Podfile.lock、Manifest.lock、Pods.xcodeproj 等文件。</p>
<h4 id="4-一次详细的加载过程"><a href="#4-一次详细的加载过程" class="headerlink" title="4.一次详细的加载过程"></a>4.一次详细的加载过程</h4><p>前面提到 CocoaPods 是开源的,所以我们可以把源码下载下来进行研究。<code>pod install</code> 这个命令定义在 <a href="https://github.com/CocoaPods/Core" target="_blank" rel="external">CocoaPods/Core</a> 这 gem 中。</p>
<h5 id="执行-pod-install-命令"><a href="#执行-pod-install-命令" class="headerlink" title="执行 pod install 命令"></a>执行 pod install 命令</h5><p>所有命令都是通过 <code>Command</code> 类管理的,执行 <code>pod install</code> 时代码如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/command/install.rb</span></span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">Pod</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Command</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Install</span> < Command</span></span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">run</span></span></span><br><span class="line"> verify_podfile_exists!</span><br><span class="line"> installer = installer_for_config</span><br><span class="line"> installer.repo_update = repo_update?(<span class="symbol">:default</span> => <span class="keyword">false</span>)</span><br><span class="line"> installer.update = <span class="keyword">false</span></span><br><span class="line"> installer.install!</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>执行时会先生成一个 <code>installer</code> 实例。然后设置 <code>repo_update</code> 属性和 <code>update</code> 属性,最后执行 <code>install</code> 方法。</p>
<h5 id="Podfile-解析"><a href="#Podfile-解析" class="headerlink" title="Podfile 解析"></a>Podfile 解析</h5><p>执行 <code>pod install</code> 命令具体细节前,首先要解析 Podfile。这一过程在初始化 <code>installer</code> 实例时就已经开始:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">installer_for_config</span></span></span><br><span class="line"> Installer.new(config.sandbox, config.podfile, config.lockfile)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<h5 id="pod-install-方法定义"><a href="#pod-install-方法定义" class="headerlink" title="pod install 方法定义"></a>pod install 方法定义</h5><p>pod install 方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">install!</span></span></span><br><span class="line"> prepare</span><br><span class="line"> resolve_dependencies</span><br><span class="line"> download_dependencies</span><br><span class="line"> validate_targets</span><br><span class="line"> generate_pods_project</span><br><span class="line"> <span class="keyword">if</span> installation_options.integrate_targets?</span><br><span class="line"> integrate_user_project</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> UI.section <span class="string">'Skipping User Project Integration'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> perform_post_install_actions</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>从方法定义中,可以看出 <code>pod install</code> 的执行分为如下几部:<strong>准备阶段、解决依赖冲突、下载依赖文件、校验 target、整合 project 文件、输出执行结果</strong>。下面将按照这个步骤逐步分析。</p>
<h5 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h5><p>准备阶段代码如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">prepare</span></span></span><br><span class="line"> <span class="comment"># Raise if pwd is inside Pods</span></span><br><span class="line"> <span class="keyword">if</span> Dir.pwd.start_with?(sandbox.root.to_path)</span><br><span class="line"> message = <span class="string">'Command should be run from a directory outside Pods directory.'</span></span><br><span class="line"> message << <span class="string">"\n\n\tCurrent directory is <span class="subst">#{UI.path(Pathname.pwd)}</span>\n"</span></span><br><span class="line"> raise Informative, message</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> UI.message <span class="string">'Preparing'</span> <span class="keyword">do</span></span><br><span class="line"> deintegrate_if_different_major_version</span><br><span class="line"> sandbox.prepare</span><br><span class="line"> ensure_plugins_are_installed!</span><br><span class="line"> run_plugins_pre_install_hooks</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>首先会检测目录结构,是否为可执行 pod 命令的目录,如果不是直接输出信息。如果可执行,则做一些准备工作。如果你的 Podfile 中写了一些 hooks,也会在这里执行。</p>
<h5 id="解决依赖冲突"><a href="#解决依赖冲突" class="headerlink" title="解决依赖冲突"></a>解决依赖冲突</h5><p>这一阶段的方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">resolve_dependencies</span></span></span><br><span class="line"> plugin_sources = run_source_provider_hooks</span><br><span class="line"> analyzer = create_analyzer(plugin_sources)</span><br><span class="line"></span><br><span class="line"> UI.section <span class="string">'Updating local specs repositories'</span> <span class="keyword">do</span></span><br><span class="line"> analyzer.update_repositories</span><br><span class="line"> <span class="keyword">end</span> <span class="keyword">if</span> repo_update?</span><br><span class="line"></span><br><span class="line"> UI.section <span class="string">'Analyzing dependencies'</span> <span class="keyword">do</span></span><br><span class="line"> analyze(analyzer)</span><br><span class="line"> validate_build_configurations</span><br><span class="line"> clean_sandbox</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> analyzer</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>根据方法定义,我们可以看出这一阶段处理的事情:启动 hooks 并创建一个 <code>analyzer</code>,然后使用这个 <code>analyzer</code> 更新本地 specs 库、处理版本依赖。</p>
<p>首先是创建 <code>analyzer</code>,创建过程中将 <code>Podfile</code> 和 <code>lockfile</code> 等一些文件信息全部传入,并在这个类中将这些文件解析。创建 <code>analyzer</code> 代码如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_analyzer</span><span class="params">(plugin_sources = <span class="keyword">nil</span>)</span></span></span><br><span class="line"> Analyzer.new(sandbox, podfile, lockfile, plugin_sources).tap <span class="keyword">do</span> |analyzer|</span><br><span class="line"> analyzer.installation_options = installation_options</span><br><span class="line"> analyzer.has_dependencies = has_dependencies?</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>然后是更新本地 specs 库。从代码中可以看出有一个 <code>repo_update?</code> 判断,也就是说这个标志位真的时候,才会更新本地 specs 库。也就是我们常用的一条命令:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod repo udpate</span><br></pre></td></tr></table></figure>
<p>最后是处理依赖关系。其中 <code>Podfile</code>、<code>lockfile</code> 也是使用 <code>Analyzer</code> 这个类中解析。下面是解析方法的定义 :</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer/analyzer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">analyze</span><span class="params">(allow_fetches = <span class="keyword">true</span>)</span></span></span><br><span class="line"> validate_podfile! <span class="comment"># step1: 解析并校验 podfile</span></span><br><span class="line"> validate_lockfile_version! <span class="comment"># step2: 解析并校验 lockfile 中的库的版本</span></span><br><span class="line"> @result = AnalysisResult.new <span class="comment"># step3: 新建 result 实例</span></span><br><span class="line"> ...</span><br><span class="line"> @result.specifications = generate_specifications(resolver_specs_by_target)</span><br><span class="line"> @result.targets = generate_targets(resolver_specs_by_target)</span><br><span class="line"> @result.sandbox_state = generate_sandbox_state</span><br><span class="line"> @result.specs_by_target = resolver_specs_by_target.each_with_object({}) <span class="keyword">do</span> |rspecs_by_target, hash|</span><br><span class="line"> hash[rspecs_by_target[<span class="number">0</span>]] = rspecs_by_target[<span class="number">1</span>].map(&<span class="symbol">:spec</span>)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> @result.specs_by_source = Hash[resolver_specs_by_target.values.flatten(<span class="number">1</span>).group_by(&<span class="symbol">:source</span>).map { |source, specs| [source, specs.map(&<span class="symbol">:spec</span>).uniq] }]</span><br><span class="line"> sources.each { |s| @result.specs_by_source[s] ||= [] }</span><br><span class="line"> @result</span><br><span class="line"> <span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>最终会将解析结果保存在一个 <code>@result</code> 实例中,进行后面步骤时,会使用这个解析结果。<code>AnalysisResult</code> 类定义如下,注释我就不翻译了,看原味的英文更有助于理解具体意思:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer/analyzer/analysis_result.rb</span></span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">Pod</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Installer</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Analyzer</span></span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">AnalysisResult</span></span></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [SpecsState] the states of the Podfile specs.</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:podfile_state</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [Hash{TargetDefinition => Array<Specification>}] the</span></span><br><span class="line"> <span class="comment"># specifications grouped by target.</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:specs_by_target</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [Hash{Source => Array<Specification>}] the</span></span><br><span class="line"> <span class="comment"># specifications grouped by spec repo source.</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:specs_by_source</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [Array<Specification>] the specifications of the resolved</span></span><br><span class="line"> <span class="comment"># version of Pods that should be installed.</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:specifications</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [SpecsState] the states of the {Sandbox} respect the resolved</span></span><br><span class="line"> <span class="comment"># specifications.</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:sandbox_state</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [Array<AggregateTarget>] The aggregate targets created for each</span></span><br><span class="line"> <span class="comment"># {TargetDefinition} from the {Podfile}.</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:targets</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [Hash{TargetDefinition => Array<TargetInspectionResult>}] the</span></span><br><span class="line"> <span class="comment"># results of inspecting the user targets</span></span><br><span class="line"> <span class="keyword">attr_accessor</span> <span class="symbol">:target_inspections</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># <span class="doctag">@return</span> [Hash{String=>Symbol}] A hash representing all the user build</span></span><br><span class="line"> <span class="comment"># configurations across all integration targets. Each key</span></span><br><span class="line"> <span class="comment"># corresponds to the name of a configuration and its value to</span></span><br><span class="line"> <span class="comment"># its type (`:debug` or `:release`).</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">all_user_build_configurations</span></span></span><br><span class="line"> targets.reduce({}) <span class="keyword">do</span> |result, target|</span><br><span class="line"> result.merge(target.user_build_configurations)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>关于 <code>Podfile</code> 的解析过程,有兴趣的可以查看一下 <code>PodfileValidator</code> 类,在目录 <code>CocoaPods/lib/cocoapods/installer/analyzer/podfile_validator.rb</code>。</p>
<h5 id="下载依赖文件"><a href="#下载依赖文件" class="headerlink" title="下载依赖文件"></a>下载依赖文件</h5><p>下载依赖文件方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">download_dependencies</span></span></span><br><span class="line"> UI.section <span class="string">'Downloading dependencies'</span> <span class="keyword">do</span></span><br><span class="line"> create_file_accessors</span><br><span class="line"> install_pod_sources</span><br><span class="line"> run_podfile_pre_install_hooks</span><br><span class="line"> clean_pod_sources</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>这个方法中调用了几个其他方法。作用分别为:创建文件存储器,以便向沙盒里面写入数据;下载数据;启动 hooks;进行清理操作。具体每个方法的定义,可以查看文件源码。这里主要说一下 <code>install_pod_sources</code> 方法。</p>
<p><code>install_pod_sources</code> 方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="comment"># Downloads, installs the documentation and cleans the sources of the Pods</span></span><br><span class="line"><span class="comment"># which need to be installed.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># <span class="doctag">@return</span> [void]</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">install_pod_sources</span></span></span><br><span class="line"> @installed_specs = []</span><br><span class="line"> pods_to_install = sandbox_state.added | sandbox_state.changed</span><br><span class="line"> title_options = { <span class="symbol">:verbose_prefix</span> => <span class="string">'-> '</span>.green }</span><br><span class="line"> root_specs.sort_by(&<span class="symbol">:name</span>).each <span class="keyword">do</span> |spec|</span><br><span class="line"> <span class="keyword">if</span> pods_to_install.<span class="keyword">include</span>?(spec.name)</span><br><span class="line"> <span class="keyword">if</span> sandbox_state.changed.<span class="keyword">include</span>?(spec.name) && sandbox.manifest</span><br><span class="line"> previous = sandbox.manifest.version(spec.name)</span><br><span class="line"> title = <span class="string">"Installing <span class="subst">#{spec.name}</span> <span class="subst">#{spec.version}</span> (was <span class="subst">#{previous}</span>)"</span></span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> title = <span class="string">"Installing <span class="subst">#{spec}</span>"</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> UI.titled_section(title.green, title_options) <span class="keyword">do</span></span><br><span class="line"> install_source_of_pod(spec.name)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> UI.titled_section(<span class="string">"Using <span class="subst">#{spec}</span>"</span>, title_options) <span class="keyword">do</span></span><br><span class="line"> create_pod_installer(spec.name)</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>首先确定需要 install 的组件。这里主要针对新加的组件和变更的组件进行 install,至于这些信息是通过 <code>sandbox_state</code> 获取的。然而 <code>sandbox_state</code> 方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">sandbox_state</span></span></span><br><span class="line"> analysis_result.sandbox_state</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>这里的 <code>analysis_result</code> 就是我们上一步中解析出的结果,在这里用到了。</p>
<p>第二步创建 title 配置信息,后面针对变更的组件,会用这个配置标记。相信每一位开发者进行 pod install 操作的时候,都会注意到变更的组件自动标记为绿色。</p>
<p>最后一步是下载对应文件。这里分了三种情况:如果组件已经下载且版本号没有发生变化,则直接提示 “Using xxx”,如下图中的 “YYCache” 组件;如果组件已经下载,但是版本号发生了变化,则更新组件并提示 “Installing xxx 版本号 (之前版本号)”,如下图中的 “AFNetworking” 组件;如果组件第一次下载,则进行下载,并提示 “Installing xxx”,如下图中的 “YYImage”。</p>
<p><img src="/uploads/Cocoa-Pods/podInstall.png" alt="podInstall.png"></p>
<h5 id="校验-target"><a href="#校验-target" class="headerlink" title="校验 target"></a>校验 target</h5><p>校验 target 代码如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">validate_targets</span></span></span><br><span class="line"> validator = <span class="symbol">Xcode:</span><span class="symbol">:TargetValidator</span>.new(aggregate_targets, pod_targets)</span><br><span class="line"> validator.validate!</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer/xcode/target_validator.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">validate!</span></span></span><br><span class="line"> verify_no_duplicate_framework_and_library_names</span><br><span class="line"> verify_no_static_framework_transitive_dependencies</span><br><span class="line"> verify_no_pods_used_with_multiple_swift_versions</span><br><span class="line"> verify_framework_usage</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>这个方法中,创建了一个 <code>TargetValidator</code> 实例,并调用 <code>validate()</code> 方法进行校验。这方方法主要分为以下几步:</p>
<ul>
<li><p>检测是否有多重引用 framework 或者 library 的情况。因为一个组件可能分成多个 subspec,如果不清楚 subspec 中的依赖关系。使用时可能会出现多重引用的情况。举个例子,下面是 “<a href="https://github.com/netease-im/NIM_iOS_UIKit/blob/master/NIMKit.podspec" target="_blank" rel="external">网易云信</a>“ 的 podspec 文件,以及其中依赖的两个组件的 podspec 文件:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment"># NIMKit.podspec</span></span><br><span class="line"><span class="symbol">Pod:</span><span class="symbol">:Spec</span>.new <span class="keyword">do</span> |s| </span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> s.subspec <span class="string">'Full'</span> <span class="keyword">do</span> |cs| </span><br><span class="line"> cs.source_files = <span class="string">'NIMKit/NIMKit/**/*.{h,m}'</span> </span><br><span class="line"> cs.dependency <span class="string">'NIMKit/Core'</span> </span><br><span class="line"> cs.dependency <span class="string">'NIMSDK'</span>, <span class="string">'~> 4.9.0'</span> </span><br><span class="line"> <span class="keyword">end</span> </span><br><span class="line"></span><br><span class="line"> s.subspec <span class="string">'Lite'</span> <span class="keyword">do</span> |cs| </span><br><span class="line"> cs.source_files = <span class="string">'NIMKit/NIMKit/**/*.{h,m}'</span> </span><br><span class="line"> cs.dependency <span class="string">'NIMKit/Core'</span> </span><br><span class="line"> cs.dependency <span class="string">'NIMSDK_LITE'</span>, <span class="string">'~> 4.9.0'</span> </span><br><span class="line"> <span class="keyword">end</span> </span><br><span class="line"></span><br><span class="line"> s.subspec <span class="string">'Core'</span> <span class="keyword">do</span> |os| </span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">end</span> </span><br><span class="line"></span><br><span class="line"> s.default_subspec = <span class="string">'Lite'</span> </span><br><span class="line"><span class="keyword">end</span> </span><br><span class="line"></span><br><span class="line"> <span class="comment"># NIMSDK.podspec</span></span><br><span class="line"> <span class="symbol">Pod:</span><span class="symbol">:Spec</span>.new <span class="keyword">do</span> |s| </span><br><span class="line"> ...</span><br><span class="line"> s.vendored_libraries = <span class="string">'**/Libs/*.a'</span> </span><br><span class="line"> s.vendored_frameworks = <span class="string">'**/NIMSDK.framework'</span>,<span class="string">'**/NIMAVChat.framework'</span></span><br><span class="line"> ... </span><br><span class="line"> <span class="keyword">end</span> </span><br><span class="line"> </span><br><span class="line"> <span class="comment"># NIMSDK_LITE.podspec</span></span><br><span class="line"><span class="symbol">Pod:</span><span class="symbol">:Spec</span>.new <span class="keyword">do</span> |s| </span><br><span class="line"> ...</span><br><span class="line"> s.vendored_libraries = <span class="string">'NIMSDK/Libs/*.a'</span></span><br><span class="line"> s.vendored_frameworks = <span class="string">'**/NIMSDK.framework'</span> </span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
</li>
</ul>
<pre><code>然后我这样去引用:
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod <span class="string">'NIMKit'</span>, <span class="symbol">:subspecs</span> => [<span class="string">'Lite'</span>,<span class="string">'Full'</span>]</span><br></pre></td></tr></table></figure>
因为两个 spec 中都引用了 NIMKit framework,所以执行 `pod install` 的时候就会出现如下问题:
</code></pre><p><img src="/uploads/Cocoa-Pods/target_confilct.png" alt="target_confilct.png"></p>
<pre><code>> 这里还是不太理解,可能表述有误。如果清楚请指出,我加以改正。
</code></pre><ul>
<li><p>处理静态库传递依赖问题。如果 A 组件依赖 B 组件,B 组件中含有通过vendored_libraries加载的静态库.a或framewrok。如果 <code>Podfile</code> 中不使用 <code>use_frameworks!</code>,不会出现任何问题;如果使用 <code>use_frameworks!</code>,那么打包的 <code>framework</code> 会将 <code>vendored_libraries</code> 库中的内容包含进来,这就出现了符号冲突的问题了。如果出现了这种问题,CocoaPods 会报出如下错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">The 'pod-name' target has transitive dependencies that include static binaries: (static_libs.to_sentence)</span><br></pre></td></tr></table></figure>
</li>
</ul>
<pre><code>因为在 swift 中必须使用 `use_frameworks`,所以 swift 中经常会遇到这种问题。解决办法就是修改 `podspec` 和 `Podfile` 两个文件:
podspec
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">s.dependency 'xxx', '~> 15.2.0'</span><br><span class="line"></span><br><span class="line"> s.pod_target_xcconfig = {</span><br><span class="line"> 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PODS_ROOT)/xxx',</span><br><span class="line"> 'OTHER_LDFLAGS' => '$(inherited) -undefined dynamic_lookup'</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
Podfile
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">pre_install <span class="keyword">do</span> |installer|</span><br><span class="line"> <span class="comment"># workaround for https://github.com/CocoaPods/CocoaPods/issues/3289</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">installer</span>.<span class="title">verify_no_static_framework_transitive_dependencies</span>;</span> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span></span><br></pre></td></tr></table></figure>
</code></pre><ul>
<li><p>校验不同 target 所引用的代码中,如果包含 swift,所使用的 swift 版本是否相同。如果不同则会报出如下错误:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">The following pods are integrated into targets that <span class="keyword">do</span> <span class="keyword">not</span> have the same Swift <span class="symbol">version:</span>{error_messages.join}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>当在 swift 中使用时,校验是否在 <code>Podfile</code> 中是否添加了 <code>use_frameworks!</code>。如果不添加便会报错。例如:</p>
<p> Podfile</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">source <span class="string">'https://github.com/CocoaPods/Specs.git'</span></span><br><span class="line">platform <span class="symbol">:ios</span>, <span class="string">'8.0'</span></span><br><span class="line"><span class="comment"># ignore all warnings from all pods</span></span><br><span class="line">inhibit_all_warnings!</span><br><span class="line"></span><br><span class="line">target <span class="string">'SwiftTest'</span> <span class="keyword">do</span></span><br><span class="line"> pod <span class="string">'AFNetworking'</span>,<span class="string">'3.0'</span></span><br><span class="line"> pod <span class="string">'Alamofire'</span>, <span class="string">'~> 4.6'</span></span><br><span class="line"> pod <span class="string">'YYCache'</span></span><br><span class="line"> pod <span class="string">'YYImage'</span></span><br><span class="line"> pod <span class="string">'YYImage'</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
</li>
</ul>
<pre><code>对上述 `Podfile` 文件,执行 `pod install` 时便会报出如下错误:
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[!] Pods written in Swift can only be integrated as frameworks; add `use_frameworks!` to your Podfile or target to opt into using it. The Swift Pod being used is: Alamofire</span><br></pre></td></tr></table></figure>
</code></pre><h6 id="整合-project-文件"><a href="#整合-project-文件" class="headerlink" title="整合 project 文件"></a>整合 project 文件</h6><p>依赖文件下载完毕之后,会将这些文件打包成 <code>Pods.xcodeproj</code>。这一过程方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"> <span class="comment"># Generate the 'Pods/Pods.xcodeproj' project.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">generate_pods_project</span><span class="params">(generator = create_generator)</span></span></span><br><span class="line"> UI.section <span class="string">'Generating Pods project'</span> <span class="keyword">do</span></span><br><span class="line"> generator.generate!</span><br><span class="line"> @pods_project = generator.project</span><br><span class="line"> run_podfile_post_install_hooks</span><br><span class="line"> generator.write</span><br><span class="line"> generator.share_development_pod_schemes</span><br><span class="line"> write_lockfiles</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>这里会通过 <code>generator</code> 实例执行 <code>generate!</code> 方法。我们主要说一下这个方法:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CocoaPods/lib/cocoapods/installer/xcode/pods_project_generator.rb</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">generate!</span></span></span><br><span class="line"> prepare</span><br><span class="line"> install_file_references</span><br><span class="line"> install_libraries</span><br><span class="line"> integrate_targets</span><br><span class="line"> set_target_dependencies</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>这个方法做了这样几件事:</p>
<ul>
<li>生成一个 <code>Pods.xcodeproj</code> 工程</li>
<li>将下载的依赖文件加入工程</li>
<li>将下载的 Library 加入工程</li>
<li>处理 target 依赖</li>
</ul>
<p>这一系列过程的操作,主要依赖于前面所提到的 <strong><a href="https://github.com/CocoaPods/Xcodeproj" target="_blank" rel="external">CocoaPods/Xcodeproj</a></strong> 组件。</p>
<h6 id="执行下载过程"><a href="#执行下载过程" class="headerlink" title="执行下载过程"></a>执行下载过程</h6><p>这是最后一个阶段,会下载每个组件的具体源文件,并输出最终的执行结果。方法定义如下:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment"># CocoaPods/lib/cocoapods/installer.rb</span></span><br><span class="line"> <span class="comment"># Performs any post-installation actions</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">perform_post_install_actions</span></span></span><br><span class="line"> unlock_pod_sources</span><br><span class="line"> run_plugins_post_install_hooks</span><br><span class="line"> warn_for_deprecations</span><br><span class="line"> warn_for_installed_script_phases</span><br><span class="line"> lock_pod_sources</span><br><span class="line"> print_post_install_message</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>这一过程一般是最慢的一个过程。偷懒一下,其中的过程方法我就不一一讲解了。看一下最后输出信息这个方法吧:</p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">print_post_install_message</span></span></span><br><span class="line"> podfile_dependencies = podfile.dependencies.uniq.size</span><br><span class="line"> pods_installed = root_specs.size</span><br><span class="line"> title_options = { <span class="symbol">:verbose_prefix</span> => <span class="string">'-> '</span>.green }</span><br><span class="line"> UI.titled_section(<span class="string">'Pod installation complete! '</span> \</span><br><span class="line"> <span class="string">"There <span class="subst">#{podfile_dependencies == <span class="number">1</span> ? <span class="string">'is'</span> <span class="symbol">:</span> <span class="string">'are'</span>}</span> <span class="subst">#{podfile_dependencies}</span> "</span> \</span><br><span class="line"> <span class="string">"<span class="subst">#{<span class="string">'dependency'</span>.pluralize(podfile_dependencies)}</span> from the Podfile "</span> \</span><br><span class="line"> <span class="string">"and <span class="subst">#{pods_installed}</span> total <span class="subst">#{<span class="string">'pod'</span>.pluralize(pods_installed)}</span> installed."</span>.green,</span><br><span class="line"> title_options)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>也就是我们常见的输出结果:</p>
<p>!()[]</p>
<p>执行一次 <code>pod install</code> 的过程到此结束了。如果你大致读一遍源码,执行 <code>pod install</code> 再遇到问题时,可以快速断定问题原因并修复。<code>pod update</code> 和 <code>pod install</code> 还是有一些差别的,有兴趣的同学可以读一下 <code>pod update</code> 的源码。我这里就不在写了,就算你读不吐我都快写吐了。</p>
<h3 id="CocoaPods-使用"><a href="#CocoaPods-使用" class="headerlink" title="CocoaPods 使用"></a>CocoaPods 使用</h3><h4 id="1-安装-CocoaPods"><a href="#1-安装-CocoaPods" class="headerlink" title="1.安装 CocoaPods"></a>1.安装 CocoaPods</h4><p>这里假设你什么都没有安装,从 0 开始。如果你已经安装了某些东西,可以跳过。</p>
<ul>
<li><p>安装 rvm</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">curl -L get.rvm.io | bash -s stable </span><br><span class="line"></span><br><span class="line">source ~/.bashrc</span><br><span class="line"></span><br><span class="line">source ~/.bash_profile</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>查看 rvm 版本</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rvm -v</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rvm 1.29.3 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>查看可安装 Ruby 版本</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rvm list known</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>安装一个版本,我一般选最高,这里是 2.4.1</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rvm intall 2.4.1</span><br></pre></td></tr></table></figure>
</li>
<li><p>因为你后面可能会稀里糊涂装很多版本,所以设置这个版本为默认版本</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rvm use 2.4.1 --default</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>更换 Ruby 源。因为国内被墙,所以需要切换。之前很多教程中说使用 <a href="https://ruby.taobao.org,但是淘宝源已经停止维护,现在建议使用" target="_blank" rel="external">https://ruby.taobao.org,但是淘宝源已经停止维护,现在建议使用</a> <a href="https://gems.ruby-china.org。" target="_blank" rel="external">https://gems.ruby-china.org。</a></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> sudo gem update --system</span><br><span class="line"></span><br><span class="line"> gem sources --remove https://rubygems.org/</span><br><span class="line"></span><br><span class="line"> gem sources -a https://gems.ruby-china.org/</span><br><span class="line"> </span><br><span class="line"> // 查看当前源</span><br><span class="line"> gem sources -l</span><br><span class="line"> ``` </span><br><span class="line"></span><br><span class="line">* 安装 CocoaPods</span><br></pre></td></tr></table></figure>
<p> // 安装 CocoaPods<br> sudo gem install cocoapods</p>
<p> // 安装本地库,需要等待很长时间<br> pod setup</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">* 如果安装了多个 Xcode,需要选择一个。</span><br></pre></td></tr></table></figure>
<p> sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">* 检测是否安装好,search 一个组件,能 search 到证明安装好了</span><br></pre></td></tr></table></figure>
<p> pod search [一个组件]</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">* CocoaPods 版本操作</span><br></pre></td></tr></table></figure>
<p> // 查看当前安装的所有 CocoaPods 版本<br> gem list –local | grep cocoapods</p>
<p> // 当前使用 pod 版本<br> pod –version</p>
<p> // 更新到最新稳定版本<br> sudo gem install cocoapods</p>
<p> // 更新到一个 pre-release 版本<br> sudo gem install cocoapods –pre</p>
<p> // 安装指定版本<br> sudo gem install cocoapods -v [版本号]</p>
<p> // 移除 CocoaPods,如果你安装多个,会列出一个 list 让你选择删除那个。如果只安装一个,也会给你提示,问你是否确定删除。<br> sudo gem uninstall cocopods</p>
<p> // 移除指定版本<br> sudo gem uninstall cocopods -v [版本号]</p>
<p> // 使用指定版本执行命令<br> pod <em>1.3.1</em> install</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">#### 2.使用 CocoPods</span><br><span class="line"></span><br><span class="line">* 基础操作</span><br></pre></td></tr></table></figure>
<p> // 打开一新的工程,执行命令<br> pod init<br> // Podfile 中添加<br> pod ‘AFNetworking’<br> // install<br> pod install</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">* 想要看到 install 的详细过程</span><br></pre></td></tr></table></figure>
<p> pod install –verbose</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">* 更新某一个组件</span><br></pre></td></tr></table></figure>
<p> // 不添加组件名则更新所有<br> pod update [组件名]</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">* 更新本地依赖</span><br></pre></td></tr></table></figure>
<p> pod repo update</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">* 不想在 install/update 时更新本地依赖。这样执行 `pod install` 会快一些。但是如果 github 或者私有仓库上面有了最新版本,本地搜到的还是旧版本。如果 `Podfile` 中使用新的版本号,这样是无法执行成功的。</span><br></pre></td></tr></table></figure>
<p> // –verbose 可省略<br> pod install –verbose –no-repro-update</p>
<p> pod update –verbose –no-repro-update</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">* 校验本地 lib repro 有效性</span><br></pre></td></tr></table></figure>
<p> pod lib lint –allow-warnings</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">* 校验 spec 文件</span><br></pre></td></tr></table></figure>
<p> pod spec lint</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">* 自定义组件时,将组件的 spec 文件上传到远端仓库。</span><br></pre></td></tr></table></figure>
<p> // [reponame] 一般可以在路径 ~/.cocoapods/repo 下查看,选择你需要的 name.<br> pod repo push [reponame] [name.podspec] –verbose –sources=master,[reponame] –use-libraries –allow-warnings</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">想了解更多命令,请查看官方文档中 [Command Line API](https://guides.cocoapods.org/terminal/commands.html) 这一章节。</span><br><span class="line"></span><br><span class="line">#### Podfile 书写规范</span><br><span class="line"></span><br><span class="line">[Podfile Syntax Reference v1.4.0](https://guides.cocoapods.org/syntax/podfile.html#script_phase)</span><br><span class="line"></span><br><span class="line">```rb</span><br><span class="line">source 'https://github.com/CocoaPods/Specs.git' # 组件依赖文件所存放仓库,根据需求可引入多个</span><br><span class="line">source 'https://github.com/artsy/Specs.git'</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">platform :ios, '8.0' # </span><br><span class="line">inhibit_all_warnings! # 忽视引用的代码中的警告</span><br><span class="line"></span><br><span class="line">workspace 'CocoaPodsDemo' # 指定生成的 workspace 名字</span><br><span class="line"></span><br><span class="line">def common_pods # 如果有多个 target,可以将公共部分进行 def 定义再引入</span><br><span class="line"> pod 'xxx'</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">target 'CocoaPodsDemo' do</span><br><span class="line"> project 'DemoProject' # 可用于指定实际的工程</span><br><span class="line"></span><br><span class="line"> use_frameworks! # 是否以 framework 形式引入。swift 必须有这个关键字 </span><br><span class="line"></span><br><span class="line"> common_pods # 公共引入的组件</span><br><span class="line"></span><br><span class="line"> pod 'SSipArchive', :inhibit_warnings => true # 屏蔽某个 pod 的 warning</span><br><span class="line"></span><br><span class="line"> pod 'AFNetworking', '3.2' # 使用 3.2 版本</span><br><span class="line"> pod 'YYCache', '~> 0.3' # pod update 时最高升级到 < 1.0,不包括 1.0</span><br><span class="line"> </span><br><span class="line"> # Build 环境配置</span><br><span class="line"> pod 'PonyDebugger', :configurations => ['Debug', 'Beta']</span><br><span class="line"> pod 'PonyDebugger', :configuration => 'Debug'</span><br><span class="line"></span><br><span class="line"> # 使用具体的某个 subspec</span><br><span class="line"> pod 'QueryKit/Attribute'</span><br><span class="line"> pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']</span><br><span class="line"> </span><br><span class="line"> # 引用本地组件</span><br><span class="line"> pod 'AFNetworking', :path => '~/Documents/AFNetworking'</span><br><span class="line"> </span><br><span class="line"> # 使用具体仓库</span><br><span class="line"> pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git'</span><br><span class="line"> # 使用具体仓库具体分支</span><br><span class="line"> pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev'</span><br><span class="line"> # 使用具体仓库的某个 tag</span><br><span class="line"> pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'</span><br><span class="line"> # 使用具体仓库的某个 commit</span><br><span class="line"> pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af'</span><br><span class="line"> </span><br><span class="line"> # 使用指定路径的 spec 文件</span><br><span class="line"> pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec'</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> target 'ShowsApp' do</span><br><span class="line"> pod 'ShowsKit'</span><br><span class="line"></span><br><span class="line"> # Has its own copy of ShowsKit + ShowTVAuth</span><br><span class="line"> target 'ShowsTV' do</span><br><span class="line"> pod 'ShowTVAuth'</span><br><span class="line"> end</span><br><span class="line"></span><br><span class="line"> # Has its own copy of Specta + Expecta</span><br><span class="line"> # and has access to ShowsKit via the app</span><br><span class="line"> # that the test target is bundled into</span><br><span class="line"> target 'ShowsTests' do</span><br><span class="line"> # inherit! 有三种类型:':complete' 继承父级所有行为;':none' 什么行为都不继承;':search_paths' 继承父级的 search paths</span><br><span class="line"> inherit! :search_paths</span><br><span class="line"> pod 'Specta'</span><br><span class="line"> pod 'Expecta'</span><br><span class="line"> end</span><br><span class="line"> end</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line"># hook 配置, 在 preparing 阶段后,install 之前</span><br><span class="line">pre_install do |installer|</span><br><span class="line"> </span><br><span class="line">end</span><br><span class="line"></span><br><span class="line"># hook 配置,在 pod install 之后,可用于修改工程配置等</span><br><span class="line">post_install do |installer|</span><br><span class="line"> installer.pods_project.targets.each do |target|</span><br><span class="line"> target.build_configurations.each do |config|</span><br><span class="line"> config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'</span><br><span class="line"> end</span><br><span class="line"> end</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h4 id="Podspec-书写规范"><a href="#Podspec-书写规范" class="headerlink" title="Podspec 书写规范"></a>Podspec 书写规范</h4><p><a href="https://guides.cocoapods.org/syntax/podspec.html" target="_blank" rel="external">Podspec Syntax Reference v1.4.0</a></p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">Pod:</span><span class="symbol">:Spec</span>.new <span class="keyword">do</span> |spec|</span><br><span class="line"></span><br><span class="line"><span class="comment"># 组件基本信息配置</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"> <span class="comment"># 组件名</span></span><br><span class="line"> spec.name = <span class="string">'Reachability'</span></span><br><span class="line"> <span class="comment"># 组件版本号,命名规则遵循 [semantic versioning](https://semver.org/)</span></span><br><span class="line"> spec.version = <span class="string">'3.1.0'</span> </span><br><span class="line"> <span class="comment"># 许可证</span></span><br><span class="line"> spec.license = { <span class="symbol">:type</span> => <span class="string">'BSD'</span> }</span><br><span class="line"> <span class="comment"># 仓库主页</span></span><br><span class="line"> spec.homepage = <span class="string">'https://github.com/tonymillion/Reachability'</span></span><br><span class="line"> <span class="comment"># 一个作者用 spec.author = 'Darth Vader'</span></span><br><span class="line"> spec.authors = { <span class="string">'Tony Million'</span> => <span class="string">'tonymillion@gmail.com'</span> }</span><br><span class="line"> <span class="comment"># 组件概述</span></span><br><span class="line"> spec.summary = <span class="string">'ARC and GCD Compatible Reachability Class for iOS and OS X.'</span></span><br><span class="line"> <span class="comment"># 组件源码地址</span></span><br><span class="line"> spec.source = { <span class="symbol">:git</span> => <span class="string">'https://github.com/tonymillion/Reachability.git'</span>, <span class="symbol">:tag</span> => <span class="string">'v3.1.0'</span> }</span><br><span class="line"></span><br><span class="line"><span class="comment"># 组件平台支持</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"> <span class="comment"># 支持单平台使用</span></span><br><span class="line"> spec.platform = <span class="symbol">:osx</span>, <span class="string">'10.8'</span></span><br><span class="line"> spec.platform = <span class="symbol">:ios</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 支持多平台使用</span></span><br><span class="line"> spec.ios.deployment_target = <span class="string">'6.0'</span></span><br><span class="line"> spec.osx.deployment_target = <span class="string">'10.8'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Build settings</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"> spec.dependency <span class="string">'AFNetworking'</span>, <span class="string">'~> 1.0'</span> <span class="comment"># 组件依赖的第三方库</span></span><br><span class="line"> spec.requires_arc = <span class="keyword">false</span> <span class="comment"># 是否要求 ARC 环境</span></span><br><span class="line"> spec.requires_arc = [<span class="string">'Classes/*ARC.m'</span>, <span class="string">'Classes/ARC.mm'</span>]</span><br><span class="line"> spec.frameworks = <span class="string">'QuartzCore'</span>, <span class="string">'CoreData'</span> <span class="comment"># 组件引用的 framework spec.weak_frameworks = 'Twitter', 'SafariServices' # 组件弱引用的 framework</span></span><br><span class="line"> spec.libraries = <span class="string">'xml2'</span>, <span class="string">'z'</span> <span class="comment"># 组件引用的 library</span></span><br><span class="line"> ... 更多请看官方文档</span><br><span class="line"> </span><br><span class="line"><span class="comment"># File patterns</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"> spec.source_files = <span class="string">'Classes/**/*.{h,m}'</span> <span class="comment"># 接入方使用组件时,引入的源文件,正则匹配</span></span><br><span class="line"> spec.public_header_files = <span class="string">'Headers/Public/*.h'</span> <span class="comment"># 引入的共有头文件</span></span><br><span class="line"> spec.private_header_files = <span class="string">'Headers/Private/*.h'</span> <span class="comment"># 引入的私有头文件</span></span><br><span class="line"> spec.vendored_frameworks = <span class="string">'MyFramework.framework'</span><span class="comment"># 引入的 framework</span></span><br><span class="line"> spec.vendored_libraries = <span class="string">'libProj4.a'</span> <span class="comment"># 引入的 library</span></span><br><span class="line"> <span class="comment"># 以 bundle 形式引入的资源</span></span><br><span class="line"> spec.resource_bundles = {</span><br><span class="line"> <span class="string">'MapBox'</span> => [<span class="string">'MapView/Map/Resources/*.png'</span>],</span><br><span class="line"> <span class="string">'OtherResources'</span> => [<span class="string">'MapView/Map/OtherResources/*.png'</span>]</span><br><span class="line"> }</span><br><span class="line"> <span class="comment"># 直接引入资源</span></span><br><span class="line"> spec.resources = [<span class="string">'Images/*.png'</span>, <span class="string">'Sounds/*'</span>]</span><br><span class="line"> ... 更多请看官方文档</span><br><span class="line"></span><br><span class="line"><span class="comment"># Subspecs</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"> <span class="comment"># 将组件分为多个子组件,接入方可以根据需求只接入几个子组件,减少包体积</span></span><br><span class="line"> subspec <span class="string">'Twitter'</span> <span class="keyword">do</span> |sp|</span><br><span class="line"> sp.source_files = <span class="string">'Classes/Twitter'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="comment"># 测试组件</span></span><br><span class="line"> spec.test_spec <span class="keyword">do</span> |test_spec|</span><br><span class="line"> test_spec.source_files = <span class="string">'NSAttributedString+CCLFormatTests.m'</span></span><br><span class="line"> test_spec.dependency <span class="string">'Expecta'</span></span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="comment"># 默认子组件。也就是当接入方不作区分时,直接使用组件名引入时,所引入子组件</span></span><br><span class="line"> spec.default_subspec = <span class="string">'Core'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 多平台支持</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"> spec.ios.source_files = <span class="string">'Classes/ios/**/*.{h,m}'</span></span><br><span class="line"> spec.osx.source_files = <span class="string">'Classes/osx/**/*.{h,m}'</span></span><br></pre></td></tr></table></figure>
<p>Cocopods 基本使用内容就这些。具体可以查看官方文档中 <a href="https://guides.cocoapods.org/" target="_blank" rel="external">Reference</a> 这一章节。</p>
<h3 id="一些问题"><a href="#一些问题" class="headerlink" title="一些问题"></a>一些问题</h3><p>这里是一些经常遇到的问题。不是很全面,希望对你有帮助。</p>
<h4 id="1-项目使用了-CocoaPods-之后,为什么要以-Workspace-形式打开"><a href="#1-项目使用了-CocoaPods-之后,为什么要以-Workspace-形式打开" class="headerlink" title="1.项目使用了 CocoaPods 之后,为什么要以 Workspace 形式打开"></a>1.项目使用了 CocoaPods 之后,为什么要以 Workspace 形式打开</h4><p>因为执行 <code>pod install</code> 之后,下载完的文件会通过使用 <a href="https://github.com/CocoaPods/Xcodeproj" target="_blank" rel="external">CocoaPods/Xcodeproj</a> 合成一个 Project。Xcode 通过使用 Workspace 管理多个 Project,使各个 Project 之间可以相互引用。为了使工程中的文件能够引用组件中的文件,所以这里需要以 Workspace 形式打开。</p>
<h4 id="2-pod-install-vs-pod-update"><a href="#2-pod-install-vs-pod-update" class="headerlink" title="2.pod install vs. pod update"></a>2.pod install vs. pod update</h4><p>这是 <a href="https://guides.cocoapods.org/using/pod-install-vs-update.html" target="_blank" rel="external">官方文档</a> 中描述的一个经典问题。</p>
<p><strong>pod install</strong>: 优先安装 Podfile 中改变的组件,并优先遵循 Podfile 中的版本号,其次遵循 Podfile.lock 中的版本号。如果使用的 Podfile 中版本号,会将新的版本号更新到 Podfile.lock 中。</p>
<p><strong>pod update [PODNAME]</strong>: 会根据当前 Podfile 规则更新组件。如果 Podfile 中没有指定版本号,并不会遵循 Podfile.lock,而是会拉取最新版本,并更新 Podfile.lock。</p>
<p>官方建议:</p>
<ul>
<li>新添加一个 pod 时,使用 <code>pod install</code>,不要使用 <code>pod update</code> 去下载一个新的组件,避免跟新其他 pod 的版本。</li>
<li>更新 pod 版本时,使用 <code>pod update [PODNAME]</code>。</li>
<li>没有必要的话,不要使用全局更新 <code>pod update</code>,避免不必要的更新。</li>
</ul>
<h4 id="3-校验-podspec-文件出现问题(pod-spec-lint)"><a href="#3-校验-podspec-文件出现问题(pod-spec-lint)" class="headerlink" title="3.校验 podspec 文件出现问题(pod spec lint)"></a>3.校验 podspec 文件出现问题(pod spec lint)</h4><p><strong>swift 版本问题</strong></p>
<p>问题:</p>
<p><img src="/uploads/Cocoa-Pods/swfit_error.png" alt="swift_error"></p>
<p>解决方案:</p>
<blockquote>
<p>2.3, run终端输入:echo “2.3” > .swift-version</p>
</blockquote>
<p><strong>验证出现警告问题</strong></p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod spec lint xxx.podspec --allow-warning</span><br></pre></td></tr></table></figure>
<p><strong>找不到头文件</strong></p>
<figure class="highlight rb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod spec lint --allow-warnings --use-libraries</span><br></pre></td></tr></table></figure>
<p>当然 CocoaPods 还有很多问题,这里就不一一列举了,如果遇到问题自行 Google 吧,很多问题都已经有了答案。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>CocoaPods 的相关知识,就总结到这里。花时间如仔细研究一下,还是能学到很多东西的。这样在今后的项目开发中遇到问题后,可以快速定位并解决,提高开效率。</p>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><p>1.<a href="https://cocoapods.org/" target="_blank" rel="external">CocoaPods 官方文档</a><br>2.<a href="https://www.objc.io/issues/6-build-tools/cocoapods-under-the-hood/" target="_blank" rel="external">CocoaPods Under The Hood</a></p>
<a id="more"></a>
<p>很久之前读了一遍 <a href="https://guides.cocoapods.org/" target="_blank" rel="external">Cocoa Pods 官方文档</a>,对 Cocoa Pods 有了一个简单
2017 个人总结
http://yoursite.com/2018/02/13/2017 个人总结/
2018-02-13T02:05:06.000Z
2018-06-03T13:12:43.285Z
<a id="more"></a>
<p>又到了写个人总结的日子。每到年末的时候,静下来回顾这一年,写写总结,是一件很有意思的事。鉴于上半年<br>过得乱七八糟,一切从下半年开始说起吧。</p>
<p>六月底,和少帮主自驾游回了一次学校。去之前,少帮主叮嘱我最好在京东买两天保险。自驾游意在散心,没有走高速,全程慢慢悠悠的开。从早上十一点出发,中间一度绕到了山里,路很宽,车很少,风景很不错,具体画面可以参考 《平凡之路》 MV,也就是在这种路上,少帮主才放心让我我开。到了学校已经八点多,停下来在学校门口 “大骨拉面” 吃了点东西,吃完九点多,打算去学校逛逛。</p>
<p>时隔一年回到这里,有种说不出的感觉。因为是毕业季,有些人在拉着箱子往外走,有些人刚刚聚餐回来,去年我们毕业的时候也是如此。进了学校之后,看到三教实验室还在亮着灯,就跑上去看了看,看看自己当年 “奋斗” 的地方。因为 ID 卡已经注销,图书馆也进不去了,所以下来之后就直接穿过小树林,绕过 “学友”,奔着四舍楼下去了,少帮主当然是奔着她的二舍去了。回学校之前,是没告诉任何人的,然后在四舍楼下碰到了邹导…就好像是晚上自习完回宿舍,到了楼下发现邹导在值班一样,这偶遇也没谁了。因为第二天是信院终期答辩,导师没有时间,所以晚上十点多被导师叫去他家聊天,师命不可违。和导师边走边聊,说不完的话,导师从燕大西苑一路把我送到燕大小东门,也就是燕大宾馆那个门。第二天中午,又去了三食堂二楼吃了一顿我梦寐以求的香锅,没吃完我还打包带了回去。之后又在海边转了一圈,就回去了。回去走的高速,几个小时就到北京了。当然是少帮主开的。</p>
<p>七月至十月初,是结婚的高峰期。这几个月,我参加了很多场婚礼,也错过了很多场婚礼。吃过的狗粮可以养活四只哈士奇,随过的份子可以买一部顶配 iPhone X。其中十一假期,参加了志爽的婚礼。也因为这场婚礼,让我们一群班长时隔一年又聚在了一起。没有什么比一群逗比坐在一起扯淡更开心了。这几个月,除了上班之外,唯一的户外活动就是参加婚礼了。总结来说就是:参加不完的婚礼,吃不完的狗粮,随不完的份子。</p>
<p>十一月,和斌哥他们一起去鸟巢看了 2017 LPL 世界总决赛。没有中国队不重要,两个韩国队 3:0 结束也不重要,重要的是开场有周杰伦。据网易云音乐统计,我 2017 年一共听了 7243 首歌,听了周杰伦 3741 次,真爱粉无需多言。英雄联盟还不知道能火几年,下次在中国办总决赛还不知道什么时候,即使在中国也不一定再会有周杰伦。所以这一次也算是有生之年系列了。</p>
<p>十二月,我破了职业生涯两个记录:连续工作一个月;完成一次通宵加班并且第二天正常上班。这一年我加了很多班,这是最狠的一个月,我的 2017 也在这充实的加班中完美收尾。</p>
<p>这一年值得说的事件也就这些了,但是显然还不够 800 字,所以下面用一些总结性的事情凑一下,中学时代传统不能丢。</p>
<p>关于读书。每年我都会强迫自己多读一些非技术类书籍,以让自己的思维开阔一些。否则常年和机器打交道,容易变得呆滞。然而这一年只读了几本,少之又少。为了生计,不得不花大量时间读一些技术类书籍,以补发育。所以这个 todo 只能放在下一年。</p>
<p>关于英语。大学时期,我最讨厌的两门课程就是数学和英语。然而现在的工作和这两门课程联系的最紧密,我也是哔了狗了。大学四级我考了三次才勉强通过,六级考了一次再也没有考过。为了不让自己掉段,这一年我阅读了大量的英文文档,几本英文书籍,还翻译了一些文档。如果现在让我考四级,我觉得我还能勉强通过。</p>
<p>关于健身。IT 是一个高危职业,没赚哪天你坐在椅子上敲着 code 就心梗了,留下一堆 bug 撒手离去。为了降低心梗概率,我不得不强迫自己经常锻炼。开始的时候,是每周跑步两次,周三晚上跑五公里,周六跑一次十公里,断断续续。健身卡到期之后,又开始在交大操场跑,每天早上起来跑两圈。随着天气变冷,后来也就起不来了。随着加班越来越多,我又开启了 “跑步续命” 模式。周一至周五,每天晚上去公司健身房跑三公里,完后喝 500ml 脱脂牛奶。这一跑就是两三个月,如今已养成习惯,每天不跑都那首。这个年纪,养成一个好习惯已经很不容易了。</p>
<p>关于炒股。2017 年是港美股牛市,于是一直怀有金融梦的我加入炒股行列。当然不能因为炒个股就把自己说成金融人士,就好比你不能因为会装个系统就把自己说成黑客一样。炒股很练心态,练决策力,显然我还不到火候。有幸的是,折腾了一年没有亏钱。</p>
<p>最大收获。这一年最大收获就是心态变得更加平和。扎心疼,扎透了就习惯了。你以为你比较穷,不要逗了,你何止穷,你还长得丑、双商低。“不能改变的事情就去适应”,我经常那这句话劝别人劝自己。丢一千块钱不要伤心,等哪天你丢一万块就会觉得那都不是事儿。多扎心的事情,终有一天会被你当做笑话讲出来。有时间去烦恼,不如出踏踏实实做一些你能改变事情。基于这个心态,我现在好高骛远的性格好了很多。</p>
<p>总的来说,这一年全程都在补发育,所以也活很累。从学校出来,就没想要轻松过。人最可怕的就是一生碌碌无为,还自我安慰的说平凡难能可贵。我还没有到享受 “平凡可贵” 的年纪。《老情书》中有这样一段话:老太太说:“我就特别看不起你们这帮年轻人,二三十岁就叨逼叨说平平淡淡才是真。你们配吗?我上山下乡,知青当过,饥荒捱过,这你们没办法经历。但我今儿个平安喜乐,没事打几圈牌,早睡早起,你以为凭空得来的心静自然凉?老和尚说终归要见山是山,但你们经历见山不是山了吗?不乘着年轻拔腿就走,去刀山火海,不入世就自以为出世,以为自己活佛涅槃来的?我的平平淡淡是苦处来的,你们的平平淡淡是懒惰,是害怕,是贪图安逸,是一条不敢见世面的土狗。” 我不是土狗,我想去经历见山不是山。</p>
<p>2017,一把辛酸泪,一纸荒唐言,到此结束。</p>
<p>2018,to be continue. Write the code, change the world.</p>
<p>2018.02.12 by bool周</p>
<a id="more"></a>
<p>又到了写个人总结的日子。每到年末的时候,静下来回顾这一年,写写总结,是一件很有意思的事。鉴于上半年<br>过得乱七八糟,一切从下半年开始说起吧。</p>
<p>六月底,和少帮主自驾游回了一次学校。去之前,少帮主叮嘱我最好在京东买两天保险。
查询规划器(译)
http://yoursite.com/2018/01/10/查询规划器/
2018-01-10T10:04:49.000Z
2018-06-03T13:12:53.834Z
<a id="more"></a>
<p>这是一篇 <a href="https://www.sqlite.org/queryplanner.html" target="_blank" rel="external">Query Planning</a> 的译文。</p>
<h3 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h3><ol>
<li><p>查询<br> 1.1 无索引表查询<br> 1.2 使用 rowid 查询<br> 1.3 使用索引查询<br> 1.4 多行内容查找<br> 1.5 使用 AND 链接多个 WHERE 条件查询<br> 1.6 多列查询<br> 1.7 覆盖索引查询<br> 1.8 使用 OR 链接多个 WHERE 条件查询</p>
</li>
<li><p>排序<br> 2.1 使用 rowid 排序<br> 2.2 使用索引排序<br> 2.3 覆盖索引排序</p>
</li>
<li><p>查询并排序<br> 3.1 通过多列索引查询并排序<br> 3.2 通过覆盖索引查询并排序<br> 3.3 通过索引进行局部排序</p>
</li>
<li><p>无 rowid 的表</p>
</li>
</ol>
<h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>SQL 最主要的特征 (在 <em>所有</em> 使用 SQL 语句的数据库中,不只是 SQLite)在于它是一中 <em>表述式编程语言</em>,而不是一种 <em>过程化语言</em>。在使用 SQL 时,你只需要告诉系统你想要计算什么,不需要描述如何去计算。计算结果的方式取决于 SQL 数据库引擎的内部查询规划器。</p>
<p>对于一条 SQL 语句,可能有成百上千种执行算法。所有的算法都可以计算出正确的结果,但是有的计算的快,有的计算的慢。查询规划器相当于一个 <a href="https://en.wikipedia.org/wiki/Artificial_intelligence" target="_blank" rel="external">AI</a>,为每条 SQL 语句尽可能规划最快的执行算法。</p>
<p>多数情况下,查询规划器在 SQLite 中表现的十分出色。但是查询规划器需要使用索引进行协助。这些索引需要由开发者在设计数据库时加上。有时候,查询规划器会选择次优算法,而不是最优的。这种情况下,需要开发者进行一些辅助操作来帮助查询规划器更好的工作。</p>
<p>这篇文章主要讲解了 SQLite 查询规划器和查询引擎背后的工作原理。有必要的时候,开发者可以根据这些原理更好地创建索引,帮助查询规划器高效地工作。</p>
<p>更多信息可以查看 <a href="https://www.sqlite.org/optoverview.html" target="_blank" rel="external">SQLite query planner</a> 和 <a href="https://www.sqlite.org/queryplanner-ng.html" target="_blank" rel="external">next generation query planner</a>.</p>
<h3 id="1-查询"><a href="#1-查询" class="headerlink" title="1.查询"></a>1.查询</h3><h4 id="1-1-无索引表查询"><a href="#1-1-无索引表查询" class="headerlink" title="1.1 无索引表查询"></a>1.1 无索引表查询</h4><p>在 SQLite 中,大多数表由一行或者多行组成,每一行都有一个独一无二的 key (<a href="https://www.sqlite.org/lang_createtable.html#rowid" target="_blank" rel="external">rowid</a> 或者 <a href="https://www.sqlite.org/lang_createtable.html#rowid" target="_blank" rel="external">INTEGER PRIMARY KEY</a>)(<a href="https://www.sqlite.org/withoutrowid.html" target="_blank" rel="external">WITHOUT ROWID</a> 表是一个特例)。这些数据通常会按照递增顺序排列。例如,这篇文章使用的表为 “FruitsForSale”,主要存储了各种各样的水果以及水果的产地和价格信息。表结构如下:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> FruitsForSale(</span><br><span class="line"> Fruit <span class="built_in">TEXT</span>,</span><br><span class="line"> State <span class="built_in">TEXT</span>,</span><br><span class="line"> Price <span class="built_in">REAL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>写入一些数据之后,这张表将以 figure 1 图中所示的形式存储于磁盘中:</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/tab.gif" width="305" height="140"><br></div></p>
<center>Figure 1: “FruitsForSale” 表结构</center>
<p>在这个表里,rowid 并不是连续,但却是有序排列的。通常情况下,SQLite 创建一条数据时,这条数据的rowid 是在上一条 rowid 基础上加 1。如果某一行被删除,rowid 则会不连贯。如果有必要,创建一条数据时可以指定 rowid 的序号,并不是只能在末尾追加数据。但无论如何添加,每个 rowid 都是唯一的,有序排列的。</p>
<p>当你想查询桃子的价格,查询语句可能像下面这样:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Peach'</span>;</span><br></pre></td></tr></table></figure>
<p>为了满足这条查询,SQLite 会读取表中每一行数据,首先检索 ‘fruit’ 这一列看是否有一条数据的值为 ‘Peach’,如果有的话,输出这一条数据对应的 ‘price’ 的值。检索过程如图 figure2 所示。这种算法叫做 <em>全表遍历</em> —— 需要读入整张表并检索。这个表只有 7 条数据,检索起来还好,但如果有 7 百万条数据,为了检索一条 8-byte 的数据需要读入并遍历 1M 的数据。为此,尽量避免全表遍历。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/fullscan.gif" width="356" height="190"><br></div></p>
<center>Figure 2: 全表遍历 </center>
<h4 id="1-2-使用-rowid-查询"><a href="#1-2-使用-rowid-查询" class="headerlink" title="1.2 使用 rowid 查询"></a>1.2 使用 rowid 查询</h4><p>使用 rowid 查询可以避免全表遍历 (等价于通过 <a href="https://www.sqlite.org/lang_createtable.html#rowid" target="_blank" rel="external">INTEGER PRIMARY KEY</a> 查询)。查询桃子的价格,直接检索 rowid 为 4 的数据即可:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> <span class="keyword">rowid</span>=<span class="number">4</span>;</span><br></pre></td></tr></table></figure>
<p>因为每条数据是以 rowid 为顺序存储在表中的,SQLite 可以对这些数据进行二分查找。如果表中含有 N 条数据,查询一条数据的时间以 logN 为比例系数增长,而不是以 N 为比例系数增长。假如一个表中含有 1 千万条数据,这意味遍历全表操作时快了 N/logN 倍,也就是 1 百万倍的速度。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/rowidlu.gif" width="406" height="138"><br></div></p>
<center>Figure 3: 通过 rowid 查询 </center>
<h4 id="1-3-使用索引查询"><a href="#1-3-使用索引查询" class="headerlink" title="1.3 使用索引查询"></a>1.3 使用索引查询</h4><p>使用 rowid 查询固然很快,但当你不知道 rowid 时怎么办?这时使用 rowid 查询就不行了。</p>
<p>为提高查询速度,我们可以将 “fruitsforsalt” 表中 “fruit” 这一列设置为索引,像下面这样:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">INDEX</span> Idx1 <span class="keyword">ON</span> fruitsforsale(fruit);</span><br></pre></td></tr></table></figure>
<p>索引表是与原来的 “fruitsforsale” 表相关联的另外一张表,索引表中包含索引内容(这里指 fruit 这一列)和 rowid 这两列,其中索引内容在前,所有数据按照索引内容排序。Figure 4 中为索引表的结构。”fruit” 这一列作为主键,”rowid” 作为辅助索引,如果多条主键字段值相同,则用辅助索引进行区别。在下面示例中,”Ornage”字段值相同时,使用 rowid 区别。你可能注意到,在原始表中每条数据的 rowid 都是唯一的,所以通过 “fruit” 和 “rowid” 组成的复合键可以为每条数据确定一个唯一索引。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/idx1.gif" width="135" height="135"><br></div></p>
<center>Figure 4: 索引表 </center>
<p>使用索引可以更快的查询出 “桃子的价格” :</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Peach'</span>;</span><br></pre></td></tr></table></figure>
<p>执行这条时,SQLLite 先在索引表中进行二分查找,找到 fruit=’Peach’,然后取出这一行的 rowid。使用 rowid 在原始表 ‘FruitForSale’ 中进行第二次二分查找。找到对应行之后,取出 price 字段值。检索过程如图 figure 5 所示。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/idx1lu1.gif" width="627" height="137"><br></div></p>
<center>Figure 5: 通过索引查找桃子的价格 </center>
<p>为了查询桃子的价格, SQLite 进行了两次二分查找。对于含有大量数据的表,这种方式仍然要快于全表遍历。</p>
<h4 id="1-4-多行查找"><a href="#1-4-多行查找" class="headerlink" title="1.4 多行查找"></a>1.4 多行查找</h4><p>在前面的查询中,通过 fruit=’Peach’ 约束条件查询出了一条数据。但是有时候一个约束条件可能对应多条数据。例如,我们要查询橘子的价格,将会出现如下情况:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span>;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/idx1lu2.gif" width="630" height="142"><br></div></p>
<center>Figure 6: 通过索引查询橘子价格</center>
<p>在这里,SQLite 仍然是先进行一次二分查找,找到索引为 fruit=’Orange’ 的数据。然后取出 rowid,使用这个 rowid 去原始表再进行一次二分查找,找到对应的 price。之后 SQLite 并不会终止查询,而是继续去查询下一条符合条件的数据。使用二分查找,查询下一条数据的消耗远远小于第一次,因为第二条数据和第一条数据一般会在同一页内,就像上图展示的那样。这样第二次查找十分廉价,以致可以忽略不计。所以整个查询大约进行了三次二分查找。如果数据库中有 K 条数据符合条件,整个表总共有 N 条数据,那么一次查询所消耗时间的比例系数大约为 (K+1)*logN.</p>
<h4 id="1-5-使用-AND-链接多个-WHERE-条件查询"><a href="#1-5-使用-AND-链接多个-WHERE-条件查询" class="headerlink" title="1.5 使用 AND 链接多个 WHERE 条件查询"></a>1.5 使用 AND 链接多个 WHERE 条件查询</h4><p>接下来,你想要查询 California 生产的橘子的价格。查询条件如下所示:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">AND</span> state=<span class="string">'CA'</span>;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/idx1lu3.gif" width="628" height="140"><br></div></p>
<center>Figure 7: 使用索引查询 California 生产的橘子的价格</center>
<p>一种查询路径是,先通过 fruit=’Orange’ 条件找出所有橘子的数据,然后过滤掉产地不是 California 的数据。查询过程如图 Figure 7 所示。多数情况这是一种合理的途径。但是,数据库需要做一次额外的二分查找来过滤掉产地为 Florida 的数据,并不是想象中那么高效。</p>
<p>既然可以将 “fruit” 这一列设置为索引,也可以考虑将 “state” 这一列设置为索引。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">INDEX</span> Idx2 <span class="keyword">ON</span> fruitsforsale(state);</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/idx2.gif" width="136" height="138"><br></div></p>
<center> Figure 8: 将 State 这一列设置为索引</center>
<p>这个索引表中的 “state” 这一列和 Idx1 中的 “fruit” 类似,”state” 一列作为主键,”rowid” 一列作为辅助索引。在这个 model 中,”state” 这一列也有很多重复项,还是需要使用 “rowid” 来区分。</p>
<p>使用索引表 Idx2,SQLite 有了新的查询方式:先使用索引表找出 California 对应的行,然后过滤掉未生产橘子的行。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/idx2lu1.gif" width="630" height="140"><br></div></p>
<center> Figure 9: 使用索引查询 California 生产的橘子 </center>
<p>这里与使用 idx1 查询最终得到的是相同的结果(使用索引是为了提高 SQLite 的查询速度,不应改变查询结果)。这两种索引方式工作量是相同的,所以在查询价格这个 case 上,使用 Idx2 并不能提高性能。</p>
<p>在本例中,最后这两种查询方式使用时间相同。我们应该使用哪种呢?如果 <a href="https://www.sqlite.org/lang_analyze.html" target="_blank" rel="external">ANALYZE</a> 命令开启,SQLite 可以收集使用索引表的统计信息。然后 SQLite 就会知道使用 Idx1 进行索引查询,多数情况下只会查询到一行数据(这个表中 fruit=’Orange’ 属于一种特殊情况);而使用 Idx2 进行所用查询,很多情况会查询到两行数据。所以如果其他查询情况相同,SQLite 会选择 Idx1 进行索引查询,以减少查询到的行数。这种选择是由 <a href="https://www.sqlite.org/lang_analyze.html" target="_blank" rel="external">ANALYZE</a> 提供的。如果 <a href="https://www.sqlite.org/lang_analyze.html" target="_blank" rel="external">ANALYZE</a> 没有运行在数据库上,SQLite 选择每种查询方式的概率是一样的。</p>
<h4 id="1-6-多列索引查询"><a href="#1-6-多列索引查询" class="headerlink" title="1.6 多列索引查询"></a>1.6 多列索引查询</h4><p>为最大化提高 “AND 链接多个 WHERE 条件查询” 的性能,你需要设置根据 AND 的链接项建立一个多列索引表。在这里我们为 FruitsForSale 表中的 “fruit” 和 “state” 两列创建为一个索引表:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">INDEX</span> Idx3 <span class="keyword">ON</span> FruitsForSale(fruit, state);</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/idx3.gif" width="220" height="136"><br></div></p>
<center> Figure 10: 两列索引 </center>
<p>多列索引表的形式和单列索引表的形式相同,都是索引列在前,rowid 列在后。最左一列用来确定要查询的行数,第二列用来过滤不符合要求的行数。如果这里有三列,第三列则用来过滤前两列结果,以此类推。这种情况一般在我们这种简单数据模型中不会出现。但也有特例,如过滤条件为 fruit=’Orange’ 时会有两行数据,需要根据索引表中的第二列来过滤掉脏数据。因为 rowid 是唯一的,所以索引表中的每一行都是唯一的,尽管两行内容一样。</p>
<p>使用新的索引表 Idx3,SQLite 查询 California 生产的橘子的价格只需要两次二分查找:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">AND</span> state=<span class="string">'CA'</span>;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/idx3lu1.gif" width="676" height="140"><br></div></p>
<center> Figure 11: 使用两列索引的索引表查询 </center>
<p>在 Idx3 中使用 WHERE 约束进行查询,SQLite 只需要做一次二分查找就可以找出 “California 生产的橘子” 这一行对应的 rowid,然后再从原始的表中进行一次二分查找,找出对应橘子的价格。这是一种非常高效的查询方式。</p>
<p>既然 Idx3 中已经包含了 <a href="http://www.sqlite.org/queryplanner.html#fig3" target="_blank" rel="external">Idx1</a> 中的所有信息,那么我们就不需要 Idx1 了。如果要查询 “桃子的价格”,可以忽略掉 “state” 字段,直接使用 Idx3 进行查询:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Peach'</span>;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/idx3lu2.gif" width="645" height="132"><br></div></p>
<center> Figure 12: 使用 Idx3 进行查询 </center>
<p>因此,在今后设计数据库时最好遵循这样一个原则:不要让一个索引表包含另外一个索引表。虽然 SQLite 对于较长索引仍然可以进行高效查找,但是在设计时尽可能减少索引表的列数。</p>
<h4 id="1-7-覆盖索引"><a href="#1-7-覆盖索引" class="headerlink" title="1.7 覆盖索引"></a>1.7 覆盖索引</h4><p>通过使用索引表 Idx3 查询 “California 生产的橘子的价格” 已经十分高效。但还可以提高:将 “price” 这一列加入索引表,使用含有 3 列选项的索引表:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">INDEX</span> Idx4 <span class="keyword">ON</span> FruitsForSale(fruit, state, price);</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/idx4.gif" width="304" height="136"><br></div></p>
<center> Figure 13: 覆盖索引表 </center>
<p>这个索引表中包含了 FruitesForSale 表中的所有字段。我们称这种查询方式为 “覆盖查询”。因为所有的字段信息都被设置为了索引。SQLite 不需要再查询原始表就可以查询出对应水果的价格。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitsforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">AND</span> state=<span class="string">'CA'</span>;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/idx4lu1.gif" width="374" height="138"><br></div></p>
<center> Figure 14: 使用覆盖索引查询 </center>
<p>将要查询的结果的那一列数据也加入到索引表中,这样就不用再与原始表相关联,也使二分查找次数减半。这种查询虽然使性能有了提升(查询大约速度提升一倍)。但是,这只是细微提升。在性能提升这一方面,提升一倍往往不如提升数百万倍。所以对于大多数查询来说,1 微秒与 2 微秒之间的的差异是微不足道的。</p>
<h4 id="1-8-使用-OR-链接多个-WHERE-条件查询"><a href="#1-8-使用-OR-链接多个-WHERE-条件查询" class="headerlink" title="1.8 使用 OR 链接多个 WHERE 条件查询"></a>1.8 使用 OR 链接多个 WHERE 条件查询</h4><p>多列索引表只适用于用 AND 连接的 WHERE 条件的查询。所以当约束条件为 <strong>California 生产和橘子</strong> 时 Idx3 和 Idx4 两个索引表才有帮助;当约束条件变为 <strong>California 生产或橘子</strong> 时,这两个索引表将不再有什么帮助。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> FruitsForSale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">OR</span> state=<span class="string">'CA'</span>;</span><br></pre></td></tr></table></figure>
<p>当面对使用 OR 连接 WHERE 条件时,SQLite 会先通过索引表查询出每个条件对应行的 rowid。然后将这些 rowid 做一个并集,再去原始表中去查询。下面是查询过程:</p>
<p><div align="center"><br><img src="http://www.sqlite.org/images/qp/orquery.gif" width="700" height="308"><br></div></p>
<center> Figure 15: 使用 OR 连接的查询 </center>
<p>如上图所示,SQLite 首先查询出符合条件的 rowid,然后先将两部分做并集,再使用这些 rowid 去原始表中查询。这些 rowid 的排列是非常离散的,SQLite 使用索引查询一次 rowid 之后,会记住遍历过的索引,这样可以减少下次查询的计算量。当然,这只是其中一个实现细节。上图中不能表示完整的检索细节,但是展示了一个大概的过程。</p>
<p>上图所示的 OR-by-UNION 技术是很适用的,前提索引表中必须有满足条件的数据。如果索引表中没有满足 OR 连接的约束条件的数据,那么 SQLite 会去原始表中进行全表遍历。而不是通过 rowid 集合进行二分查找,这将十分耗费性能。</p>
<p>我们可以看到,OR-by-UNION 这个技术进行多索引查询时,实际上就是先通过索引表查询符合条件的 rowid,再将这些 rowid 进行 <strong>并集</strong> 操作;类似的,通过 AND 连接的 WHERE 条件的查询,也可以先通过索引表将符合条件的 rowid 查询出来,然后取 <strong>交集</strong>,很多 SQL 型数据库的原理就是这样的。但是是用单列索引的索引表和 OR-by-INTERSECT 进行 AND 查询,性能会比较差,所以一般都是使用多列索引进行 AND 查询。随着 SQLite 的不断优化,后序可能支持 OR-by-INTERSECT 查询。</p>
<h3 id="2-排序"><a href="#2-排序" class="headerlink" title="2.排序"></a>2.排序</h3><p>SQLite (像很多其他 SQL 数据库引擎一样) 可以使用索引进行 ORDER BY 查询,不仅加快查询速度。还可以加速排序速度。</p>
<p>如果没有索引进行辅助,一个 ORDERT BY 查询需要先进行排序。看一下下面这个语句:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitsforsale <span class="keyword">ORDER</span> <span class="keyword">BY</span> fruit;</span><br></pre></td></tr></table></figure>
<p>SQLite 首先检索出所有结果,然后再通过使用一个 sorter 进行排序输出。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/obfruitnoidx.gif" width="480" height="185"><br></div></p>
<center> Figure 16: 无索引排序 </center>
<p>如果要输出的行数为 K 条,那么排序所需时间的比例系数为 KlogK.如果 K 的值比较小,那么排序时间无足轻重。但是像上图所示那样 K==N,排序时间远远大于需要遍历全表的时间。此外,所有的检索结果都需要先放在临时缓存区(可能是运存或者硬盘缓存,依赖于编译时和运行时的设置),这意味着在语句执行完之前需要占据一块很大的缓存。</p>
<h4 id="2-1-使用-rowid-排序"><a href="#2-1-使用-rowid-排序" class="headerlink" title="2.1 使用 rowid 排序"></a>2.1 使用 rowid 排序</h4><p>排序操作是十分昂贵的,SQLite 很难将 ORDER BY 转化为一个非耗时操作。如果 SQLite 要输出的数据已经排序好了,这样就不用进行排序了。例如,你如果你按照 rowid 的排序输出结果,就不需要进行排序:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitsforsale <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">rowid</span>;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/obrowid.gif" width="380" height="185"><br></div></p>
<center> Figure 17: 使用 rowid 进行排序检索 </center>
<p>你也可以进行倒序检索:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitsforsale <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">rowid</span> <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure>
<p>这样 SQLite 虽然不会进行排序。但是为了进行倒序输出,SQLite 需要从 table 最后一条开始向前遍历,而不是从前往后遍历。如图 Figure 17 所示。</p>
<h4 id="2-2-使用索引排序"><a href="#2-2-使用索引排序" class="headerlink" title="2.2 使用索引排序"></a>2.2 使用索引排序</h4><p>然而,在实际使用中,很少直接通过 rowid 进行有序输出。一般都是通过其他条件进行有序检索。如果一个索引可以适用于进行 ORDER BY 查询,那么这个索引也可以用来进行排序。例如,对 “fruit” 这一列排序进行输出:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitsforsale <span class="keyword">ORDER</span> <span class="keyword">BY</span> fruit;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/obfruitidx1.gif" width="595" height="190"><br></div></p>
<center> Figure 18: 使用索引进行排序 </center>
<p>首先从上向下遍历 Idx1 索引表(如果查询语句为 “ORDER BY fruit DESC” 则从下向上遍历),按顺序检索出每个 fruit 对应的 rowid。然后通过 rowid 在原始表中进行二分查找并输出对应的数据。因为从索引中检索 rowid 时已经排好顺序,所以直接按照 rowid 的排列顺序在原始表中将数据检索并输出即可,不需要将所有检索结果再次排序。</p>
<p>但是这样做真的节省时间吗?在本节开始时所描述的方式中,先对数据查找再排序,所需要的时间比例系数为 NlogN,因为这需要对 N 条数据进行排序。而通过 Idx 索引表进行有序查找,我们需要对 N 个 rowid 进行二分查找,每个查找时间为 logN,总时间的比例系数同样为 NlogN.</p>
<p>SQLite 的查询规划器遵循 “低成本原则”。当有两种甚至有更多种查询方式时,SQLite 会先对每一种查询方式进行时间预估,然后选择成本最低的那种方式。成本的高低大多数情况下由预估时间决定,所以最终选择哪种方式取决于要查询的表的大小和 WHERE 条件的复杂度。通常情况下,使用索引进行有序查找一般作为首选。主要原因在于,使用索引查找不需要额外的临时存储空间来对数据进行排序,可以减少内存消耗。</p>
<h4 id="2-3-使用覆盖索引排序"><a href="#2-3-使用覆盖索引排序" class="headerlink" title="2.3 使用覆盖索引排序"></a>2.3 使用覆盖索引排序</h4><p>如果覆盖索引可以用于查询,那么查询 rowid 这一步则可以省去,这样消耗成本急剧降低。</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/obfruitidx4.gif" width="370" height="190"><br></div></p>
<center> Figure 19: 使用覆盖索引进行有序查找 </center>
<p>使用覆盖索引,SQLite 可以简单的对所有数据进行遍历,然后将结果输出,所需时间比例系数问 N。而且不需要额外开辟临时缓存区对数据进行排序。</p>
<h3 id="3-同时进行查询和排序"><a href="#3-同时进行查询和排序" class="headerlink" title="3.同时进行查询和排序"></a>3.同时进行查询和排序</h3><p>前面针对查询和排序两个主题分别作了讲解。但是在实际使用中,开发者需要将查找和排序同时进行。幸运的是,通过单个索引就可以完成这个操作。</p>
<h4 id="3-1-通过多列索引进行同时查找和排序操作"><a href="#3-1-通过多列索引进行同时查找和排序操作" class="headerlink" title="3.1 通过多列索引进行同时查找和排序操作"></a>3.1 通过多列索引进行同时查找和排序操作</h4><p>假如我们有这样一个需求:我们想要查询所有橘子的价格,并且按照橘子产地进行排序输出。查询语句如下:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> price <span class="keyword">FROM</span> fruitforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> state;</span><br></pre></td></tr></table></figure>
<p>这条查询语句中,既包含了查询,又包含了排序。使用索引表 Idx3 中的两列索引,可以将满足这两个条件的数据查询出来.</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/fruitobstate0.gif" width="678" height="142"><br></div></p>
<center> Figure 20: 使用多行索引进行查找并排序 </center>
<p>查询过程中,SQLite 先进行一次二分查找,找到 fruit=’Orange’ 对应的 rowid。(因为 fruit 是最左端的一列,所以整个索引表就是按照 furit 的拼写顺序进行排序的,因此两个相同的 fruit 在表中也是相邻的。)然后使用 rowid 在原始表中进行二分查找,找出对应的水果的价格。</p>
<p>你可能注意到,这里没有任何排序过程。没有特意过程去执行 ORDER BY 操作。没有排序过程,是因为在 index 表中查出数据的时候就已经按照 state 排好顺序了。在一个索引表中,如果第一列的值相同(例如上图中的 ‘Orange’),那么其对应的第二列的值也会像第一列那样按照顺序进行排列。所以,如果我们在一个索引表中遍历 fruit 值相同的两行,那么这两行数据的 state 列一定是按照顺序排列的。</p>
<h4 id="3-2-使用覆盖索引进行查找和排序"><a href="#3-2-使用覆盖索引进行查找和排序" class="headerlink" title="3.2 使用覆盖索引进行查找和排序"></a>3.2 使用覆盖索引进行查找和排序</h4><p>覆盖索引也可以用来查找和排序,例如下面这样:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> state;</span><br></pre></td></tr></table></figure>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/fruitobstate.gif" width="393" height="136"><br></div></p>
<center> Figure 21: 使用覆盖索引进行查找并排序 </center>
<p>按照之前说的,为满足 WHERE 条件约束,SQLite 会进行一次二分查找,从上向下遍历索引表,以找到符合条件的数据。如果 WHERE 条件所约束的值在索引表中有多条数据,那么这些条数据一定是相邻排列的。遍历时是按照从上向下顺序遍历的。因为 fruit 这一列后面一列就是 state,所以当 fruit 值相等时就会按照 state 这一列进行排列,以此类推。根据这个原理,查找出来的数据直接就是已经排好顺序的,十分高效。</p>
<p>SQLite 同样也可以进行降序查询:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitforsale <span class="keyword">WHERE</span> fruit=<span class="string">'Orange'</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> state <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure>
<p>基本原理是类似的,只不过这次是从下向上遍历,这样查询出来的数据也是降序排列的。</p>
<h4 id="3-3-使用索引进行局部排序"><a href="#3-3-使用索引进行局部排序" class="headerlink" title="3.3 使用索引进行局部排序"></a>3.3 使用索引进行局部排序</h4><p>有些情况下,索引表只能满足部分属性的排序。例如下面这个查询:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> fruitforsale <span class="keyword">ORDER</span> <span class="keyword">BY</span> fruit, price;</span><br></pre></td></tr></table></figure>
<p>如果使用覆盖索引表进行遍历,fruit 这一列肯定是按照顺序排列的,但是如果表中有多条 fruit 字段值相同的数据,它们的 price 字段值就不一定按照顺序排列了。当出现这种状况时,SQLite 会进行很多局部排序操作,每次只针对某个 fruit 进行排序,而不是针对整个表排序。Figure 22 展示了这一过程:</p>
<p><div align="center"><br><img src="https://www.sqlite.org/images/qp/partial-sort.gif" width="476" height="186"><br></div></p>
<center> Figure 22: 使用索引进行局部排序 </center>
<p>在这个示例中,并不是对 7 条数据进行整体排序,而是进行了 5 次单条排序(其实不用排)和 1 次两条排序(fruit=’Orange’ 这两条数据)。</p>
<p>进行多次局部排序,而不是进行整体排序的优点在于:</p>
<ol>
<li>相对于一次整体排序,多个局部排序同时进行可以减少 CPU 的时钟周期。</li>
<li>每个局部排序可以很快运行完毕,这意味着不用将大量信息暂存到内存缓存中,减少内存的占用。</li>
<li>有些 sort key 已经在索引表中排好顺序了,写 SQL 的可以省略,这样可以减少内存占用和 CPU 执行时间。</li>
<li>每当一次局部排序完成,便会将数据返回给应用;整体查询需要遍历完整表才会将数据返回。前者更好。</li>
<li>如果使用了 LIMIT 条件,还可以避免遍历整个表。</li>
</ol>
<p>因为这些优点,SQLite 经常使用索引进行局部排序,而不是进行整体排序。</p>
<h4 id="4-无-rowid-的表"><a href="#4-无-rowid-的表" class="headerlink" title="4.无 rowid 的表"></a>4.无 rowid 的表</h4><p>以上描述的这些基本原则,同时适用于含有 rowid 的表和<a href="https://www.sqlite.org/withoutrowid.html" target="_blank" rel="external">无 rowid 的表</a>。唯一的不同就是,有 rowid 的表,rowid 这一列一般会作为一个表的键。创建索引表之后,rowid 会在所以表中最右端用来关联索引表和原始表,在索引表中它的位置会被主键代替。</p>
<h4 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h4><ul>
<li><a href="https://www.sqlite.org/queryplanner.html" target="_blank" rel="external">https://www.sqlite.org/queryplanner.html</a></li>
</ul>
<a id="more"></a>
<p>这是一篇 <a href="https://www.sqlite.org/queryplanner.html" target="_blank" rel="external">Query Planning</a> 的译文。</p>
<h3
iOS 中的单元测试
http://yoursite.com/2017/10/04/iOS 中的单元测试/
2017-10-04T13:05:30.000Z
2018-06-03T13:14:07.359Z
<a id="more"></a>
<p>最近团队内部为了保证代码质量,要求单元测试覆盖率 80%+。在编写单元测试过程中,等到了一些收获,为此总结一下。</p>
<h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>Unit test 是保证代码质量的重要模块,为模块编写 Unit test 可以减少开发中的 bug。同时在重构代码时,如果有一定粒度的 Unit test 覆盖,可以降低重构风险,这些大家都深有体会。</p>
<h5 id="1-核心观点"><a href="#1-核心观点" class="headerlink" title="1.核心观点"></a>1.核心观点</h5><ul>
<li>开发必须重视 unit test,不仅是点缀和补充,要和功能开发放到同样主要地位,加入工作量评估.</li>
<li>加入 daily build,build break 要追责.</li>
<li>对于某些大的代码改动,发 merge request 之前要跑 unit test.</li>
<li>不要写完了功能再补充 unit test,尽可能做到 TDD.</li>
</ul>
<h5 id="2-覆盖粒度"><a href="#2-覆盖粒度" class="headerlink" title="2.覆盖粒度"></a>2.覆盖粒度</h5><p>原则上,unit test 要尽可能覆盖所有 case,粒度拆分越细越好。但是实际代码中,并不是所有 case 都需要覆盖,有的覆盖了没有意义;还有些 case 无法覆盖到,所以做到 100% 并不可能。unit test 诣在提高代码质量,减少失误,不要为了提高覆盖率而去强行覆盖某个无意义的 case。</p>
<h4 id="Unit-Test-基础知识"><a href="#Unit-Test-基础知识" class="headerlink" title="Unit Test 基础知识"></a>Unit Test 基础知识</h4><h5 id="1-Get-Start"><a href="#1-Get-Start" class="headerlink" title="1.Get Start"></a>1.Get Start</h5><p>简单来说,创建 unit test 的 target,在这个 target 中创建对应的 test 文件,然后运行,测试,check 结果就可以了。但是有一些问题需要注意一下:</p>
<ul>
<li><p>每个 unit test 类中都会有一个 <code>-[setUp]</code> 方法和一个 <code>-[tearDown]</code> 方法。可以将需要公共初始化操作放在 <code>-[setUp]</code> 中,将需要重置或者销毁的操作放在 <code>-[tearDown]</code> 中。<strong>每个 test 方法的执行,都会 new 一个新的实例,并调用这两个方法。如果一个类中有多个 test 方法,这两个方法会被调用多次。</strong></p>
</li>
<li><p>在执行 unit test 时,第一需要将 target 选为 unit test 那个 target;第二需要 run 选项选为 “Test”,如下图。不选择为 “Test” 也会运行,但有时候会出一些错误,运行结果不准。<br> <img src="/uploads/iOS-unit-test/runItem.png" alt="启动项"></p>
</li>
<li><p>如果某个方法没有运行的”小菱形”,如下图。检查一下你这文件是否添加到 test target 里面(读取本地文件时,读取不到可能也是如此)。<br> <img src="/uploads/iOS-unit-test/diamond.png" alt="小菱形"></p>
</li>
</ul>
<h5 id="2-Test-Assertions-概览"><a href="#2-Test-Assertions-概览" class="headerlink" title="2. Test Assertions 概览"></a>2. Test Assertions 概览</h5><p>在一些 test 方法中,会使用一些 XCTest Framework 提供的 assert 进行判断,这些 assert 主要分为以下几类:</p>
<ul>
<li><strong>Boolean Assertions</strong>,主要用来判断结果是 true 还是 false。例如 <code>XCTAssertTure</code>、<code>XCTAssertFalse</code>。</li>
<li><strong>Nil and Non-nil Assertions</strong>,判断结果是否为 nil。例如 <code>XCTAssertNil</code>、<code>XCTAssertNotNil</code>。</li>
<li><strong>Equality and Inequality Assertions</strong>,判断两个类或者值是否相等。例如 <code>XCTAssertEqual</code>、<code>XCTAssertEqualObjects</code>、<code>XCTAssertEqualWithAccuracy</code>。</li>
<li><strong>Comparable Value Assertions</strong>,主要用于大小比较(>,<,>=,<=)。例如 <code>XCTAssertGreaterThan</code>、<code>XCTAssertCreaterThanOrEqual</code>。</li>
<li><strong>Error Assertions</strong>,主要用于异常测试,判断一个表达是否会抛出异常,以及异常具体信息。例如 <code>XCTAssertThrows</code>、<code>XCTAssertThrowsSpecific</code>。</li>
<li><strong>Failing Unconditionally</strong>,想主动触发一个失败,或者标记一个失败。例如 <code>XCTAssertFail</code>。</li>
<li><strong>Asynchronous Tests and Expectations</strong>,这不是 assert,这是测试时需要的一些 exception,异步,KVO,Notification 等。例如 <code>XCTestExpectation</code>、<code>XCTKVOExpectation</code>、<code>XCTNSNotificationExpectation</code>。</li>
</ul>
<p>前几个都比较熟悉,这里说一下最后一项,有几个 expectation 对于我们来说比较陌生,主要用于 UI Test。在 <a href="https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations" target="_blank" rel="external">Asynchronous Tests and Expectations</a> 里面主要有如下几个类:</p>
<ul>
<li>XCTKVOException,当监听一个对象的属性变化(KVO)时使用。例如:</li>
</ul>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- (void)testMethod {</span><br><span class="line"> UIView *view <span class="built_in">=</span> [UIView new]; // 监听对象</span><br><span class="line"> XCTKVOExpectation *kvoExceptation <span class="built_in">=</span> [[XCTKVOExpectation alloc] initWithKeyPath:@<span class="string">"tag"</span> object:view];</span><br><span class="line"> XCTWaiterResult result <span class="built_in">=</span> [XCTWaiter waitForExpectations:@[kvoExceptation] timeout:<span class="number">3</span>];</span><br><span class="line"> XCTAssertTrue(result <span class="built_in">=</span><span class="built_in">=</span> XCTWaiterResultCompleted);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>XCTNSNotificationExpectation/XCTDarwinNSNotificationExpectation,测试发通知时使用(NSNotification 和 Darwin NSNotification)。例如:</li>
</ul>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (void)testMethod {</span><br><span class="line"> XCTNSNotificationExpectation *notificationExpectation <span class="built_in">=</span> [[XCTNSNotificationExpectation alloc] initWithName:@<span class="string">"kNotificationName"</span>];</span><br><span class="line"> XCTWaiterResult result <span class="built_in">=</span> [XCTWaiter waitForExpectations:@[notificationExpectation] timeout:<span class="number">3</span>];</span><br><span class="line"> XCTAssertTrue(result <span class="built_in">=</span><span class="built_in">=</span> XCTWaiterResultCompleted);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>XCTNSPredicateExpectation,测试谓词表达式时使用,例如:</li>
</ul>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">NSNumber *str <span class="built_in">=</span> @<span class="number">123</span>;</span><br><span class="line"> NSPredicate *predicate <span class="built_in">=</span> [NSPredicate predicateWithFormat:@<span class="string">"SELF BETWEEN {100, 200}"</span>];</span><br><span class="line"> XCTNSPredicateExpectation *predicateExpectation2 <span class="built_in">=</span> [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:str];</span><br><span class="line"> XCTWaiterResult result <span class="built_in">=</span> [XCTWaiter waitForExpectations:@[predicateExpectation2] timeout:<span class="number">3</span>];</span><br><span class="line"> XCTAssertTrue(result <span class="built_in">=</span><span class="built_in">=</span> XCTWaiterResultCompleted);</span><br></pre></td></tr></table></figure>
<h5 id="3-运行-unit-test-快捷键"><a href="#3-运行-unit-test-快捷键" class="headerlink" title="3.运行 unit test 快捷键"></a>3.运行 unit test 快捷键</h5><p>运行 unit test 试的方式有很多,可能直接点击 run 按钮运行;可以在 Test navigator 中选择运行全部 or 单个类 or 单个 case;也可以在源码中点击运行等。当然也可以通过快捷键,这里介绍一些快捷键:</p>
<ul>
<li>run 所有 unit test: Command + U</li>
<li>只 build unit test: Shift + Command + U</li>
<li>只 run,不 build unit test: Control + Command + U</li>
<li>只 run 一个 case (当前光标停留的这个 case): Control + Option + Command + U</li>
</ul>
<h5 id="4-断点调试"><a href="#4-断点调试" class="headerlink" title="4.断点调试"></a>4.断点调试</h5><p>在 Breakpoint navigator 中添加一个 ‘Test Failure Breakpoint’ 断点,当出现失败时,就会出停下,方便调试。</p>
<p> <img src="/uploads/iOS-unit-test/testBreakpoint.png" alt="test breakpoint"></p>
<h5 id="5-Code-Coverage"><a href="#5-Code-Coverage" class="headerlink" title="5.Code Coverage"></a>5.Code Coverage</h5><p>通过 code coverage 可以查看每个模块 unit test 的覆盖率,甚至可以具体到每个类里面,每个 case 的覆盖率。可以在 scheme 菜单中开启 code coverage。</p>
<p> <img src="/uploads/iOS-unit-test/openCodeCoverage.png" alt="openCodeCoverage"></p>
<p>查看结果具体如下:</p>
<p> <img src="/uploads/iOS-unit-test/codeCoverage.png" alt="codeCoverage"></p>
<p>在代码中可以查看方法是否被覆盖到,如下图中,红色代表未被覆盖,绿色代表被覆盖,绿色中的数字代表在测试过程中这段代码被命中的次数。可以通过 Editor -> Hide/Show Code Coverage 打开和关闭覆盖信息。</p>
<p> <img src="/uploads/iOS-unit-test/hitCode.png" alt="hitCode"></p>
<h5 id="6-命令行运行-unit-test"><a href="#6-命令行运行-unit-test" class="headerlink" title="6.命令行运行 unit test"></a>6.命令行运行 unit test</h5><p>这目前没有什么实际用途,在这里只是简单提一下。通过以下命令格式可以直接运行 unit test:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">xcodebuild test [-workspace <your_workspace_name>]</span><br><span class="line"></span><br><span class="line"> [-project <your_project_name>]</span><br><span class="line"></span><br><span class="line"> -scheme <your_scheme_name></span><br><span class="line"></span><br><span class="line"> -destination <destination-specifier></span><br><span class="line"></span><br><span class="line"> [-only-testing:<test-identifier>]</span><br><span class="line"></span><br><span class="line"> [-skip-testing:<test-identifier>]</span><br></pre></td></tr></table></figure>
<p>eg: <code>xcodebuild test -workspace MTBusinesskitDev.xcworkspace -scheme MTBusinessKitTests -destination 'platform=iOS Simulator,name=iPhone 7'</code></p>
<p>想了解更多信息可以查看 <a href="How do I run unit tests from the command line?">How do I run unit tests from the command line?</a>。</p>
<h4 id="Practics"><a href="#Practics" class="headerlink" title="Practics"></a>Practics</h4><h5 id="1-测试路径"><a href="#1-测试路径" class="headerlink" title="1.测试路径"></a>1.测试路径</h5><ul>
<li><p>既要考虑正确路径,还要考虑非正确路径,故意创建一些错误 case。例如在 <code>MTBAdLoadInfoTest</code> 中,故意创造了几组错误数据进行测试。</p>
</li>
<li><p>测试多路径。很多的类中,可能有多条 case,需要覆盖完全。例如 <code>MTBMeituBusinessAdRequest</code> 中,根据 load from cache or load from web,phase1 or phase2,缓存是否有效等情况,组合起来会有多种 case,组要考虑周全,完全覆盖到。</p>
</li>
<li>考虑边界情况。例如在 <code>MTBBatchReportDataManager</code> 类中,测试“是否超过 15 天”(<code>-[checkDateIsPast:]</code> 方法) 时。出了需要测试未超过 15 天和超过 15 天的 case,还需要测试恰好 15 天的 case。</li>
</ul>
<h5 id="2-测试一些私有方法及使用到私有属性"><a href="#2-测试一些私有方法及使用到私有属性" class="headerlink" title="2.测试一些私有方法及使用到私有属性"></a>2.测试一些私有方法及使用到私有属性</h5><p>测试过程中,可能需要调用或者测试一些私有方法,也有可能需要使用一些私有属性。这时可以新建一个 private category 文件,将一些私有方法和属性放到这个 category 中。然后将这个文件引入到 test case.m 中即可。</p>
<blockquote>
<p>原则上这个 private category 文件只放在 test target 中,并且只被 test case.m 引入。</p>
</blockquote>
<p>例如在测试 <code>MTBBusinessAdPreload</code> 类时,添加了 <code>MTBBusinessAdPreload+Private.h</code> 文件,内容如下:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">@<span class="keyword">interface</span> MTBPreloadModel ()</span><br><span class="line">+ (instancetype)preloadModelWithInfo:(NSDictionary *)info parsingError:(NSString *__autoreleasing *)errorStr;</span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@<span class="keyword">interface</span> MTBBusinessAdPreload (Pirvate)</span><br><span class="line"></span><br><span class="line">@property (nonatomic, strong) NSMutableArray <NSDictionary *> *resourceToDownloadDic;</span><br><span class="line"></span><br><span class="line">// preload 相关方法</span><br><span class="line">- (MTBPosition *)createPositionWithAdIndexInfo:(MTBAdIndexInfo *)adIndexInfo;</span><br><span class="line">- (void)replaceRoundAndIdeaIDWithPreloadData:(MTBPreloadModel *)preloadModel;</span><br><span class="line">- (NSDictionary <NSString *, NSArray *> *)replaceCreativesWithPreloadData:(MTBPreloadModel *)preloadModel;</span><br><span class="line"></span><br><span class="line">// download 相关方法</span><br><span class="line">- (void)downloadMaterials:(NSDictionary *)resourcesToDownload;</span><br><span class="line">// cache 操作相关方法</span><br><span class="line">- (void)cacheResourceToDownload:(NSDictionary *)dic;</span><br><span class="line">- (NSMutableDictionary *)cachedResourceToDownload;</span><br><span class="line">- (void)removeResourceFromCache:(NSString *)creativeId;</span><br><span class="line">@end</span><br></pre></td></tr></table></figure>
<h5 id="3-异步方法测试"><a href="#3-异步方法测试" class="headerlink" title="3.异步方法测试"></a>3.异步方法测试</h5><p>测试异步逻辑,系统提供了专门的 API。所有涉及 通知、观察者、listener 等回调机制的 API 都可以写 case,不同的平台各自有支持。例如在 <code>MTBAnalyticsReportDataTest</code> 中的异步测试:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">- (void)testReportAdInfo {</span><br><span class="line"> </span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"> XCTestExpectation *exception1 <span class="built_in">=</span> [self expectationWithDescription:@<span class="string">"Report Data"</span>];</span><br><span class="line"> [[MTBAnalyticsReportData shared] logEventWithReportInfo:allParams completion:^(NSError *error, BOOL success) {</span><br><span class="line"> XCTAssertNil(error);</span><br><span class="line"> XCTAssertTrue(success);</span><br><span class="line"> [exception1 fulfill];</span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> XCTestExpectation *exception2 <span class="built_in">=</span> [self expectationWithDescription:@<span class="string">"Report nil"</span>];</span><br><span class="line"> [[MTBAnalyticsWebService shared] reportAdInfo:nil completion:^(NSError *error, BOOL success) {</span><br><span class="line"> XCTAssertTrue(error.code <span class="built_in">=</span><span class="built_in">=</span> <span class="number">1010</span>);</span><br><span class="line"> XCTAssertFalse(success);</span><br><span class="line"> [exception2 fulfill];</span><br><span class="line"> }];</span><br><span class="line"> </span><br><span class="line"> [self waitForExpectationsWithTimeout:<span class="number">8.0</span> handler:nil];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>有些异步测试,可能需要验证线程是否安全。例如:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">MTBReqeust *request <span class="built_in">=</span> [MTBReqeust new];</span><br><span class="line">[request loadData:^(id data){</span><br><span class="line"> XCTAssertTrue([NSThread mainThread]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>除此以外, 不仅要写独立调用异步 API 的 case, 还可以考虑对同一个对象多次调用该 API 的并发逻辑.</p>
<p>因为对于异步API, 我们设计时总是隐含地给他制定了一个内在的重复调用时的回调逻辑, 一般对于异步 API , 我们在调用了该 API 方法后 callback 没回调的情况下, 又对<strong>同一个对象</strong>再次调用了该 API 时的相应逻辑(ie. 并发模型), 会是以下几类之一</p>
<ul>
<li>API 每次调用, 总会在将来确定触发一个 callback, 多次调用间互不影响, 调用几次就会 callback 几次</li>
<li>新的调用被忽略, 已存在的调用继续执行 (在前面的例子里, 第二次调用 request 方法会立刻同步返回<code>false</code>), 旧调用在将来某时刻触发 callback</li>
<li>旧的调用立刻被自动 cancel/旧的调用会立刻触发 failure callback, 新的调用正常执行, 在将来某时刻触发 callback</li>
<li>新旧两次调用合并成一次调用/旧的 callback 被新的 callback 接管, 旧的 callback 不再触发, 在新的调用完成时再进行<strong>1次</strong>回调</li>
<li>API 不允许第一个调用没 callback 前就触发新的调用, 如果出现这种情况立刻抛出异常</li>
</ul>
<p>如果在写 case 之前从来没考虑过这个问题, 那么可能使用这个 API 时已经有隐藏的危险了<br>无论我们的 API 采用哪一种并发调用策略, 都可以编写对应的 case 来严格验证这个问题, 此处不赘述.</p>
<h5 id="4-模拟操作"><a href="#4-模拟操作" class="headerlink" title="4.模拟操作"></a>4.模拟操作</h5><p>在写一些 case 时,有时候我们无法创造真实的场景,这时就需要进行模拟。</p>
<p>(1)模拟系统通知</p>
<p>视频在收到 home 出去时需要暂停. 我们有2种方式</p>
<ul>
<li>1是模拟系统发 notification. 这种方式简单, 但是可能会影响一些其他逻辑</li>
<li>2重构视频类的接口, 把 <code>func pause()</code>方法扩展成 <code>func pause(cause:)</code> 其中 <code>cause</code> 参数表示了pause的原因, 例如包括”主动点击”, “页面消失”, “进如后台”等等, 然后把一部分原先在 <code>pause</code> 外的逻辑移到方法里面来, 对传入的不同参数进行不同的处理. 这样在test case里只需要用不同的 <code>cause</code> 参数调用pause方法即可.</li>
</ul>
<p>(2)模拟时间流逝</p>
<p>在开屏的一些逻辑中,home 出去回来,需要根据上次展示时间判断,是否有必要展示开屏;在批量上报逻辑中,测试数据是否过期时,需要创造一个过期时间。例如在 <code>MTBSplashAdManagerTest</code> 中,在现在基础上减去 200s 并传入,这样程序执行时拿到的时间就是一个 “过去” 时间:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">- (void)testAppWillEnterForeground4 {</span><br><span class="line"> [MTBSplashStatus setAppeared:NO];</span><br><span class="line"> self.manager.splashShownCountInWarmStart <span class="built_in">=</span> <span class="number">0</span>;</span><br><span class="line"> self.manager.isIntervalLargerThanSetting <span class="built_in">=</span> YES;</span><br><span class="line"> self.manager.hasPendingDisplayTask <span class="built_in">=</span> NO;</span><br><span class="line"> // 倒退 <span class="number">200</span>s,再次进入 APP 时距离上次超过 <span class="number">120</span>s,展示开屏。</span><br><span class="line"> NSTimeInterval interval <span class="built_in">=</span> [[NSDate date] timeIntervalSince1970] - <span class="number">200</span>;</span><br><span class="line"> self.manager.lastLeaveDate <span class="built_in">=</span> [NSDate dateWithTimeIntervalSince1970:interval];</span><br><span class="line"> [self.manager appWillEnterForeground];</span><br><span class="line"> XCTAssertFalse(self.manager.isColdStart);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="5-网络相关测试"><a href="#5-网络相关测试" class="headerlink" title="5.网络相关测试"></a>5.网络相关测试</h5><p>原则上,本地单元测试不依赖网络情况。因为打包时跑单元测试,打包机网络可能会挂掉,或者服务器挂掉。这是单元测试跑不过,打包就会挂掉。因此需要针对一些网络请求进行模拟。</p>
<p>在测试一些接口解析时,需要一些 json 数据,平时这些数据是从服务端请求。而在测试过程中,可以直接读取本地数据。例如在测试 preload,load,setting 接口时,都是从本地读取数据。</p>
<p>除此之外,还可以继承并重写 <code>MTBSimpleHttpFetcher</code> 类中的一些方法,指定返回数据。本质上和从本地读取数据是一样的,例如:</p>
<figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">@<span class="keyword">interface</span> MTBSimpleHttpFetchreMock:MTBSimpleHttpFetcher</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@<span class="keyword">implementation</span> MTBSimpleHttpFetchreMock</span><br><span class="line"></span><br><span class="line">- (id<MTBCancellable>)loadResource:(MTBRemoteResource *)resource</span><br><span class="line"> reportQueue:(dispatch_queue_t)reportQueue</span><br><span class="line"> completion:(MTBRequestCompletion)completion {</span><br><span class="line"> ...</span><br><span class="line"> dispatch_async(reportQueue, ^{</span><br><span class="line"> id data <span class="built_in">=</span> [NSObject new]; // 自定义 data,也可以自定义 error 等。</span><br><span class="line"> completion(data,data,nil);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line">@end</span><br></pre></td></tr></table></figure>
<p>然后通过创建 <code>MTBSimpleHttpFetchreMock</code> 类,调用 <code>-[loadResource:reprotQueue:completion:]</code> 方法模拟网络拉取。</p>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><h5 id="1-unit-test-需要与-APP-本身隔离,unit-test-执行不应该影响到-APP-本身逻辑"><a href="#1-unit-test-需要与-APP-本身隔离,unit-test-执行不应该影响到-APP-本身逻辑" class="headerlink" title="1. unit test 需要与 APP 本身隔离,unit test 执行不应该影响到 APP 本身逻辑"></a>1. unit test 需要与 APP 本身隔离,unit test 执行不应该影响到 APP 本身逻辑</h5><ul>
<li>iOS的unit test是否需要host到app这个问题, 尽量不要, 但目前因为bundle info取不到只能要host, 后面应该拆离。</li>
<li>test case执行的process和main app不是一个process, 但是对于某些系统提供的全局UI对象, 如UIApplication和视图层级, 却是统一的, 因此要避免test case对main app的UI进行操作. 如果执行case一定会操作到的, 需要考虑用其他方式屏蔽.</li>
<li>通过在scheme中增加test时的环境变量, 可以判断某个方法被调用到时所在的环境是main app进程还是test case代码的进程.</li>
<li>注重test case退出时的数据清理, 都在<code>XCTestCase</code>里<ul>
<li>func teardown()</li>
<li>static func teardown()</li>
<li>waitForExpectations(timeout:handler:)第二个参数</li>
</ul>
</li>
<li>涉及到复杂持久化存储的, 如果不太容易和main app数据分开来, 或者无法单独清理test数据, 那么应该在数据存储源头上就指定不同的路径, 通过上面提到的方式把这部分数据源抽象出来, 让test case可以重新指定另一个测试用的数据源。</li>
</ul>
<h5 id="2-unit-test对开发的启示"><a href="#2-unit-test对开发的启示" class="headerlink" title="2.unit test对开发的启示"></a>2.unit test对开发的启示</h5><ul>
<li>模块高内聚低耦合, 一个类干明确的1件事</li>
<li>多用组合(相比之于继承)</li>
<li>不要滥用单例, 滥用单例会导致很难对这个类单独编写test case </li>
</ul>
<p>后面如果谁有一些 best practics 可以直接更新文档。</p>
<a id="more"></a>
<p>最近团队内部为了保证代码质量,要求单元测试覆盖率 80%+。在编写单元测试过程中,等到了一些收获,为此总结一下。</p>
<h4 id="概述"><a href="#概述" class="headerlink" title="概述"></
代码整洁之道
http://yoursite.com/2017/02/26/Clean-code/
2017-02-26T11:41:39.000Z
2018-06-03T13:13:51.757Z
<a id="more"></a>
<p>代码的整洁与否是一个程序员的个人卫生问题。一个程序员穿着可以稍邋遢一些,但是代码要写的干净、利落。<br>如果你想成为一个更好的程序员,除了要学习语法、设计模式之外,还要学习如何写出整洁有效的代码,这本书会教你如何写出这样的代码。书中代码全部为 Java 语言,如果没有接触过 Java 语言,读起来可能会有点困难。</p>
<p>这本书大致可以分为三个部分:第一部分占据了大约一半的章节,主要介绍编写整洁代码的原则、模式和实践,读起来比较容易理解。如果你读完这部分感觉已经掌握了如何写好整洁代码,那么你要失望了,其实你只学到一点皮毛。要知道,<strong>容易学会的东西一般价值都不高</strong>。当然如果你是天才,学什么都快,就当我没说。</p>
<p>第二部分主要是对几个复杂性不断增加的案例的研究,这是最有价值的一部分,也是最难读的一部分。在这部分你会读大量的 Java 代码,然后逐渐掌握如何写出整洁有效的代码。</p>
<p>第三部分是从研究案例得到的一些启示与灵感。如果你仔细读了第二部分,这部分将是对你的回报,否则这部分对你来说可能所值无几。</p>
<p>这本书是基于 Java 语言写的,因为语法的差异性,有些规则并不适合 OC。下面我主要针对 OC 总结的一些 Tips。</p>
<h3 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h3><p>命名是令程序员最头疼的事之一,良好的命名习惯是写好整洁代码的基本素养,命名遵循的原则就是:<strong>有意义且不误导读者</strong>。在 Apple 的官方文档 <a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html" target="_blank" rel="external">Coding Guidelines for Cocoa</a> 中,有规范的命名规则。作为一名 iOS 开发者,有必要读一下这篇文档。如果说 Apple 官方文档是针对 OC 的定制规则,那么下面说的将是一些通用规则。</p>
<h4 id="1-有意义的命名"><a href="#1-有意义的命名" class="headerlink" title="1.有意义的命名"></a>1.有意义的命名</h4><p>只有编程初学者才会用 <code>a</code>,<code>b</code>,<code>sss</code>,<code>tyq</code> 去做变量或常量名,这点相信大家都不会了。在命名的时候,想要表达什么,就去用什么样的名字。在 OC 的编码习惯中,基本都是用完整的单词,尽量<strong>不要用缩写</strong>,多个单词采用”驼峰标记法”。如果你深受 Windows 的 C 语言 API 毒害的话,赶快忘掉那该死的”匈牙利标记法”吧。例如下面的代码中:<br><a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE" target="_blank" rel="external"></a></p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UILabel</span> *nameLbl = [<span class="built_in">UILabel</span> new]; <span class="comment">// nameLbl 中不应把 'Label' 缩写</span></span><br><span class="line"><span class="built_in">NSString</span> *logStr = <span class="string">@"this is log"</span>; <span class="comment">// 即使是 NSString 类型,也不应该缩写为 Str</span></span><br><span class="line"><span class="built_in">UIViewController</span> *loginVC = [<span class="built_in">UIViewController</span> new]; <span class="comment">// 不建议把 ViewController 所以为 VC</span></span><br></pre></td></tr></table></figure>
<p><a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE" target="_blank" rel="external">Apple 官方指定的一些缩写</a>,在命名时这些变量可以使用缩写。</p>
<p>尽量避免使用一些无意义的”魔法数字”,可以使用枚举、常量、宏或者其他方式代替,在进行全局搜索时也很方便。例如下面例子:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1,2,3 各代表什么?</span></span><br><span class="line"><span class="keyword">if</span> (state == <span class="number">1</span>) {...}</span><br><span class="line"><span class="keyword">if</span> (state == <span class="number">2</span>) {...}</span><br><span class="line"><span class="keyword">if</span> (state == <span class="number">3</span>) {...}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果写成这样,则很明确</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_ENUM</span>(<span class="built_in">NSUInteger</span>, VideoState) {</span><br><span class="line"> VideoStateOpen = <span class="number">1</span>,</span><br><span class="line"> VideoStatePause,</span><br><span class="line"> VideoStateClose</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (state == VideoStateOpen) {...}</span><br><span class="line"><span class="keyword">if</span> (state == VideoStatePause) {...}</span><br><span class="line"><span class="keyword">if</span> (state == VideoStateClose) {...} <span class="comment">// 换成 switch 或许更好一些</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 还有这种的,40.0f 和 23.0f 是什么鬼?为什么不定义为有名字的常量?</span></span><br><span class="line"><span class="built_in">CGFloat</span> viewHeight = <span class="number">40.0</span>f + <span class="number">23.0</span>f;</span><br></pre></td></tr></table></figure>
<h4 id="2-避免误导"><a href="#2-避免误导" class="headerlink" title="2.避免误导"></a>2.避免误导</h4><p>有意义的命名是先决条件,在有意义的基础上,还要做到不能误导读者。不要使用关键字或者一些专属名词命名;不要使用双关语;不要单个使用 ‘o’、’l’ 这种字母,以免与 ‘0’ 、 ‘1’ 混淆读者。下面示例引以为戒:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// list 在很多语言中是一种容器类型,尽量不要作为变量名</span></span><br><span class="line"><span class="built_in">NSArray</span> *list = [<span class="built_in">NSArray</span> new]; </span><br><span class="line"></span><br><span class="line"><span class="comment">// o or 0,1 or l 很容易混淆,尽管现在的 IDE 会对数字和字母有高亮区分,还是不建议这样写</span></span><br><span class="line"><span class="keyword">int</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">if</span> (O == <span class="number">1</span>) {</span><br><span class="line"> a = O1;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> l = <span class="number">01</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//</span></span><br></pre></td></tr></table></figure>
<h4 id="OC-中变量、属性、方法、类的命名"><a href="#OC-中变量、属性、方法、类的命名" class="headerlink" title="OC 中变量、属性、方法、类的命名"></a>OC 中变量、属性、方法、类的命名</h4><p>在进行团队合作时,通常团队内部会统一一套编码风格,否则在定义”个人信息”时有的用 ‘personData’,有的用 ‘personInfo’ 岂不是乱套了。在没有统一的时候,应按照如下规则。</p>
<p>在对基本类型的变量或属性进行命名时,遵循有意义且不误导原则;在对对象类型的变量或属性进行命名时,经常在名字后面加上对象类型。不要觉得长,OC 的语法命名一向都很长,看 API 就知道了。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基本类型命名</span></span><br><span class="line"><span class="built_in">CGRect</span> avatarViewFrame = {<span class="number">0</span>,<span class="number">0</span>,<span class="number">40</span>,<span class="number">40</span>};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对象类型命名,多以 '名称+类型后缀'</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UILabel</span> *titleLabel;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UIView</span> *tagView;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSDictionary</span> *namesArray;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 也有一些特例,例如 NSString 类型有时候就不带后缀</span></span><br><span class="line">@Property (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *name;</span><br></pre></td></tr></table></figure>
<p>方法命名,要遵循一下几条原则:</p>
<ul>
<li>以小写字母开始,之后单词的首字母大写,即”驼峰标识”。</li>
<li>如果方法代表对象接收的动作,以动词开始。尽量不要使用 ‘get’、’set’ 命名,set 方法可以使用 ‘set’ 开头命名。</li>
<li>如果方法返回接收者的属性,以 接收者 + 接收的属性 命名。</li>
<li>参数名以小写字母开始,之后的单词首字母大写,不要使用缩写。</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 动词开头</span></span><br><span class="line">- (<span class="keyword">void</span>)pushToLoginViewController;</span><br><span class="line">- (<span class="keyword">void</span>)downloadImageWithURLString:(<span class="built_in">NSString</span> *)imageURL;</span><br><span class="line">- (<span class="built_in">CGSize</span>)logoViewSize; <span class="comment">// 接受者 + 属性</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// set 方法</span></span><br><span class="line">- (<span class="keyword">void</span>)setUserName:(<span class="built_in">NSString</span> *)userName;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一些反例</span></span><br><span class="line">— (<span class="built_in">NSInteger</span>)getNumber;</span><br><span class="line">- (<span class="keyword">void</span>)showimage;</span><br><span class="line">- (instancetype)initWithRequest:(<span class="built_in">NSURLRequest</span> *) req;</span><br></pre></td></tr></table></figure>
<p>类命名,一般在开发项目时,会规定类得前缀。因为 Apple 的 API 前缀一般为两个大写字母,为了避免冲突,自定义类名一般前缀为三个大写字母。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 所有前缀为 BLC(BoolChow) 缩写</span></span><br><span class="line">BLCLoginViewController.h</span><br><span class="line">BLCLoginViewController.m</span><br><span class="line"></span><br><span class="line">BLCNetWork.h</span><br><span class="line">BLCNetWork.m</span><br></pre></td></tr></table></figure>
<p>在 Xcode 中,选中项目在右侧工具栏中可以设置类的前缀,这样在新建类时会默认加上前缀。如下图:</p>
<p><img src="/uploads/Clean-Code/prefixSet.png" alt="设置前缀"></p>
<p><img src="/uploads/Clean-Code/prefixEg.png" alt="设置前缀示例"></p>
<h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><p>好的代码是不用注释的,好的注释是你想办法不去写注释,听着有点废话,但确实如此。之因为写注释,是因为代码写的比较乱,怕其他人看不懂,因为标上清晰的注释就能掩盖代码的丑陋。但是往往注释写太多,代码显得就 low 了。</p>
<p>OC 的命名一般都使用单词全拼,很少使用缩写。有时候一个方法的名字特别长,读起来像一句话。所以在 OC 中,如果名字起得好,很少用的到注释。当然,在一些开源框架、SDK 或者官方 API 中,头文件中很多方法都会标有大量注释,方便使用者理解,这并不矛盾。为了注释到恰到好处,下面从”什么时候写”,”怎么写”,”在哪写”三个方面简述几条注释的规则。</p>
<h4 id="1-当代码表述不清,或者容易误导读者的时候,加以注释。"><a href="#1-当代码表述不清,或者容易误导读者的时候,加以注释。" class="headerlink" title="1.当代码表述不清,或者容易误导读者的时候,加以注释。"></a>1.当代码表述不清,或者容易误导读者的时候,加以注释。</h4><p>最好情况下是将代码表述清楚,但是有时候想要表述的内容太多,代码无法表述,这时候要加上注释进行辅助表述。例如 <code>ViewController</code> 的生命周期方法:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 想要表述的内容太多,以至于方法名字不能表述出来。</span></span><br><span class="line">- (<span class="keyword">void</span>)loadView; <span class="comment">// This is where subclasses should create their custom view hierarchy if they aren't using a nib. Should never be called directly.</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad; <span class="comment">// Called after the view has been loaded. For view controllers created in code, this is after -loadView. For view controllers unarchived from a nib, this is after the view is set.</span></span><br></pre></td></tr></table></figure></p>
<p>有时候代码表述的有歧义,可能会误导读者,这时候要加上注释进行辅助表述。而且尽量不要写误导性代码。</p>
<p>在写 SDK 或者其他开源框架时,需要在头文件中添加注释,表述每个方法的作用。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// Mantle 中,MTLJsonAdapter.h 中对每个方法都做了详细的注释,方便使用者理解</span></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">MTLJSONAdapter</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/// Attempts to parse a JSON dictionary into a model object.</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// modelClass - The MTLModel subclass to attempt to parse from the JSON.</span></span><br><span class="line"><span class="comment">/// This class must conform to <MTLJSONSerializing>. This</span></span><br><span class="line"><span class="comment">/// argument must not be nil.</span></span><br><span class="line"><span class="comment">/// JSONDictionary - A dictionary representing JSON data. This should match the</span></span><br><span class="line"><span class="comment">/// format returned by NSJSONSerialization. If this argument is</span></span><br><span class="line"><span class="comment">/// nil, the method returns nil.</span></span><br><span class="line"><span class="comment">/// error - If not NULL, this may be set to an error that occurs during</span></span><br><span class="line"><span class="comment">/// parsing or initializing an instance of `modelClass`.</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// Returns an instance of `modelClass` upon success, or nil if a parsing error</span></span><br><span class="line"><span class="comment">/// occurred.</span></span><br><span class="line">+ (<span class="keyword">id</span>)modelOfClass:(Class)modelClass fromJSONDictionary:(<span class="built_in">NSDictionary</span> *)JSONDictionary error:(<span class="built_in">NSError</span> **)error;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// Attempts to parse an array of JSON dictionary objects into a model objects</span></span><br><span class="line"><span class="comment">/// of a specific class.</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// modelClass - The MTLModel subclass to attempt to parse from the JSON. This</span></span><br><span class="line"><span class="comment">/// class must conform to <MTLJSONSerializing>. This argument must</span></span><br><span class="line"><span class="comment">/// not be nil.</span></span><br><span class="line"><span class="comment">/// JSONArray - A array of dictionaries representing JSON data. This should</span></span><br><span class="line"><span class="comment">/// match the format returned by NSJSONSerialization. If this</span></span><br><span class="line"><span class="comment">/// argument is nil, the method returns nil.</span></span><br><span class="line"><span class="comment">/// error - If not NULL, this may be set to an error that occurs during</span></span><br><span class="line"><span class="comment">/// parsing or initializing an any of the instances of</span></span><br><span class="line"><span class="comment">/// `modelClass`.</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// Returns an array of `modelClass` instances upon success, or nil if a parsing</span></span><br><span class="line"><span class="comment">/// error occurred.</span></span><br><span class="line">+ (<span class="built_in">NSArray</span> *)modelsOfClass:(Class)modelClass fromJSONArray:(<span class="built_in">NSArray</span> *)JSONArray error:(<span class="built_in">NSError</span> **)error;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<h4 id="2-注释要求简洁、明确、有意义。"><a href="#2-注释要求简洁、明确、有意义。" class="headerlink" title="2.注释要求简洁、明确、有意义。"></a>2.注释要求简洁、明确、有意义。</h4><p>既然是辅助表述,就不要啰嗦一顿还没说清楚,也不要喃喃自语。不需要注释的不要画蛇添足,强行注释;需要注释的,应以最简洁的语言将想要表达的意思表述清楚。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 例如下面的注释,纯属多余。因为命名已经将意思表达的很清楚。</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UIImageView</span> *avatarImageView; <span class="comment">///< 头像视图</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSString</span> *userName; <span class="comment">///< 用户名</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/** 播放视频 */</span></span><br><span class="line">- (<span class="keyword">void</span>)playVideo {</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="3-在正确的位置注释"><a href="#3-在正确的位置注释" class="headerlink" title="3.在正确的位置注释"></a>3.在正确的位置注释</h4><p>一般在类的头文件中注释,在源文件中不需要再次注释。例如下面代码,为什么要注释两次?</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">CodeReview</span> : <span class="title">NSObject</span></span></span><br><span class="line"><span class="comment">/**</span><br><span class="line"> * 更新用户信息</span><br><span class="line"> * @param userModel 用户信息 model</span><br><span class="line"> */</span></span><br><span class="line">- (<span class="keyword">void</span>)updateUserInfoWithModel:(UserModel *)userModel;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">CodeReview</span></span></span><br><span class="line"><span class="comment">/** 更新用户信息 */</span></span><br><span class="line">- (<span class="keyword">void</span>)updateUserInfoWithModel:(UserModel *)userModel {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>尽量不要在过程代码中添加注释。除非在过程中有一段不易于理解的代码,可以加注释阐述一下。尤其是有一段代码需要其他开发者注意,这时候可以<strong>加注释起到放大、警示作用</strong>。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下面的方法中有两个问题,一是过程代码中太多的注释;二是方法中执行事件太多,一个方法原则上只执行一件事,后面会说到。</span></span><br><span class="line">- (<span class="keyword">void</span>)func {</span><br><span class="line"> <span class="comment">// 暂停播放</span></span><br><span class="line"> [<span class="keyword">self</span>.player pause];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取视频播放进度</span></span><br><span class="line"> <span class="built_in">CGFloat</span> totalTime = <span class="keyword">self</span>.player.currentItem.Duration.value /</span><br><span class="line"> <span class="keyword">self</span>.player.currentItem.Duration.value.timescale;</span><br><span class="line"> <span class="built_in">CGFloat</span> currentTime = <span class="keyword">self</span>.player.currentTime.value /</span><br><span class="line"> <span class="keyword">self</span>.player.currentTime.timescale;</span><br><span class="line"> <span class="built_in">NSString</span> *playerProgress = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%.2f"</span>,currentTime / currentTime];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 上传视频播放进度</span></span><br><span class="line"> NetWorkManager *manager = [NetWorkManager new];</span><br><span class="line"> [manager uploadViewProgress:playerProgress];</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>注释的样式因注释的内容不同,有着不同的格式。有些格式是能<strong>被 Xcode 识别的</strong>,有些格式是不能被识别的。在使用 Xcode 编写代码时,会有代码自动提示功能,同时如果一个如果这个变量或方法的注释被识别,则也会显示在代码提示栏中,例如下面的示例:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Documentation</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSString</span> *personalInfo; <span class="comment">///< 个人信息</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#import <span class="meta-string">"ViewController.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"Documentation.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> </span><br><span class="line"> Documentation *documentation = [Documentation new];</span><br><span class="line"> documentation.pers</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><img src="/uploads/Clean-Code/document.png" alt="document"></p>
<p>swift 的注释与 OC 略有不同,有兴趣的可以看一下这篇<a href="http://nshipster.com/swift-documentation/" target="_blank" rel="external">文章</a>对于 OC ,常用的注释方式有一下几种:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">1.文件信息的注释,在创建文件时编译器会为我们生成,不可被识别</span><br><span class="line">//</span><br><span class="line">// CodeReivew.h</span><br><span class="line">// BlogTest</span><br><span class="line">//</span><br><span class="line">// Created by boolChow on 17/2/18.</span><br><span class="line">// Copyright © 2016年 xxx All rights reserved.</span><br><span class="line">//</span><br><span class="line"></span><br><span class="line">2.头文件(.h)中方法的注释,Xcode8 快捷键 'option + command + /',可以被识别。</span><br><span class="line">/**</span><br><span class="line"> * <#description#></span><br><span class="line"> *</span><br><span class="line"> * param <#param description#></span><br><span class="line"> * return <#return value description#></span><br><span class="line"> */</span><br><span class="line"> </span><br><span class="line">3.源文件(.m)中私有方法的注释,可以和上面一样,也可以按照如下方式。</span><br><span class="line">/** <#descriotion#> */。</span><br><span class="line">- (void)func {...}</span><br><span class="line"></span><br><span class="line">4.属性的注释,常用两种</span><br><span class="line">@property (nonatomic, strong) UIView *view; ///< <#description#></span><br><span class="line">@property (nonatomic, strong) UIButton *button; //!< <#description#></span><br><span class="line"></span><br><span class="line">5.过程代码注释,一般使用 '// <#description#>'。</span><br><span class="line">6.枚举类型注释,一般使用 '/** <#descriotion#> */'。</span><br></pre></td></tr></table></figure>
<p>在 Xcode 中,<code>//</code> 这种注释是不能被识别的,能识别的一般有 <code>/** */</code>、<code>///</code>、<code>//!</code>、<code>///<</code>、<code>//!<</code>。另外,对于因为废弃而注释掉的代码,或者在调试过程中注释掉的代码,<strong>在进行代码提交之前一定要删掉!</strong></p>
<h3 id="格式"><a href="#格式" class="headerlink" title="格式"></a>格式</h3><p>排版格式是影响代码整洁度的一个重要因素,虽然现在大部分 IDE 都对代码进行了排版,但是还有一些地方是 IDE 不能做到的,这需要我们手动去排版。我下面将”从小->大”讲述一下格式的细节。</p>
<h4 id="1-大括号-‘-’"><a href="#1-大括号-‘-’" class="headerlink" title="1.大括号 ‘{}’"></a>1.大括号 ‘{}’</h4><p>对于大括号,有的习惯将在方法后面紧跟大括号的左半部分 ‘{‘,有的习惯换一行在写 ‘{‘,我习惯前者。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 普通方法</span></span><br><span class="line">- (<span class="keyword">void</span>)func {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// if else 等类似语句,else 跟在 if 语句结束后面,而不是换行。如果 if 语句中只有一句代码,也建议加上大括号。</span></span><br><span class="line"><span class="keyword">if</span> (condition) {</span><br><span class="line"> ...</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-空格"><a href="#2-空格" class="headerlink" title="2.空格"></a>2.空格</h4><p>在适当的地方使用空格,能够使代码显得更加清晰。</p>
<p>定义方法时,方法的 ‘-‘ 与方法返回值间添加空格,参数之间添加空格,方法名与方法开头的大括号之间添加空格<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 建议</span></span><br><span class="line">- (<span class="built_in">UIView</span> *)createBannerWithImage:(<span class="built_in">UIImage</span> *)image frame:(<span class="built_in">CGRect</span>)frame {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不建议</span></span><br><span class="line">-(<span class="built_in">UIView</span> *)createBannerWithImage : (<span class="built_in">UIImage</span> *)image frame:(<span class="built_in">CGRect</span>)frame{</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>定义属性时,@property 与属性关键字之间空格,多个属性关键字之间使用空格,属性类型与属性名称之间使用空格,’*’紧跟属性名称。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 建议</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>, <span class="keyword">readonly</span>) <span class="built_in">NSString</span> *password;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不建议</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">strong</span>,<span class="keyword">readonly</span>)<span class="built_in">NSString</span>*password;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>, <span class="keyword">readonly</span>) <span class="built_in">NSString</span> * password;</span><br></pre></td></tr></table></figure>
<p>运算符两侧之间添加空格。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 建议</span></span><br><span class="line"><span class="keyword">if</span> (isOpen == <span class="literal">YES</span>) {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (username != <span class="literal">nil</span>) {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">self</span>.backgroundColor = [<span class="built_in">UIColor</span> clearColor];</span><br><span class="line"><span class="keyword">self</span>.state = [value isEqualString:<span class="string">@""</span>] ? <span class="string">@"close"</span> : <span class="string">@"open"</span>;</span><br></pre></td></tr></table></figure>
<p>添加注释时,’//‘与内容之间添加空格,注释之间如果有英文单词,中英文之间添加空格。<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 建议</span></span><br><span class="line"><span class="comment">// 根据 URL 对内容进行预加载</span></span><br><span class="line">- (<span class="keyword">void</span>)prelaodDataWithURL:(<span class="built_in">NSString</span> *)url;</span><br></pre></td></tr></table></figure></p>
<h4 id="3-空行"><a href="#3-空行" class="headerlink" title="3.空行"></a>3.空行</h4><p>恰当是用空行能够使代码结构更加清晰,但是滥用空行会使代码显得更加糟糕。加空行的原则是:相关的代码组成一个”代码块”,两个代码块之间添加空行。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 建议范例</span></span><br><span class="line">- (instancetype)initWithUserModel:(UserModel *)userModel {</span><br><span class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> init];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>) {</span><br><span class="line"> _userModel = userModel;</span><br><span class="line"> _imageArray = [<span class="built_in">NSArray</span> array];</span><br><span class="line"> _attribute = [<span class="built_in">NSDictionary</span> dictionary];</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span> loadData];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不建议</span></span><br><span class="line">- (<span class="built_in">UIView</span> *)func {</span><br><span class="line"> <span class="built_in">CGFloat</span> height = <span class="keyword">self</span>.avatarImageView.frame.size.height;</span><br><span class="line"> <span class="built_in">CGFloat</span> width = <span class="keyword">self</span>.avatarImageView.frame.size.width;</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">UILabel</span> *descriptionLabel = [<span class="built_in">UILabel</span> new];</span><br><span class="line"> </span><br><span class="line"> descriptionLabel.frame = <span class="built_in">CGRectMake</span>(<span class="number">20.</span>f,<span class="number">30.</span>f,height,width);</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> newView;</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="4-模块分类"><a href="#4-模块分类" class="headerlink" title="4.模块分类"></a>4.模块分类</h4><p>对于头文件,可以分为系统 API、Pod文件、自定义 Model、自定义 View、自定义 Controller、自定义 Utils 等。具体分类依个人情况,只要合理即可。另外,个人习惯按照头文件长度从小到大排列。虽然现在有一些插件可以管理头文件,但是一开始就写清晰更好。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// System</span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><CoreData/CoreData.h></span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><CoreMedia/CoreMedia.h></span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><AVFoundation/AVFoundation.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Pod</span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><Mantle/Mantle.h></span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><YYImage/YYImage.h></span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><CocoaLumberjack/DDLog.h></span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><AFNetworking/AFNetworking.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Model</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"FeedModel.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"UserModel.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"CommentModel.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// View</span></span><br><span class="line"><span class="meta">#improt <span class="meta-string">"BannerView.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"PersonalInfoView.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Controller</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"VideoDetailViewController.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"PersonalInfoViewController.h"</span></span></span><br></pre></td></tr></table></figure>
<p>对于属性分类,类似于头文件,例如按照 Data、View、Bool、Custom Class 等类型。依个人喜好,合理即可。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Data</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">assign</span>) <span class="built_in">CGFloat</span> maxHeight;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSString</span> *username;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSString</span> *password;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSDictionary</span> *params;</span><br><span class="line"></span><br><span class="line"><span class="comment">// View</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UIView</span> *subView;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UIView</span> *leftLine;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UITableView</span> *feedTableView;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Custom Class</span></span><br><span class="line">@Property (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) TimelineRequest *request;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) UserInfoManager *userInfoManager;</span><br></pre></td></tr></table></figure>
<p>一个类中,方法之间通过 ‘#pragma mark - xxx’ 进行模块划分。例如在一个 ViewController 中,按照 init Method、Setup Method、LifeCycle Method、Public Method、Private Method等。例如下面途中即按照模块进行分割。</p>
<p><img src="/uploads/Clean-Code/separateCodeBlock.png" alt="separateCodeBlock"></p>
<h4 id="5-一些原则"><a href="#5-一些原则" class="headerlink" title="5.一些原则"></a>5.一些原则</h4><p>对于代码的书写格式,需要遵循一些原则。类之间如何进行归类划分,属于设计模式的范畴,这里只从一个类说起。对于一个类文件,垂直方向代码长度最多建议 <strong>700~800 行</strong>(超过 500 行就有点不能忍了),如果超过了 1000 行,则应进行拆分抽取,否则可阅读行会很差;水平方向,建议最多不要超过 <strong>80 个字符</strong>,如果超过,建议进行换行。例如下面注册 <code>Notification</code> 时:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不建议</span></span><br><span class="line">[[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserver:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(updateData) name:ZHIDidDeleteMediaNotification object:<span class="literal">nil</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 建议</span></span><br><span class="line">[[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserver:<span class="keyword">self</span></span><br><span class="line"> selector:<span class="keyword">@selector</span>(updateData)</span><br><span class="line"> name:UpdateDataNotification</span><br><span class="line"> object:<span class="literal">nil</span>];</span><br></pre></td></tr></table></figure>
<p>为了提高可阅读行,一个类中的方法,最好按照<strong>调用顺序</strong>进行编写,并进行模块分类。这样在其他人阅读代码时不用跳来跳去。</p>
<p>按照正常人的审美观,适当的缩进、空格、空行、对齐,可以提高代码整洁度与美感。按照正常人的逻辑思维,例如从小到大、由表及里、从短到长等一些逻辑顺序去划分模块,编写代码,可以提高代码可阅读性。按照正常人的单元理解能力,垂直方向编写适当范围行数代码,水平方向按照适当范围换行,也可以提高代码的可阅读行。</p>
<h3 id="方法与数据结构"><a href="#方法与数据结构" class="headerlink" title="方法与数据结构"></a>方法与数据结构</h3><h4 id="1-只做一件事"><a href="#1-只做一件事" class="headerlink" title="1.只做一件事"></a>1.只做一件事</h4><p>方法是对一段过程代码的封装,为了保证代码的整洁性,这段代码最好只执行一个事件,即一个方法最好只做一件事。先理解怎样才算是”一件事”,请看下面的代码:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">self</span> setupNotification];</span><br><span class="line"> [<span class="keyword">self</span> setupNavgationItme];</span><br><span class="line"> [<span class="keyword">self</span> setupTableView];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)setupTableView {</span><br><span class="line"> <span class="keyword">self</span>.tableView = [[<span class="built_in">UITableView</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">-64.0</span>f, MTScreenWidth, MTScreenHeight) style:<span class="built_in">UITableViewStylePlain</span>];</span><br><span class="line"> <span class="keyword">self</span>.tableView.delegate = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.dataSource = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.tableFooterView = [<span class="built_in">UIView</span> new];</span><br><span class="line"> <span class="keyword">self</span>.tableView.tableHeaderView = <span class="keyword">self</span>.tableHeaderView;</span><br><span class="line"> <span class="keyword">self</span>.tableView.separatorStyle = <span class="built_in">UITableViewCellSeparatorStyleNone</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.backgroundColor = [<span class="built_in">UIColor</span> colorWithHexString:<span class="string">@"f2f2f2"</span>];</span><br><span class="line"> [<span class="keyword">self</span>.view addSubview:<span class="keyword">self</span>.tableView];</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span>.tableView registerClass:[CommonCell class] forCellReuseIdentifier:kCommonCellReuseIdentifier];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>上面代码中,<code>viewDidLoad</code> 方法做了三个操作:注册通知、初始化导航栏、初始化 <code>tableView</code>,这是三件事还是一件事呢?写过 OC 代码的都知道,这些操作”均属于初始化一些基本信息的操作”,这三个操作都在<strong>同一个抽象层级</strong>上,因此算是一件事。</p>
<p>同样的 <code>setupTableView</code> 方法中,分别进行了:新建 <code>tableView</code> 对象,设定 <code>tableView</code> 代理、数据、<code>footerView</code> 等一系列信息,注册 <code>cell</code>。那么这算几件事呢?这些操作均属于初始化 <code>tableView</code>,因此这个方法也就只做了”初始化 <code>tableView</code>“这一件事。</p>
<p>刚才提到了”同一个抽象层级”,一个方法中应该只有一个抽象层级。如果混杂不同的抽象层级,会让人迷惑,方法看起来就像一个垃圾桶。例如下面的代码:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> <span class="keyword">self</span>.dataURL = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%@%@%@"</span>,host,baseURL,userID];</span><br><span class="line"> [<span class="keyword">self</span> setupView];</span><br><span class="line"> [<span class="keyword">self</span> setupNotification];</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span> uploadData];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>上述代码虽然只有几行,确混论不堪。拼接 <code>dataURL</code> 属于较低抽像层级;初始化视图和注册通知属于中间抽象层级;上传数据属于较高抽象层级。短短几行尚且能够读懂,多了之后读起来会乱七八糟。不建议这样写。</p>
<p>一个好的方法应该遵循这样的原则:在尽量<strong>短小</strong>的情况下,所有过程代码属于<strong>同一抽象层级</strong>,按照<strong>自顶向下</strong>的顺序书写代码。如果方法还能拆分,证明不合格。</p>
<h4 id="2-逻辑语句"><a href="#2-逻辑语句" class="headerlink" title="2.逻辑语句"></a>2.逻辑语句</h4><p>每种语言基本都会有 <code>if-else</code>、<code>for循环</code>、<code>switch</code> 等这些逻辑语句,过多的使用这些语句,会使代码变得冗长、丑陋。当你的一段代码嵌套2~3个 <code>if-else</code> 语句或者 <code>for循环</code>时,你就应该考虑一下是否有更优雅的写法呢。下面通过代码来分析一下:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 给一个变量赋值需要这么多行代码吗?</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">self</span>.isFriend) {</span><br><span class="line"> <span class="keyword">self</span>.permission = <span class="string">@"YES"</span>;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">self</span>.permission = <span class="string">@"NO"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i<objcArray.count; i++) {</span><br><span class="line"> Object *obj = objcArray[i];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,[obj description]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或许这种写法更简洁呢</span></span><br><span class="line"><span class="keyword">self</span>.permission = <span class="keyword">self</span>.isFriend ? <span class="string">@"YES"</span> : <span class="string">@"NO"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (Object *obj <span class="keyword">in</span> objcArray) {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,[obj description]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// if 判断中包含多个条件</span></span><br><span class="line"><span class="keyword">if</span> (name != <span class="literal">nil</span> && password != <span class="literal">nil</span> && phone != <span class="literal">nil</span> && sex != <span class="literal">nil</span>) {...}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这种情况最好封装成方法,if 判断中不要有太多的判断参数</span></span><br><span class="line"><span class="keyword">if</span> ([<span class="keyword">self</span> userInfoIsEmpty]) {...}</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">BOOL</span>)userInfoIsEmpty {</span><br><span class="line"> <span class="keyword">return</span> name != <span class="literal">nil</span> && password != <span class="literal">nil</span> && phone != <span class="literal">nil</span> && sex != <span class="literal">nil</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这些语句是一些基本的语句,用起来简单,不用太走心,同时容易把代码写的冗长不堪。因此在写代码时要尽量减少这些语句的使用,用其他更加优雅的方式代替。</p>
<p>使用这些语句也暴露出一个问题,证明方法中有多处逻辑判断,每种条件下对应一个事件,是这个方法中包含太多的事件,极易违反”只做一件事”这一原则,也不易于阅读。</p>
<h4 id="3-参数"><a href="#3-参数" class="headerlink" title="3.参数"></a>3.参数</h4><p>一个方法,好情况下是没有参数,其次是一个、两个、三个(init 方法稍有特殊,可能会有多个参数)。当一个方法参数超过三个,那说明其中的一些参数可以封装为类了。</p>
<p>方法是对过程代码的封装,参数越多,暴露的内容就越多,封装性就越差。过多的参数,也不易于理解。</p>
<p>不建议向一个方法中传入布尔类型参数,否则的话就说明这个方法很有可能不只做一件事。YES 的时候会这样做,NO 的时候会那样做。</p>
<h4 id="4-结构化编程"><a href="#4-结构化编程" class="headerlink" title="4.结构化编程"></a>4.结构化编程</h4><p>每个方法,每个方法中的每个代码块都应该有一个入口、一个出口。遵循这个原则,意味着每个方法中只有一个 <code>return</code> 语句,循环中不能有 <code>break</code> 或 <code>continue</code> 语句,更不能有 <code>goto</code> 语句。结构化编程规范,对于小方法助易不大,只有在大的方法中,这些规范才会有明显的好处。所以,在保持方法短小的前提下,偶尔出现 <code>return</code>、<code>break</code>、<code>continue</code> 语句没有坏处,甚至比单入单出原则更有表达力。</p>
<h4 id="5-时序性耦合"><a href="#5-时序性耦合" class="headerlink" title="5.时序性耦合"></a>5.时序性耦合</h4><p>一个方法的过程代码中,有时会出现时序性耦合。例如下面代码:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">self</span> registerCell];</span><br><span class="line"> [<span class="keyword">self</span> registerNotification];</span><br><span class="line"> [<span class="keyword">self</span> setupData];</span><br><span class="line"> [<span class="keyword">self</span> setupTableView];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)registerCell {</span><br><span class="line"> [<span class="keyword">self</span>.tableView registerClass:[EmptyTableViewCell class] forCellReuseIdentifier:kEmptyCellReuseIdentifier];</span><br><span class="line"> [<span class="keyword">self</span>.TableView registerClass:[CommonCell class] forCellReuseIdentifier:kCommonCellReuseIdentifier];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)setupTableView {</span><br><span class="line"> <span class="keyword">self</span>.tableView = [[<span class="built_in">UITableView</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">-64.0</span>f, MTScreenWidth, MTScreenHeight) style:<span class="built_in">UITableViewStylePlain</span>];</span><br><span class="line"> <span class="keyword">self</span>.tableView.delegate = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.dataSource = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.tableFooterView = [<span class="built_in">UIView</span> new];</span><br><span class="line"> <span class="keyword">self</span>.tableView.tableHeaderView = <span class="keyword">self</span>.tableHeaderView;</span><br><span class="line"> <span class="keyword">self</span>.tableView.separatorStyle = <span class="built_in">UITableViewCellSeparatorStyleNone</span>;</span><br><span class="line"> [<span class="keyword">self</span>.view addSubview:<span class="keyword">self</span>.tableView];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>上述代码中,在 <code>viewDidLoad</code> 方法中进行一些初始化操作。但是细心一些你会发现,”注册 <code>cell</code>“在”初始化 <code>tableView</code>“之前。但是在注册 <code>cell</code> 的时候会用到 <code>tableView</code>,这时候 <code>tableView</code> 还是 <code>nil</code>。所以这两个方法调用是有顺序的,否则就会出错。但是如果这样写,即使写对了,后面再修改代码时也容易颠倒位置,导致错误。那么最合适的做法应是这样:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将两个方法合并</span></span><br><span class="line">- (<span class="keyword">void</span>)setupTableView {</span><br><span class="line"> <span class="keyword">self</span>.tableView = [[<span class="built_in">UITableView</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">-64.0</span>f, MTScreenWidth, MTScreenHeight) style:<span class="built_in">UITableViewStylePlain</span>];</span><br><span class="line"> <span class="keyword">self</span>.tableView.delegate = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.dataSource = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">self</span>.tableView.tableFooterView = [<span class="built_in">UIView</span> new];</span><br><span class="line"> <span class="keyword">self</span>.tableView.tableHeaderView = <span class="keyword">self</span>.tableHeaderView;</span><br><span class="line"> <span class="keyword">self</span>.tableView.separatorStyle = <span class="built_in">UITableViewCellSeparatorStyleNone</span>;</span><br><span class="line"> [<span class="keyword">self</span>.view addSubview:<span class="keyword">self</span>.tableView];</span><br><span class="line"> </span><br><span class="line"> [<span class="keyword">self</span>.tableView registerClass:[EmptyTableViewCell class] forCellReuseIdentifier:kEmptyCellReuseIdentifier];</span><br><span class="line"> [<span class="keyword">self</span>.TableView registerClass:[CommonCell class] forCellReuseIdentifier:kCommonCellReuseIdentifier];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<h3 id="类与对象"><a href="#类与对象" class="headerlink" title="类与对象"></a>类与对象</h3><p>面向对象语言的特性就是抽象、封装、继承、多态,而”类”则将这些特性全部包含在内。所以一个好的类,便是将这些特性展现出来。</p>
<h4 id="1-类应该短小"><a href="#1-类应该短小" class="headerlink" title="1.类应该短小"></a>1.类应该短小</h4><p>在前面已经提过,一个类文件代码长度再好保持在 700~800 行以内。虽然一个类文件可能会有多个类与分类,但是具体到每个类,也应该保持短小,方便理解和阅读。</p>
<p>这里说的短小,并不单指代码行数方面的衡量。对于一个类,最主要的衡量方式是:<strong>权责</strong>。一个类不能有太多的权责,即使代码行数比较短,但是负责了太多事情,这个类仍然是一个臃肿的类。如果一个类有了太多权责,那么这个类就需要拆分,从而保持整洁性。这样会带了另外一个问题:类爆炸,似的项目中有太多短小单一的类。然而每达到一定规模的系统都会包括大量逻辑和复杂性,系统应该由许多短小的类而不是少量巨大的类组成。</p>
<h4 id="2-封装"><a href="#2-封装" class="headerlink" title="2.封装"></a>2.封装</h4><p>程序设计的原则是高内聚、低耦合。因此,一个类不应该暴露太多的属性(变量)与方法。下面是在使用 OC 进行程序设计时的一些建议。</p>
<p>对于头文件的引入,大多在 .m 文件中引入。如果 .h 文件中需要使用到其他的类,优先使用 <code>@class</code> 关键字引入,不能解决需求的情况下,再在 .h 文件中引入其他类的头文件。</p>
<p>对于属性,除了使用 <code>nonatomic/atomic</code>、<code>strong/weak/copy</code>关键字修饰之外,最好还要标明读写属性。如果一个属性可以是 <code>readonly</code>,就不要写成 <code>readwrite</code>,不要让其他的类随意修改本类的属性。</p>
<p>对于方法,尽量不要暴露太多的方法。一个类不要提供给外部太多乱七八糟的方法,以保证类的封装性。在提供方法时,方法的参数最好标明 <code>nonull/nullable</code> 属性,而且一个方法不要有太多参数。</p>
<h4 id="3-内聚"><a href="#3-内聚" class="headerlink" title="3.内聚"></a>3.内聚</h4><p>类应该只有少量的实体变量。类中的每个方法都应该操作一个或多个这种变量。通常而言,方法操作的变量越多,就越黏聚到类上。如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。在 OC 中,如果一个属性在多个方法中被使用,是比较合理的;但是如果一个属性只在一个方法中使用,那么就有必要考虑一下这个属性的存在性,是否可以用局部变量代替。</p>
<h3 id="本书精华"><a href="#本书精华" class="headerlink" title="本书精华"></a>本书精华</h3><p>本书的第 14~16 章是对几个案例分析,第 17 张是一些总结性知识点,是本书的精华所在。如果用烹饪做比喻,那么前面的一些章节只是告诉你该用什么原料,油盐酱醋放的剂量以及火候;后面三章才是给你演示一遍整个烹饪过程,真正的授之以渔。</p>
<p>第 14 章《逐步改进》,是对一个自定义 <code>Args</code> 类的重构。通过逐步改进的方式,对原有类进行一步步分解。在这一过程中,遵循前面叙述的一些原则,对方法、变量进行大规模修改。其中有一点很值得学习,在进行重构之前,作者先写了一个覆盖这个类所有方法的单元测试。每次修改一些代码之后,都会跑一遍测试,如果测试通过,则继续修改;否则就需要找出问题,修复之后再继续。这非常值得我们学习,想象一下如果你不这样做,在重构完代码后,发现无法编译了,也不知道在哪个阶段修改出了问题,那会是多么糟糕的场景!</p>
<p>第 15 章《JUnit内幕》,是对 <code>JUnit</code> 框架部分代码的重构。同样,通过对代码的层层剖析,对代码的命名、函数的结构、模块的划分进行了一些改进,将前面讲的一些原则进行了推演。</p>
<p>第 16 章《重构SerialDate》,是对开源框架 <code>SerialDate</code> 的重构过程。先让代码跑通,然后从代码的注释开始,对代码进行修剪、结构调整。如书中所写,这一章分了两部分:”首先,让它工作”;”让它作对”。</p>
<p>第 17 章《味道与启发》,是一个总结性章节。对前面的一些规则,以及在重构过程中的一些启发的总结。如果你仔细阅读的前面的章节,尤其是第 14~16 章,这一章的内容你会充分的吸收。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>以上,是我对这本书做的一个总结。本书值得学习的地方远不止这些,如果能用一篇文章说清楚,作者干嘛还要写一本书。因此,如果你对代码的整洁性要求十分严苛,想成为一个更好的 coder,建议你读一读这本书。</p>
<h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul>
<li><a href="https://book.douban.com/subject/5442024/" target="_blank" rel="external">代码整洁之道</a></li>
</ul>
<a id="more"></a>
<p>代码的整洁与否是一个程序员的个人卫生问题。一个程序员穿着可以稍邋遢一些,但是代码要写的干净、利落。<br>如果你想成为一个更好的程序员,除了要学习语法、设计模式之外,还要学习如何写出整洁有效的代码,这本书会教你如何写出这样的代码。书中代码
聊聊 Objective-C 中的一些关键字
http://yoursite.com/2017/02/26/Keywords-in-Objective-C/
2017-02-26T10:04:49.000Z
2018-06-03T13:15:44.713Z
<a id="more"></a>
<p>在 OC 中,有很多常用的关键字。如何正确使用这些关键字,是学习一本语言的基础。通常面试官只需要问几<br>个关键字相关的问题,就能看出面试者的基础如何。例如 <code>#include、#import、@class的区别</code>,什么时候用 <code>const NSString *</code> 什么时候用 <code>NSString * const</code>,<code>define</code> 和 <code>static</code> 的正确使用等。不仅要知道怎么用,还要知道为什么这样用,不能只是“我看别人这么写”。</p>
<p>这篇文章将介绍一些关键字的使用及原理。</p>
<h3 id="static-amp-const-amp-extern"><a href="#static-amp-const-amp-extern" class="headerlink" title="static & const & extern"></a>static & const & extern</h3><p>将一些重复使用的字符串定义为<strong>字符串常量</strong>是一种良好的习惯,这样写起来代码便于维护。当然有时也会定义成<strong>宏</strong>,后面会解释两者区别。在定义常量时,通常会用 <code>static</code> 和 <code>extern</code> 来定义常量的<strong>作用域</strong>,用 <code>const</code> 来定义常量的<strong>可变性</strong>。</p>
<h4 id="1-static"><a href="#1-static" class="headerlink" title="1.static"></a>1.static</h4><p><code>static</code> 关键字,主要定义变量的<strong>作用域</strong>和<strong>生命周期</strong>。</p>
<p><code>static</code> 修饰局部变量,主要定义变量生命周期,静态局部变量,因为存储在全局数据区,不会像其他存储在栈区的局部变量一样随着函数体结束被释放。</p>
<p><code>static</code> 修饰全局变量,定义变量的作用域,被 <code>static</code> 修饰的量,只存储一份,始化一次,其他地方共享这一份数据。在 OC 中,<code>static</code> 变量声明一般在源文件( “.m” )中,如果放在头文件( “.h” )中,其他文件引入这个头文件时,容易引起命名冲突。被 <code>static</code> 修饰的全局变量,作用域为当前文件。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1.申明在源文件中,声明在头文件中容易引起命名冲突</span></span><br><span class="line"><span class="comment">// 在类 TestClass 中</span></span><br><span class="line"><span class="keyword">static</span> <span class="built_in">NSString</span> *str = <span class="string">@"hello world"</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">TestClass</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在类 OtherClass 中</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"TestClass.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="built_in">NSString</span> *str = <span class="string">@"welcome!"</span>; <span class="comment">// 编译不通过,这里会报 “Redefinition of 'str'” 重复定义的错误</span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">OtherClass</span> </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line">=======================================================</span><br><span class="line"><span class="comment">// 2.只存储一份,初始化一次,其他使用地方共享</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">int</span> numA = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TestViewcontroller</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i < <span class="number">5</span>; i++) {</span><br><span class="line"> [<span class="keyword">self</span> addNum];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// numA = numC = 5; numB 一直为1,方法调用结束后被释放。</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)addNum {</span><br><span class="line"> <span class="keyword">int</span> numB = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">int</span> numC = <span class="number">0</span>;</span><br><span class="line"> numA++;</span><br><span class="line"> numB++;</span><br><span class="line"> numC++;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>tips: 如果一个变量在当前文件中被多处使用,建议使用 static 定义为当前类的全局变量</p>
</blockquote>
<h4 id="2-extern"><a href="#2-extern" class="headerlink" title="2.extern"></a>2.extern</h4><p><code>extern</code> 关键字,主要用来定义<strong>外部全局变量</strong>。前面说用 <code>static</code> 定义作用域为当前文件的全局变量。那如果想定义作用域为整个工程文件全局变量,即外部全局变量,则用 <code>extern</code>。</p>
<p>一般在头文件中使用 <code>extern</code> 声明变量,在源文件中赋值,尽量不要将外部全局变量的值暴露在头文件中;或者在头文件中声明,在其他文件中使用的时候再进行赋值。<code>extern</code> 关键字只对变量进行声明,表明该变量可能在本模块使用也可以在其他模块使用。例如类B如果想使用类A中定义的全局变量,只需要引入类A的头文件即可,这样即使在编译的时候找类B不到变量的定义也不会报错,它会在链接的时候在类A的目标代码中找到这个变量。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="built_in">NSString</span> * notificationName;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">TestClass</span>: <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"><span class="comment">// TestClass.m 源文件中</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"TestClass.h"</span></span></span><br><span class="line"><span class="keyword">const</span> <span class="built_in">NSString</span> * notificationName = <span class="string">@"notificationName"</span>;</span><br></pre></td></tr></table></figure>
<p><strong>多说一点</strong><br>在 Apple API 中,我们可以看到一些与 <code>extern</code> 相关的宏定义,例如 <code>FOUNDATION_EXTERN</code> 、 <code>UIKIT_EXTERN</code>等。我们可以看一下其中一个的定义:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FOUNDATION_EXTERN 定义</span></span><br><span class="line"><span class="meta">#if defined(__cplusplus)</span></span><br><span class="line"><span class="meta">#define FOUNDATION_EXTERN extern <span class="meta-string">"C"</span></span></span><br><span class="line"><span class="meta">#else</span></span><br><span class="line"><span class="meta">#define FOUNDATION_EXTERN extern</span></span><br><span class="line"><span class="meta">#endif</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// AVKIT_EXTERN 定义</span></span><br><span class="line"><span class="meta">#ifdef __cplusplus</span></span><br><span class="line"><span class="meta">#define AVKIT_EXTERN extern <span class="meta-string">"C"</span> __attribute__((visibility (<span class="meta-string">"default"</span>)))</span></span><br><span class="line"><span class="meta">#else</span></span><br><span class="line"><span class="meta">#define AVKIT_EXTERN extern __attribute__((visibility (<span class="meta-string">"default"</span>)))</span></span><br><span class="line"><span class="meta">#endif</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// UIKIT_EXTERN 定义</span></span><br><span class="line"><span class="meta">#ifdef __cplusplus</span></span><br><span class="line"><span class="meta">#define UIKIT_EXTERN extern <span class="meta-string">"C"</span> __attribute__((visibility (<span class="meta-string">"default"</span>)))</span></span><br><span class="line"><span class="meta">#else</span></span><br><span class="line"><span class="meta">#define UIKIT_EXTERN extern __attribute__((visibility (<span class="meta-string">"default"</span>)))</span></span><br><span class="line"><span class="meta">#endif</span></span><br></pre></td></tr></table></figure>
<p>OC 是支持与 C++ 混编的。<code>__cplusplus</code> 是 C++ 中自定义宏,上面这段宏表示如果这是一段 C++ 代码,则使用 <code>extern "C"</code>。那么问题来了,为什么要用 <code>extern "C"</code> 呢?在 C++ 中,是支持重载的。就是函数名可以一样,在编译处理时,会将“函数名及返回类型+参数及返回类型”合成一个字段,以此判断是哪个函数;但是 C 中是不支持重载的,编译时只会将函数名合成一个字段,即 C 和 C++ 对函数名的处理是不一样的。C++ 为了兼容 C,在C++代码中调用 C 编码的文件,就需要用 <code>extern"C"</code> 来告诉编译器:这是一个用 C 编码的文件,请用 C 的方式来链接它们。因此在进行 OC 与 C++ 混编时,用<code>FOUNDATION_EXTERN</code> 去修饰全局方法。</p>
<p>其他的例如 <code>UIKIT_EXTERN</code>、<code>AVKIT_EXTERN</code> 等与此类似,只是名字不同,目的是为了在不同的 framework 中使用时命名区分。平常定义一些外部全局变量时,直接使用 <code>extern</code> 即可。</p>
<h4 id="3-const"><a href="#3-const" class="headerlink" title="3.const"></a>3.const</h4><p><code>const</code> 关键字,多与 <code>static</code> 和 <code>extern</code> 连用,定义的类型为<strong>常类型</strong>,属性为 <strong>readonly</strong>。当初学习 C++ 时经常被这几个名词搞懵逼:常指针,指向常量的指针,指向常量的常指针。对应到 OC 上大同小异,请注意”异”在哪里。<code>const</code> 一般有两种用法:<br>(1)修饰基本变量,即 int、double、float 等类型<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下面两种写法是等价的</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> a = <span class="number">10</span>; <span class="comment">// a 不可变</span></span><br><span class="line"><span class="keyword">int</span> <span class="keyword">const</span> a = <span class="number">10</span>; <span class="comment">// a 不可变</span></span><br><span class="line">a = <span class="number">12</span>; <span class="comment">// error</span></span><br></pre></td></tr></table></figure></p>
<p>(2) 修饰指针变量。在 OC 中,很多数据对象类型都有 <code>mutable(可变)</code> 和 <code>immutable(不可变)</code> 两种。<code>const</code> 在修饰”不可变”的指针变量时,多被用做定义”指针常量”。因为指针已经为不可变,再用 <code>const</code> 修饰没有意义。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// const 定义 "常量指针",没有什么意义。'值'不可变的本身就不可变,可变的依然可变。</span></span><br><span class="line"><span class="keyword">const</span> <span class="built_in">NSString</span> *str1 = <span class="string">@"hello"</span>; <span class="comment">// const 修饰不可变字符串,字符串本身就不可变。</span></span><br><span class="line"><span class="keyword">const</span> <span class="built_in">NSMutableString</span> *str2 = [<span class="built_in">NSMutableString</span> stringWithFormat:<span class="string">@"you"</span>]; <span class="comment">// const 修饰可变字符串</span></span><br><span class="line">[str2 appendString:<span class="string">@"name"</span>]; <span class="comment">// str2 的值为@"you name"。str2 可以改变,const 没有起到作用。</span></span><br><span class="line"></span><br><span class="line">==================================================================================================</span><br><span class="line"></span><br><span class="line"><span class="comment">// const 定义指针常量</span></span><br><span class="line"><span class="built_in">NSString</span> * <span class="keyword">const</span> str3 = <span class="string">@"const value"</span>;</span><br><span class="line">str3 = <span class="string">@"change point"</span>; <span class="comment">// 此种操作不合法,str3 指向对象不能改变</span></span><br><span class="line"><span class="built_in">NSMutableString</span> * <span class="keyword">const</span> str4 = [<span class="built_in">NSMutableString</span> stringWithFormat:<span class="string">@"mutable value"</span>];</span><br><span class="line">str4 = [<span class="built_in">NSMutableString</span> stringWithFormat:<span class="string">@"change point"</span>]; <span class="comment">// 此种操作不合法,str4 指向对象不能改变</span></span><br><span class="line">[str4 appendString:<span class="string">@"change value"</span>]; <span class="comment">// str4 的值可以改变。所以 str4 的定义方式没有意义。</span></span><br></pre></td></tr></table></figure>
<p>综上,如果想定义不可变字符串(不可变数据对象),直接用 <code>NSString</code>;如果想定义可变字符串(可变数据对象),直接用 <code>NSMutableString</code>;如果想定义一个不可以改变的字符串(数据对象),即值不可变,指向对象也不可变,用 <code>NSString * const str = @"xxx"</code> 方式。且定义时就应赋值,如果不赋值,后面一直为 <code>nil</code>;如果想定义一个值可以改变,所指对象可以改变的字符串(数据对象),直接用 <code>NSString</code> 不就可以了么?</p>
<h4 id="4-const-与-static、extern-混用"><a href="#4-const-与-static、extern-混用" class="headerlink" title="4.const 与 static、extern 混用"></a>4.const 与 static、extern 混用</h4><p>如果需要在文件内部定义一个全局不可变常量,例如 <code>NSDictionary</code> 的”key”,可以这样定义:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .m 文件中</span></span><br><span class="line"><span class="keyword">static</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> kValueKey = <span class="string">@"key"</span>; <span class="comment">// 如果变量只在当前文件使用,变量名前面加小写字母 'k',习惯。</span></span><br></pre></td></tr></table></figure></p>
<p>如果需要定义一个外部使用的全局不可变常量,例如 <code>NSNotification</code> 的”name”,可以这样定义:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h 文件中</span></span><br><span class="line"><span class="keyword">extern</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> defineNotification;</span><br><span class="line"></span><br><span class="line"><span class="comment">// .m 文件中</span></span><br><span class="line"><span class="built_in">NSString</span> * <span class="keyword">const</span> defineNotification = <span class="string">@"defineNotification"</span>;</span><br></pre></td></tr></table></figure>
<p>如果只是单纯的定义通知名字,Apple 给提供了关键字 <code>NSNotificationName</code>。本质上没有什么区别,只不过命名习惯让人看起来舒服些。定义方式如下:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h 文件中</span></span><br><span class="line"><span class="keyword">extern</span> <span class="built_in">NSNotificationName</span> <span class="keyword">const</span> defineNotification;</span><br><span class="line"></span><br><span class="line"><span class="comment">// .m 文件中</span></span><br><span class="line"><span class="built_in">NSNotificationName</span> <span class="keyword">const</span> defineNotification = <span class="string">@"defineNotification"</span>;</span><br></pre></td></tr></table></figure></p>
<h3 id="define"><a href="#define" class="headerlink" title="#define"></a>#define</h3><p>宏定义(#define)从上古 C 系编程的时代就存在,一个好的宏定义,能够让代码看起来更简洁、优雅。宏定义主要分为<strong>对象宏</strong>和<strong>函数宏</strong>。宏定义在预编译阶段进行替换,不做类型检查。因此,宏定义的使用过程中有很多坑,尤其是在函数宏中。如果没有足够的功底,不要轻易写函数宏,否则会有惊喜。有关宏的深入了解,可以看一下喵神的<a href="https://onevcat.com/2014/01/black-magic-in-macro/" target="_blank" rel="external">宏定义的黑魔法 - 宏菜鸟起飞手册</a>。希望你看完之后能够更优雅的使用宏,尤其是函数宏。</p>
<p>宏定义可以提升代码的优雅度,但也不能滥用。像上文中说的,一些”key”或者”notificationName”最好定义为静态常量。建议,将系统主题配置的数据定义为对象宏,例如主题色、字体大小、高度等,方便修改和使用;将常用并且冗长的 API 调用定义为函数宏,例如屏幕大小、系统版本判断等,用起来简洁、方便,减少大量冗余代码。还有很多使用场景,可以参考 Apple API,或者在平时敲码中进行积累。</p>
<p>最后,一个烂大街的问题就是:”#define 和 const”的区别。主要由以下几种区别:</p>
<ul>
<li><strong>编译处理过程的区别</strong><br>define宏在预处理阶段进行展开、替换,<strong>define宏没有类型</strong>,不做类型安全检查。宏定义是在预处理阶段进行替换,大量使用宏定义会造成编译时间过长。;const 常量在编译阶段使用,有具体类型,在编译阶段会进行类型检查。也就是说你用 define 定义一个字符串类型,然后赋值给一个浮点型变量,在编译阶段是不会报错的。但是现在的一些 IDE 都会有提示,例如 Xcode 就会提示对应错误。</li>
</ul>
<blockquote>
<p>编译四个大体步骤:预处理->编译->汇编->链接</p>
</blockquote>
<ul>
<li><strong>内存管理方式的区别</strong><br>正如很多文章里说的那样,宏定义不分配内存,变量定义分配内存。宏定义给出的是立即数,每有一次替换,变会分配一次内存,在内存中有若干个拷贝;const 常量给出的是内存地址,存储在全局静态区,只有一次拷贝,一份内存,效率要比宏定义高。</li>
</ul>
<p>这里有一个误区:这里说的”分配内存”是指在给变量或者常量赋值时,<strong>创建临时变量分配的内存</strong>。不是变量或者常量占用的内存。例如下面:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#define MAX_COUNT 100 // 宏常量</span></span><br><span class="line"><span class="keyword">const</span> <span class="built_in">CGFloat</span> height = <span class="number">20.5</span>f; <span class="comment">// 定义时并未分配内存</span></span><br><span class="line"><span class="keyword">int</span> count1 = MAX_COUNT; <span class="comment">// 编译期间进行替换,编译期间不进行内存分配。运行时为 count1 赋值时,需要创建 MAX_COUNT 临时变量,宏的多次分配内存,是为赋值时 MAX_COUNT 这个临时变量分配的内存。不是指的 count1 ,不要混淆。</span></span><br><span class="line"><span class="built_in">CGFolat</span> viewHeight = height; <span class="comment">// 此时为 const 常量 height 分配内存,此后不再分配。</span></span><br><span class="line"><span class="keyword">int</span> count2 = MAX_COUNT; <span class="comment">// 再次为创建 MAX_COUNT 临时变量分配内存。</span></span><br><span class="line"><span class="built_in">CGFloat</span> labelHeight = height; <span class="comment">// 此时不再为 height 分配内存</span></span><br></pre></td></tr></table></figure></p>
<ul>
<li><strong>修饰区别</strong><br>define宏可以定义常量,也可以定义方法;const只能用来定义常量,不能用来修饰方法。</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#define NSLogRect(rect) NSLog(@<span class="meta-string">"%s x:%.4f, y:%.4f, w:%.4f, h:%.4f"</span>, #rect, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height) // 喵神写的一个输出 rect 的函数宏</span></span><br><span class="line"><span class="meta">#define SCREEN_WITH [[UIScreen mainScreen] bounds].size.width // 屏幕高度</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">NSString</span> * <span class="keyword">const</span> key = <span class="string">@"key"</span>; <span class="comment">// 只能定义常量,不能定义函数</span></span><br></pre></td></tr></table></figure>
<h3 id="id-amp-instancetype"><a href="#id-amp-instancetype" class="headerlink" title="id & instancetype"></a>id & instancetype</h3><p>id 被称为”万能指针”,可以指向任何对象,可以用于任何类型的对象。由 id 关键字定义的对象,在编译器看来只是一个对象指针,关于对象的信息,需要等到运行时才能确定。也就是说,id 定义的对象不做类型检查,向它发送未知的消息,编译阶段不会报错。id 在 OC 中如下定义:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#if !OBJC_TYPES_DEFINED</span></span><br><span class="line"><span class="comment">/// Class 是一个 objc_class 结构体指针.</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> objc_class *Class;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// objc_object 结构体,里面是一个 Class 类型成员.</span></span><br><span class="line"><span class="keyword">struct</span> objc_object {</span><br><span class="line"> Class isa OBJC_ISA_<span class="built_in">AVAILABILITY</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/// id 为一个 objc_object 结构体指针.</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> objc_object *<span class="keyword">id</span>;</span><br><span class="line"><span class="meta">#endif</span></span><br></pre></td></tr></table></figure>
<p>从上面代码可以看出,id 本质是一个结构体指针,结构体中只有一个成员 <code>isa</code>。任何一个 OC 对象,都会带一个默认的 <code>isa</code> 指针来存储对象的具体类型和信息。</p>
<p>id 关键字主要有以下几个使用场景:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1.定义 id 类型对象</span></span><br><span class="line"><span class="keyword">id</span> newObj = [<span class="built_in">NSArray</span> array]; <span class="comment">// newObj 在运行时才确定指向对象类型为 NSArray,编译时不确定</span></span><br><span class="line">[newObj log]; <span class="comment">// NSArray 类中并没有 'log' 这个对象方法,但是编译时不报错,运行时报错.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2.定义 delegate</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">weak</span>) <span class="keyword">id</span><MyDelegate> delegate; <span class="comment">// 不确定什么类型的对象作为代理,定义为 id 类型.并且规定实现 <MyDelegate> 这个协议的对象才能作为代理.因此像 delegate 发送消息时,首先要做 respondsToSelector:<#(SEL)#> 检查</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3.作为返回类型</span></span><br><span class="line">- (<span class="keyword">id</span>)<span class="keyword">copy</span>;</span><br></pre></td></tr></table></figure>
<p>从 clang3.5 开始,出现类 <code>instancetype</code> 关键字。它可以表示一个方法的相关返回类型,与 <code>id</code> 不同的是,<code>instancetype</code> 返回是相关类的具体类型,编译器可以清楚的明确该类的信息,在调用该类的方法和属性时会进行检查。目前一般类的初始化方法,返回类型都为 <code>instancetype</code>。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NSArray 的一些初始化方法</span></span><br><span class="line">+ (instancetype)array;</span><br><span class="line">+ (instancetype)arrayWithObject:(ObjectType)anObject;</span><br><span class="line">+ (instancetype)arrayWithObjects:(<span class="keyword">const</span> ObjectType [])objects count:(<span class="built_in">NSUInteger</span>)cnt;</span><br><span class="line">+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ... <span class="built_in">NS_REQUIRES_NIL_TERMINATION</span>;</span><br><span class="line">+ (instancetype)arrayWithArray:(<span class="built_in">NSArray</span><ObjectType> *)array;</span><br><span class="line"></span><br><span class="line">- (instancetype)initWithObjects:(ObjectType)firstObj, ... <span class="built_in">NS_REQUIRES_NIL_TERMINATION</span>;</span><br><span class="line">- (instancetype)initWithArray:(<span class="built_in">NSArray</span><ObjectType> *)array;</span><br><span class="line">- (instancetype)initWithArray:(<span class="built_in">NSArray</span><ObjectType> *)array copyItems:(<span class="built_in">BOOL</span>)flag;</span><br></pre></td></tr></table></figure>
<h3 id="include-amp-import-amp-class-amp-import"><a href="#include-amp-import-amp-class-amp-import" class="headerlink" title="#include & #import & @class & @import"></a>#include & #import & @class & @import</h3><h4 id="1-include"><a href="#1-include" class="headerlink" title="1.#include"></a>1.#include</h4><p>在编译预处理阶段,预处理器会将一些引入的头文件替换成其对应的内容。例如,在源文件中引入了如下代码:<br><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string"><Foundation/Foundation.h></span></span></span><br></pre></td></tr></table></figure></p>
<p>预处理器对这行代码的处理是用 Foundation.h 文件中的内容去替换这行代码,如果 Foundation.h 中也引用了其他头文件,例如 <code>#import <Foundation/NSArray.h></code>,则会按照同样的处理方式对引入的头文件进行逐级替代,依次递归下去。</p>
<p>在 C/C++ 中,我们用 <code>#include</code> 引入头文件,用 <code>#include ""</code> 引入自定义头文件,用 <code>#include <></code> 引入系统头文件。使用双引号 <code>""</code>,系统会优先从自定义头文件去查找,找不到再去系统头文件中找,如果还找不到,编译报错;使用尖括号 <code><></code>,系统会直接从系统头文件找,找不到会报错。如果直接用尖括号引入自定义头文件,则会直接报错。使用合理的方式去引入头文件,能够提高编译效率。</p>
<h4 id="2-import"><a href="#2-import" class="headerlink" title="2.#import"></a>2.#import</h4><p><code>#import</code> 可以说是 <code>#include</code> 的一个升级,有关 <code>""</code> 和 <code><></code> 的使用与 <code>#include</code> 相同。除此之外,<code>#import</code> 解决了”重复引用“的问题。例如,A,B,C 三个文件,B 引用了 A,C 引用了 B 和 A,这时 C 相当于引用了两次 A。如果直接用 <code>#include</code> 编译会出问题,如果想使用 <code>#include</code> 应该这样写:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#ifndef(XXX)</span></span><br><span class="line"> <span class="meta">#define XXX</span></span><br><span class="line"><span class="meta">#endif</span></span><br><span class="line"></span><br><span class="line">例如:</span><br><span class="line"></span><br><span class="line"><span class="meta">#ifndef _AFNETWORKING_</span></span><br><span class="line"> <span class="meta">#define _AFNETWORKING_</span></span><br><span class="line"> <span class="meta">#import <span class="meta-string">"AFURLRequestSerialization.h"</span></span></span><br><span class="line"> <span class="meta">#import <span class="meta-string">"AFURLResponseSerialization.h"</span></span></span><br><span class="line"><span class="meta">#endif</span></span><br></pre></td></tr></table></figure>
<p>如果直接使用 <code>#import</code>,可以避免这个重复引用的问题。在编译的时候它会进行判断,如果已经引入了就不会再次引入。最好的习惯还是尽量不要在头文件(.h)中引入过多的文件,以免加长编译时间。另外,在引入系统文件或者 Pod 中的文件时,最好将包含头文件的外层文件夹一起引入。如果不引入,虽然编译能够通过,但是 Xcode 会提示一些错误,而且调用里面 API 时不会有代码提示。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string"><AVFoundation/AVFoundation.h></span> // 前面添加 AVFoundation 文件夹</span></span><br></pre></td></tr></table></figure>
<h4 id="3-class"><a href="#3-class" class="headerlink" title="3.@class"></a>3.@class</h4><p><code>@class</code> 是告诉编译器有这样一个类,但是具体这个类里面有什么不知道。好比只给了你一本书的目录,但是没有给你书的内容。那么什么情况下使用 <code>@class</code> 呢?在编译预处理阶段,会将文件中的 .h 文件替换为对应的内容,如果 .h 文件中还引入了其他的 .h 文件,则进行逐级替换,依次递归。因此,<strong>尽量不要在 .h 文件中引入其他的 .h 文件</strong>。如果在声明一下方法或者属性时,需要用到某个类,这时可以用 <code>@class</code>,并且需要在 .m 文件中以 <code>#improt</code> 的方式再次引入这个文件。代码如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h 文件中</span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><UIKit/UIKit.h></span></span></span><br><span class="line"><span class="class"><span class="keyword">@class</span> <span class="title">UserModel</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ViewController</span> : <span class="title">UIViewcontroller</span></span></span><br><span class="line">- (instancetype)initWithUserModel:(UserModel *)userModel; <span class="comment">// 此处用到了 UserModel 定义参数类型</span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// .m 文件中</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">"UserModel.h"</span></span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> </span></span><br><span class="line"><span class="comment">// 在这里需要使用 UserModel 中的具体内容,此时需要以 #import 的方式引入。</span></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>上面说过,<code>@class</code> 只是告诉有这么一个类,如果使用类中的内容,则会出错。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TestOne.h</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_EUMU</span>(<span class="built_in">NSInteger</span>, ReachabilityStatus) {</span><br><span class="line"> ReachabilityStatusUnknown = <span class="number">-1</span>,</span><br><span class="line"> ReachabilityStatusNotReachable = <span class="number">0</span>,</span><br><span class="line"> ReachabilityStatusReachableViaWWAN = <span class="number">1</span>,</span><br><span class="line"> ReachabilityStatusReachableViaWiFi = <span class="number">2</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// OtherClass.h</span></span><br><span class="line"><span class="class"><span class="keyword">@class</span> <span class="title">TestOne</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">OtherClass</span> : <span class="title">NSObject</span></span></span><br><span class="line">- (<span class="keyword">void</span>)JudgeStatusWith:(ReachabilityStatus)status; <span class="comment">// 这里使用 ReachabilityStatus 会报错</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<h4 id="4-import"><a href="#4-import" class="headerlink" title="4.@import"></a>4.@import</h4><p>在说和这个关键字之前,先说一下 <strong>Moudles</strong>。<code>#import</code> 相对于 <code>#include</code> 解决了重复引用的问题,但同时也带来另外一个问题:当引用关系很复杂时,编译时引用所占的代码量就会大幅上升。如果想解决这个问题,可以在项目文件中的 Supporting Files 组内的 .pch 文件中将经常引用的一些头文件添加进去,解决编译时间问题。默认情况下,里面会引入 <code>UIKit</code>,这是每个文件中经常引用到的文件。</p>
<p>但是并不能把所有的文件都放到 .pch 文件中,因为放入 .pch 中的头文件,每个文件都能访问,这样有些文件就能访问它本不应该访问的文件。</p>
<p>为了解决这个问题,Moudles 出现了。Modules 相当于将框架进行了封装,然后加入在实际编译之时加入了一个用来存放已编译添加过的 Modules 列表。如果在编译的文件中引用到某个 Modules 的话,将首先在这个列表内查找,找到的话说明已经被加载过则直接使用已有的;如果没有找到,则把引用的头文件编译后加入到这个表中。这样被引用到的 Modules 只会被编译一次,提升速度,从而解决了编译时间和访问混乱的问题。</p>
<p>Apple 在 LLVM5.0 引入了一个新的编译符号 <code>@import</code>,使用 @ 符号将告诉编译器去使用 Modules 的引用形式。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string"><Foundation/FoundationErrors.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 下面等价于 #import <UIKit/UIKit.h>,同时还增加了 Moudles 的特性。</span></span><br><span class="line">@import Foundation.FoundationErrors;</span><br></pre></td></tr></table></figure>
<h3 id="pragma"><a href="#pragma" class="headerlink" title="pragma"></a>pragma</h3><p><code>pragma</code> 是一个预处理指令,在 OC 中主要有两个作用:<strong>整理代码</strong> 和 <strong>防止警告</strong>。</p>
<ul>
<li><strong>整理代码</strong><br>代码是一种艺术,代码写的优雅整洁是艺术的提现。使用 <code>pragma</code> 能够是代码结构看起来更加整洁。具体语法为 <code>#pragma mark 描述内容</code>,或者 <code>#pragma mark - 描述内容</code>。</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - Lifecycle Method</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewWillAppear:(<span class="built_in">BOOL</span>)animated {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)dealloc {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - Private Method</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)setupView {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)layoutSubviews {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#pragma mark - UIScrollViewDelegate</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)scrollViewDidScroll:(<span class="built_in">UIScrollView</span> *)scrollView {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>在 Xcode 导航栏看起来效果如下:<br>(1)#pragma mark 描述内容<br> <img src="/uploads/Keywords-in-Objective-C/pragma.png" alt="pragma 效果图"><br>(2)#pragma mark - 描述内容 (添加了 ‘-‘),代码块之间会有一条线,更加清晰。<br> <img src="/uploads/Keywords-in-Objective-C/pragmaLine.png" alt="pragma 效果图"></p>
<ul>
<li><strong>防止警告</strong><br>比起代码结构乱七八糟,更让人崩溃的是,代码有一堆警告。编译器或者静态分析器会针对一些不合格的代码提示”警告”,目的是为了帮助开发者写出更加优秀的代码。在 Xcode 的 Build Settings 里面有关于 <code>warning</code> 提示的设定,如下图:<br><img src="/uploads/Keywords-in-Objective-C/warningSettings.png" alt="warning 设置"><br>其中三个设定都为 NO,<code>Inhibit All Warnings</code> 意为忽略所有警告,如果你想写出规范的代码,不要开启这个设定;<code>Pedantic Warnings</code> 开启之后,会更加严格检查代码的标准,如果使用系统不支持的一些扩展,会报 <code>Warning</code>;<code>Treat Warning as Error</code> 意为将 <code>warning</code> 作为 <code>error</code> 处理,也就是说,开启之后所有的 <code>Warning</code> 全部变为 <code>Error</code>,只要有警告则编译不通过。如果你要严格要求自己,那就开启吧…</li>
</ul>
<p>但是有时候代码必须要这样写,又不想看到 <code>Warning</code>,可以用预编译指令来处理。这时候可以使用 <code>#pragma</code>,代码如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ZBWeakTimerTarget</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 下面方法中,屏蔽了 '-Warc-performSelector-leaks' 警告,如果不屏蔽,会报出警告 'PreformSelector may </span></span><br><span class="line"><span class="comment">// cause a leak because its selector is unknown'</span></span><br><span class="line">- (<span class="keyword">void</span>) fire:(<span class="built_in">NSTimer</span> *) timer {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>.target) {</span><br><span class="line"><span class="meta">#pragma clang diagnostic push</span></span><br><span class="line"><span class="meta">#pragma clang diagnostic ignored <span class="meta-string">"-Warc-performSelector-leaks"</span></span></span><br><span class="line"> [<span class="keyword">self</span>.target performSelector:<span class="keyword">self</span>.selector withObject:timer.userInfo];</span><br><span class="line"><span class="meta">#pragma clang diagnostic pop</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> [<span class="keyword">self</span>.timer invalidate];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>如上述代码中,如果不用 <code>#pragma</code> 进行处理,会报有内存泄漏的警告。因为在 ARC 环境下调用方法时,Runtime 需要知道如何处理返回值。返回值会有 <code>void</code>、<code>int</code>、<code>char</code>、<code>NSString *</code>、<code>id</code>等类型,ARC 通常会根据你所在操作的对象的头文件进行处理,或忽略,或 retain 等。这个问题的具体解释可以去查看一下 stackoverflow 上面的前三个<a href="http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown/20058585#20058585" target="_blank" rel="external">高票回答</a>。在此主要阐明用 <code>#pragma</code> 可以消除这个 warning。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>以上只是对 OC 中部分常用关键字进行一下总结,在 OC 中还有很多关键字,在此就不进行一一分析了。关键字这个东西,用好了能够提高代码的效率和鲁棒性,乱用的话则会造成意想不到的结果。对于一些常用的关键字,建议了解其作用与原理后再去使用。</p>
<h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul>
<li><a href="https://onevcat.com/2014/01/black-magic-in-macro/" target="_blank" rel="external">宏定义的黑魔法 - 宏菜鸟起飞手册</a></li>
<li><a href="http://nshipster.com/pragma/" target="_blank" rel="external">#pragma</a></li>
<li><a href="http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown" target="_blank" rel="external">performSelector may cause a leak because its selector is unknown</a></li>
</ul>
<a id="more"></a>
<p>在 OC 中,有很多常用的关键字。如何正确使用这些关键字,是学习一本语言的基础。通常面试官只需要问几<br>个关键字相关的问题,就能看出面试者的基础如何。例如 <code>#include、#import、@class的区别</code>
2016年个人总结
http://yoursite.com/2017/01/25/annual-report-2016/
2017-01-25T03:55:37.000Z
2018-06-03T13:13:45.194Z
<a id="more"></a>
<p><strong>其</strong>实,真的没有什么好写的。七月份的毕业总结已经把上半年总结在内,下半年又过的不痛不痒。如果生<br>活的满分是 10 分的话,这半年的生活我给 6 分,及格分(画外音:这个X装的我给满分)。但是年关将至,不矫情一下似乎不是我的风格。不是这个性格,也不会现在蹲在北京南站的星巴克里写总结了。还好不是写代码。</p>
<p>去年春节,在家里仅仅呆了 7 天,大年初四就回到了学校。在燕大小区租的房子里,每天恶补面试相关知识。当时学校外面的饭店还没有开门,初五的时候,房东老奶奶给我端过一碗饺子来,感动的不行。这是我第一次大过年的,在异地他乡陌生人家里吃着饺子。后来老奶奶看我没地方吃饭,让我跟着他们吃,直到外卖饭店开门。当然后来我也给人家饭钱了,毕竟都挺不容易的。后来就是面试,面试,面试…折腾了近半年,临近毕业之际,找到了一份差强人意的工作。毕业前最后一段时间,本是旅行的日子,也在毕设与考驾照中等乱七八糟的事情中度过,上半年也就这样过去了。有关毕业的一些感慨,详情请看<a href="http://luoanhao.github.io/2016/07/10/Graduation/#more" target="_blank" rel="external">我毕业了</a>。</p>
<p>七月二号来到北京,四号入职,然后就开始了上班族生涯。起初一个月感觉还不错,毕竟生活场景刚刚转换过来,对什么都充满新鲜感。起初我们租的房子里住了四个人,比较热闹。大家每天上下班,周六日就窝在家里看电视剧、打游戏;偶尔去健身、打球、养花,过得颓废而又自在。</p>
<p>毕业之后,班级群异常的活跃,一会儿不看就 ‘99+’,不得不把它屏蔽了。以前在学校,大家学累了、玩累了就去别的宿舍转转,闲扯几句。只不过现在宿舍没有了,大家无聊了就去群里扯扯,装装X。因为我比较能装,所以有些都围绕我起了昵称。每天早上群里会发两个红包,大的运气王第二天和我接着发,小的运气王去整理歌单,为大家百无聊赖的生活增添了点乐趣。就这样开始了“混工资”的生活,一混就是几个月。</p>
<p>但是后来我就开始慌了,感觉不能再这样下去。重复格调,重复频率的生活,时间久了就会让人生厌。每天上班下班,周六日休息,周而复始,完美的闭循环,无趣的很。毕竟还没有到过“岁月静好,现世安稳”那种生活的年纪,我还年轻。中学时期读了太多的韩寒、郭敬明,把我启蒙成一个“伪文青”,总是想象背包旅行走天涯才是自己想要的生活。但旅行是需要资金的,因为穷不得不“曲线救国”,先去赚钱,再去旅行。于是我由一名“伪文青”转换成一名“程序员”,梦想是三十岁以前做两年自由开发者,背着电脑去旅行,走累了停下来写会儿代码…但是照目前状态看来,“曲线救国”的半径是越来越大了。虽然平时假期也有时间去各地走走,但我宁可待在家里,毕竟据官方统计,中国现在已经有14亿人了。</p>
<p>大约十二月份的时候,意识到了不能在这样下去,否则这辈子也就这样了。于是想了好几天,开始制定计划,很多计划都延续到了17年。所以说这一年其实没有什么可以写的,除了毕业和找到工作,其余时间都在无所事事,机械地生活。为了给自己年终憋出一点成果来,在最后两个月多读了几本书。同时在最近工作不忙的几天里,不停的写博客,直到昨天晚上还在校验最近写的几篇博客。与其说是对知识做总结,不如说是在为自己寻求一点心理安慰,也许这就是大家常说的“间歇性踌躇满志”。</p>
<p>今年的总结大约也就这些东西,综上所述,找工作、毕业、上班。希望今年指定的计划能在明年完成,年终可以总结一些有价值的事,而不是拿一些小插曲来拼凑。就总结到这吧,我是在编不下去了,但愿我的“踌躇满志”可以延续到17年底。</p>
<p>待续…</p>
<a id="more"></a>
<p><strong>其</strong>实,真的没有什么好写的。七月份的毕业总结已经把上半年总结在内,下半年又过的不痛不痒。如果生<br>活的满分是 10 分的话,这半年的生活我给 6 分,及格分(画外音:这个X装的我给满分)。但是年关将至,
iOS 简单容错处理
http://yoursite.com/2016/09/08/Reduce-crash/
2016-09-08T10:55:57.000Z
2018-06-03T13:16:19.796Z
<a id="more"></a>
<p><strong>对</strong>一些代码进行容错处理,如果处理的好,会减少很多 crash。尤其对于像我这样的新手,稍不注意就会<br>写出几十个 bug。以下是针对刚入职这段时间所做项目的一个总结,同时提醒自己不要再犯同样的错误。</p>
<p>美好的一天,从没有 bug 开始~</p>
<hr>
<h4 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h4><p>新手(像我这样)在使用一些常用的数据类型时,例如 <code>NSArray</code> 、<code>NSDictionary</code> 、<code>NSNumber</code> 、<code>NSString</code>等,经常会遇到一些崩溃问题。如果平时写程序首先进行容错判断,会减少很多崩溃问题。下面列举一些新手需要注意的情况。</p>
<h5 id="1-NSArray-amp-NSMutableArray"><a href="#1-NSArray-amp-NSMutableArray" class="headerlink" title="1.NSArray & NSMutableArray"></a>1.NSArray & NSMutableArray</h5><ul>
<li>+(instancetype)arrayWithObject:(ObjectType)anObject;</li>
</ul>
<p>提前判断对象是否为 nil,传入 nil 会引起崩溃</p>
<ul>
<li>-(ObjectType)objectAtIndex:(NSUInteger)index;</li>
</ul>
<p>提前判断 index 是否小于数组个数,否则会因数组越界引起崩溃</p>
<ul>
<li>-(NSArray<objecttype> *)arrayByAddingObject:(ObjectType)anObject;</objecttype></li>
</ul>
<p>提前判断传入的对象是否为 nil,传入 nil 会引起崩溃</p>
<ul>
<li>-(void)addObject:(ObjectType)anObject;</li>
</ul>
<p>提前判断传入的对象是否为 nil,传入 nil 会引起崩溃</p>
<ul>
<li>-(void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;</li>
</ul>
<p>提前对 anyObject 进行非空判断 && 对 index 进行越界判断,否则可能引起崩溃</p>
<ul>
<li>-(void)removeObjectAtIndex:(NSUInteger)index;</li>
</ul>
<p>提前对 index 进行越界判断,否则可能会因数组越界引起崩溃</p>
<ul>
<li>-(void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;</li>
</ul>
<p>提亲对 index进行越界判断 && 对 anyObject 进行非空判断,否则可能引起崩溃</p>
<h5 id="2-NSDictionary-amp-NSMutableDictionary"><a href="#2-NSDictionary-amp-NSMutableDictionary" class="headerlink" title="2.NSDictionary & NSMutableDictionary"></a>2.NSDictionary & NSMutableDictionary</h5><ul>
<li>+(instancetype)dictionaryWithObject:(ObjectType)object forKey:(KeyType <nscopying>)key;</nscopying></li>
</ul>
<p>提前判断 object 和 key 是否为 nil,如有一个为 nil 则会崩溃</p>
<ul>
<li>-(nullable id)objectForKey:(NSString *)anAttribute;</li>
</ul>
<p>提前判断传入参数是否为 nil,传入 nil 会引起崩溃</p>
<ul>
<li>-(void)setObject:(ObjectType)anObject forKey:(KeyType <nscopying>)aKey;</nscopying></li>
</ul>
<p>提前判断 anyObject 和 aKey 是否为 nil,有一个为空都会崩溃</p>
<ul>
<li>-(void)removeObjectForKey:(KeyType)aKey;</li>
</ul>
<p>提前判断 akey 是否为 nil, 传入 nil 会引起崩溃</p>
<h5 id="3-NSNumber"><a href="#3-NSNumber" class="headerlink" title="3.NSNumber"></a>3.NSNumber</h5><p><code>NSNumber</code> 在进行类型转换时,需要先判断是否响应转换方法。在 <code>NSNumber</code> 的实例,可以转化为的类型有:</p>
<p><code>char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long、long long、unsigned long long、float、double、BOOL、NSInteger、NSUInteger</code> </p>
<p>例如下面这种情况,如果不提前进行判断,会引起崩溃:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)transType {</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSArray</span> *testArray = @[@<span class="number">1</span>,@<span class="number">2</span>,@<span class="number">3</span>];</span><br><span class="line"> <span class="built_in">NSDictionary</span> *testDict = @{<span class="string">@"key"</span>:testArray};</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSNumber</span> *testNumber = (<span class="built_in">NSNumber</span> *) [testDict objectForKey:<span class="string">@"key"</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> iValue = [testNumber intValue]; <span class="comment">// 程序会在此处崩溃</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在我们初始化 <code>NSNumber</code> 对象时,可能会使用一些意想不到的对象进行初始化。例如上面 <code>testNumer</code> 实际获得的是一个 <code>NSArray</code> 类型的对象,并不能响应 <code>intValue</code> 方法,因此正确的写法应为:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> ([testNumber respondsToSelector:<span class="keyword">@selector</span>(intValue)]) {</span><br><span class="line"> <span class="keyword">int</span> iVaule = [testNumber intValue];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="4-NSString"><a href="#4-NSString" class="headerlink" title="4.NSString"></a>4.NSString</h5><p><code>NSString</code> 类使用时需要注意两点:</p>
<ul>
<li><p>在使用一些字符串长度操作的方法,例如 <code>- (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement</code> 时,需要判断传入的 <code>range</code> 是否越界。</p>
</li>
<li><p>在使用类似 <code>NSNumber</code> 的模糊类型转换方法时,首先进行 <code>respondsToSelector:</code> 判断。</p>
</li>
</ul>
<blockquote>
<p>以上实例方法的容错判断限于<strong>实例对象不为空</strong>的情况下,如果实例对象都为空了,即使传入空值也不会崩溃。</p>
</blockquote>
<h4 id="数据类型番外篇"><a href="#数据类型番外篇" class="headerlink" title="数据类型番外篇"></a>数据类型番外篇</h4><p>在项目开发过程中,很多数据都是依赖服务端返回。如果服务端不靠谱,你不知道服务端会返回给你什么乱七八糟的东西。在加上自己粗心忘记进行了 nil 判断,很容易造成崩溃。如果每次都去判断,会很麻烦,我们需要一个统一的方法进行非空判断。</p>
<p>你可能会想到 <code>Category</code> ,我开始也是想到使用 <code>Category</code> ,但是写到一半你会发现有很多问题。如果使用 <code>Category</code> 方式去重写 <code>objectAtIndex:</code> 方法,你可能无法处理通过下标[]访问数据的问题;另外 <code>NSArray</code> 是一个 <strong>类簇</strong> ,重写起来十分麻烦,工作量很大。</p>
<h5 id="类簇"><a href="#类簇" class="headerlink" title="类簇"></a>类簇</h5><blockquote>
<p>Class clusters are a design pattern that the Foundation framework makes extensive use of. Class clusters group a number of private concrete subclasses under a public abstract superclass. The grouping of classes in this way simplifies the publicly visible architecture of an object-oriented framework without reducing its functional richness. Class clusters are based on the Abstract Factory design pattern.</p>
</blockquote>
<p>简单说就是:类簇将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构。这是一种基于 <code>工厂模式</code> 的实现。</p>
<p><code>NSArray</code> 、<code>NSDictionary</code> 、<code>NSNumber</code> 、 <code>NSString</code> 这些都是类簇。<a href="https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html" target="_blank" rel="external">官方文档</a> 通过 <code>NSNumber</code> 对类簇进行了解释。</p>
<p>针对 <code>NSArray</code> ,进行了如下测试:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)classClustersTest {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">id</span> iArray1 = [<span class="built_in">NSArray</span> alloc];</span><br><span class="line"> <span class="keyword">id</span> iArray2 = [iArray1 init];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,[[iArray1 class] description]); <span class="comment">// __NSPlaceholderArray</span></span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,[[iArray2 class] description]); <span class="comment">// __NSArray0</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">id</span> mArray1 = [<span class="built_in">NSMutableArray</span> alloc];</span><br><span class="line"> <span class="keyword">id</span> mArray2 = [mArray1 init];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,[[mArray1 class] description]); <span class="comment">// __NSPlaceholderArray</span></span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,[[mArray2 class] description]); <span class="comment">// __NSArrayM</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看出,iArray1 与 mArray1 为同一个类,都为 <code>__NSPlaceholderArray</code>。但是 iArray2 为 <code>___NSArray0</code> (NSArray) ,mArray2 为 <code>__NSArrayM</code> (NSMutableArray) 类。因此对于类簇,在使用 <code>alloc + init</code> 方法进行初始化时,<code>alloc</code> 方法先生成一个中间类,在 <code>init</code> 方法时,生成对应的具体类型。具体在执行 <code>init</code> 方法时是如何区分 <code>immutable</code> 还是 <code>mutable</code> 未搞清楚。</p>
<h5 id="使用-Method-swizzling-进行方法交换"><a href="#使用-Method-swizzling-进行方法交换" class="headerlink" title="使用 Method swizzling 进行方法交换"></a>使用 Method swizzling 进行方法交换</h5><p>上面说了,使用 <code>Category</code> 会很麻烦,而且移植性较差。因此想到了使用 <code>Method swizzling</code>。在使用 <code>Method swizzling</code> 时,有一步是根据 <code>类名</code> 和 <code>selector</code> 获取响应的方法,即使用 <code>class_getInstanceMethod(Class cls, SEL name)</code>。如果你像下面这样写,就会出现问题了:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)load {</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">dispatch_once</span>(&onceToken, ^{</span><br><span class="line"> </span><br><span class="line"> Method originalMethod = class_getClassMethod(<span class="built_in">NSClassFromString</span>(<span class="string">@"NSArray"</span>), <span class="keyword">@selector</span>(objectAtIndex:));</span><br><span class="line"> Method newMthod = class_getClassMethod(<span class="built_in">NSClassFromString</span>(<span class="string">@"NSArray"</span>), <span class="keyword">@selector</span>(myMethod));</span><br><span class="line"> method_exchangeImplementations(originalMethod, newMethod);</span><br><span class="line"> </span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面提到,<code>NSArray</code> 是类簇,是一个抽象类的集合。<code>objectAtIndex:</code> 真正所属的类应该是 <code>__NSArrayI</code>。因此,正确的写法应该这样:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#import <span class="meta-string">"SafeArray.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string"><objc/runtime.h></span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">NSArray</span> (<span class="title">SafeFunc</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">NSArray</span> (<span class="title">SafeFunc</span>)</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">id</span>)safeObjectAtIndex:(<span class="built_in">NSUInteger</span>)index {</span><br><span class="line"> <span class="keyword">if</span> (index < <span class="keyword">self</span>.count) {</span><br><span class="line"> <span class="keyword">id</span> obj = [<span class="keyword">self</span> safetObjectAtIndex:index];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (obj == [<span class="built_in">NSNull</span> null]) { <span class="comment">// 为什么使用 [NSNull null]?</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> obj;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">SafeArray</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)load {</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">dispatch_once</span>(&onceToken, ^{</span><br><span class="line"> </span><br><span class="line"> Method originalMethod = class_getClassMethod(<span class="built_in">NSClassFromString</span>(<span class="string">@"NSArray"</span>), <span class="keyword">@selector</span>(objectAtIndex:));</span><br><span class="line"> Method newMthod = class_getClassMethod(<span class="built_in">NSClassFromString</span>(<span class="string">@"NSArray"</span>), <span class="keyword">@selector</span>(safeObjectAtIndex:));</span><br><span class="line"> method_exchangeImplementations(originalMethod, newMethod);</span><br><span class="line"> </span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>NSArray 或者 NSDictionary 中不会有 nil 对象,但是可能会有 ‘空值’,因此使用 <code>obj == [NSNUll null]</code> ,如果使用 <code>obj == nil</code> 进行判断,那么这句话等于浪费。有关 <code>nil / Nil / NULL / NSNull</code>,请参考<a href="http://nshipster.com/nil/" target="_blank" rel="external">这里</a></p>
</blockquote>
<p>对于 <code>NSNumber</code> 、 <code>NSDictionary</code> 这些有 immutable 和 mutable 类使用 <code>Method swizzling</code> 时都需要注意以上问题,找到真正的 <code>具体类</code> 进行操作。</p>
<h4 id="Delegate-使用"><a href="#Delegate-使用" class="headerlink" title="Delegate 使用"></a>Delegate 使用</h4><p>关于 <code>delegate</code> 的使用,需要注意三个问题:</p>
<h5 id="1-delegate-属性都要为-weak,不解释"><a href="#1-delegate-属性都要为-weak,不解释" class="headerlink" title="1.delegate 属性都要为 weak,不解释"></a>1.delegate 属性都要为 weak,不解释</h5><h5 id="2-‘委托方’调用代理方法时,需要通过-respondsToSelector-进行判断,否则代理对象没有实现这个方法,会导致崩溃"><a href="#2-‘委托方’调用代理方法时,需要通过-respondsToSelector-进行判断,否则代理对象没有实现这个方法,会导致崩溃" class="headerlink" title="2.‘委托方’调用代理方法时,需要通过 respondsToSelector: 进行判断,否则代理对象没有实现这个方法,会导致崩溃"></a>2.‘委托方’调用代理方法时,需要通过 <code>respondsToSelector:</code> 进行判断,否则代理对象没有实现这个方法,会导致崩溃</h5><h5 id="3-不要在单例中使用-delegate"><a href="#3-不要在单例中使用-delegate" class="headerlink" title="3.不要在单例中使用 delegate"></a>3.不要在单例中使用 delegate</h5><p>代理属性 <code>delegate</code> 是一个弱引用指针,指向的是<strong>代理对象</strong>的的内存地址。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@protocol</span> <span class="title">ZBDelegate</span> <<span class="title">NSObject</span>></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)doSomething;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ClassA</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">weak</span>) <span class="keyword">id</span><ZBDelegate> delegate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ClassA</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)delegateTest {</span><br><span class="line"> [<span class="keyword">self</span>.delegate doSomething]; <span class="comment">// self.delegate 指向 ClassB 的内存地址</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ClassB</span> : <span class="title">NSObject</span> <<span class="title">ZBDelegate</span>></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ClassB</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)doSomething {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"hello"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>如果在单例中使用 <code>delegate</code> ,因为单例对象使用都是一个对象,这样 <code>self.delegate</code> 就会不断被从新赋值,只保留最后一个,这样最终只有一个对象响应代理方法,其他对象都不会响应。</p>
<h4 id="NSnotification-使用"><a href="#NSnotification-使用" class="headerlink" title="NSnotification 使用"></a>NSnotification 使用</h4><p>关于 <code>NSnotification</code> 的使用,需要注意一下几个问题:</p>
<h5 id="1-注册问题"><a href="#1-注册问题" class="headerlink" title="1.注册问题"></a>1.注册问题</h5><p>如果一个对象注册了一个通知,然后又注册了一次,这两次不会合并,通知回调会被调用两次。因此在注册通知的时候,需要在 <code>init</code> 或者 <code>viewDidload</code> 这些一般整个生命周期只执行一次的方法里注册,不要在一些可重入的方法里面注册。<strong>避免重复注册问题。</strong></p>
<h5 id="2-发送通知"><a href="#2-发送通知" class="headerlink" title="2.发送通知"></a>2.发送通知</h5><p>建议所有的通知都要在 <strong>主线程</strong> 中发送,没有例外。如果在其他线程运行,需要发送通知时,回到主线程发送,否则注销通知时,因为发送通知和注销通知不在同一个线程,造成一些意想不到的结果(竞态条件)。</p>
<h5 id="3-注销通知"><a href="#3-注销通知" class="headerlink" title="3.注销通知"></a>3.注销通知</h5><p>如果在一个对象销毁时,不注销当前对象注册的通知,对象销毁后,再次向这个对象发送通知,会造成 crash。因此在类的 <code>dealloc</code> 方法中需要注销对象。</p>
<p>建议使用 <code>[[NSNotificationCenter defaultCenter] removeObserver:self];</code> 这种整体注销的方式,避免遗漏。</p>
<h4 id="NSTimer-使用"><a href="#NSTimer-使用" class="headerlink" title="NSTimer 使用"></a>NSTimer 使用</h4><p>使用 <code>NSTimer</code> 时需要注意 ‘repeat timer’ 的释放问题。如果你想在 <code>- (void)dealloc</code> 中执行 <code>[self.timer invalidate];</code>,一般情况下都是释放不了的。原因如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">TimerTest</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSTimer</span> *timer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TimerTest</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)setupTimer {</span><br><span class="line"> <span class="keyword">self</span>.timer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:<span class="number">3.0</span>f</span><br><span class="line"> target:<span class="keyword">self</span></span><br><span class="line"> selector:<span class="keyword">@selector</span>(doSomething)</span><br><span class="line"> userInfo:<span class="literal">nil</span></span><br><span class="line"> repeats:<span class="literal">YES</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)doSomething {</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"hello"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)dealloc {</span><br><span class="line"> [<span class="keyword">self</span>.timer invalidate];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>在 <code>dealloc</code> 方法中,并不能将 timer 销毁,因为这个方法并不能执行。原因是:<code>Timer</code> 加到 <code>Runloop</code> 中,会被 <code>Runloop</code> 强引用,然后 <code>Timer</code> 对 <code>self</code> 有一个强引用,导致 <code>self</code> 不能够被释放,不能执行 <code>dealloc</code> 方法。</p>
<p><strong>要想销毁 <code>repeat</code> 类型的 <code>Timer</code>,必须要执行 <code>invalidate</code> 方法。</strong>可以去手动 (action)方式去调用,也可以在执行 <code>delloc</code> 之前去执行 <code>invalidate</code> 方法。如果想要在 <code>dealloc</code> 方法中去销毁,可以自己封装一个类,给 <code>Timer</code> 传一个假的 <code>target</code>,如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 创建类</span></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">MTBWeakTimerTarget</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">weak</span>) <span class="keyword">id</span> target;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">assign</span>) SEL selector;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">weak</span>) <span class="built_in">NSTimer</span> *timer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MTBWeakTimerTarget</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>) fire:(<span class="built_in">NSTimer</span> *) timer {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>.target) {</span><br><span class="line"><span class="meta">#pragma clang diagnostic push</span></span><br><span class="line"><span class="meta">#pragma clang diagnostic ignored <span class="meta-string">"-Warc-performSelector-leaks"</span></span></span><br><span class="line"> [<span class="keyword">self</span>.target performSelector:<span class="keyword">self</span>.selector withObject:timer.userInfo];</span><br><span class="line"><span class="meta">#pragma clang diagnostic pop</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> [<span class="keyword">self</span>.timer invalidate];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MTBWeakTimer</span></span></span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)scheduledTimerWithInterval:(<span class="built_in">NSTimeInterval</span>)interval</span><br><span class="line"> target:(<span class="keyword">id</span>)aTarget</span><br><span class="line"> selector:(SEL)aSelector</span><br><span class="line"> userInfo:(<span class="keyword">id</span>)userInfo</span><br><span class="line"> repeats:(<span class="built_in">BOOL</span>)repeats {</span><br><span class="line"> MTBWeakTimerTarget *timerTarget = [MTBWeakTimerTarget new];</span><br><span class="line"> timerTarget.target = aTarget;</span><br><span class="line"> timerTarget.selector = aSelector;</span><br><span class="line"> timerTarget.timer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:interval</span><br><span class="line"> target:timerTarget selector:<span class="keyword">@selector</span>(fire:) userInfo:userInfo</span><br><span class="line"> repeats:repeats];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> timerTarget.timer;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">self</span>.timer = [MTBWeakTimer scheduledTimerWithInterval:<span class="number">3.0</span></span><br><span class="line"> target:<span class="keyword">self</span></span><br><span class="line"> selector:<span class="keyword">@selector</span>(doSomething)</span><br><span class="line"> userInfo:<span class="literal">nil</span></span><br><span class="line"> repeats:<span class="literal">YES</span>];</span><br></pre></td></tr></table></figure>
<p>当然这个解决方案不是我想的,具体请看<a href="http://blog.callmewhy.com/2015/07/06/weak-timer-in-ios/" target="_blank" rel="external">作者原创</a>。</p>
<h4 id="线程安全问题"><a href="#线程安全问题" class="headerlink" title="线程安全问题"></a>线程安全问题</h4><p>在多线程环境中,因为线程安全问题引发的 crash 有很多,尤其是对一些数据类型进行操作时。有人可能认为使用 <code>immutable</code> 类型的就安全了,但是并不是你想象的那样。请看下面示例:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)threadSafe {</span><br><span class="line"> <span class="built_in">NSArray</span> *dataArray = [<span class="built_in">NSArray</span> array];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// in thread one</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">NSArray</span> *otherArray = @[@<span class="number">1</span>,@<span class="number">2</span>,@<span class="number">3</span>,@<span class="number">4</span>,@<span class="number">5</span>]; <span class="comment">// array count = 5</span></span><br><span class="line"> dataArray = [otherArray <span class="keyword">copy</span>];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// in thread two</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">NSArray</span> *anotherArray = @[@<span class="number">1</span>,@<span class="number">2</span>,@<span class="number">3</span>]; <span class="comment">// array count = 3</span></span><br><span class="line"> dataArray = [anotherArray <span class="keyword">copy</span>];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// in main thread</span></span><br><span class="line"> <span class="keyword">int</span> a = dataArray[<span class="number">4</span>]; <span class="comment">// ????</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的代码中可能会出现 crash。所以不要认为使用 <code>immutable</code> 类型的就线程安全了。处理线程安全问题,没有公式化的方法,不可能对所有用到的数据类型进行加锁,那样太损耗性能,只有对于一些特殊的数据对象,在读写时进行加锁。是否有必要加锁,写程序的时候还需要自己注意。</p>
<h5 id="关于-‘锁’-的一些问题"><a href="#关于-‘锁’-的一些问题" class="headerlink" title="关于 ‘锁’ 的一些问题"></a>关于 ‘锁’ 的一些问题</h5><p>今天写这个的时候,正好看到了<a href="http://m.weibo.cn/3321824014/4017336178496605?moduleID=feed&uicode=10000002&mid=4017421889132510&luicode=10000011&_status_id=4017336178496605&lfid=2304133290954642_-_WEIBO_SECOND_PROFILE_WEIBO&lcardid=" target="_blank" rel="external">南大</a>今天发的《iOS知识小集》,讲述了一下关于锁的问题。文中这样描述:</p>
<p>为了保证线程安全,可能会使用 <code>NSLock, @synchornized, pthread_mutex_t</code> 等方法,但是加锁和解锁是非常昂贵的操作,对性能会有影响。可以用GCD提供的信号量来进行优化。如下是使用 <code>锁</code> 和使用 <code>信号量</code> 处理相同数据所需时间的对比:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">5</span>; i++) {</span><br><span class="line"> [<span class="keyword">self</span> lockTime];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"========================================="</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">5</span>; i++) {</span><br><span class="line"> [<span class="keyword">self</span> semaphoreTime];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">double</span> subtractTimes(uint64_t endTime, uint64_t startTime) {</span><br><span class="line"> </span><br><span class="line"> uint64_t difference = endTime - startTime;</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">double</span> conversion = <span class="number">0.0</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(conversion == <span class="number">0.0</span>) {</span><br><span class="line"> </span><br><span class="line"> mach_timebase_info_data_t info;</span><br><span class="line"> kern_return_t err = mach_timebase_info(&info);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(err == <span class="number">0</span>)</span><br><span class="line"> conversion = <span class="number">1e-9</span> * (<span class="keyword">double</span>) info.numer / (<span class="keyword">double</span>) info.denom;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> conversion * (<span class="keyword">double</span>)difference;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用锁</span></span><br><span class="line">- (<span class="keyword">void</span>)lockTime {</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSMutableSet</span> *items = [<span class="built_in">NSMutableSet</span> set];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSLock</span> *lock = [<span class="built_in">NSLock</span> new];</span><br><span class="line"> </span><br><span class="line"> uint64_t start = mach_absolute_time();</span><br><span class="line"> </span><br><span class="line"> dispatch_apply(<span class="number">50</span>, queue, ^(size_t inddex) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">10000</span>; i++) {</span><br><span class="line"> [lock lock];</span><br><span class="line"> [items addObject:<span class="string">@"hi"</span>];</span><br><span class="line"> [lock unlock];</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> uint64_t stop = mach_absolute_time();</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"use lock time :%f"</span>, subtractTimes(stop, start));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用信号量</span></span><br><span class="line">- (<span class="keyword">void</span>)semaphoreTime {</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSMutableSet</span> *items = [<span class="built_in">NSMutableSet</span> set];</span><br><span class="line"> </span><br><span class="line"> dispatch_semaphore_t itemLock = dispatch_semaphore_create(<span class="number">1</span>);</span><br><span class="line"> </span><br><span class="line"> uint64_t start = mach_absolute_time();</span><br><span class="line"> </span><br><span class="line"> dispatch_apply(<span class="number">50</span>, queue, ^(size_t inddex) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">10000</span>; i++) {</span><br><span class="line"> dispatch_semaphore_wait(itemLock, DISPATCH_TIME_FOREVER);</span><br><span class="line"> [items addObject:<span class="string">@"hi"</span>];</span><br><span class="line"> dispatch_semaphore_signal(itemLock);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> uint64_t stop = mach_absolute_time();</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"use semaphore time :%f"</span>, subtractTimes(stop, start));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>程序运行结果如下图 (真机上测试运行) :</p>
<p><img src="/uploads/Reduce-crash/锁与信号量对比输出.png" alt="锁与信号量对比输出"></p>
<p>从上面的 Log 中可以看出,使用 <code>锁</code> 和使用 <code>信号量</code> 处理相同的数据,时间不是一个量级的。因此,在做优化的时候,建议使用 <code>信号量</code> 来代替锁。</p>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>以上是我作为一个 iOS 开发新手,在近期遇到的一些 crash 问题。对此做一个总结,以提醒自己今后不会再犯相同的错误。可能总结的有遗漏,或者有一些问题。如果有什么问题,还请大家指正。</p>
<h5 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h5><p><a href="http://blog.sunnyxx.com/2014/12/18/class-cluster/" target="_blank" rel="external">1.从NSArray看类簇</a></p>
<p><a href="http://www.cocoachina.com/ios/20150925/13459.html" target="_blank" rel="external">2.打造Objective-C安全的Collection类型</a></p>
<p><a href="http://inessential.com/2015/05/21/how_not_to_crash_3_nsnotification" target="_blank" rel="external">3.How Not to Crash #3: NSNotification</a></p>
<p><a href="http://blog.callmewhy.com/2015/07/06/weak-timer-in-ios/" target="_blank" rel="external">4.iOS 中的 NSTimer</a></p>
<p><a href="http://m.weibo.cn/3321824014/4017336178496605?moduleID=feed&uicode=10000002&mid=4017421889132510&luicode=10000011&_status_id=4017336178496605&lfid=2304133290954642_-_WEIBO_SECOND_PROFILE_WEIBO&lcardid=" target="_blank" rel="external">5.南峰子-iOS知识小集</a></p>
<a id="more"></a>
<p><strong>对</strong>一些代码进行容错处理,如果处理的好,会减少很多 crash。尤其对于像我这样的新手,稍不注意就会<br>写出几十个 bug。以下是针对刚入职这段时间所做项目的一个总结,同时提醒自己不要再犯同样的错误。<
我毕业了
http://yoursite.com/2016/07/10/Graduation/
2016-07-10T14:50:52.000Z
2018-06-03T13:13:58.365Z
<a id="more"></a>
<p><strong>现在</strong>写毕业感言,或许有点晚了吧。6月29号毕业典礼结束后,也就宣布我们正式毕业了,不只是大学毕<br>业,而是我的学生生涯毕业。按照我矫情的性格,对于这种具有重大纪念意义的时刻,必然会写一篇文章来抒发一下感慨。但是由于毕业时候各种事情,没来的及,一直拖到上班一周之后,赶在这个周末补上。</p>
<p>在这篇随笔里,不会有什么华丽的辞藻,甚至语言逻辑可能都会不通顺。因为我不想再去拽文,再去狠狠地装一波。我只想通过流水账的形式表达我的一些感受,这也是我最真实的感受。我不是写给谁看,而是纪念我自己内心的感受,写给我自己,不喜勿喷,毕竟我还没有到达“嬉笑怒骂结成文章”的境界,虽然我也姓周。</p>
<p><img src="/uploads/Graduation/admission.jpg" alt="录取通知书"><img src="/uploads/Graduation/admission2.jpg" alt="录取通知书2"></p>
<p>2012年9月1日,我来到了燕山大学信息科学与工程学院软件工程2班。然后当天晚上我就去了网吧,写了一篇日志作为纪念,不信你们可以去我QQ空间去看。因为高中语文成绩略优,所以当时的我自认文笔非凡,再加上我闷骚的性格,遇事便拽文纪念一番。</p>
<p>刚步入大学,对什么都很好奇,再加上我一贯喜欢装逼的性格,一冲动便竞选了班长。这个冲动也给我带来了惩罚——我决定毕业三年之后不当领导(太TM累心了)。没进入大学之前,我说希望的生活是这样的:没事去图书馆看看书,写点东西,然后做点自己喜欢的事,典型一个文艺青年范。结果你们可想而知。</p>
<p>这几年里,我几乎每天都在折腾,争名夺利,勾心斗角(其实没那么黑暗)。当时不懂事,只想装个逼,就竞选了班长。之后感觉自己装大了,不得不改变人生规划。然后我做出了如下规划:大一将班级带入正轨,顺便搞搞人际关系;大二开始拿各种证书和荣誉;大三开始深入学习技术;大四找一份好工作,顺利毕业。然后我就顺着这个计划,忙碌了四年。</p>
<p>虽然我没有把班级带的太好,但我确实尽力了,能力有限,而且真的很累。之后为了那证书和奖学金,各种找导师做项目,混加分。毕竟我智力有限,是学不过我们班那群人的。个人认为,我的能力不在于智力或者知识储备等方面,而在于综合各种渠道去完成某件事。例如我学习学不过你们,我可以利用我的优势搞到加分,最终成绩还是比你们高。问了争夺名利,得罪了很多人,现在想想当时的自己多么心机。</p>
<p>每段没有固定的主题内容,为了阅读方便,我瞎分的。</p>
<p>到了大二下学期,我拿到了各种荣誉和证书,技术也有所提高。当时的我内心膨胀到极点,十分自负。自认技术全院无人能比,各方面综合起来也是全院优秀,睥睨天下。当然现在我也认为整个学院技术我也是最牛逼(吼哈哈哈哈哈)。从哪个时候我也给自己立下目标:毕业的时候我的工资要全院最高,入职公司要全院最好,这也是我给自己挖的最大的一个坑。</p>
<p>我十分庆幸我在大学期间认识了很多老师(真实感受),有的思想启蒙,有的技术指导,有的给我开后门。不夸张的说,在信息学院没有几个人认识(熟悉)的老师数量比我还要多。当时我所的最狂妄的一句话就是:(考试)“我要是抄起来谁敢管”。虽然考试每次我都做第二排,但是我还是肆无忌惮的抄。虽然我四年没少作弊,但是有几点需要说明:第一,拿国家励志那一年的成绩靠的是真本事,没抄;第二,专业技术课不抄,例如C++、数据结构等;第三,大四为了找工作,很多课考试都是跟老师打招呼过的,这才使我顺利毕业(一节课都没去过,想过还不跟老师打招呼你试试?)。</p>
<p>这几年我过的真的很累,有多累?我给你描述一下。按照优先级可以这样分:比较轻松的一年——大三,这一年只是做项目,偶尔失眠;心累的一年——大一,刚当上班长,很多事要处理,时常着急上火;比较拼的一年——大二,这一年为了那奖学金和科技比赛证书,既要学习跟上,又要做项目,还得管理班级,要知道仅凭努力(非智力)从专业二十名开外混到专业前三也是有点吃力的,这一年经常失眠;最累的一年——大四,我本以为这会是我最轻松的一年,签了工作就完事了,结果这是我身心最累的一年。</p>
<p>关于大四这一年,我单独写一段。</p>
<p>拿了各种奖学金、各种计算机大赛证书、班长、党员、三好学生、技术牛,集各种荣誉于一身的我,本以为好的工作信手拈来,结果我差点没在大四跳楼了,按照惯例我们每年都会有一个跳楼的。从七月份开始,我就带着我的自负心去找工作。第一个是阿里,我周围只有我通过了笔试和电面,通知去北京面试。当时我的内心是无比自豪的。结果挂了,我可以负责任的说,不是阿里缩招的原因,就是我自己实力不行。开始我还觉得,反正是第一家,后面还有很多,不着急。陆陆续续的,百度,挂了;新浪,挂了;爱奇艺,挂了;折八百,挂了;搜狐,挂了;搜狗,挂了;滴滴,挂了。。。。。当时我的内心几乎是崩溃的。之后再清华大学实习了三个月,但是是带着压抑的心情去实习的。还有在这个过程中,有个人在一直支持着我,鼓励着我,要不然我真的会崩溃。我从15年7月份开始找工作,到了16年6月份拿到4份offer(小公司)结束,我将战线拉了一年,我真的一点都不比考研的轻松。这是我继高考复习以来,又一次经历如此的压抑,同时也又老了十岁。</p>
<p>在大学的最后一段时间里,按照设定我应该是和朋友们吃吃饭,出去旅旅行,然后好好玩玩。然而呢,最后整整两个月,我在学驾照、做毕设、写论文、处理班级事务,一直到现在才有闲心写点东西。这毕业季过得,真憋屈。</p>
<p><img src="/uploads/Graduation/dgree.jpg" alt="学位授予"></p>
<p>高中毕业的时候,我把所有的东西都带走了,连张试卷都没留给学校。大学毕业的时候,我终于发现,有些东西是带不走的,有些东西是留不住的。</p>
<p>其实还有很多东西要写,其实我可以好好组织一下语言去写,但是我不写了。第一,刚刚入职,还有很多工作上事务要处理;第二,过深的回忆容易感伤。所以就这样吧。</p>
<p><img src="/uploads/Graduation/class.JPG" alt="班级合影"></p>
<p>这四年,我带了一个让我骄傲自豪的班级;我遇到了一群给我欢乐的朋友;我认识了一批照顾我四年的领导;我拜谒了众多受我学识的灵魂导师。这四年,我很累,但是如果再给我一次机会,我还会选择燕山大学,信息科学与工程学院,软件工程2班,和你们,一起,再度过,四年。</p>
<p><strong>—by 周博 2016年7月10日 in 北京</strong></p>
<a id="more"></a>
<p><strong>现在</strong>写毕业感言,或许有点晚了吧。6月29号毕业典礼结束后,也就宣布我们正式毕业了,不只是大学毕<br>业,而是我的学生生涯毕业。按照我矫情的性格,对于这种具有重大纪念意义的时刻,必然会写一篇文章来抒发一下
iOS 内存管理之四:内存优化
http://yoursite.com/2016/04/04/Memory-Manage-4-Memory-Optimization/
2016-04-04T08:05:46.000Z
2018-06-03T13:16:10.333Z
<a id="more"></a>
<p>所谓的内存优化,在设计程序的过程中,我们要在保证程序运行效率的前提下,尽量压缩程序运行时所占用的内<br>存。无论硬件设备的内存有多大,程序运行时占用内存越少越好。下面我将介绍在开发项目过程中,一些优化内存的方法。</p>
<h4 id="1-关于UITableView"><a href="#1-关于UITableView" class="headerlink" title="1.关于UITableView"></a>1.关于UITableView</h4><p>在项目开发中,<code>UITableView</code> 是用的比较多的一个视图控件。如果能够对 <code>UITableView</code> 的使用做好优化,程序的性能将提高很多。</p>
<h5 id="(1)善于使用UITableViewCell的重用机制"><a href="#(1)善于使用UITableViewCell的重用机制" class="headerlink" title="(1)善于使用UITableViewCell的重用机制"></a>(1)善于使用UITableViewCell的重用机制</h5><blockquote>
<p><strong>重用机制</strong>:这种机制下系统默认有一个可变数组 <code>NSMutableArray* visiableCells</code>,用来保存当前显示的cell。一个可变字典 <code>NSMutableDictnery* reusableTableCells</code> ,用来保存可重复利用的cell。<code>UITableView</code> 只会创建一屏幕的cell,放在 <code>visiableCells</code>中。每当cell滑出屏幕,就会放到 <code>reusableTableCells</code> 中,当要显示某一个位置的cell时,先去 <code>reusableTableCells</code> 中取,如果有,直接取来用;如果没有,就会创建。这样极大减少了内存的开销。</p>
</blockquote>
<p>在iOS 6之后,在UITableView和UICollectionView中除了可以复用cell,还可以复用各个Section的Header和Footer。可见Apple一直在不断优化。在项目开发中,我们需要给 <code>UITableViewCells</code>、 <code>UICollectionViewCells</code>、<code>UITableViewHeaderFooterViews</code>设置正确的 <code>reuseIdentifier</code>。当有多类cell需要复用是,我们可以根据 <code>reuseIdentifier</code> 区分。我们可以在Xcode中设置,如下图:</p>
<p><img src="/uploads/Memory-Manage-4-Memory-Optimization/reuseIdentifier.png" alt="setReuseImg"></p>
<p>下面是一个简单的cell复用的示例:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">UITableViewCell</span> *)tableView:(<span class="built_in">UITableView</span> *)tableView cellForRowAtIndexPath:(<span class="built_in">NSIndexPath</span> *)indexPath {</span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">NSString</span> *cellIdentifier = <span class="literal">nil</span>;</span><br><span class="line"> <span class="built_in">UITableViewCell</span> *cell = <span class="literal">nil</span>;</span><br><span class="line"> </span><br><span class="line"> cellIdentifier = <span class="string">@"你的xib文件视图中标注的reuseIdentifier"</span>;</span><br><span class="line"> cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; <span class="comment">//根据identifier复用cell</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//如果没有对应的cell,创建cell</span></span><br><span class="line"> <span class="keyword">if</span>(!cell){</span><br><span class="line"> cell = [[<span class="built_in">UITableViewCell</span> alloc] initWithStyle:<span class="built_in">UITableViewCellStyleDefault</span> reuseIdentifier:cellIdentifier];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> cell;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>复用cell是一个很好的机制,但是使用不当也会出现问题,也就是所谓的<strong>复用重叠</strong>问题。看下面代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">UITableViewCell</span> *)tableView:(<span class="built_in">UITableView</span> *)tableView cellForRowAtIndexPath:(<span class="built_in">NSIndexPath</span> *)indexPath {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">NSString</span> *cellIdentifier = <span class="string">@"myCell1"</span>;</span><br><span class="line"> <span class="built_in">UITableViewCell</span> *cell = <span class="literal">nil</span>;</span><br><span class="line"> cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (cell == <span class="literal">nil</span>) {</span><br><span class="line"> cell = [[<span class="built_in">UITableViewCell</span> alloc] initWithStyle:<span class="built_in">UITableViewCellStyleDefault</span> reuseIdentifier:cellIdentifier];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> ((indexPath.row%<span class="number">2</span>) == <span class="number">0</span>) {</span><br><span class="line"> cell.backgroundColor = [<span class="built_in">UIColor</span> blueColor];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> cell.textLabel.text = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%ld"</span>,(<span class="keyword">long</span>)indexPath.row];</span><br><span class="line"> <span class="comment">// Configure the cell...</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> cell;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我本打算将偶数行的设置为蓝色,基数行为默认颜色,并将cell的内容设置为行数,加以区分。结果如图:</p>
<p><img src="/uploads/Memory-Manage-4-Memory-Optimization/cell.png" alt="cell1Img"></p>
<p>从上图可以看出,开始初始化的13~14个cell正常,但是当滑动tableview时,就出现了问题,有的基数行cell也变为了蓝色。这是因为,下面的cell基本都是复用的,当没有显示指定cell的属性时,它就会使用已经创建过的cell的属性,导致有的蓝色有的白色。解决办法就是像下面这样写:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">UITableViewCell</span> *)tableView:(<span class="built_in">UITableView</span> *)tableView cellForRowAtIndexPath:(<span class="built_in">NSIndexPath</span> *)indexPath {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">static</span> <span class="built_in">NSString</span> *cellIdentifier = <span class="string">@"myCell1"</span>;</span><br><span class="line"> <span class="built_in">UITableViewCell</span> *cell = <span class="literal">nil</span>;</span><br><span class="line"> cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (cell == <span class="literal">nil</span>) {</span><br><span class="line"> cell = [[<span class="built_in">UITableViewCell</span> alloc] initWithStyle:<span class="built_in">UITableViewCellStyleDefault</span> reuseIdentifier:cellIdentifier];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> ((indexPath.row%<span class="number">2</span>) == <span class="number">0</span>) {</span><br><span class="line"> cell.backgroundColor = [<span class="built_in">UIColor</span> blueColor];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> cell.textLabel.text = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%ld"</span>,(<span class="keyword">long</span>)indexPath.row];</span><br><span class="line"> <span class="comment">// Configure the cell...</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> cell;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>切记:当对多种cell赋予属性时,一定不能写在 <code>if (!cell){}</code> 里面,避免复用出现问题。</p>
</blockquote>
<h5 id="(2)优化UITableViewCell高度计算"><a href="#(2)优化UITableViewCell高度计算" class="headerlink" title="(2)优化UITableViewCell高度计算"></a>(2)优化UITableViewCell高度计算</h5><p>UITableView有两个很重要的回调方法:<code>tableView:cellForRowAtIndexPath:</code>和<code>tableView:heightForRowAtIndexPath:</code>。很多人认为,在初始化tableview时,会先调用前者进行创建,然后再调用后者进行布局和属性设置。然而并非如此。真实的情况是这样的:UITableView是继承自UIScrollView的,需要先确定它的contentSize及每个Cell的位置,然后才会把重用的Cell放置到对应的位置。所以事实上,UITableView的回调顺序是先多次调用 <code>tableView:heightForRowAtIndexPath:</code> 以确定contentSize及Cell的位置,然后才会调用 <code>tableView:cellForRowAtIndexPath:</code>,从而来显示在当前屏幕的Cell。</p>
<p>举个例子:如果现在要显示20个Cell,当前屏幕显示5个。那么刷新(reload)UITableView时,UITableView会先调用20次 <code>tableView:heightForRowAtIndexPath:</code> 方法,然后调用5次<code>tableView:cellForRowAtIndexPath:</code>方法;滚动屏幕时,每当Cell滚入屏幕,都会调用一次<code>tableView:heightForRowAtIndexPath:</code>、<code>tableView:cellForRowAtIndexPath:</code>方法。</p>
<p>所以,对于UITableViewCell的高度计算的优化,就是对这两个函数的处理。至于如何优化<a href="http://weibo.com/u/1364395395?from=myfollow_all&is_all=1" target="_blank" rel="external">@我就叫Sunny怎么了</a>写了一篇很好的<a href="http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/" target="_blank" rel="external">文章</a>去介绍。我就不多说了。</p>
<h5 id="(3)-懒加载(延迟加载)"><a href="#(3)-懒加载(延迟加载)" class="headerlink" title="(3) 懒加载(延迟加载)"></a>(3) 懒加载(延迟加载)</h5><p>懒加载并不是减少了程序内存消耗,而是将加载对象的时间推迟,在使用到对象的时候在对其进行初始化。例如一个UITableView一共有20行,但是屏幕只显示5行数组。那么在初始化tableview的时候,可以只先加载5行数据,另外15行等到显示的时候再去加载。这样可以减少初始化tableview时所需要的内存。(这样说有点牵强,因为实时加载会影响tableview的流畅度,但是大体就是这个意思 ><)</p>
<h4 id="2-关于图片的处理"><a href="#2-关于图片的处理" class="headerlink" title="2.关于图片的处理"></a>2.关于图片的处理</h4><p>图片在内存中会占很大开销,如果适当的处理图片,会减少很多内存的消耗。</p>
<h5 id="(1)缓存图片"><a href="#(1)缓存图片" class="headerlink" title="(1)缓存图片"></a>(1)缓存图片</h5><p>常见的从bundle中加载图片的方式有两种,一个是用<code>imageNamed</code>,二是用<code>imageWithContentsOfFile</code>,第一种比较常见一点。</p>
<p><code>imageNamed</code>的优点是当加载时会缓存图片。<a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html" target="_blank" rel="external"><code>imageNamed</code></a>的文档中这么说:<br>这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。</p>
<p>也就是说,<code>imageNamed</code>方法加载的图片,会对图片进行缓存。而 <code>imageWithContentsOfFile</code> 方法不会。</p>
<p>所以,如果要加载的图片比较小,而且会反复使用,这种情况选择用 <code>imageNamed</code>;如果要加载一个大图片,而且是一次性使用,那就使用 <code>imageWithContentsOfFile</code>,没必要浪费内存去缓存它。</p>
<p>代码示例:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 对图片进行缓存</span></span><br><span class="line"><span class="built_in">UIImage</span> *img = [<span class="built_in">UIImage</span> imageNamed:<span class="string">@"imgName"</span>]; </span><br><span class="line"> </span><br><span class="line"><span class="comment">// 不对图片缓存,用完即释放</span></span><br><span class="line"> <span class="built_in">UIImage</span> *img = [<span class="built_in">UIImage</span> imageWithContentsOfFile:<span class="string">@"imgName"</span>];</span><br></pre></td></tr></table></figure>
<h5 id="(2)调整图片大小"><a href="#(2)调整图片大小" class="headerlink" title="(2)调整图片大小"></a>(2)调整图片大小</h5><p>我们经常从网络获取图片或者从本地bundle获取图片,然后加载到 <code>UIImageView</code> 中。在加载图片时,应尽量保证图片大小和 <code>UIImageView</code> 大小相同。因为在运行中缩放图片很耗费资源,如果 <code>UIImageView</code> 嵌套在 <code>UIScrollView</code> 或者 <code>UITableView</code>中,会更耗费资源。</p>
<p>对于从本地bundle中加载的图片,我们可以事先件图片处理好。对于从网络下载的图片,在下载完成后,我们需要对图片进行缩放,然后再加载。</p>
<h5 id="(3)代码渲染-or-直接获取"><a href="#(3)代码渲染-or-直接获取" class="headerlink" title="(3)代码渲染 or 直接获取"></a>(3)代码渲染 or 直接获取</h5><p>前面已经说过,用代码去渲染一张图片会使图片占用内存翻倍。但是用代码去绘制图片,能够很好的去控制图片,并且能够做出很多漂亮的效果,前提是牺牲一部分内存;那如果所有图片都从bundle中加载呢?那会使bundle的体积增大,同时不能够用代码去灵活处理图片的效果。<br>所以,在开发过程中,是代码渲染图片,还是从bundle获取图片,需要做一个权衡。</p>
<h4 id="3-数据处理"><a href="#3-数据处理" class="headerlink" title="3.数据处理"></a>3.数据处理</h4><p>在项目开发中,我们会使用到各种格式的数据,例如 <code>JSON</code>、<code>XML</code> 等。还有各种各样的数据结构,例如数组、链表、字典、集合等。使用正确的数据格式和使用正确的数据结构,会减少我们的资源消耗。</p>
<h5 id="(1)选择正确的数据格式"><a href="#(1)选择正确的数据格式" class="headerlink" title="(1)选择正确的数据格式"></a>(1)选择正确的数据格式</h5><p>App与网络进行交互时,常常采用 <code>JSON</code> 或者 <code>XML</code> 类型的数据格式。</p>
<p><code>JSON</code> 是一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。解析 <code>JSON</code> 会比 <code>XML</code> 更快,但是 <code>JSON</code> 传输的数据比较小。</p>
<p><code>XML</code> 是一种重量级的数据交换格式,适用于很大的数据传输。当数据量较大时,使用 <code>XML</code> 数据格式,会极大减少内存消耗,增加性能。</p>
<p>另外,尽量避免数据多次转化。例如tableview中需要以数组的形势去赋值。那么服务器尽量返回数组类型。如果返回 <code>JSON</code> 类型,在去转换为 <code>NSArray</code> 类型,也会增加开销。</p>
<h5 id="(2)选择正确的数据结构"><a href="#(2)选择正确的数据结构" class="headerlink" title="(2)选择正确的数据结构"></a>(2)选择正确的数据结构</h5><p>不同的数据结构,处理数据的速度是不同的。</p>
<ul>
<li><strong>数组</strong> NSArray NSMutableArray:有序的一组值。使用索引来查询很快,使用值查找很慢, 插入/删除很慢。</li>
<li><strong>字典</strong> NSDictionary NSMutableDictionary:存储键值对。用键来查找比较快。</li>
<li><strong>集合</strong> NSSet NSMutableSet:无序的一组值。用值来查找很快,插入/删除很快。</li>
</ul>
<h4 id="4-View的处理"><a href="#4-View的处理" class="headerlink" title="4.View的处理"></a>4.View的处理</h4><h5 id="(1)避免使用过于复杂的xib"><a href="#(1)避免使用过于复杂的xib" class="headerlink" title="(1)避免使用过于复杂的xib"></a>(1)避免使用过于复杂的xib</h5><p>在目前很多项目开发中,还经常用到 <code>xib</code>。当加载一个 <code>xib</code> 时,所有的内容都会放到内存里,包括任何图片。如果 <code>xib</code> 文件过于庞大,会占用很多内存。<code>xib</code> 与 <code>storyboard</code> 不同,<code>xib</code>即使暂时用不到,view也会存在于内存里;<code>storyboard</code> 仅在需要时实例化一个<code>view controller</code>。</p>
<p>而且设置view属性时,尽可能的<strong>把 <code>opaque</code> 属性设置为YES(不透明)</strong>。这样会提高渲染系统优化一些渲染过程和提高性能。</p>
<h5 id="(2)正确设置View的背景"><a href="#(2)正确设置View的背景" class="headerlink" title="(2)正确设置View的背景"></a>(2)正确设置View的背景</h5><p>设置UIView的背景图片主要有两种方式:</p>
<ul>
<li>使用 <code>UIColor</code>的 <code>colorWithPatternImage</code> 来设置背景色;</li>
<li>给 <code>UIView</code> 添加 <code>UIImageView</code> 子视图。</li>
</ul>
<p>第一种方式,适合使用小图平铺创建背景,能更快渲染也不会会费很多内存。例如使用一个10x10的像素大小重复背景。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">self</span>.view.backgroundColor = [<span class="built_in">UIColor</span> colorWithPatternImage:[<span class="built_in">UIImage</span> imageNamed:<span class="string">@"backgroundImg"</span>]];</span><br></pre></td></tr></table></figure>
<p>第二种方式,适合于使用大图,即整张图片来设置背景。如果使用 <code>colorWithPatternImage</code> 会消耗太多内存从而收到内存警告导致应用程序突然崩溃。而使用 <code>UIImageView</code> 会节约不少内存。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UIImageView</span> *backgroundView = [[<span class="built_in">UIImageView</span> alloc] initWithImage:[<span class="built_in">UIImage</span> imageNamed:<span class="string">@"backgroundImg"</span>]];</span><br><span class="line">[<span class="keyword">self</span>.view addSubview:backgroundView];</span><br></pre></td></tr></table></figure>
<h5 id="(3)设定Shadow-Path"><a href="#(3)设定Shadow-Path" class="headerlink" title="(3)设定Shadow Path"></a>(3)设定Shadow Path</h5><p>如果用下面代码给 <code>view.layer</code> 添加一个shadow:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UIView</span> *view = [[<span class="built_in">UIView</span> alloc] init];</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Setup the shadow ...</span></span><br><span class="line">view.layer.shadowOffset = <span class="built_in">CGSizeMake</span>(<span class="number">-1.0</span>f, <span class="number">1.0</span>f);</span><br><span class="line">view.layer.shadowRadius = <span class="number">5.0</span>f;</span><br><span class="line">view.layer.shadowOpacity = <span class="number">0.6</span>;</span><br></pre></td></tr></table></figure>
<p>这会使<code>Core Animation</code> 不得不在后台得出图形并加好阴影之后再去渲染,这会开销很大。</p>
<p>如果使用shadowPath则会避免这种问题:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">view.layer.shadowPath = [[<span class="built_in">UIBezierPath</span> bezierPathWithRect:view.bounds] <span class="built_in">CGPath</span>];</span><br></pre></td></tr></table></figure>
<h4 id="5-合理使用Autorelease-Pool"><a href="#5-合理使用Autorelease-Pool" class="headerlink" title="5.合理使用Autorelease Pool"></a>5.合理使用Autorelease Pool</h4><p><code>NSAutoreleasePool</code>负责释放block中的autoreleased objects。一般情况下它会自动被UIKit调用。但是有些状况下也需要手动去创建它。</p>
<p>假如创建很多临时对象,你会发现内存一直在减少直到这些对象被release的时候。这是因为只有当UIKit用光了autorelease pool的时候memory才会被释放。</p>
<p>但是如果自己定义 <code>@autoreleasepool</code> ,在里面创建临时对象,可以避免这个问题:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSArray</span> *urls = <<span class="meta"># An array of file URLs #>;</span></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">NSURL</span> *url <span class="keyword">in</span> urls) {</span><br><span class="line"> <span class="keyword">@autoreleasepool</span> {</span><br><span class="line"> <span class="built_in">NSError</span> *error;</span><br><span class="line"> <span class="built_in">NSString</span> *fileContents = [<span class="built_in">NSString</span> stringWithContentsOfURL:url</span><br><span class="line"> encoding:<span class="built_in">NSUTF8StringEncoding</span> error:&error];</span><br><span class="line"> </span><br><span class="line"><span class="comment">/* Process the string, creating and autoreleasing more objects. */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="6-正确处理缓存"><a href="#6-正确处理缓存" class="headerlink" title="6.正确处理缓存"></a>6.正确处理缓存</h4><p>缓存可以分为<strong>内存缓存</strong>和<strong>磁盘缓存</strong>。在项目开发过程中,我们经常会对一些图片、声音、数据进行缓存。合理利用缓存机制,会大大提高程序的性能,提高APP的流畅性。例如被广为使用的 <a href="https://github.com/rs/SDWebImage" target="_blank" rel="external">SDWebImage</a>,它使用的缓存机制是这样的:</p>
<p>(1)先根据查看内存缓存,如果有直接获取。</p>
<p>(2)如果内存没有,从磁盘缓存获取。</p>
<p>(3)如果磁盘缓存也没有,直接通过URL从网络下载。</p>
<p>当然这只是一个简单的描述,更加详细请看<a href="http://weibo.com/touristdiary?is_all=1" target="_blank" rel="external">@南峰子_老驴</a>的一篇<a href="http://southpeak.github.io/blog/2015/02/07/sourcecode-sdwebimage/" target="_blank" rel="external">SDWebImage实现分析</a>。</p>
<p>合理处理缓存,能够提高程序的性能,不用每次都从网络获取数据。但是也不能什么都存入缓存,这会消耗很多内存和磁盘空间。所以应合理使用缓存机制。</p>
<h4 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h4><p>以上,是我对于内存优化的一些理解。在写这篇文章过程中,参考了很多大牛的文章。对于一名在校应届本科生来说,我对于oc的理解还很浅薄,如果有错误或者有需要添加的地方,希望大家能够指出。我会加以改正并学习。</p>
<h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><ul>
<li><a href="https://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks" target="_blank" rel="external">25 iOS App Performance Tips & Tricks</a></li>
<li><a href="http://longxdragon.github.io/2015/05/26/UITableView%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7/" target="_blank" rel="external">UITableView优化技巧</a></li>
</ul>
<a id="more"></a>
<p>所谓的内存优化,在设计程序的过程中,我们要在保证程序运行效率的前提下,尽量压缩程序运行时所占用的内<br>存。无论硬件设备的内存有多大,程序运行时占用内存越少越好。下面我将介绍在开发项目过程中,一些优化内存的方法。</p>
<h4 id=
iOS内存管理之三:ARC(Automatic Reference Counting)
http://yoursite.com/2016/03/30/Memory-Manage-3-ARC/
2016-03-30T07:13:21.000Z
2018-06-03T13:16:00.491Z
<a id="more"></a>
<p>在<a href="http://luoanhao.github.io/2016/03/29/Memory-Manage-2-MRC/" target="_blank" rel="external">上一篇</a>文章中,我们主要<br>介绍了基于<strong>MRC</strong>环境下的内存管理。这篇文章主要介绍基于<strong>ARC</strong>环境下的内存管理。从WWDC2011到现在已经有近5年的时间,ACR机制的应用已经十分成熟,如今在Xcode中新建项目,都默认开启ARC。下面我会从ARC的原理到使用进行详细讲解。</p>
<h3 id="一、什么是ARC"><a href="#一、什么是ARC" class="headerlink" title="一、什么是ARC"></a>一、什么是ARC</h3><p>ARC——Automatic Reference Counting,自动引用计数。它<strong>不是运行时特性,不是垃圾回收器(GC)</strong>,而是一种<strong>编译时特性</strong>。 </p>
<blockquote>
<p>Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.</p>
</blockquote>
<p>与MRC模式相比,在ARC模式下会减少相应的工作量。为什么这样说呢?因为在ARC模式下编写代码,不需要写<code>retain</code>、<code>release</code>、<code>autorelease</code>这三个关键字来对实例对象进行手动管理内存,这会减少很多代码。当开启ARC时,编译器在编译代码时会自动在代码合适的地方插入<code>retain</code>、<code>release</code>和<code>autorelease</code>。也就是说,原来在MRC模式下需要写的类似于<code>[obj release]</code> 这样的代码,在ARC模式下编译器会自动帮我们完成,不需要我们去写,这就是所谓的<strong>自动引用计数</strong>。这样会相应地提高开发效率。</p>
<h3 id="二、ARC工作原理"><a href="#二、ARC工作原理" class="headerlink" title="二、ARC工作原理"></a>二、ARC工作原理</h3><p>ARC模式的基本原理与MRC相同,都是<strong>引用计数原理</strong>,只是书写方式不同。在MRC模式下,如果想要保持一个对象使其不被释放,需要使用<code>retain</code>关键字。在ARC模式下要做的就是用一个指针指向这个对象,只要指针没有被置空,对象就会一直保持在堆上。当将指针指向新值时,原来的对象会被release一次。</p>
<p>ARC可以为开发者节省很多代码,使用ARC以后再也不需要关心什么时候retain,什么时候release,但是这并不意味你可以不思考内存管理,我们需要经常性地问自己这个问题:<strong>谁持有这个对象?</strong></p>
<h4 id="1-“持有”概念"><a href="#1-“持有”概念" class="headerlink" title="1.“持有”概念"></a>1.“持有”概念</h4><p>在ARC中,我们说对象A“持有”对象B,就是说对象A“强引用”对象B。写法如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSObject</span> * obj = [[<span class="built_in">NSObject</span> alloc] init];</span><br><span class="line"><span class="built_in">NSObject</span> * A = obj; <span class="comment">//A指向obj,此时A对obj有一个引用,强引用</span></span><br></pre></td></tr></table></figure>
<p>引用又分为<strong>强引用</strong>和<strong>弱引用</strong>。被 <code>strong</code> 关键字修饰的对象A,如果指向对象obj,即obj被一个 <code>strong</code> 指针指向,obj被强引用,则obj不会销毁。如果对象没有任何 <code>strong</code> 指针指向,那么就讲销毁。被 <code>weak</code> 关键字修饰的对象B,如果指向对象obj,那么对象obj被一个 <code>weak</code> 指针指向,obj被弱引用,obj是否销毁与其无关。</p>
<p>一个 <code>weak</code> 指针P指向一个对象obj,并没有增加P的引用计数。另外,在ARC模式下,<strong>所有对象指针类型默认为 <code>strong</code> 类型</strong>。</p>
<h4 id="2-理解strong和weak"><a href="#2-理解strong和weak" class="headerlink" title="2.理解strong和weak"></a>2.理解strong和weak</h4><p><code>strong</code> 和 <code>weak</code> 类似于MRC模式下的 <code>retain</code> 和 <code>assign</code> 。请看下图:<br><img src="/uploads/Memory-Manage-3-ARC/Strong.png" alt="strongImg"></p>
<p>在上图中,有两个 <code>strong</code> 类型指针A和B指向O,一个 <code>weak</code> 类型指针C指向O。每有一个 <code>strong</code> 类型指针指向O,在编译时,对象O会进行 <code>[O retain]</code> 一次,此时对象O的引用计数为2。<code>weak</code> 指针对其引用计数没有影响。当对象A或者对象B不再指向O时,对象O的引用计数减1,当没有对象持有时,进行释放。说到底,ARC模式的管理方式还是基于引用计数。</p>
<h3 id="三、ARC修饰符"><a href="#三、ARC修饰符" class="headerlink" title="三、ARC修饰符"></a>三、ARC修饰符</h3><p>在ARC环境下,有4个与内存相关的<strong>变量所有权修饰符</strong>,他们分别是:</p>
<ul>
<li>__strong</li>
<li>__weak</li>
<li>__autoreleasing</li>
<li>__unsafe_unretained</li>
</ul>
<p>这里所说的<strong>变量所有权修饰符</strong>,与属性(property)中的<strong>属性修饰符</strong>不同,他们有如下对应关系:</p>
<ul>
<li><code>assign</code> 对应的所有权类型是 <code>__unsafe_unretained</code></li>
<li><code>copy</code> 对应的所有权类型是 <code>__strong</code></li>
<li><code>retain</code> 对应的所有权类型是 <code>__strong</code></li>
<li><code>strong</code> 对应的所有权类型是 <code>__strong</code></li>
<li><code>unsafe_unretained</code> 对应的所有权类型是 <code>__unsafe_unretained</code></li>
<li><code>weak</code> 对应的所有权类型是 <code>__weak</code></li>
</ul>
<p>关于<strong>属性修饰符</strong>,后面我会写一篇关于 <code>property</code> 的文章进行详细介绍,在此暂时不做介绍。接下来主要介绍一下4个<strong>变量所有权修饰符</strong>。</p>
<h4 id="1-strong"><a href="#1-strong" class="headerlink" title="1.__strong"></a>1.__strong</h4><p><code>__strong</code> 表示引用为强引用。对应定义 property 时用到的 <code>strong</code> 。当对象没有任何一个强引用指向它时,它才会被释放。如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要保证所有指向对象强引用置为 nil。__strong 修饰符是 id 类型和对象类型默认的所有权修饰符。</p>
<p><strong><code>__strong</code>修饰的变量会自动初始化为 <code>nil</code></strong>。</p>
<h4 id="2-weak"><a href="#2-weak" class="headerlink" title="2.__weak"></a>2.__weak</h4><p><code>__weak</code> 表示弱引用,对应定义 property 时用到的 <code>weak</code>。<code>__weak</code> 最常见的一个作用就是<strong>用来避免强引用循环</strong>。但是需要注意的是,<code>__weak</code> 修饰符只能用于 iOS5 以上的版本,在 iOS4 及更低的版本中使用 <code>__unsafe_unretained</code> 修饰符来代替。关于 <code>__weak</code>,有以下几点需要注意:</p>
<p>(1)弱引用不会影响对象的释放,而当对象被释放时,所有指向它的弱引用都会自定被置为 nil,这样可以防止野指针。如下图:</p>
<p><img src="/uploads/Memory-Manage-3-ARC/weak.png" alt="weakImg"><br>对于对象N,开始有一个强引用指针A和一个弱引用指针B指向它,之后A指向M,没有强引用指针指向N,N被释放,此时弱引用指针B自动被置为 <code>nil</code>,防止变为野指针。</p>
<p>(2)<code>__weak</code> 主要用来避免循环引用,主要有以下几个应用场景:</p>
<ul>
<li>在使用 <code>delegate</code> 时,我们需要将 <code>delegate</code> 的属性定义为 <code>weak</code>,以避免强引用循环。</li>
</ul>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">ClassOneVC:</span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ClassOneViewController</span> ()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *name;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">weak</span>) <span class="keyword">id</span> <myDelegate> delegate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TestViewController</span></span></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"></span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> [<span class="keyword">self</span>.delegate func];</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line">ClassTwoVC:</span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ClassOneViewController</span> ()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TestViewController</span></span></span><br><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> </span><br><span class="line"> ClassOneViewController * classOneVC = [ClassOneViewController new];</span><br><span class="line"> classOneVC.delegate = <span class="keyword">self</span> <span class="comment">//delegate为weak类型,不会对self强引用,避免循环引用。</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//delegate func</span></span><br><span class="line">- (<span class="keyword">void</span>)func{};</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<ul>
<li>在 Block 中防止强引用循环,后面细讲。</li>
<li>用来修饰指向由 Interface Builder 创建的控件。比如:@property (weak, nonatomic) IBOutlet UIImageView myImgV。<blockquote>
<p>对于在类中使用的UIKit控件,一般为 <code>strong</code> 类型,至于为什么在Interface Builder或者StoryBoard中创建的控件可以用 <code>weak</code> ,有一种解释是在Interface Builder或者StoryBoard中进行了strong,具体什么原理还请大神解答。</p>
</blockquote>
</li>
</ul>
<h4 id="3-autoreleasing"><a href="#3-autoreleasing" class="headerlink" title="3.__autoreleasing"></a>3.__autoreleasing</h4><p>用 <code>__autoreleasing</code> 修饰一个对象,表示这个对象被添加到 <code>autorelease pool</code>中自动释放引用。这和MRC模式下的 <code>autorelease</code> 的用法相同。只不过在MRC模式下,不能够再显示使用 <code>autorelease</code> 方法了,但是 <code>autorelease</code> 的机制还是有效的,即通过使用 <code>autorelease</code> 修饰对象。</p>
<p>下面两行代码意义相同: </p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSString * str = [[[NSString alloc] initWithFormat:@"hello"] autorelease]; //MRC</span><br><span class="line">NSString * __autoreleasing str = [[NSString alloc] initWithFormat:@"hello"]; //ARC</span><br></pre></td></tr></table></figure>
<p>另外,定义property时不能使用这个修饰符,因为任何一个对象的property都不应该是 <code>autorelese</code> 类型。</p>
<p>在ARC模式下,使用(隐式使用)<code>__autoreleasing</code> 的几个场景:</p>
<ul>
<li>方法返回值</li>
<li>访问 <code>__weak</code> 修饰的变量</li>
<li>id类型指针</li>
<li>指针的指针(id *)</li>
<li>某些类方法隐式创建自己的 <code>autorelease pool</code></li>
</ul>
<blockquote>
<p>id 类型类似于(NSObject <em> ),所以(id </em>)类似于(NSObject ** )。</p>
</blockquote>
<h5 id="(1)方法返回值"><a href="#(1)方法返回值" class="headerlink" title="(1)方法返回值"></a>(1)方法返回值</h5><p>请看下面代码: </p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">NSObject</span> *)myObject {</span><br><span class="line"> <span class="built_in">NSObject</span> *obj = [[<span class="built_in">NSObject</span> alloc] init];</span><br><span class="line"> <span class="keyword">return</span> obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个方法中,<code>obj</code> 的默认所有权修饰符为 <code>__strong</code> 。当return时,使 <code>obj</code>超出其作用域,它强引用持有的对象本应该释放,但是由于该对象作为方法的返回值,所以一般情况下编译器会自动将 <code>obj</code> 注册到 Autorelease Pool中。这样就<strong>延长了</strong>对象的生命周期,使其出了作用域之后,还能够使用。当Autorelease Pool 被销毁的时候,对象的生命周期才会结束。</p>
<p>Autorelease Pool 是与线程一一映射的,这就是说一个 autoreleased 的对象的延迟释放是发生在它所在的 Autorelease Pool 对应的线程上的。。因此,在方法返回值的这个场景中,如果 Autorelease Pool 的 drain 方法没有在接收方和提供方交接的过程中触发,那么 autoreleased 对象是不会被释放的。所以不必担心 “Autorelease Pool 都销毁了,接收方还没接收到对象”这样的问题。</p>
<p>关于Autorelease Pool何时释放,生命周期的问题,实现原理等问题,可以参考这篇文章:<a href="http://blog.sunnyxx.com/2014/10/15/behind-autorelease/" target="_blank" rel="external">黑幕背后的Autorelease</a>。</p>
<p>#####(2)访问 <code>__weak</code> 修饰变量<br>当访问由 <code>__weak</code> 修饰的变量时,实际访问的是注册到 Autorelease Pool中的对象,例如下面两段代码意义相同:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSObject</span> *obj0 = [<span class="built_in">NSObject</span> new];</span><br><span class="line"></span><br><span class="line"><span class="keyword">id</span> __<span class="keyword">weak</span> obj1 = obj0;</span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"class=%@"</span>, [obj1 class]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译时会处理成以下这样:</span></span><br><span class="line"><span class="keyword">id</span> __<span class="keyword">weak</span> obj1 = obj0;</span><br><span class="line"><span class="keyword">id</span> __autoreleasing A = obj1;</span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"class=%@"</span>, [A class]);</span><br></pre></td></tr></table></figure>
<p>这样做是为了延长对象的生命周期。因为在 <code>__weak</code> 修饰符只持有对象的弱引用,而在访问对象的过程中,该对象有可能被废弃,如果把被访问的对象注册到 Autorelease Pool 中,就能保证 Autorelease Pool 被销毁前对象是存在的。</p>
<h5 id="3-id类型指针"><a href="#3-id类型指针" class="headerlink" title="(3) id类型指针"></a>(3) id类型指针</h5><p>一个被引用过几百遍的例子,如在使用NSError时:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSError</span> *__autoreleasing error; </span><br><span class="line"><span class="keyword">if</span> (![data writeToFile:filename options:<span class="built_in">NSDataWritingAtomic</span> error:&error]) </span><br><span class="line">{ </span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"Error: %@"</span>, error); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上面的代码中,如果error定义为 <code>strong</code>类型,即使不用 <code>__autoreleasing</code> 修饰,编译器也会帮你自动添加,保证你传入的是一个 <code>autoreleaing</code> 类型的引用,如下(意义与上段代码相同):</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSError</span> *error; </span><br><span class="line"><span class="built_in">NSError</span> *__autoreleasing tempError = error; <span class="comment">// 编译器自动添加 </span></span><br><span class="line"><span class="keyword">if</span> (![data writeToFile:filename options:<span class="built_in">NSDataWritingAtomic</span> error:&tempError]) </span><br><span class="line">{ </span><br><span class="line"> error = tempError; <span class="comment">// 编译器自动添加添加 </span></span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"Error: %@"</span>, error); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>但是为了提高程序效率,我们在定义的error的时候,一般都声明为 <code>autoreleasing</code> 类型。</p>
<h5 id="(4)指针的指针"><a href="#(4)指针的指针" class="headerlink" title="(4)指针的指针"></a>(4)指针的指针</h5><p>在ARC环境下,所有种指针的指针类型(id *)的函数参数如果不加修饰符,编译器会默认将他们认定为 <code>__autoreleasing</code> 类型。例如下面两段代码等价:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)myFunc:(<span class="built_in">NSObject</span> **)obj</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// do something </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)myFunc:(<span class="built_in">NSObject</span> * __autoreleasing *)obj</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// do something </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="5-类方法隐式创建-Autorelease-Pool"><a href="#5-类方法隐式创建-Autorelease-Pool" class="headerlink" title="(5)类方法隐式创建 Autorelease Pool"></a>(5)类方法隐式创建 Autorelease Pool</h5><p>某些类的方法会隐式地使用自己的Autorelease Pool,例如NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)loopThroughDictionary:(<span class="built_in">NSDictionary</span> *)dict error:(<span class="built_in">NSError</span> **)error</span><br><span class="line">{</span><br><span class="line"> [dict enumerateKeysAndObjectsUsingBlock:^(<span class="keyword">id</span> key, <span class="keyword">id</span> obj, <span class="built_in">BOOL</span> *stop){</span><br><span class="line"></span><br><span class="line"> <span class="comment">// do stuff </span></span><br><span class="line"> <span class="keyword">if</span> (there is some error && error != <span class="literal">nil</span>)</span><br><span class="line"> {</span><br><span class="line"> *error = [<span class="built_in">NSError</span> errorWithDomain:<span class="string">@"MyError"</span> code:<span class="number">1</span> userInfo:<span class="literal">nil</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面代码中,会隐式创建一个Autorelease Pool,等价于:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)loopThroughDictionary:(<span class="built_in">NSDictionary</span> *)dict error:(<span class="built_in">NSError</span> **)error</span><br><span class="line">{</span><br><span class="line"> [dict enumerateKeysAndObjectsUsingBlock:^(<span class="keyword">id</span> key, <span class="keyword">id</span> obj, <span class="built_in">BOOL</span> *stop){</span><br><span class="line"></span><br><span class="line"> <span class="keyword">@autoreleasepool</span> <span class="comment">// 被隐式创建</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (there is some error && error != <span class="literal">nil</span>)</span><br><span class="line"> {</span><br><span class="line"> *error = [<span class="built_in">NSError</span> errorWithDomain:<span class="string">@"MyError"</span> code:<span class="number">1</span> userInfo:<span class="literal">nil</span>];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// *error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>为了能够正常的使用*error,我们需要一个strong型的临时引用,在dict的枚举Block中是用这个临时引用,保证引用指向的对象不会在出了dict的枚举Block后被释放,正确的方式如下:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)loopThroughDictionary:(<span class="built_in">NSDictionary</span> *)dict error:(<span class="built_in">NSError</span> **)error</span><br><span class="line">{</span><br><span class="line"> __block <span class="built_in">NSError</span>* tempError; <span class="comment">// 加__block保证可以在Block内被修改 </span></span><br><span class="line"> [dict enumerateKeysAndObjectsUsingBlock:^(<span class="keyword">id</span> key, <span class="keyword">id</span> obj, <span class="built_in">BOOL</span> *stop)</span><br><span class="line"> { </span><br><span class="line"> <span class="keyword">if</span> (there is some error) </span><br><span class="line"> { </span><br><span class="line"> *tempError = [<span class="built_in">NSError</span> errorWithDomain:<span class="string">@"MyError"</span> code:<span class="number">1</span> userInfo:<span class="literal">nil</span>]; </span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> }] </span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (error != <span class="literal">nil</span>) </span><br><span class="line"> { </span><br><span class="line"> *error = tempError; </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="4-unsafe-unretained"><a href="#4-unsafe-unretained" class="headerlink" title="4.__unsafe_unretained"></a>4.__unsafe_unretained</h4><p>ARC是在iOS 5引入的,而这个修饰符主要是为了在ARC刚发布时兼容iOS 4以及版本更低的设备,因为这些版本的设备没有weak pointer system,简单的理解这个系统就是我们上面讲weak时提到的,能够在 <code>weak</code> 引用指向对象被释放后,把引用值自动设为 <code>nil</code> 的系统。这个修饰符在定义property时对应的是”unsafe_unretained”,实际可以将它理解为MRC时代的 <code>assign</code> :纯粹只是将引用指向对象,没有任何额外的操作,在指向对象被释放时依然原原本本地指向原来被释放的对象(所在的内存区域)。所以非常不安全。</p>
<p>现在可以完全忽略掉这个修饰符了,因为iOS 4早已退出历史舞台,目前的APP基本都不会再去兼容iOS4。</p>
<h3 id="四、ARC中的Block"><a href="#四、ARC中的Block" class="headerlink" title="四、ARC中的Block"></a>四、ARC中的Block</h3><p>一般情况下,block捕获的外部变量,可以在block内部使用,但是无法修改,例如下面代码:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="built_in">NSString</span> * str = <span class="string">@"hello"</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">void</span> (^ block)();</span><br><span class="line"> block = ^ (<span class="keyword">void</span>){</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,str);</span><br><span class="line"> <span class="comment">//str = @"change";</span></span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> block();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注:static的变量和全局变量不需要加__block就可以在Block中修改</p>
</blockquote>
<p>如果修改 <code>str</code>,编译器会报错。如果想要修改 <code>str</code> ,需要用 <code>__block</code> 修饰符修饰要修改的变量,但也会引入新的问题,请看下面示例:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> MyViewController * __block myController = [[MyViewController alloc] init…];</span><br><span class="line"> </span><br><span class="line"> myController.completionHandler = ^(<span class="built_in">NSInteger</span> result) {</span><br><span class="line"> [myController dismissViewControllerAnimated:<span class="literal">YES</span> completion:<span class="literal">nil</span>];</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上面这段代码中,<code>myController</code> 的 <code>completionHandler</code> 调用了 <code>myController</code> 的方法[dismissViewController…],这时 <code>completionHandler</code> 会对 <code>myController</code> 做 <code>retain</code> 操作。而我们知道,<code>myController</code> 对 <code>completionHandler</code> 也至少有一个retain(一般准确讲是copy),这时就出现了在内存管理中最糟糕的情况:循环引用!</p>
<p>简单点说就是:myController retain了completionHandler,而completionHandler也retain了myController。循环引用导致了myController和completionHandler最终都不能被释放。</p>
<p>针对以上问题,如果循环引用已经产生了,我们可以这样去解决:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> __block MyViewController * myController = [[MyViewController alloc] init…];</span><br><span class="line"> </span><br><span class="line"> myController.completionHandler = ^(<span class="built_in">NSInteger</span> result) {</span><br><span class="line"> [myController dismissViewControllerAnimated:<span class="literal">YES</span> completion:<span class="literal">nil</span>];</span><br><span class="line"> myController = <span class="literal">nil</span>; <span class="comment">// 注意这里,将myController置为nil,保证了block结束myController强引用的解除</span></span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>为了避免循环引用,大家可能想到这样一个方法:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> MyViewController *myController = [[MyViewController alloc] init…];</span><br><span class="line"> </span><br><span class="line"> MyViewController * __<span class="keyword">weak</span> weakMyController = myController;</span><br><span class="line"> </span><br><span class="line"> myController.completionHandler = ^(<span class="built_in">NSInteger</span> result) {</span><br><span class="line"> [weakMyViewController dismissViewControllerAnimated:<span class="literal">YES</span> completion:<span class="literal">nil</span>];</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上述代码中,我们让block捕获了一个弱引用,即 <code>weakMyController</code>。但是问题又来了:block如果捕获一弱引用,在编译后会将其捕获在自己的函数栈中,当block函数执行完毕,就会释放这个弱引用。那么当myController指向的对象在completionHandler被调用前释放,那么completionHandler就不能正常的运作了。在一般的单线程环境中,这种问题出现的可能性不大,但是到了多线程环境,就很不好说了。</p>
<p>针对这个问题,有引入了下面的<strong>最佳解决方案</strong>:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> MyViewController *myController = [[MyViewController alloc] init…];</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> MyViewController * __<span class="keyword">weak</span> weakMyController = myController;</span><br><span class="line"> </span><br><span class="line"> myController.completionHandler = ^(<span class="built_in">NSInteger</span> result) {</span><br><span class="line"> MyViewController *strongMyController = weakMyController;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (strongMyController) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> [strongMyController dismissViewControllerAnimated:<span class="literal">YES</span> completion:<span class="literal">nil</span>];</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// do something...</span></span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>block内部定义了一个强引用,这就保证捕获的弱引用 <code>weakMyController</code> 在block函数栈运行结束后不会释放。如果说block存在于堆上,那么 <code>strongMyController</code> 作为block的成员,也会存在于堆上,只有在blokc销毁时,它才会销毁。</p>
<p>关于理解被Block捕获的引用和在Block内定义的引用的区别,及block底层原理,请看唐巧这篇<a href="http://blog.devtang.com/2013/07/28/a-look-inside-blocks/" target="_blank" rel="external">关于block</a>的文章。</p>
<p>最后关于block再说一点。__block在MRC时代有两个作用: </p>
<ul>
<li>说明变量可改</li>
<li>说明指针指向的对象不做这个隐式的retain操作,用于避免循环引用。</li>
</ul>
<p>在ARC模式下,<strong>__block修饰符只说明变量可修改</strong>。</p>
<h3 id="五、ARC与Toll-Free-Bridging"><a href="#五、ARC与Toll-Free-Bridging" class="headerlink" title="五、ARC与Toll-Free Bridging"></a>五、ARC与Toll-Free Bridging</h3><p>Toll-Free Briding 保证了在程序中,可以方便和谐的使用 Core Foundation 类型的对象和Objective-C 类型的对象。</p>
<h4 id="1-问题的引入"><a href="#1-问题的引入" class="headerlink" title="1.问题的引入"></a>1.问题的引入</h4><p>在 MRC 时代,由于 Objective-C 类型的对象和 Core Foundation 类型的对象都是相同的 release 和 retain 操作规则,所以 Toll-Free Bridging 的使用比较简单,但是自从切换到 ARC 后,Objective-C 类型的对象内存管理规则改变了,不能使用release和retain操作,而 Core Foundation 依然是之前的机制,也就是说,<strong>Core Foundation 不支持 ARC</strong>。</p>
<p>这时候我们就需要解决一个问题:在做 Core Foundation 与 Objective-C 类型转换的时候,我们不仅要做类型转换,还要将其内存管理规则进行转换。</p>
<p>于是苹果在引入 ARC 之后对 Toll-Free Bridging 的操作也加入了对应的方法与修饰符,用来指明用哪种规则管理内存,或者说是内存管理权的归属。这些方法和修饰符分别是:</p>
<ul>
<li>__bridge(修饰符)</li>
<li>__bridge_retained(修饰符) or CFBridgingRetain(函数)</li>
<li>__bridge_transfer(修饰符) or CFBridgingRelease(函数)</li>
</ul>
<p>#####(1)__bridge</p>
<p>只是声明类型准换,不做内存管理规则转换。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="comment">//NSString 转换为 CFStringRef</span></span><br><span class="line"> <span class="built_in">CFStringRef</span> s1 = (__bridge <span class="built_in">CFStringRef</span>) [[<span class="built_in">NSString</span> alloc] initWithFormat:<span class="string">@"Hello"</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//其他对象类型转换</span></span><br><span class="line"> <span class="built_in">CFTypeRef</span> s2 = (__bridge <span class="built_in">CFTypeRef</span>)[<span class="built_in">NSObject</span> new];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>只是做了类型的转化,但管理规则未变,依然要用 Objective-C 类型的 ARC 来管理 s1,你不能用 CFRelease() 去释放 s1。</p>
<p>#####(2)__bridge_retained or CFBridgingRetain<br>表示将指针类型转变的同时,将内存管理的责任由原来的 Objective-C 交给Core Foundation 来处理,也就是,将 ARC 转变为 MRC。例如:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="built_in">NSString</span> *s1 = [[<span class="built_in">NSString</span> alloc] initWithFormat:<span class="string">@"Hello"</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CFStringRef</span> s2 = (__bridge_retained <span class="built_in">CFStringRef</span>)s1; <span class="comment">//将内存管理权交给s2</span></span><br><span class="line"> <span class="built_in">CFRelease</span>(s2); <span class="comment">// 注意要在使用结束后加这个</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这时内存管理规则由ARC变为了MRC,我们需要手动的来管理s2的内存,而对于s1,我们即使将其置为nil,也不能释放内存。</p>
<p>上面代码也等价于:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="built_in">NSString</span> *s1 = [[<span class="built_in">NSString</span> alloc] initWithFormat:<span class="string">@"Hello, %@!"</span>, name];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CFStringRef</span> s2 = (<span class="built_in">CFStringRef</span>)<span class="built_in">CFBridgingRetain</span>(s1);</span><br><span class="line"> <span class="built_in">CFRelease</span>(s2); <span class="comment">// 注意要在使用结束后加这个</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="3-bridge-transfer(修饰符)-or-CFBridgingRelease(函数)"><a href="#3-bridge-transfer(修饰符)-or-CFBridgingRelease(函数)" class="headerlink" title="(3)__bridge_transfer(修饰符) or CFBridgingRelease(函数)"></a>(3)__bridge_transfer(修饰符) or CFBridgingRelease(函数)</h5><p>这个修饰符和函数的功能和上面那个__bridge_retained相反,它表示将管理的责任由Core Foundation转交给Objective-C,即将管理方式由MRC转变为ARC。</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="built_in">CFStringRef</span> cfStr = <span class="built_in">CFURLCreateStringByAddingPercentEscapes</span>(. . .);</span><br><span class="line"> <span class="built_in">NSString</span> *str = (__bridge_transfer <span class="built_in">NSString</span> *)cfStr;</span><br><span class="line"> <span class="comment">//or NSString *str = (NSString *)CFBridgingRelease(cfStr);</span></span><br><span class="line"> <span class="keyword">return</span> str;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里我们将result的管理责任交给了ARC来处理,我们就不需要再显式调用CFRelease()了。</p>
<h3 id="六、循环引用"><a href="#六、循环引用" class="headerlink" title="六、循环引用"></a>六、循环引用</h3><p>在ARC模式下,不用我们去手动管理内存,这方便了很多,也减少了很多工作量。但是ARC模式也有它自己需要注意的问题,那就是<strong>循环引用</strong>。</p>
<h4 id="1-什么是循环引用"><a href="#1-什么是循环引用" class="headerlink" title="1.什么是循环引用"></a>1.什么是循环引用</h4><p>如下图中,对象A和对象B,相互引用对方作为自己的成员变量,只有当对象销毁时,才会将成员变量的计数器减1。但是对象A的销毁依赖于对象B的销毁,对象B的销毁依赖于对象A的销毁。他们互相依赖,谁都不能销毁,这就造成了循环引用。这样即使没有其他强引用指针指向它们,它们也不会销毁。</p>
<p><img src="/uploads/Memory-Manage-3-ARC/retainCircle.png" alt="retainCircleImg"></p>
<p>简单代码示例:</p>
<figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">void</span>)viewDidLoad {</span><br><span class="line"> [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line"> <span class="comment">//两个数组,arr1和arr2互相引用</span></span><br><span class="line"> <span class="built_in">NSMutableArray</span> *arr1 = [<span class="built_in">NSMutableArray</span> new];</span><br><span class="line"> <span class="built_in">NSMutableArray</span> *arr2 = [<span class="built_in">NSMutableArray</span> new];</span><br><span class="line"> </span><br><span class="line"> [arr1 addObject:arr2];</span><br><span class="line"> [arr2 addObject:arr1];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用Instruments测试结果:<br><img src="/uploads/Memory-Manage-3-ARC/leaksTest.png" alt="leaksTestImg"></p>
<p>还有一种复杂的循环引用情景,那就是多个对象间依次持有,形成一个环状,这也会造成循环引用问题。例如下图中的情况:<br><img src="/uploads/Memory-Manage-3-ARC/retainCircle2.png" alt="retainCircle2Img"><br>在实际项目开发中,项目的环境比较大,所以一旦产生这种多个对象之间的循环引用,修改起来十分繁琐,所以在实际开发中,应当注意。</p>
<h4 id="2-容易产生循环引用场景"><a href="#2-容易产生循环引用场景" class="headerlink" title="2.容易产生循环引用场景"></a>2.容易产生循环引用场景</h4><p>iOS开发中,有三个场景容易造成循环引用:</p>
<ul>
<li>block 使用</li>
<li>delegate 使用</li>
<li>NSTimer 使用</li>
</ul>
<p>具体如何产生于解除,请看<a href="http://www.cnblogs.com/wengzilin/p/4347974.html" target="_blank" rel="external">这篇文章</a>。</p>
<h4 id="3-避免和解除循环引用"><a href="#3-避免和解除循环引用" class="headerlink" title="3.避免和解除循环引用"></a>3.避免和解除循环引用</h4><p>1.如果想要避免产生循环引用,最长见的就是使用弱引用 (<code>weak reference</code>)。弱引用虽然持有对象,但是不增加引用计数,这样就避免了循环引用的产生。</p>
<p>2.如果循环引用已经产生,想要解除循环引用的话,需要开发者手动断开依赖对象。所以如果知道在什么时候断开循环引用回收内存,那就在相应的位置将对象手动置为 <code>nil</code>。</p>
<p>有关ARC模式下内存管理的内容,就写到这里。还请大家勘误。下一篇将介绍几种简单的内存优化方案。</p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p>1.<a href="https://www.raywenderlich.com/5677/beginning-arc-in-ios-5-part-1" target="_blank" rel="external">Beginning ARC in iOS 5 Tutorial Part 1</a></p>
<p>2.<a href="http://www.cnblogs.com/flyFreeZn/p/4264220.html" target="_blank" rel="external">iOS开发ARC内存管理技术要点</a></p>
<p>3.<a href="http://www.samirchen.com/ios-arc/" target="_blank" rel="external">iOS ARC 内存管理要点</a></p>
<p>4.<a href="https://book.douban.com/subject/26287173/" target="_blank" rel="external">iOS开发进阶</a></p>
<a id="more"></a>
<p>在<a href="http://luoanhao.github.io/2016/03/29/Memory-Manage-2-MRC/" target="_blank" rel="external">上一篇</a>文章中,我们主要<br>