-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
530 lines (291 loc) · 239 KB
/
atom.xml
File metadata and controls
530 lines (291 loc) · 239 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>张三の锅</title>
<icon>https://www.gravatar.com/avatar/ceda53888b8ef623959d14c61af2d1c0</icon>
<link href="/atom.xml" rel="self"/>
<link href="http://zhaoxinyu.me/"/>
<updated>2017-11-14T05:26:28.425Z</updated>
<id>http://zhaoxinyu.me/</id>
<author>
<name>张三</name>
<email>zhanglevan@gmail.com</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Phantom Types in Swift</title>
<link href="http://zhaoxinyu.me/2017-11-14-Phantom-Types/"/>
<id>http://zhaoxinyu.me/2017-11-14-Phantom-Types/</id>
<published>2017-11-14T05:30:00.000Z</published>
<updated>2017-11-14T05:26:28.425Z</updated>
<content type="html"><![CDATA[<p>在 Objc.io 上看到了这样<a href="https://talk.objc.io/episodes/S01E71-type-safe-file-paths-with-phantom-types" target="_blank" rel="external">一期视频</a>。主要介绍了一种叫 Phantom Types 的技巧,它的作用就是在类型(type),而不是值(value)这个层面上来表示状态,而且在编译时期对错误类型间的运算做出提示。</p><p>Phantom Types(幽灵类型) 其实就是空类型。比如这样,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Miles</span> </span>{}</div><div class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Kilometers</span> </span>{}</div></pre></td></tr></table></figure><p>它比较实际的一个应用是,让编译器帮你检查某些对象在特定的状态下能够调用哪些方法。也能通过类型来表示状态。</p><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><p>举一个 Foundation 中 API 的例子。有这样一个类 <code>NSFileHandle</code>,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">+ (<span class="keyword">nullable</span> <span class="keyword">instancetype</span>)fileHandleForReadingAtPath:(<span class="built_in">NSString</span> *)path;</div><div class="line">+ (<span class="keyword">nullable</span> <span class="keyword">instancetype</span>)fileHandleForWritingAtPath:(<span class="built_in">NSString</span> *)path;</div><div class="line"></div><div class="line">- (<span class="built_in">NSData</span> *)readDataToEndOfFile;</div><div class="line">- (<span class="keyword">void</span>)writeData:(<span class="built_in">NSData</span> *)data;</div></pre></td></tr></table></figure><p>它有几种初始化方法,当你创建一个读方式的 <code>fileHandle</code> 时,你只能调用读相关的 API,调用写相关的是没有意义的。但是在 Objective-C 中,不足以在编译时期把这个问题搞定,你还是可以开一个读的 <code>fileHandle</code>,然后对它调用写的方法,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSFileHandle</span> *handle = [<span class="built_in">NSFileHandle</span> fileHandleForReadingAtPath:<span class="string">@"path"</span>];</div><div class="line"><span class="comment">/// it does't make sense</span></div><div class="line">[handle writeData:someData];</div></pre></td></tr></table></figure><p>如何通过 Phantom Types 来解决这个问题呢?实现的方式很简单,借助 Swift 中的泛型,把当前类型下能够调用的方法定义在类型限定的 <code>extension</code> 中就可以了。</p><p>我们定义出两个 Phantom Types,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Write</span> </span>{}</div><div class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Read</span> </span>{}</div></pre></td></tr></table></figure><p>然后定义出支持泛型的 <code>FileHandle</code> 类,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FileHandle</span><<span class="title">OperationType</span>> </span>{</div><div class="line"> <span class="keyword">let</span> path: <span class="type">String</span></div><div class="line"> <span class="keyword">init</span>(<span class="number">_</span> path: <span class="type">String</span>) {</div><div class="line"> <span class="keyword">self</span>.path = path</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>把不同类型 <code>handle</code> 可以调用的方法放到不同的 <code>extension</code> 中,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">FileHandle</span> <span class="title">where</span> <span class="title">OperationType</span> == <span class="title">Write</span> </span>{</div><div class="line"> <span class="comment">/// 把初始化方法也放到各自的原因是可以让编译器推倒类型</span></div><div class="line"> <span class="keyword">static</span> <span class="function"><span class="keyword">func</span> <span class="title">handleWithWirtePath</span><span class="params">(path: String)</span></span> -> <span class="type">FileHandle</span><<span class="type">Write</span>> {</div><div class="line"> <span class="keyword">return</span> <span class="type">FileHandle</span><<span class="type">Write</span>>(path)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">write</span><span class="params">(string: String)</span></span> {</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">FileHandle</span> <span class="title">where</span> <span class="title">OperationType</span> == <span class="title">Read</span> </span>{</div><div class="line"> <span class="keyword">static</span> <span class="function"><span class="keyword">func</span> <span class="title">handleWithReadPath</span><span class="params">(path: String)</span></span> -> <span class="type">FileHandle</span><<span class="type">Read</span>> {</div><div class="line"> <span class="keyword">return</span> <span class="type">FileHandle</span><<span class="type">Read</span>>(path)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">read</span><span class="params">()</span></span> -> <span class="type">String</span> {</div><div class="line"> <span class="keyword">return</span> <span class="string">""</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>调用就很简单了,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> f = <span class="type">FileHandle</span>.handleWithReadPath(path: <span class="string">"path/to/resources"</span>)</div><div class="line">f.read()</div></pre></td></tr></table></figure><p>如果你想要通过上面的 <code>handle</code> 调用 <code>write</code> 相关的方法,编译器会给出提示,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// error: 'FileHandle<Read>' is not convertible to 'FileHandle<Write>'</span></div><div class="line">f.write(string: <span class="string">""</span>)</div></pre></td></tr></table></figure><h2 id="再多说两句"><a href="#再多说两句" class="headerlink" title="再多说两句"></a>再多说两句</h2><p>在 Haskell 中,这种应用是会在编译时期被优化掉的,所以并不会对性能产生任何影响。不知道 Swift 是不是也是一样(but who cares)。</p><p>以后在看到一些空类型的定义可以先怀疑它是不是 Phantom Types 的一种应用,也许它的出现是存在意义的。要好好利用类型系统的强大。</p><h2 id="Credits"><a href="#Credits" class="headerlink" title="Credits"></a>Credits</h2><ul><li><a href="https://talk.objc.io/episodes/S01E71-type-safe-file-paths-with-phantom-types" target="_blank" rel="external">Type-Safe File Paths with Phantom Types</a></li><li><a href="http://jayeshkawli.ghost.io/using-phantom-types-in-swift/" target="_blank" rel="external">Using Phantom Types in Swift</a></li><li><a href="https://www.natashatherobot.com/swift-money-phantom-types/" target="_blank" rel="external">Swift: Money with Phantom Types 👻</a></li></ul>]]></content>
<summary type="html">
<p>在 Objc.io 上看到了这样<a href="https://talk.objc.io/episodes/S01E71-type-safe-file-paths-with-phantom-types" target="_blank" rel="external">一期视
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>升级 Swift 4</title>
<link href="http://zhaoxinyu.me/2017-09-21-Swift-4/"/>
<id>http://zhaoxinyu.me/2017-09-21-Swift-4/</id>
<published>2017-09-21T14:37:00.000Z</published>
<updated>2017-09-22T05:23:54.000Z</updated>
<content type="html"><![CDATA[<p>公司的 CI 终于升级到了 Xcode 9,所以项目的代码也可以升级一下 Swift 4 了。因为 Xcode 9 是支持 Swift 4 和 Swift 3.2 同时存在的,如果你所有依赖的第三方库都能够支持到 Swift 3.2 及以上,那你的项目就能无痛升级了。如果真的希望一半是 3.2 一半是 4,可以通过<a href="https://gist.github.com/JohnSundell/519cb322978ac59f5ac161ff67e4413b" target="_blank" rel="external">这样</a>更改 <code>Podfile</code> 实现这种需求。</p><p>使用 Xcode 提供的转换工具 <code>Edit - Conver - To current Swift syntax…</code>,勾选默认的选项 <code>Minimize Inference</code>,就可以开始进行编译器辅助地转换流程了。这还可以减少一定的包大小,为什么呢?</p><p>对于 Objective-C 和 Swift 混合的项目,在 Objective-C 调用 Swift 代码之前是需要引用 <code>xxx-Swift.h</code> 头文件的,这个文件是由编译器动态生成的。在 Swift 4 之前,也就是对应下面的 <code>Match Swift 3 Behavior</code> ,编译器会无脑推断,把所有的 <code>pulic</code> 属性和方法暴露给 Objective-C,也就是会有很多 <code>xxx-Swift.h</code> 而且每个都很大。但是 Swift 4 中变得严格了,只有被标记为 <code>@objc</code> 的方法和属性才会暴露给 Objective-C。通过这种方式,可以减少一些代码的生成,从而减少包大小。</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170921223606_7bm7p7_Screenshot.png" alt="Minimize Inference"></p><p>点击 Next。经过一段漫长的等待(需要一次完整的编译),编译器会帮你把项目中它能转换的代码都转换了,顺便把工程设置中的 Build Setting - Swift version 改为 4。但你以为这样就大功告成了吗?Too yong!编译器能帮你做的只是一些微小的工作,而且它还可能给你改错!一定要在它改完之后再检查一次,以免发生错误。再 build 一次你就会发现大量的 error,尤其是一个 Objective-C 和 Swift 混合的项目,升级 Swift 4 就是一个不停在 Swift 属性和方法名前加 <code>@objc</code> 的过程,加到你怀疑人生。。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><a href="https://www.raywenderlich.com/163857/whats-new-swift-4" target="_blank" rel="external">What’s New in Swift 4?</a></li></ul>]]></content>
<summary type="html">
<p>公司的 CI 终于升级到了 Xcode 9,所以项目的代码也可以升级一下 Swift 4 了。因为 Xcode 9 是支持 Swift 4 和 Swift 3.2 同时存在的,如果你所有依赖的第三方库都能够支持到 Swift 3.2 及以上,那你的项目就能无痛升级了。如果真
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>如果你也用 Development Pods</title>
<link href="http://zhaoxinyu.me/2017-09-16-development-pods-dependency-checker/"/>
<id>http://zhaoxinyu.me/2017-09-16-development-pods-dependency-checker/</id>
<published>2017-09-16T08:50:00.000Z</published>
<updated>2017-09-16T08:48:15.000Z</updated>
<content type="html"><![CDATA[<!-- TOC --><ul><li><a href="#起源">起源</a></li><li><a href="#为某个-development-pods-创建独立工程">为某个 Development Pods 创建独立工程</a></li><li><a href="#development-pods-dependency-checker">Development Pods Dependency Checker</a></li></ul><!-- /TOC --><blockquote><p>如果你的 iOS 项目是使用 Development Pods 来做组件化的话,这篇文章或许值得一看。</p></blockquote><h2 id="起源"><a href="#起源" class="headerlink" title="起源"></a>起源</h2><p>知乎的 iOS 项目大概在 2016 年 Q4 开始进行组件化的工作,当时的计划就是把主 target 内的文件以 Development Pods 的形式拆分为 Basic,Core,Middleware,Module 四种类别的 Development Pods 供主 target 进行依赖。</p><p>这几层结构之间有下面这个下面这个约束关系,Pods 只能依赖比它们层级低的而不能依赖比它们层级高的,而且同层间也尽量避免依赖关系。换名话说,Core 可以依赖 Basic 但是 Basic 不能依赖 Core。这样做的原因很简单,就是避免循环引用的问题。</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170916152928_zC1Cz2_Screenshot.png" alt=""></p><p>组件化的目的之一是减少依赖,可以让一个组件独立于主 target 之外独立运行,这样为了这个组件建立单独的工程,独立编译、开发,提升效率。</p><h2 id="为某个-Development-Pods-创建独立工程"><a href="#为某个-Development-Pods-创建独立工程" class="headerlink" title="为某个 Development Pods 创建独立工程"></a>为某个 Development Pods 创建独立工程</h2><p>但是当我为一个组件单独创建工程的时候,遇到了一个问题,姑且把它叫作组件 R 吧。作为一个 Pod,R 的依赖关系是由 R.podspec 体现的,它只需要关心它直接依赖的东西就好了,对于它依赖的依赖它是不需要关心的,Cocoapods 已经帮我们处理好了。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">Pod::Spec.new <span class="keyword">do</span> <span class="params">|s|</span></div><div class="line"> s.name = <span class="string">"R"</span></div><div class="line"><span class="comment"># some other fields...</span></div><div class="line"> s.dependency <span class="string">'A'</span></div><div class="line"> s.dependency <span class="string">'C'</span></div><div class="line"> s.dependency <span class="string">'E'</span></div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><p>比如,上图的 R 依赖了 A、C、E 三个 Pods。</p><p>为 R 创建依赖独立工程的时候,需要把 R 作为这个工程的一个依赖,使用路径的形式引入。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment"># Podfile</span></div><div class="line"></div><div class="line">target <span class="string">'Example'</span> <span class="keyword">do</span></div><div class="line"> pod <span class="string">'R'</span>, <span class="symbol">:path</span> => <span class="string">'../../R'</span>,</div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><p>如果你以为这样写就大功告成了,就大错特错了。执行 <code>pod install</code> 后就会发现 <code>未能找到 A</code> 类似的错误。为什么会出现这样的问题呢?其实我也不知道具体的原因😂,但是一个可能的猜测是<br> <code>Podfile</code> 中的依赖关系必须要明确,尤其是这些 pods 都是以本地路径关系相互依赖的情况下。而 <code>podspec</code> 中的依赖不需要那么明确,Cocoapods 会帮你处理。</p><p>因为我们使用 Development Pods,所以所有的 Pods 必然以本地路径的关系进行依赖。所以在为 R 创建独立工程的时候,需要把 R 依赖的所有 Pods 的路径标记出来给 CocoaPods 看。所以你可能会写出下面的 <code>Podfile</code> ,</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">target <span class="string">'Example'</span> <span class="keyword">do</span></div><div class="line"> pod <span class="string">'R'</span>, <span class="symbol">:path</span> => <span class="string">'../../R'</span></div><div class="line"> pod <span class="string">'A'</span>, <span class="symbol">:path</span> => <span class="string">'../../A'</span></div><div class="line"> pod <span class="string">'C'</span>, <span class="symbol">:path</span> => <span class="string">'../../C'</span></div><div class="line"> pod <span class="string">'E'</span>, <span class="symbol">:path</span> => <span class="string">'../../E'</span></div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><p>满欣欢喜地跑去 <code>pod install</code> ,结果又失败了,原来 C 还依赖了 B!于是你又跑去给 <code>podfile</code> 加了一行,</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">target <span class="string">'Example'</span> <span class="keyword">do</span></div><div class="line"> pod <span class="string">'R'</span>, <span class="symbol">:path</span> => <span class="string">'../../R'</span></div><div class="line"> pod <span class="string">'A'</span>, <span class="symbol">:path</span> => <span class="string">'../../A'</span></div><div class="line"> pod <span class="string">'B'</span>, <span class="symbol">:path</span> => <span class="string">'../../B'</span></div><div class="line"> pod <span class="string">'C'</span>, <span class="symbol">:path</span> => <span class="string">'../../C'</span></div><div class="line"> pod <span class="string">'E'</span>, <span class="symbol">:path</span> => <span class="string">'../../E'</span></div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><p>好吧,你又失败了,E 还依赖了 D…… Stop! 如果总是这样一次又一次 <code>pod install</code> 才能知道自己全部依赖的 Pods 有什么是不是太搓了?如果依赖少还好,但是对于依赖很多情况,这样一次次处理就有点 2 了。能不能有什么别的方法能一次性知道某个 Development Pod 的全部依赖呢?</p><p>答案是可以的,感兴趣的可以直接移步这里 <a href="https://github.com/X140Yu/development-pods-dependency-checker" target="_blank" rel="external">X140Yu/development-pods-dependency-checker</a> </p><h2 id="Development-Pods-Dependency-Checker"><a href="#Development-Pods-Dependency-Checker" class="headerlink" title="Development Pods Dependency Checker"></a>Development Pods Dependency Checker</h2><p>效果在 repo 里的 README 就可以看到了,它能找出工程中的所有依赖,并且把每个 Pod 的依赖展开,显示出它的全部依赖,而不是 podspec 中的那一小部分。</p><p>它的原理很简单,在 <code>pod install</code> 成功之后, <code>Pods/Local Podspecs</code> 目录中会出现一堆 <code>*.podspec.json</code> 文件,你可以把它们理解为 <code>podspec</code> 的 JSON 版本,这对 JS 处理更加友好。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// R.podspec.json</span></div><div class="line">{</div><div class="line"> <span class="string">"name"</span>: <span class="string">"R"</span>,</div><div class="line"> <span class="string">"version"</span>: <span class="string">"0.0.1"</span>,</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> <span class="string">"dependencies"</span>: {</div><div class="line"> <span class="string">"A"</span>: [],</div><div class="line"> <span class="string">"C"</span>: [],</div><div class="line"> <span class="string">"E"</span>: []</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>而作为 localpods 的 A, C, E 也会有类似这样的文件。所以我们要做的事情就很简单了,挨个 parse 各个 JSON 文件,递归处理 <code>dependencies</code> 中的每一项,直到它的 <code>dependencies</code> 是空为止。其实核心的逻辑也就下面这一小段,</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">trigger</span>(<span class="params">pods</span>)</span>{</div><div class="line"> <span class="keyword">let</span> podsRet =[]</div><div class="line"> pods.forEach(<span class="function"><span class="params">ele</span> =></span>{</div><div class="line"> podsRet.push(recursiveFindDependencies(ele, pods))</div><div class="line"> })</div><div class="line"> <span class="keyword">return</span> podsRet</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">recursiveFindDependencies</span>(<span class="params">ele, pods</span>)</span>{</div><div class="line"> <span class="keyword">let</span> pod ={}</div><div class="line"> <span class="keyword">if</span> (ele.name == <span class="literal">undefined</span>){</div><div class="line"> <span class="keyword">return</span></div><div class="line"> }</div><div class="line"> pod.name = ele.name</div><div class="line"> pod.dependencies =[]</div><div class="line"> ele.dependencies.forEach(<span class="function"><span class="params">dep</span> =></span>{</div><div class="line"> <span class="keyword">if</span> (pod.dependencies.filter(<span class="function"><span class="params">es</span> =></span> es == dep).length == <span class="number">0</span>){</div><div class="line"> pod.dependencies.push(dep)</div><div class="line"> }</div><div class="line"> <span class="keyword">let</span> deps = findDependencies(pods, dep).forEach(<span class="function"><span class="params">e</span> =></span>{</div><div class="line"> <span class="built_in">Array</span>.prototype.push.apply(pod.dependencies, recursiveFindDependencies(e, pods))</div><div class="line"> </div><div class="line"> <span class="keyword">if</span> (pod.dependencies.filter(<span class="function"><span class="params">es</span> =></span> es == e).length == <span class="number">0</span>){</div><div class="line"> pod.dependencies.push(e)</div><div class="line"> }</div><div class="line"> })</div><div class="line"> })</div><div class="line"> <span class="keyword">return</span> pod</div><div class="line">}</div></pre></td></tr></table></figure><p>有了这个工具以后,就能够看清一个 pod 到底依赖了什么,首先它更便于为 pod 创建独立运行的工程,其次,也能在开发阶段知道依赖关系,避免同层依赖及低层向更高层的 pod 依赖。</p><p>关于这个小工具,还想说两句。它是用 <a href="https://electron.atom.io/" target="_blank" rel="external">electron</a> 写的,其实这东西完全用不上 electron 的跨平台便利性,因为它只可能在 mac 上运行,但我还是脑一抽用它写了,毕竟 HTML & CSS 比原生开发效率高得多。</p>]]></content>
<summary type="html">
<!-- TOC -->
<ul>
<li><a href="#起源">起源</a></li>
<li><a href="#为某个-development-pods-创建独立工程">为某个 Development Pods 创建独立工程</a></li>
<li><a href=
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="electron" scheme="http://zhaoxinyu.me/tags/electron/"/>
</entry>
<entry>
<title>为你的 pod 添加测试</title>
<link href="http://zhaoxinyu.me/2017-08-05-add-test-to-your-pod/"/>
<id>http://zhaoxinyu.me/2017-08-05-add-test-to-your-pod/</id>
<published>2017-08-05T14:00:00.000Z</published>
<updated>2017-08-05T14:07:42.000Z</updated>
<content type="html"><![CDATA[<!-- TOC --><ul><li><a href="#过去我们如何添加测试">过去我们如何添加测试</a></li><li><a href="#现在我们如何添加测试">现在我们如何添加测试</a></li><li><a href="#举个例子">举个例子</a></li><li><a href="#something-else">Something else</a></li></ul><!-- /TOC --><p>前几天 Cocoapods release 了 <a href="http://link.zhihu.com/?target=http%3A//blog.cocoapods.org/CocoaPods-1.3.0/" target="_blank" rel="external">1.3.0</a> ,一个比较令人激动的功能是可以在 pod 内部添加测试了。</p><h2 id="过去我们如何添加测试"><a href="#过去我们如何添加测试" class="headerlink" title="过去我们如何添加测试"></a>过去我们如何添加测试</h2><p>在过去不支持这个功能的时候,如果要为一个 pod 添加测试就需要另外一个工程依赖之,然后把测试文件都添加到离此 pod 十万八千里的地方,如下图所示,</p><p><img src="https://pic1.zhimg.com/v2-dc07e962a10db0e609a37fc855a3f080_b.png" alt=""></p><p>这样子的弊端是十分明显的,</p><p>首先就是<strong>代码和测试分离了</strong>(关于为什么要把代码和测试放到一起,<a href="http://link.zhihu.com/?target=https%3A//kickstarter.engineering/why-you-should-co-locate-your-xcode-tests-c69f79211411" target="_blank" rel="external">这篇文章</a>解释地很清楚)。这种分离还不是同一个 project 中不同 target 的分离,而是不同 project 的那种分离。也就是说,我为了看到这个 pod 中的测试,还必须要下载对应的 project 打开才能看到。</p><p>其次,这个 pod 中的测试没法办作为一个 target 单独运行。当然了,这只是个小问题。</p><h2 id="现在我们如何添加测试"><a href="#现在我们如何添加测试" class="headerlink" title="现在我们如何添加测试"></a>现在我们如何添加测试</h2><p>那 CocoaPods 1.3 之后是怎样了呢?</p><p><img src="https://pic4.zhimg.com/v2-54e34b9e175ca1a85492d0f05a9946af_b.png" alt=""></p><p>可以看到,源码和测试已经在一起了。如果我们想看到对应 pod 中的测试,现在已经不需要下载它 Example 或者 Demo 的 project 来看了,而是可以直接在对应的 pod 文件夹中看到,十分方便。而且这个 pod 中的测试可以通过一个单独的 target 运行起来(如果看不到需要在 <code>Manage Schemes</code> 中把测试 target 勾选 <code>show</code>)。于是上面两个问题都得到了解决。</p><p><img src="https://pic4.zhimg.com/v2-db1f12ee5f31964825d030dea3d5233f_b.png" alt=""></p><h2 id="举个例子"><a href="#举个例子" class="headerlink" title="举个例子"></a>举个例子</h2><p>我的一个 pod <a href="http://link.zhihu.com/?target=https%3A//github.com/X140Yu/pangu.Swift" target="_blank" rel="external">pangu.Swift</a> 收到了一条来自 <a href="http://www.zhihu.com/people/ce825ce5cb9e00b0c9046197f9edbcbd" target="_blank" rel="external">@Gao JiJi</a> 的 issue,</p><p><img src="https://pic4.zhimg.com/v2-4cbfa018fe2e7a94c32a4d0c6370f087_b.png" alt=""></p><p>作为一个有追求的 engineer,怎么会不写测试呢?我们就以这个 repo 为例,看看如何为这个 pod 添加测试。</p><p>首先你要确保 Cocoapods 的版本 >= 1.3,版本不够的同学可以执行,</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">gem install cocoapods</div></pre></td></tr></table></figure><p>打开对应的 podspec,添加下面几行,</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">Pod::Spec.new <span class="keyword">do</span> <span class="params">|s|</span></div><div class="line"></div><div class="line"> <span class="comment"># ...</span></div><div class="line"></div><div class="line"> s.test_spec <span class="string">'Tests'</span> <span class="keyword">do</span> <span class="params">|test_spec|</span></div><div class="line"> test_spec.source_files = <span class="string">'Tests/*.swift'</span></div><div class="line"> <span class="keyword">end</span></div><div class="line"></div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><p>意思是我们添加了一个叫 <code>Tests</code> 的 <code>test_spec</code>,里面测试文件的位置在 <code>Tests/*.swift</code>。</p><p>如果你的测试需要依赖其它 pods,可以像 sub_spec 一样,添加 test_spec 的 dependency,具体做法 cocoapods 中的那篇文章中有写。</p><p>然后我们就可以在 <code>Tests</code> 目录中添加测试文件了,添加完毕后别忘记自己测试一下,然后就可以升级个版本然后 push 到 spec repo 中了。是的,就是这么简单。</p><p>对于此 pod 使用者,如果你不想看到它的测试,那什么都不用做就好了。但是如果你想在工程里看到并运行对应的测试,就需要给 Podfile 中的引用添加一个参数 <code>testspecs</code>,</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">target <span class="string">'Demo'</span> <span class="keyword">do</span></div><div class="line"></div><div class="line"> pod <span class="string">'Pangu-Swift'</span>, <span class="symbol">:testspecs</span> => [<span class="string">'Tests'</span>]</div><div class="line"></div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><p>这个参数对应的是它 podspec 中的 ‘Tests’。</p><p>更改之后记得 <code>pod install</code>,这样,此 pod 中的测试文件就都被引入了,而且还多一个测试的 target 以支持单独运行。</p><h2 id="Something-else"><a href="#Something-else" class="headerlink" title="Something else"></a>Something else</h2><ul><li>testspec 和 subspec 一样,都支持多个。如果你的 pod 测试太多,可以把它们拆到不同的 testspec 中</li><li>写测试是一种美德</li></ul>]]></content>
<summary type="html">
<!-- TOC -->
<ul>
<li><a href="#过去我们如何添加测试">过去我们如何添加测试</a></li>
<li><a href="#现在我们如何添加测试">现在我们如何添加测试</a></li>
<li><a href="#举个例子">举个例子</a></
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>聊聊 Result 这个 Swift µframework</title>
<link href="http://zhaoxinyu.me/2017-07-23-result/"/>
<id>http://zhaoxinyu.me/2017-07-23-result/</id>
<published>2017-07-23T14:51:33.000Z</published>
<updated>2017-12-03T15:26:42.000Z</updated>
<content type="html"><![CDATA[<blockquote><p>本文主要介绍了 <a href="https://github.com/antitypical/Result" target="_blank" rel="external">Result</a> 这个 Swift 的 µframework 解决的问题以及其基本的使用。</p></blockquote><p>先来看一段我们曾经写过无数次的代码,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">login</span><span class="params">(userName: String, password: String, completionHandler: @escaping <span class="params">(token: String?, error: Error?)</span></span></span> -> <span class="type">Void</span>) {</div><div class="line"> <span class="comment">// network request ...</span></div><div class="line">}</div></pre></td></tr></table></figure><p>这么看可能看不出什么问题,我们来试着调用一下这个方法吧,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="type">RequestHelper</span>.login(username: <span class="string">"123"</span>, password: <span class="string">"123"</span>) { (token, error) <span class="keyword">in</span></div><div class="line"> <span class="comment">/// how to handle token & error?</span></div><div class="line">}</div></pre></td></tr></table></figure><p><code>completionHandler</code> 中的 token 和 error 是包在一个括号里的,包在一个括号里说明它们构成一个 tupple,tupple 代表 <code>and</code> 的意思,而且这两个参数还都是 optional 的,也就说明这个 completion 的参数有以下四种情况,</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170723172210_MAvDKk_Screenshot.png" alt=""></p><p>可以看出,只有 (1) 和 (4) 的参数组合对于调用者是有意义的。你可能会提出疑问,(2) 和 (3) 的组合应该不会有人去判断吧?的确,但是这是因为你的判断建立在大家或者团队的「隐性」约定之上,我们认为这两种情形的组合是不应当存在,所以才不去判断,而不是让代码明确地说明根本没有这两种组合的情况。</p><p>这个 <code>completionHandler</code> 参数的语意代表的应当是 <code>token | error</code> 而不是 <code>token? & error?</code> 既然 tuple 是代表 <code>&</code>,那什么什么代表 <code>|</code> 呢?</p><p>没错,就是 enum!</p><p><a href="https://github.com/antitypical/Result" target="_blank" rel="external">Result</a> 这个简单的 library 就是解决这类问题的。</p><h2 id="Result-解决了什么问题"><a href="#Result-解决了什么问题" class="headerlink" title="Result 解决了什么问题"></a>Result 解决了什么问题</h2><p>在使用了 Result 之后,函数的定义及实现变成了这样,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="class"><span class="keyword">enum</span> <span class="title">LoginError</span>: <span class="title">Error</span> </span>{</div><div class="line"> <span class="keyword">case</span> noNetwork</div><div class="line"> <span class="keyword">case</span> wrongUsername</div><div class="line"> <span class="keyword">case</span> wrongPassword</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">func</span> <span class="title">login</span>(<span class="title">username</span>: <span class="title">String</span>, <span class="title">password</span>: <span class="title">String</span>, <span class="title">completion</span>: @<span class="title">escaping</span> (<span class="title">Result</span><<span class="title">String</span>, <span class="title">LoginError</span>>) -> <span class="title">Void</span>) </span>{</div><div class="line"> <span class="comment">// network request</span></div><div class="line"> completion(.success(<span class="string">"123jl123"</span>))</div><div class="line"> <span class="comment">// or</span></div><div class="line"> completion(.failure(.wrongPassword))</div><div class="line">}</div></pre></td></tr></table></figure><p>调用者视角,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="type">RequestHelper</span>.login(username: <span class="string">"123"</span>, password: <span class="string">"123"</span>) { (result) <span class="keyword">in</span></div><div class="line"> <span class="keyword">switch</span> result {</div><div class="line"> <span class="keyword">case</span> .success(<span class="keyword">let</span> token):</div><div class="line"> <span class="comment">// do something with token</span></div><div class="line"> <span class="keyword">break</span></div><div class="line"> <span class="keyword">case</span> .failure(<span class="keyword">let</span> error):</div><div class="line"> <span class="keyword">switch</span> error {</div><div class="line"> <span class="keyword">case</span> .noNetwork:</div><div class="line"> <span class="keyword">break</span></div><div class="line"> <span class="keyword">case</span> .wrongUsername:</div><div class="line"> <span class="keyword">break</span></div><div class="line"> <span class="keyword">case</span> .wrongPassword:</div><div class="line"> <span class="keyword">break</span></div><div class="line"> }</div><div class="line"> <span class="keyword">break</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>不觉得生活更美好了吗?这其中没有隐性的约定,没有出现理解不了的情况组合,只需要按照 swich 里面的 case 处理对应的情况就好了。</p><p>所以 Result 解决了一类问题,就是把<em>可能成功</em>或者<em>可能失败</em>的情况组合从 tuple 变为了 enum。</p><p>Swift 的网络请求库 <a href="https://github.com/Moya/Moya/blob/master/Moya.podspec#L26" target="_blank" rel="external">Moya</a> 依赖了这个小框架,另一个著名的网络库 <a href="https://github.com/Alamofire/Alamofire/blob/master/Source/Result.swift" target="_blank" rel="external">Alamofire</a> 也有类似的设计。</p><p>那么问题来了,如果只是为了一个 enum 的定义,我们有什么理由依赖一个 framework 呢?</p><h2 id="Result-中的高阶函数"><a href="#Result-中的高阶函数" class="headerlink" title="Result 中的高阶函数"></a>Result 中的高阶函数</h2><p>看文档会知道 Result 类型实现了 <code>map</code> 和 <code>flatMap</code> 函数,所以 Result 是个 Monad(如果你不知道什么是 Monad 没有关系,我只是跩了一个名词,千万不要一见到 Monad 就害怕了,其实没有什么复杂的东西)。我们简单地看一下 Result 的 <code>map</code> 和 <code>flatMap</code> 都做了什么,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// Returns a new Result by mapping `Success`es’ values using `transform`, or re-wrapping `Failure`s’ errors.</span></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">func</span> <span class="title">map</span><U><span class="params">(<span class="number">_</span> transform: <span class="params">(<span class="keyword">Self</span>.Value)</span></span></span> -> <span class="type">U</span>) -> <span class="type">Result</span>.<span class="type">Result</span><<span class="type">U</span>, <span class="type">Self</span>.<span class="type">Error</span>></div><div class="line"></div><div class="line"><span class="comment">/// Returns the result of applying `transform` to `Success`es’ values, or re-wrapping `Failure`’s errors.</span></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">func</span> <span class="title">flatMap</span><U><span class="params">(<span class="number">_</span> transform: <span class="params">(<span class="keyword">Self</span>.Value)</span></span></span> -> <span class="type">Result</span>.<span class="type">Result</span><<span class="type">U</span>, <span class="type">Self</span>.<span class="type">Error</span>>) -> <span class="type">Result</span>.<span class="type">Result</span><<span class="type">U</span>, <span class="type">Self</span>.<span class="type">Error</span>></div></pre></td></tr></table></figure><p>假如我们的类型是这样的,<code>Result<T, E></code>(T 是 value 的类型,E 是 Error 的类型),观察上面的函数签名,</p><p>对它调用 <code>map</code> 方法,会返回一个新的 <code>Result<U, E></code>,也就是 Value 类型发生了改变,而 Error 的类型还跟原来一样。</p><p>对它调用 <code>flatMap</code> 方法,会返回一个新的 <code>Result<U, F></code>,value 和 error 的类型都可以被改变,</p><p>通过一个例子来理解一下。比如有这样一个 Result,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> loginResult = <span class="type">Result</span><<span class="type">String</span>, <span class="type">LoginError</span>>(<span class="string">"123"</span>)</div></pre></td></tr></table></figure><p>对它调用 <code>map</code>,<code>transform</code> 函数给了你一个改变 value 类型的机会,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">loginResult.<span class="built_in">map</span> { token -> <span class="type">U</span> <span class="keyword">in</span></div><div class="line"> <span class="comment">/// transform token to any type you like</span></div><div class="line">}</div></pre></td></tr></table></figure><p>对它调用 <code>flatMap</code>,<code>transform</code> 函数给了你一个返回一个类型完全不一样的 <code>Result</code> 的机会,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">loginResult.flatMap { token -> <span class="type">Result</span><<span class="type">U</span>, <span class="type">E</span>> <span class="keyword">in</span></div><div class="line"> <span class="comment">// trnasform token or error to any type you like</span></div><div class="line">}</div></pre></td></tr></table></figure><p>很多 framwork 中的类型都实现了 <code>map</code> 和 <code>flatMap</code>,比如 RxSwift 中的 <code>Observable</code>,Promise 中的 <code>Promise</code>(虽然命名可能是 <code>then</code> 但跟 <code>flatMap</code> 的签名是类似的)。</p><p>有了这些方法就可以对 Result 进行更多的变换操作,从而把一个不知道从什么地方飞来的 Result 变成一个你真正需要的 Result。</p><h2 id="Result-的价值"><a href="#Result-的价值" class="headerlink" title="Result 的价值"></a>Result 的价值</h2><p>它 repo 中的 README 有这样一句话,</p><blockquote><p>Using this µframework instead of rolling your own Result type allows you to easily interface with other frameworks that also use Result.</p></blockquote><p>对于很多 framework 来说,它们都需要像 Result 类似的数据结构来代表可能成功可能失败的结果。所以 Result 的愿景是,不希望大家再定义属于自己的 Result 类型,而是使用我,这样在 Result 之间的转换也比较轻松,而且还省去了很多类似的冗余代码。</p><p>如果你依赖的 framwork 已经依赖了 Result,那么在写下一个类似 <code>login</code> 的函数时,试试返回一个 Result;没有依赖也没有关系,在写下一个返回可能成功可能失败的多个参数的函数时,用 <code>enum</code> 代替 <code>tuple</code> 吧。</p>]]></content>
<summary type="html">
<blockquote>
<p>本文主要介绍了 <a href="https://github.com/antitypical/Result" target="_blank" rel="external">Result</a> 这个 Swift 的 µframework 解决的问
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>如何正确书写 iOS 中的 Initializer</title>
<link href="http://zhaoxinyu.me/2017-07-16-iOS-initializers/"/>
<id>http://zhaoxinyu.me/2017-07-16-iOS-initializers/</id>
<published>2017-07-16T08:46:33.000Z</published>
<updated>2017-07-16T08:47:41.000Z</updated>
<content type="html"><![CDATA[<p>开发过程中,写 Initializer 应该是日常。会写的可以直接不看。如果没有好好了解过如何写 Initializer,每次都凭着感觉写而且还没有任何问题,说明其中的「玄机」你已经掌握了,可以大概扫一扫。但是每次写 Initializer 都要查 Google,那这篇文章就是给你(我🌚)准备的</p><h2 id="Initializer-的种类"><a href="#Initializer-的种类" class="headerlink" title="Initializer 的种类"></a>Initializer 的种类</h2><p>一个类中可能会有多个 Initializer,但不管有多少,都只会是下面两种类型中的一种。</p><h3 id="Designated-Initializer-指定初始化方法"><a href="#Designated-Initializer-指定初始化方法" class="headerlink" title="Designated Initializer (指定初始化方法)"></a>Designated Initializer (指定初始化方法)</h3><p>是类中的一等 Initializer,会把类中所有相关的属性都初始化。需要调用父类的 Designated Initializer。</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170716155049_I9fzIr_Screenshot.png" alt=""></p><h3 id="Convenience-Initializer-便捷初始化方法"><a href="#Convenience-Initializer-便捷初始化方法" class="headerlink" title="Convenience Initializer (便捷初始化方法)"></a>Convenience Initializer (便捷初始化方法)</h3><p>是类中的二等 Initializer,会调用类中的 Designated Initializer 或 Convenience Initializer,把其它没有传进来的属性初始化为默认值。主要是给调用者以方便。被其它 Convenience Initializer 调用的 Convenience Initializer,必须调用同类中的 Designated Initializer。</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170716155026_ntKTia_Screenshot.png" alt=""></p><p>那么如何区分类中的初始化方法是 designated 的还是 convenience 的呢?有几个方法,</p><ul><li>通常来说,Designated Initializer 的参数要比 Convenience Initializer 多,因为指定初始化方法希望把这个类中的相关属性都初始化掉,而便捷初始化方法只会初始化掉一部分,其余不太要紧的属性则会提供默认值。</li><li>看这个方法的声明里有没有使用 <code>NS_DESIGNATED_INITIALIZER</code> 标记。</li></ul><p>结合一个 <code>UIViewController</code> 子类,看看 Initializer 到底该怎么写。</p><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><p>不考虑使用 StoryBoard 的情况</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ProfileViewController</span> : <span class="title">UIViewController</span></span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *userID;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *gender; <span class="comment">// "0" or "1"</span></div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure><p>在初始化的时候,<code>userID</code> 是必须要被初始化的,而 <code>gender</code> 在不知道的情况下可以被初始化为默认值,后续可以通过网络请求更新这个值。那么这个类的初始化方法大概应该长成这个样子,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">+ (<span class="keyword">instancetype</span>)new <span class="built_in">NS_UNAVAILABLE</span>; <span class="comment">// (1)</span></div><div class="line">- (<span class="keyword">instancetype</span>)init <span class="built_in">NS_UNAVAILABLE</span>; <span class="comment">// (2)</span></div><div class="line">- (<span class="keyword">instancetype</span>)initWithCoder:(<span class="built_in">NSCoder</span> *)aDecoder <span class="built_in">NS_UNAVAILABLE</span>; <span class="comment">// (3)</span></div><div class="line">- (<span class="keyword">instancetype</span>)initWithNibName:(<span class="built_in">NSString</span> *)nibNameOrNil bundle:(<span class="built_in">NSBundle</span> *)nibBundleOrNil <span class="built_in">NS_UNAVAILABLE</span>; <span class="comment">// (4)</span></div><div class="line"></div><div class="line">- (<span class="keyword">instancetype</span>)initWithUserID:(<span class="built_in">NSString</span> *)userID gender:(<span class="built_in">NSUInteger</span>)gender <span class="built_in">NS_DESIGNATED_INITIALIZER</span>; <span class="comment">// (5)</span></div><div class="line">- (<span class="keyword">instancetype</span>)initWithUserID:(<span class="built_in">NSString</span> *)userID; <span class="comment">// (6)</span></div></pre></td></tr></table></figure><p>从上往下看,(1) ~ (4) 都是从父类中继承来的初始化方法,虽然我们的类能够使用这些方法初始化,但是我们不希望调用者使用这些方法,因为这会导致 <code>userID</code> 没有被初始化,逻辑就会不正确了,作为一个有节操的 programmer,把这些方法标记为 <code>NS_UNAVAILABLE</code>,告诉调用者,不要使用这些方法初始化,Xcode 也会有调用这些方法的地方抛出错误。</p><p>(5) 会初始化掉所有相关的属性,所以它是作为 Designated Initializer 的存在,同样作为一个有节操的 programmer,我们把它标记为 <code>NS_DESIGNATED_INITIALIZER</code>,这个标记会做以下几件事情,</p><ul><li>在实现这个指定初始化方法时,如果没有调用父类的 Designated Initializer,Xcode 会抛出警告。</li><li>Xcode 会把其它没有这个标记的初始化方法自动认为是 Convenience Initializer,包括从父类继承来的 Designated Initializer,并在实现的时候检查函数内有没有调用同类中的其它初始化方法 (Convenience 和 Designated 都可以)。</li></ul><p>所以这个标记是非常有用的,如果你在适当的初始化方法前面加了这个标记,而且实现类中的所有初始化方法后还没有警告,那么出错几乎是不可能的。所以在实现新类初始化方法的时候都应该添加这个标记,让编译器提醒你到底应该做什么事情,减少犯错误的可能。</p><p>(6) 是一个 Convenience Initializer。</p><p>看到这里,实现上面类中的两个初始化方法应该很容易了吧,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">instancetype</span>)initWithUserID:(<span class="built_in">NSString</span> *)userID {</div><div class="line"> <span class="keyword">return</span> [<span class="keyword">self</span> initWithUserID:userID gender:<span class="string">@"1"</span>];</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">instancetype</span>)initWithUserID:(<span class="built_in">NSString</span> *)userID gender:(<span class="built_in">NSString</span> *)gender {</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">self</span> = [<span class="keyword">super</span> initWithNibName:<span class="string">@""</span> bundle:<span class="literal">nil</span>]) {</div><div class="line"> _userID = userID;</div><div class="line"> _gender = gender;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>一句话总结,子类的指定初始化方法必须调父类的指定初始化方法;子类的便捷初始化方法必须调用同类的其它初始化方法。</p><h2 id="Swift"><a href="#Swift" class="headerlink" title="Swift"></a>Swift</h2><p>Swift 跟 Objective-C 不太一样,不会默认要求你重写父类的指定初始化方法。如果覆写了父类中的指定初始化方法,一定要加上 <code>override</code> 关键字。</p><p>加了 <code>convience</code> 关键字的就是 Convenience Initializer,规则与 Objective-C 的相同。</p><p>对于初始化方法前面没有 <code>convience</code> 关键字的,编译器都会把它当作 Designated Initializer,跟 Objective-C 初始化方法前面加 <code>NS_DESIGNATED_INITIALIZER</code> 是一样的。在实现这些方法时,可以参考上面的规则。</p><p>对于可能失败的初始化方法,需要把 <code>init</code> 关键字换成 <code>init?</code>,调用此类初始化方法时,返回值也会从确认有值,变成一个 optional。</p><p>所以一个 Swift 的 UIViewController 子类大概需要这样写,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ProfileViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">let</span> userID: <span class="type">String</span></div><div class="line"> <span class="keyword">let</span> gender: <span class="type">String</span></div><div class="line"></div><div class="line"> <span class="keyword">init</span>(userID: <span class="type">String</span>, gender: <span class="type">String</span>) {</div><div class="line"> <span class="keyword">self</span>.userID = userID</div><div class="line"> <span class="keyword">self</span>.gender = gender</div><div class="line"> <span class="keyword">super</span>.<span class="keyword">init</span>(nibName: <span class="literal">nil</span>, bundle: <span class="literal">nil</span>)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">override</span> <span class="keyword">init</span>(nibName nibNameOrNil: <span class="type">String</span>?, bundle nibBundleOrNil: <span class="type">Bundle</span>?) {</div><div class="line"> <span class="comment">// Swift 没有类似于 Objective-C 的 NS_UNAVAILABLE 标记,所以不希望被调到的初始化方法 fatalError 就好。</span></div><div class="line"> <span class="comment">// 据我观察,只要初始化方法中写了 `fatalError`,编译器不会对此方法是否调用其它初始化方法做任何检查</span></div><div class="line"> <span class="built_in">fatalError</span>()</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">required</span> <span class="keyword">init</span>?(coder aDecoder: <span class="type">NSCoder</span>) {</div><div class="line"> <span class="built_in">fatalError</span>(<span class="string">"init(coder:) has not been implemented"</span>)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="Q-amp-A"><a href="#Q-amp-A" class="headerlink" title="Q&A"></a>Q&A</h2><p>Q: 为啥写个初始化方法还有这么多讲究?<br>A: 保证调用初始化方法后,由这个类引入的 property 都正确地被初始化,从而确保逻辑正确。</p><p>Q: Convenience Initializer 到底有啥用?<br>A: 就是给调用者图个方便嘛。比如 Texture 中的 ASDisplayNode 就提供了 4 个 Convenience Initializer,不需要初始化之后再 <code>node.xxxBlock = ^() {};</code> ,而是把常用的 block property 在初始化的时候就给你了,就是为了方便。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">instancetype</span>)init <span class="built_in">NS_DESIGNATED_INITIALIZER</span>;</div><div class="line">- (<span class="keyword">instancetype</span>)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock;</div><div class="line">- (<span class="keyword">instancetype</span>)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(<span class="keyword">nullable</span> ASDisplayNodeDidLoadBlock)didLoadBlock;</div><div class="line">- (<span class="keyword">instancetype</span>)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock;</div><div class="line">- (<span class="keyword">instancetype</span>)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(<span class="keyword">nullable</span> ASDisplayNodeDidLoadBlock)didLoadBlock;</div></pre></td></tr></table></figure><p>Q: 父类中的 Designated Initializer 在子类中还是 Designated Initializer 吗?<br>A: 父类中的 Designated Initializer 在子类中不一定必须是 Designated Initializer。如果在头文件中没对方法进行任何标记,那么父类中的 Designated Initializer 在子类中依旧是 Designated Initializer,如果在子类没有覆写这个方法,编译器会给出警告;但如果在子类中对父类的 Designated Initializer 标记为 <code>NS_UNAVAILABLE</code>,那么父类中的这个方法在子类中不再是 Designated Initializer,即使不覆写,编译器也不会有任何警告。</p><p>Q: Objective-C 中的初始化方法中,引用 propery 的时候,到底该使用下划线的方式还是 <code>self.</code> 的方式?<br>A: Prefer <code>_userID</code> to <code>self.userID</code>。</p><p>Q: 为何在初始化方法中, Swift 类的变量需要在调用父类的初始化方法之前初始化?<br>A:<br>根据文档,</p><blockquote><p>Class initialization in Swift is a two-phase process. In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.</p></blockquote><p>根 Objective-C 初始化不同的地方在第一步,就是把此类加入的 property 都初始化掉。因为 Swift 并不会像 Objective-C 一样,在没有显式初始化这些 property 的时候,自动把它们设为 <code>nil</code> 或 <code>0</code>。</p><p>举个例子解释下上面的情况。新手写 Swift 通常会遇到这样的情况,</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170716161322_RSMqFj_Screenshot.png" alt=""></p><p>编译器抱怨 <code>userID</code> 需要在 <code>super.init</code> 之前被初始化,就是因为 <code>userID</code> 没有默认值,如果在初始化的时候,我们还没给它提供值,那就会产生问题了(从编译器的角度来看,初始化的时候需要确保所有 property 都确认有值(nil 也算确认有值)。但你现在并没有告诉我它的值是什么。 )可是编译器为何没有抱怨 <code>gender</code> 和 <code>age</code> 呢?因为它们都有默认值(optional 在默认的情况下为 nil)。</p><p>Q: 为什么每次创建 Swift UIViewController 子类,只要添加了任何一个初始化方法,Xcode 都会提醒要添加 <code>required init?(coder:)</code> 方法?<br>A: 因为 UIViewController 符合了 <code>NSCoding</code> 协议,其中有一个方法是 Designated Initializer,子类必须要实现这个协议的初始化方法。</p><p>Q: 一个类可能会有多个 Designated Initializer 吗?<br>A: 可能。比如 <code>NSArray</code> 就有三个。但一般情况下都是只有一个。</p><p>上面只是一些我自己的困惑,如果你也有困惑,欢迎留言。</p><p>这下应该可以开开心心地写 Initializer 了吧 :]</p>]]></content>
<summary type="html">
<p>开发过程中,写 Initializer 应该是日常。会写的可以直接不看。如果没有好好了解过如何写 Initializer,每次都凭着感觉写而且还没有任何问题,说明其中的「玄机」你已经掌握了,可以大概扫一扫。但是每次写 Initializer 都要查 Google,那这篇文章
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Objective-C" scheme="http://zhaoxinyu.me/tags/Objective-C/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>在 Kickstarter-iOS 源码中学到的(二) - 代码</title>
<link href="http://zhaoxinyu.me/2017-07-09-ios-oss-2/"/>
<id>http://zhaoxinyu.me/2017-07-09-ios-oss-2/</id>
<published>2017-07-09T02:19:33.000Z</published>
<updated>2017-11-14T05:33:57.906Z</updated>
<content type="html"><![CDATA[<p>上一次我们简单介绍了一下 Kickstarter-iOS 项目工程方面的闪光点,这次我们探究一下,看看到底是哪些设计与细节让我推荐大家都来阅读这份代码。</p><h3 id="Environment"><a href="#Environment" class="headerlink" title="Environment"></a>Environment</h3><blockquote><p><a href="https://github.com/kickstarter/ios-oss/blob/master/Library/Environment.swift" target="_blank" rel="external">Environment.swift</a></p></blockquote><p><code>Environment</code> 这个 <code>sruct</code> 这作为一个存储整个应用状态的存在。那这个应用可以随时获取的全局状态都有什么呢?</p><ul><li>API 环境<ul><li><code>apiService</code>,包括了 <code>appID</code>,<code>apiBaseUrl</code> 等等。这里之所以把它算进 <code>Environment</code> 是因为在测试环境中,<code>apiService</code> 可能会发生改变。</li></ul></li><li>缓存<ul><li><code>cache</code>,一个 <code>NSCache</code> 缓存一些临时的数据</li><li><code>ubiquitousStore</code>,背后是一个 <code>NSUbiquitousKeyValueStore.default</code>,用于放一些有没有出现引导相关的 flag,是 iCloud 同步的</li><li><code>userDefaults</code>,其实就是 UserDefaults,用于持久化数据</li></ul></li><li>设备<ul><li><code>devie</code>,设备型号、系统版本等等</li><li><code>countryCode</code>, <code>language</code>, <code>locale</code>, 一些设备国际化的信息</li><li><code>reachability</code>,当前设备的网络环境</li></ul></li><li>当前登录用户<ul><li><code>currentUser</code></li></ul></li></ul><p>… 等等</p><p>那是不是只要 <code>AppDelegate</code> 持有一个 <code>Environment</code> 的实例就可以完成任务了呢?</p><p>是也不是。</p><p>其实作为一个应用来说,一份 <code>Environment</code> 就足以完成任务了,但 Kickstarter 为了方便 test,搞出了一个 <code>AppEnvironment</code>。</p><h4 id="AppEnvironment"><a href="#AppEnvironment" class="headerlink" title="AppEnvironment"></a>AppEnvironment</h4><blockquote><p><a href="https://github.com/kickstarter/ios-oss/blob/master/Library/AppEnvironment.swift" target="_blank" rel="external">AppEnvironment.swift</a></p></blockquote><div style="width:60%"><img src="http://oo8znht6g.bkt.clouddn.com/20170713223902_VZc8mv_Screenshot.png" alt=""></div><p>如图,<code>AppEnvironment</code> 持有了一个 <code>stack: [Environment]</code>,其实同时存这么多 <code>Environment</code> 是没有必要的。对于同一个应用来说,一个 <code>Environment</code> 就够了,那为什么它还要搞这么多出来呢?</p><p>搜索一下 <code>AppEnvironment</code> 中 <code>pushEnvironment</code> 和 <code>popEnvironment</code> 方法的调用,几乎都会出现在 <code>*Test.swift</code> 中。主要作用是测试 <code>Environment</code> 发生变化时,保留旧的 <code>Environment</code> 做一些 state 的判断。</p><p>这个 struct 还提供了一个便捷的 <code>replaceCurrentEnvironment</code> 方法,根据你提供的想要更改的值,update 一下 current 的 environment(其实 <code>push</code> 也是同理,把 current 先 copy 一份,再改需要更新的部分)。这个方法就比较有价值了。比如在 debug 的时候,可以把 <code>apiService</code> 换成 <code>mockService</code>,测试的时候,直接更新 <code>user</code> 为一个 fake user,就直接登录成功了,非常方便。</p><p>关于 Environment 的设计,我是比较认可的。相比于全局的 <code>state</code> 满天飞,这种封装会让程序干净不少。AppEnvironment 中的 stack 如果只是用来测试的话其实完全没有必要存在,第一眼看上去有多个 <code>environment</code> 看上去有些 confusing,不过它是 <code>fileprivate</code>,其实也没啥,是吧?</p><h3 id="Deep-Linking"><a href="#Deep-Linking" class="headerlink" title="Deep Linking"></a>Deep Linking</h3><blockquote><p><a href="https://github.com/kickstarter/ios-oss/blob/master/Library/Navigation.swift" target="_blank" rel="external">Navigation.swift</a></p></blockquote><p>iOS 中的 deep linking 就是通过 URL Scheme、Universal Link、3D touch 或者 notification 等 URL 的方式跳转原生页面的方式。</p><p>objc.io 里也有一期关于 Kickstarter 是如何做 deep linking 的,感兴趣的可以看一下。<a href="https://talk.objc.io/episodes/S01E49-deep-linking-at-kickstarter" target="_blank" rel="external">链接点我</a></p><p>工程里的实现跟视频里的方案差不多。有一个 Navigation 的 enum,把所有支持通过 URL 跳转的都定义成一个 case,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">enum</span> <span class="title">Navigation</span> </span>{</div><div class="line"> <span class="keyword">case</span> checkout(<span class="type">Int</span>, <span class="type">Navigation</span>.<span class="type">Checkout</span>)</div><div class="line"> <span class="keyword">case</span> emailClick(qs: <span class="type">String</span>)</div><div class="line"> <span class="keyword">case</span> emailLink</div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure><p>Navigation 存在的意义,是是把一个通过 deep link 方式传入的 URL,解析成一个 Navigation 的 case</p><p>那它是怎么实现这个过程的呢?</p><p><code>allRoutes</code> 中定义出所有支持 deep linking 的 URL 模板,以及它们对应的解析函数,构成一个 route 条目数组</p><p>暴露给外面解析 URL 的入口是 <code>match(_ url: URL)</code></p><p>这个函数首先遍历 <code>allRoutes</code> 中的所有条目,尝试把这些 URL 与 <code>allRoutes</code> 中的 URL 模板匹配 (1)</p><p>模板匹配上了,根据模板中参数的部分把原有 URL 的参数取出来,构成一个参数名为 key,参数值为 value 的 <code>[String:RouteParams]</code>(2)(3)</p><p>然后把这个字典传入 route 条目对应的解析函数(4),就可以顺利得到我们想要的 <code>Navigation</code> 枚举了(5),根据这些确定的枚举,就可以做出跳转了。</p><p>Deep link 写得特别出彩的地方是它的很多代码非常地函数式,其实解析的代码也非常适合写成函数式地。<code>map</code> <code>reduce</code> <code>zip</code> 再<br>结合操作符重载,代码可读性高,而且干净,阅读起来令人愉悦。</p><p>可以结合图看一下 deep link 的过程</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170713223830_TVnt2N_Screenshot.png" alt=""><br>现在再回头看看代码,应该是很清晰了。</p>]]></content>
<summary type="html">
<p>上一次我们简单介绍了一下 Kickstarter-iOS 项目工程方面的闪光点,这次我们探究一下,看看到底是哪些设计与细节让我推荐大家都来阅读这份代码。</p>
<h3 id="Environment"><a href="#Environment" class="heade
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>在 Kickstarter-iOS 源码中学到的(一) - 工程相关</title>
<link href="http://zhaoxinyu.me/2017-07-08-ios-oss-1/"/>
<id>http://zhaoxinyu.me/2017-07-08-ios-oss-1/</id>
<published>2017-07-08T02:19:33.000Z</published>
<updated>2017-07-09T06:19:36.000Z</updated>
<content type="html"><![CDATA[<p>其实在 Kickstarter 刚开源他们客户端源码的时候,我就从 GitHub 的 trends 里看到了。作为一个实用主义者,悄悄点了个 star 就走掉了。</p><p>后来在 <a href="objc.io">ObjC.io</a> 上的这个 <a href="https://talk.objc.io/episodes/S01E47-view-models-at-kickstarter" target="_blank" rel="external">talk</a> 里,我看到了 Kickstarter 的工程师 <a href="https://twitter.com/mbrandonw" target="_blank" rel="external">Brandon Williams</a> 介绍了他们在客户端中大量使用 ViewModel 的经历,而且在视频里用 ViewModel 重构了一段代码。当时我对 MVVM 比较迷,就抱着试试看的心态 <code>clone</code> 了一下他们的<a href="https://github.com/kickstarter/ios-oss" target="_blank" rel="external">客户端源码</a>,看过了之后我只想说,为什么我没有早点看它的代码!</p><h3 id="依赖管理"><a href="#依赖管理" class="headerlink" title="依赖管理"></a>依赖管理</h3><p>第一眼看到他们的代码,是使用的 <code>git submodule</code> 来管理依赖关系的。其实依赖的库并不是很多,所以这么管理也没有什么痛点。虽然比 Cocoapods 管理起来麻烦了一点,但是能对于依赖的东西有完整的把控能力,也算是一个优点吧。不过用 submodule 需要团队里的成员对 submodule 都有一定的了解,否则可能就会一团糟了。</p><p>关于 git submodules, Cocoapods 和 Carthage 管理依赖关系的优劣<a href="https://medium.com/real-life-programming/carthage-vs-cocoapods-vs-git-submodules-9dc341ec6710" target="_blank" rel="external">这篇文章</a>里说得很清楚了,我就不再赘述了,适合自己的才是最好的。</p><h3 id="Makefile"><a href="#Makefile" class="headerlink" title="Makefile"></a>Makefile</h3><p>看到 README 中的 Getting Started 中讲到,clone repo 之后,只要执行 <code>make bootstrap</code> 就可以安装工具和相关依赖了。</p><p>不得不说这个思路很好,用 make 来管理和运行与项目相关的一些操作,如果加了一项与工程相关的操作比如 test、 build,直接在 make 里加一条对应的就好,清晰又直观。第一次拿到项目的人一眼就知道了所有的命令 <del>不用像我司的客户端一样,脚本飞得到处都是,想运行啥都得找半天</del></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line">build: dependencies</div><div class="line">$(XCODEBUILD) $(BUILD_FLAGS) $(XCPRETTY)</div><div class="line"></div><div class="line">test-all:</div><div class="line">PLATFORM=iOS "$(MAKE)" test</div><div class="line">PLATFORM=iOS TARGET=Library "$(MAKE)" test</div><div class="line"></div><div class="line">test: bootstrap</div><div class="line">$(XCODEBUILD) test $(BUILD_FLAGS) $(XCPRETTY)</div><div class="line"></div><div class="line">clean:</div><div class="line">$(XCODEBUILD) clean $(BUILD_FLAGS) $(XCPRETTY)</div><div class="line"></div><div class="line">dependencies: submodules configs secrets opentok</div><div class="line"></div><div class="line">bootstrap: hooks dependencies</div><div class="line">brew update || brew update</div><div class="line">brew unlink swiftlint || true</div><div class="line">brew install swiftlint</div><div class="line">brew link --overwrite swiftlint</div><div class="line"></div><div class="line">submodules:</div><div class="line">git submodule sync --recursive || true</div><div class="line">git submodule update --init --recursive || true</div></pre></td></tr></table></figure><h3 id="几个方便的-Swift-脚本"><a href="#几个方便的-Swift-脚本" class="headerlink" title="几个方便的 Swift 脚本"></a>几个方便的 Swift 脚本</h3><p>其实客户端的颜色和本地化字符串管理一向是开发中的痛点。而在他们的客户端中,我看到了两个 swift 文件,就是专门解决这个问题的。</p><p>他们色表是一个 <code>Colors.json</code> 文件,放在一个 Design 的目录里,Design 里面还有他们全部设计的 Sketch 文件,不得不说很良心。有一个 <code>colors</code> 的 Swift 脚本,专门把色表 parse 成一个 <code>Colors.swift</code> 文件,调用者只需要 <code>UIColor.ksr_green_400</code> 就可以取到颜色了。这种色表管理颜色的方式应该值得每一个客户端的开发者学习。设计师负责管理色表,而脚本负责生成代码。和这样相比,直接在代码里写颜色的确不方便太多。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">UIColor</span> </span>{</div><div class="line"> <span class="comment">/// 0x07565F</span></div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">var</span> ksr_forest_600: <span class="type">UIColor</span> {</div><div class="line"> <span class="keyword">return</span> .hex(<span class="number">0x07565F</span>)</div><div class="line"> }</div><div class="line"> </div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure><p>再就是 <code>localizedString</code> 了,做过国际化的同学们一定都了解,iOS 的 <code>localizedString</code> 太难用了,难用到无法理解。而项目里的 <code>strings.swift</code> 就完美地解决了这件事情。它会 parse 工程中的 <code>Localizable.strings</code> 文件,生成对应的 <code>Strings.swift</code>,里面就有所有的本地化字符串方法,而且还有对应的提示。再也不怕本地化了!<del>其实知乎也做过类似的东西,只不过脚本不是用 Swift 写的</del></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">enum</span> <span class="title">Strings</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> "About %{reward_amount}"</span></div><div class="line"><span class="comment"></span></div><div class="line"><span class="comment"> - **es**: "Aprox. %{reward_amount}"</span></div><div class="line"><span class="comment"> - **de**: "Ungefähr %{reward_amount}"</span></div><div class="line"><span class="comment"> - **fr**: "Environ %{reward_amount}"</span></div><div class="line"><span class="comment"> - **en**: "About %{reward_amount}"</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">func</span> <span class="title">About_reward_amount</span><span class="params">(reward_amount: String)</span></span> -> <span class="type">String</span> {</div><div class="line"> <span class="keyword">return</span> localizedString(</div><div class="line"> key: <span class="string">"About_reward_amount"</span>,</div><div class="line"> defaultValue: <span class="string">"About %{reward_amount}"</span>,</div><div class="line"> <span class="built_in">count</span>: <span class="literal">nil</span>,</div><div class="line"> substitutions: [<span class="string">"reward_amount"</span>: reward_amount]</div><div class="line"> )</div><div class="line"> }</div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="Pull-request-流程"><a href="#Pull-request-流程" class="headerlink" title="Pull request 流程"></a>Pull request 流程</h3><p>在 push 代码之前,会有一个 git hook 来执行 swiftlint,来保证代码符合规范并且避免一些低级的错误。</p><p>有代码就有测试是必须的 <del>这一点可以秒杀大多数做客户端的公司了吧</del></p><p>在提每个 pull request 的时候,作者都需要说明做了什么,并且为什么这么做,这个 PR 改了哪里,截图大概长什么样子等等,可以参考一下<a href="https://github.com/kickstarter/ios-oss/pull/107" target="_blank" rel="external">这个</a>。</p><p>而每个 reviewer 都会提出很多意见。</p><h3 id="丰富的测试"><a href="#丰富的测试" class="headerlink" title="丰富的测试"></a>丰富的测试</h3><p>这里的测试不光指几乎完整覆盖的 Unit Test,还有几乎完整的 UI Test。</p><p>项目中使用 <a href="https://github.com/facebook/ios-snapshot-test-case" target="_blank" rel="external">ios-snapshot-test-case</a> 来做 UI Test,它会将项目里大部分界面配合 mock 数据生成截图保存在 Screenshots 文件夹里。 每次提交 PR 之前,都要跑这些测试,一旦图片发生变化,PR 的 diff 里就会显示这些变化,reviewer 一眼就可以看得出你的代码到底改了哪里,真是非常方便。</p><h3 id="规范的-commit-message"><a href="#规范的-commit-message" class="headerlink" title="规范的 commit message"></a>规范的 commit message</h3><p>几乎每一条 commit 都会带上一个 task 的 id,而且同一个 task 的多个 commit 在 merge 的时候会 squash,非常整洁。如果不知道怎么写 commit message 可以参考我的这一篇<a href="https://zhaoxinyu.me/2017-01-04-how-to-write-good-commit-message/">博文</a></p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170708002521_8ZHaih_Screenshot.jpeg" alt=""></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>这篇文章只是抛砖引玉,我可能只看到了这个客户端源码工程方面的冰山一角,还希望大家在有经历的情况下也去看看他们客户端的代码。其实工作经验的积累完全不需要换公司来解决。如果你有心,GitHub 上有大量的开源客户端源码,拿过来好好看一看,吸收一下,就好像吸收了一家公司的工作经验,多爽。</p><p>在 Kickstarter 的开源代码之外,我还了解到,他们 native 组的工程师同时维护 Andriod 和 iOS 客户端的代码!这也是一般的公司都比不了的吧…… 随着对他们公司了解地越来越多,我也被 Brandon 圈粉,他不但推进了 ios-oss 的开源,让我看到这么优秀的客户端源码,而且他的很多分享也令我印象深刻。只可惜最近他从 Kickstarter 离职了,不过期待他给我们带来更多精彩!</p><p>下篇关于 Kickstart iOS 的分析应该是在层次结构和代码方面 <del>希望自己不要太监</del></p>]]></content>
<summary type="html">
<p>其实在 Kickstarter 刚开源他们客户端源码的时候,我就从 GitHub 的 trends 里看到了。作为一个实用主义者,悄悄点了个 star 就走掉了。</p>
<p>后来在 <a href="objc.io">ObjC.io</a> 上的这个 <a href="
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>用 CALayer.mask 实现边界的渐隐效果</title>
<link href="http://zhaoxinyu.me/2017-07-04-calayer-mask/"/>
<id>http://zhaoxinyu.me/2017-07-04-calayer-mask/</id>
<published>2017-07-04T15:57:20.000Z</published>
<updated>2017-07-04T15:59:50.000Z</updated>
<content type="html"><![CDATA[<p>先看看最终要实现的效果</p><p><img src="http://oo8znht6g.bkt.clouddn.com/20170703210237_sUBP2Y_2.gif" alt=""></p><p>一开始看到这种效果我有点懵,这和普通的阴影不太一样,并不是带有颜色的蒙层,绞尽脑汁想了一会,应该是在上面加一个 <code>CAGradientLayer</code> 吧,但是这层 <code>layer</code> 的颜色到底是什么才对呢?翻了翻 <code>CALayer</code> 的文档,发现了这样一个属性 <code>mask: CALayer?</code>,它是这样说的,</p><blockquote><p>A layer whose alpha channel is used as a mask to select between the layer’s background and the result of compositing the layer’s contents with its filtered background. Defaults to nil. When used as a mask the layer’s <code>compositingFilter</code> and <code>backgroundFilters</code> properties are ignored. When setting the mask to a new layer, the new layer must have a nil superlayer, otherwise the behavior is undefined. Nested masks (mask layers with their own masks) are unsupported.</p></blockquote><p>大概的解释就是,这个 mask 的 alpha 通道会作用在当前的 layer 上,你可以把它想像成一个 filter,layer 后面的 backgroud 如果能透过这个 alpha 层,那它就能与当前 layer 在一起显示。经过一些实验,这个 mask 并不会影响它后面内容的显示。那我们就可以用这个 layer 的 <code>mask</code> 属性搞一些事情出来了。</p><p>要想实现上图的效果大概需要要做以下几个事情,</p><p>创建一个 <code>CAGradientLayer</code> ,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">lazy</span> <span class="keyword">var</span> gradientLayer: <span class="type">CAGradientLayer</span> = {</div><div class="line"> <span class="keyword">let</span> v = <span class="type">CAGradientLayer</span>()</div><div class="line"> <span class="comment">// 这里的颜色是什么都无所谓,因为只有 alpha 通道会起作用</span></div><div class="line"> v.colors = [<span class="type">UIColor</span>.clear.withAlphaComponent(<span class="number">0</span>).cgColor, <span class="type">UIColor</span>.clear.withAlphaComponent(<span class="number">1</span>).cgColor]</div><div class="line"> v.locations = [<span class="number">0.0</span>, <span class="number">1</span>]</div><div class="line"> v.rasterizationScale = <span class="type">UIScreen</span>.main.scale</div><div class="line"> v.startPoint = <span class="type">CGPoint</span>(x: <span class="number">0</span>, y: <span class="number">1</span>)</div><div class="line"> v.endPoint = <span class="type">CGPoint</span>(x: <span class="number">0</span>, y: <span class="number">0.8</span>)</div><div class="line"> <span class="keyword">return</span> v</div><div class="line">}()</div></pre></td></tr></table></figure><p>因为要透过后面内容的显示,tableView 的背景需要是透明的,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">tableView.backgroundColor = <span class="type">UIColor</span>.clear</div></pre></td></tr></table></figure><p>考虑到列表是会滚动的,把这个 layer 加到 tableView 的 mask 上的固定位置,那列表一滚动,效果就不对了,所以需要把 tableView 加到一个 background 上面去,然后把 <code>layer.mask</code> 设置成这我们之前创建好的 <code>gradientLayer</code></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">lazy</span> <span class="keyword">var</span> tableViewBackgroundView: <span class="type">UIView</span> = {</div><div class="line"> <span class="keyword">let</span> v = <span class="type">UIView</span>()</div><div class="line"> v.backgroundColor = <span class="type">UIColor</span>.clear</div><div class="line"> v.layer.mask = <span class="keyword">self</span>.gradientLayer</div><div class="line"> <span class="keyword">return</span> v</div><div class="line">}()</div></pre></td></tr></table></figure><p>由于是 AutoLayout 布局,而 layer 却不能加约束,所以我们监听 backgroundView 的 <code>bounds</code> 手动更新 layer 的 frame(这里用了 RxSwift,其它方式也都 OK)</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">tableViewBackgroundView.rx.observe(<span class="type">CGRect</span>.<span class="keyword">self</span>, <span class="string">"bounds"</span>)</div><div class="line"> .subscribe(onNext: { [<span class="keyword">weak</span> <span class="keyword">self</span>] bounds <span class="keyword">in</span></div><div class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> bounds = bounds <span class="keyword">else</span> { <span class="keyword">return</span> }</div><div class="line"> <span class="keyword">self</span>?.gradientLayer.frame = bounds</div><div class="line"> }).addDisposableTo(bag)</div></pre></td></tr></table></figure><p>Boom,带有边界渐隐效果的列表就 OK 了,是不是很方便捏</p>]]></content>
<summary type="html">
<p>先看看最终要实现的效果</p>
<p><img src="http://oo8znht6g.bkt.clouddn.com/20170703210237_sUBP2Y_2.gif" alt=""></p>
<p>一开始看到这种效果我有点懵,这和普通的阴影不太一样,并不是带有
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
</entry>
<entry>
<title>记一次团队的内部分享 - FRP in Swift</title>
<link href="http://zhaoxinyu.me/2017-05-25-frp-in-swift-basic/"/>
<id>http://zhaoxinyu.me/2017-05-25-frp-in-swift-basic/</id>
<published>2017-05-26T15:24:00.000Z</published>
<updated>2017-05-26T15:33:30.000Z</updated>
<content type="html"><![CDATA[<blockquote><p>这是 2017/05/26 我在知乎 iOS 团队内部做的一次分享</p></blockquote><p>第一次做技术分享,最大的问题还是出在准备上。确定了分享的时间,但是到底要讲些什么内容内心却一直没有一个准确的范围,结果就是到了前一天还是往里面加东西,前一刻还在修改 Kyenote。分享的时候因为有很多写代码的环节,所以一开始有些小紧张,手还发抖,不过到后来慢慢好起来了。本来想把实况录屏全程记录下来的,但是因为 Ariplay 的录屏貌似有些小问题,为了不耽误大家时间,我就直接放弃了,还是有些小遗憾。</p><p>以下是 Keynote 和分享的主要内容</p><script async class="speakerdeck-embed" data-slide="1" data-id="69a9cff70bc74577a78194958fe41a33" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script><h1 id="Functional-Reactive-Programming-in-Swift"><a href="#Functional-Reactive-Programming-in-Swift" class="headerlink" title="Functional Reactive Programming in Swift"></a>Functional Reactive Programming in Swift</h1><h2 id="What"><a href="#What" class="headerlink" title="What?"></a>What?</h2><h3 id="Functional-Programming"><a href="#Functional-Programming" class="headerlink" title="Functional Programming"></a>Functional Programming</h3><h4 id="First-class-function"><a href="#First-class-function" class="headerlink" title="First-class function"></a>First-class function</h4><p>Treats functions as first-class citizens</p><h4 id="Higher-order-function"><a href="#Higher-order-function" class="headerlink" title="Higher-order function"></a>Higher-order function</h4><p>A function that does at least one of the following:</p><ul><li>takes one or more functions as arguments </li><li>returns a function as its result</li></ul><h4 id="Pure-function"><a href="#Pure-function" class="headerlink" title="Pure function"></a>Pure function</h4><p>Function which has no side-effects</p><h4 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h4><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// pure function</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">addTen</span><span class="params">(<span class="number">_</span> a: Int)</span></span> -> <span class="type">Int</span> {</div><div class="line"> <span class="keyword">return</span> a + <span class="number">10</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// higher order function</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">twice</span><span class="params">(<span class="number">_</span> f: @escaping <span class="params">(Int)</span></span></span> -> (<span class="type">Int</span>)) -> (<span class="type">Int</span>) -> (<span class="type">Int</span>) {</div><div class="line"> <span class="keyword">return</span> {</div><div class="line"> f(f($<span class="number">0</span>))</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// first-class citizen</span></div><div class="line"><span class="keyword">let</span> addTenTwice = twice(addTen)</div><div class="line">addTenTwice(<span class="number">10</span>) <span class="comment">//30</span></div><div class="line"><span class="keyword">let</span> addTenFourTimes = twice(addTenTwice)</div><div class="line">addTenFourTimes(<span class="number">10</span>) <span class="comment">//50</span></div><div class="line"></div><div class="line"></div><div class="line"><span class="comment">// a little more harder</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">multiplyBySelf</span><span class="params">(<span class="number">_</span> a: Int)</span></span> -> <span class="type">Int</span> {</div><div class="line"> <span class="keyword">return</span> a * a</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">let</span> g = twice(multiplyBySelf)</div><div class="line">g(<span class="number">3</span>) <span class="comment">// 81</span></div><div class="line">twice(g)(<span class="number">3</span>) <span class="comment">// 43046721</span></div><div class="line"></div><div class="line"><span class="keyword">let</span> a = <span class="number">3</span> * <span class="number">3</span> <span class="comment">//9</span></div><div class="line"><span class="keyword">let</span> b = a * a <span class="comment">//81</span></div><div class="line"><span class="keyword">let</span> <span class="built_in">c</span> = b * b <span class="comment">//6561</span></div><div class="line"><span class="keyword">let</span> d = <span class="built_in">c</span> * <span class="built_in">c</span> <span class="comment">//43046721</span></div></pre></td></tr></table></figure><h3 id="Reactive-Programming"><a href="#Reactive-Programming" class="headerlink" title="Reactive Programming"></a>Reactive Programming</h3><h4 id="Asynchronous-Data-Streams"><a href="#Asynchronous-Data-Streams" class="headerlink" title="Asynchronous Data Streams"></a>Asynchronous Data Streams</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">--a---b-c---d---X---|-></div></pre></td></tr></table></figure><ul><li>A stream is a sequence of ongoing events ordered in time</li><li>Everything can be a stream<ul><li>touch event</li><li>KVO</li><li>Notification</li><li>callback</li><li>Network response</li><li>timer</li><li>…</li></ul></li></ul><h3 id="Functional-Reactive"><a href="#Functional-Reactive" class="headerlink" title="Functional + Reactive"></a>Functional + Reactive</h3><h4 id="Stream"><a href="#Stream" class="headerlink" title="Stream"></a>Stream</h4><ul><li>Like an Array, it can hold anything</li><li>Unlike an Array, you can’t access it anytime you want, instread, you get notified when it’s value get changed</li><li>Like a pipe, if you missed the thing through it, it’s gone forever</li></ul><h4 id="Transformation"><a href="#Transformation" class="headerlink" title="Transformation"></a>Transformation</h4><ul><li>Change a stream to another stream, just like change a sequence to another</li><li>Higher-order functions, map, filter, reduce, flatMap, etc</li></ul><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> reviewers = [<span class="string">"kimi"</span>, <span class="string">"qfu"</span>, <span class="string">"dhc"</span>, <span class="string">"x"</span>, <span class="string">"gaoji"</span>]</div><div class="line"></div><div class="line"><span class="comment">// implement our own trnasformation functions</span></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">Array</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">xy_map</span><T><span class="params">(<span class="number">_</span> transform: <span class="params">(Element)</span></span></span> -> <span class="type">T</span>) -> [<span class="type">T</span>] {</div><div class="line"> <span class="keyword">var</span> result: [<span class="type">T</span>] = []</div><div class="line"></div><div class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="keyword">self</span> {</div><div class="line"> result.append(transform(i))</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">return</span> result</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">xy_filter</span><span class="params">(<span class="number">_</span> condition: <span class="params">(Element)</span></span></span> -> <span class="type">Bool</span>) -> [<span class="type">Element</span>] {</div><div class="line"> <span class="keyword">var</span> result: [<span class="type">Element</span>] = []</div><div class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="keyword">self</span> {</div><div class="line"> <span class="keyword">if</span> condition(i) {</div><div class="line"> result.append(i)</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">xy_reduce</span><T><span class="params">(<span class="number">_</span> initialValue: T, <span class="number">_</span> combine: <span class="params">(T, Element)</span></span></span> -> <span class="type">T</span>) -> <span class="type">T</span> {</div><div class="line"> <span class="keyword">var</span> value = initialValue</div><div class="line"> </div><div class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="keyword">self</span> {</div><div class="line"> value = combine(value, i)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">return</span> value</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">reviewers.xy_map {</div><div class="line"> $<span class="number">0</span>.uppercased()</div><div class="line">}</div><div class="line"></div><div class="line">reviewers.xy_filter {</div><div class="line"> $<span class="number">0</span>.characters.<span class="built_in">count</span> > <span class="number">3</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// chain transformations</span></div><div class="line">reviewers</div><div class="line"> .xy_filter { $<span class="number">0</span>.characters.<span class="built_in">count</span> > <span class="number">3</span> }</div><div class="line"> .xy_reduce(<span class="string">""</span>) { <span class="keyword">return</span> $<span class="number">0</span> + <span class="string">"<span class="subst">\($<span class="number">1</span>)</span> review my code please~\n"</span> }</div><div class="line"></div><div class="line"><span class="comment">// the original value hasn't been changed</span></div><div class="line">reviewers</div><div class="line"></div><div class="line"><span class="comment">// a little bit about flatMap</span></div><div class="line"><span class="keyword">let</span> xxs = [[<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="number">6</span>]]</div><div class="line"><span class="keyword">let</span> xso = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="literal">nil</span>, <span class="number">5</span>]</div><div class="line"><span class="comment">// flatMap has 2 signature</span></div><div class="line">xxs.flatMap { arr <span class="keyword">in</span></div><div class="line"> arr.<span class="built_in">map</span> {$<span class="number">0</span>}</div><div class="line">} <span class="comment">// [1, 2, 3, 4, 5, 6]</span></div><div class="line">xso.flatMap {</div><div class="line"> $<span class="number">0</span></div><div class="line">} <span class="comment">// [1, 2, 3, 5]</span></div></pre></td></tr></table></figure><h4 id="Binding"><a href="#Binding" class="headerlink" title="Binding"></a>Binding</h4><p>Binding makes program more reactive</p><h3 id="in-Swift"><a href="#in-Swift" class="headerlink" title="in Swift!"></a>in Swift!</h3><ul><li>Functional language</li><li>Compiler & strong typed</li></ul><p>Functional + Reactive + Swift, write awesome program!</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><h3 id="Good"><a href="#Good" class="headerlink" title="Good"></a>Good</h3><ul><li>Improve productivity</li><li>Less and more centralised code</li><li>Easy to maintain</li><li>Avoid complexity with mutable state growing over time</li><li>Change the way you think when coding</li></ul><h3 id="Bad"><a href="#Bad" class="headerlink" title="Bad"></a>Bad</h3><ul><li>Learning curve is steep, but not that steep</li><li>Hard to debug</li></ul><p>The benefits it brings are worth we give it a try</p><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><h3 id="Unserstand-the-basic-reactive-unit"><a href="#Unserstand-the-basic-reactive-unit" class="headerlink" title="Unserstand the basic reactive unit"></a>Unserstand the basic reactive unit</h3><h4 id="Observable"><a href="#Observable" class="headerlink" title="Observable"></a>Observable</h4><p>It send messages</p><h4 id="Subscriber"><a href="#Subscriber" class="headerlink" title="Subscriber"></a>Subscriber</h4><p>It consume messages</p><p>Observables are like <code>Sequence</code>,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// Observables are like Sequence</span></div><div class="line"><span class="keyword">let</span> xs = [<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>]</div><div class="line"></div><div class="line"><span class="comment">// iterate a sequence</span></div><div class="line"><span class="keyword">for</span> x <span class="keyword">in</span> xs {</div><div class="line"> <span class="built_in">print</span>(x)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// the operation above equals</span></div><div class="line"><span class="keyword">var</span> xsIte = xs.makeIterator()</div><div class="line"><span class="keyword">while</span> <span class="keyword">let</span> x = xsIte.next() {</div><div class="line"> <span class="built_in">print</span>(x)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// we can use Sequence feature to make a CountDown</span></div><div class="line"><span class="class"><span class="keyword">struct</span> <span class="title">CountDown</span>: <span class="title">Sequence</span>, <span class="title">IteratorProtocol</span> </span>{</div><div class="line"> <span class="keyword">var</span> num: <span class="type">Int</span></div><div class="line"> </div><div class="line"> <span class="keyword">var</span> notify: (<span class="type">Int</span>?) -> ()</div><div class="line"></div><div class="line"> <span class="keyword">mutating</span> <span class="function"><span class="keyword">func</span> <span class="title">next</span><span class="params">()</span></span> -> <span class="type">Int</span>? {</div><div class="line"> notify(num)</div><div class="line"> <span class="keyword">if</span> num == <span class="number">0</span> {</div><div class="line"> <span class="keyword">return</span> <span class="literal">nil</span></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">defer</span> {</div><div class="line"> num -= <span class="number">1</span></div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> num</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">var</span> ite = <span class="type">CountDown</span>(num: <span class="number">10</span>) {</div><div class="line"> <span class="comment">// as a subscriber, we are consuming messages</span></div><div class="line"> <span class="built_in">print</span>($<span class="number">0</span>)</div><div class="line"> }.makeIterator()</div><div class="line"></div><div class="line"><span class="comment">// now it's kind like a stream</span></div><div class="line"><span class="comment">// once next() called, it'll print the latest value, it's reactive now</span></div><div class="line">ite.next() <span class="comment">//10</span></div><div class="line">ite.next() <span class="comment">//9</span></div><div class="line">ite.next() <span class="comment">//8</span></div></pre></td></tr></table></figure><p>How can we make our own Observable/Subscriber pattern?</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> Foundation</div><div class="line"><span class="keyword">import</span> UIKit</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">KeyValueObserver</span><<span class="title">A</span>>: <span class="title">NSObject</span> </span>{</div><div class="line"> <span class="keyword">let</span> block: (<span class="type">A</span>) -> ()</div><div class="line"> <span class="keyword">let</span> keyPath: <span class="type">String</span></div><div class="line"> <span class="keyword">let</span> object: <span class="type">NSObject</span></div><div class="line"> <span class="keyword">init</span>(object: <span class="type">NSObject</span>, keyPath: <span class="type">String</span>, <span class="number">_</span> block: @escaping (<span class="type">A</span>) -> ()) {</div><div class="line"> <span class="keyword">self</span>.block = block</div><div class="line"> <span class="keyword">self</span>.keyPath = keyPath</div><div class="line"> <span class="keyword">self</span>.object = object</div><div class="line"> <span class="keyword">super</span>.<span class="keyword">init</span>()</div><div class="line"> object.addObserver(<span class="keyword">self</span>, forKeyPath: keyPath, options: .new, context: <span class="literal">nil</span>)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">deinit</span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"deinit"</span>)</div><div class="line"> object.removeObserver(<span class="keyword">self</span>, forKeyPath: keyPath)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">observeValue</span><span class="params">(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)</span></span> {</div><div class="line"> block(change![.newKey] <span class="keyword">as</span>! <span class="type">A</span>)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Observable</span><<span class="title">A</span>> </span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">var</span> callbacks: [(<span class="type">A</span>) -> ()] = []</div><div class="line"> <span class="keyword">var</span> objects: [<span class="type">Any</span>] = []</div><div class="line"> </div><div class="line"> <span class="keyword">static</span> <span class="function"><span class="keyword">func</span> <span class="title">pipe</span><span class="params">()</span></span> -> ((<span class="type">A</span>) -> (), <span class="type">Observable</span><<span class="type">A</span>>) {</div><div class="line"> <span class="keyword">let</span> observable = <span class="type">Observable</span><<span class="type">A</span>>()</div><div class="line"> <span class="keyword">return</span> ({ [<span class="keyword">weak</span> observable] value <span class="keyword">in</span></div><div class="line"> observable?.send(value)}, observable</div><div class="line"> )</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">send</span><span class="params">(<span class="number">_</span> value: A)</span></span> {</div><div class="line"> <span class="keyword">for</span> callback <span class="keyword">in</span> callbacks {</div><div class="line"> callback(value)</div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">subscribe</span><span class="params">(callback: @escaping <span class="params">(A)</span></span></span> -> ()) {</div><div class="line"> callbacks.append(callback)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">UITextField</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">observable</span><span class="params">()</span></span> -> <span class="type">Observable</span><<span class="type">String</span>> {</div><div class="line"> <span class="keyword">let</span> (sink, observable) = <span class="type">Observable</span><<span class="type">String</span>>.pipe()</div><div class="line"> <span class="keyword">let</span> observer = <span class="type">KeyValueObserver</span>(object: <span class="keyword">self</span>, keyPath: #keyPath(text)) {</div><div class="line"> sink($<span class="number">0</span>)</div><div class="line"> }</div><div class="line"> observable.objects.append(observer)</div><div class="line"> <span class="keyword">return</span> observable</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">var</span> textField: <span class="type">UITextField</span>? = <span class="type">UITextField</span>()</div><div class="line"></div><div class="line">textField?.text = <span class="string">"asd"</span></div><div class="line"><span class="keyword">var</span> observable = textField?.observable()</div><div class="line"> </div><div class="line">observable!.subscribe {</div><div class="line"> <span class="built_in">print</span>($<span class="number">0</span>)</div><div class="line">}</div><div class="line"></div><div class="line">textField?.text = <span class="string">"asdjlas"</span></div><div class="line">textField?.text = <span class="string">"asdjk"</span></div><div class="line">textField = <span class="literal">nil</span></div><div class="line">observable = <span class="literal">nil</span></div></pre></td></tr></table></figure><h3 id="Integrate-a-reactive-programming-library"><a href="#Integrate-a-reactive-programming-library" class="headerlink" title="Integrate a reactive programming library"></a>Integrate a reactive programming library</h3><ul><li>ReactiveSwift</li><li>ReactiveKit</li><li>RxSwift</li></ul><p>Neither can goes wrong, but I prefer RxSwift because,</p><ul><li>It’s a <a href="http://reactivex.io/" target="_blank" rel="external">ReactiveX</a> official Swift implementation which means<ul><li>Developer won’t give it up (Maybe?)</li><li>You can easily switch to other platform</li></ul></li><li>It has a greate <a href="https://github.com/RxSwiftCommunity" target="_blank" rel="external">community</a></li></ul><h2 id="Credits"><a href="#Credits" class="headerlink" title="Credits"></a>Credits</h2><ul><li><a href="https://en.wikipedia.org/wiki/Higher-order_function#Swift" target="_blank" rel="external">wiki/Higher-order function</a></li><li><a href="https://news.realm.io/news/altconf-ash-furrow-functional-reactive-swift/" target="_blank" rel="external">Functional Reactive Awesomeness with Swift</a></li><li><a href="https://gist.github.com/staltz/868e7e9bc2a7b8c1f754" target="_blank" rel="external">The introduction to Reactive Programming you’ve been missing</a></li></ul>]]></content>
<summary type="html">
<blockquote>
<p>这是 2017/05/26 我在知乎 iOS 团队内部做的一次分享</p>
</blockquote>
<p>第一次做技术分享,最大的问题还是出在准备上。确定了分享的时间,但是到底要讲些什么内容内心却一直没有一个准确的范围,结果就是到了前一天还是往
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Swift" scheme="http://zhaoxinyu.me/tags/Swift/"/>
<category term="FRP" scheme="http://zhaoxinyu.me/tags/FRP/"/>
</entry>
<entry>
<title><译> Swift 代码小抄</title>
<link href="http://zhaoxinyu.me/2017-04-14-swift-syntax-cheat-code/"/>
<id>http://zhaoxinyu.me/2017-04-14-swift-syntax-cheat-code/</id>
<published>2017-04-14T12:20:00.000Z</published>
<updated>2017-05-17T16:11:27.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="https://medium.com/swift-programming/swift-syntax-cheat-codes-9ce4ab4bc82e#.qrmtczdec" target="_blank" rel="external">https://medium.com/swift-programming/swift-syntax-cheat-codes-9ce4ab4bc82e#.qrmtczdec</a><br>作者=Andyy Hope<br>译者=X140yu</p><!--此处开始正文--><h1 id="Swift-代码小抄"><a href="#Swift-代码小抄" class="headerlink" title="Swift 代码小抄"></a>Swift 代码小抄</h1><blockquote><p>↑ ↑ ↓ ↓ ← → ← → B A</p></blockquote><p>无论是你学习的第一门语言是 Swift 还是之前学过 Objective-C,在学习 Swift 的过程中,一定会感叹它<em>真的</em>是一门超赞的语言。但如果你不熟悉它的某些语法,就可能会被某些写法吓到。在这里我会介绍一些在写 Swift 过程中常见语法,希望你们能用 Swift 写出更简洁的代码。</p><h4 id="Closures(闭包)"><a href="#Closures(闭包)" class="headerlink" title="Closures(闭包)"></a>Closures(闭包)</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">() -> Void</div></pre></td></tr></table></figure><p>Closure 在 C 或 Objective-C 中也被称为 “unnamed function”(匿名函数) 或者 “block”(代码块)。你可以把闭包当成一个值,传来传去,当然也可以把它当成函数的参数。</p><p>如果你之前有过 iOS 开发经验,那么很有可能调用过这个 UIView 的动画 API:</p><p>class func animate(withDuration duration: NSTimeInterval, animations: @escaping <strong>() -> Void</strong>)</p><p>可以在 <code>animations:</code> 参数中传入执行动画的代码,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="type">UIView</span>.animate(withDuration: <span class="number">10.0</span>, animations: {</div><div class="line"> button.alpha = <span class="number">0</span></div><div class="line">})</div></pre></td></tr></table></figure><p>这个<code>animationWithDuration:</code> 函数会使用我们闭包中的代码,悄悄地做一些神奇的事情,把 <code>button</code> 的透明调整到 0(不可见)。</p><h4 id="Trailing-closures(尾随闭包)"><a href="#Trailing-closures(尾随闭包)" class="headerlink" title="Trailing closures(尾随闭包)"></a>Trailing closures(尾随闭包)</h4><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="type">UIView</span>.animate(withDuration: <span class="number">10.0</span>) {</div><div class="line"> button.alpha = <span class="number">0</span></div><div class="line">}</div></pre></td></tr></table></figure><p>Swift 使用这种方式来减少不必要的语法。如果你仔细看上面的代码就会发现,这和之前在 closure 中举的例子使用的是相同的 API,唯一的区别是它的语法被简化了。</p><p>因为 <code>animate</code> 函数的最后一个参数是闭包,所以它得名<em>尾随闭包</em>。尾随闭包可以省略最后一个参数的名字,并且可以完全把它移出函数参数列表圆括号的外面,这就带来了更优雅和准确的代码。下面的函数都是相同的,不过后面的使用了尾随闭包语法,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">say</span><span class="params">(<span class="number">_</span> message: String, completion: @escaping <span class="params">()</span></span></span> -> <span class="type">Void</span>) {</div><div class="line"> <span class="built_in">print</span>(message)</div><div class="line"> completion()</div><div class="line">}</div><div class="line"></div><div class="line">...</div><div class="line"></div><div class="line">say(<span class="string">"Hello"</span>, completion: {</div><div class="line"> <span class="comment">// prints: "Hello"</span></div><div class="line"> <span class="comment">// Do some other stuff</span></div><div class="line">})</div><div class="line"></div><div class="line">say(<span class="string">"Hello"</span>) {</div><div class="line"> <span class="comment">// prints: "Hello"</span></div><div class="line"> <span class="comment">// Do some other stuff</span></div><div class="line">}</div></pre></td></tr></table></figure><h3 id="Type-Alias(类型别名)"><a href="#Type-Alias(类型别名)" class="headerlink" title="Type Alias(类型别名)"></a>Type Alias(类型别名)</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">typealias</span></div></pre></td></tr></table></figure><p>Typealias 是一个能让我们的写代码的时候避免一直重复的实用小工具。有这样一个函数,参数是一个闭包,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">dance</span><span class="params">(<span class="keyword">do</span>: <span class="params">(Int, String, Double)</span></span></span> -> (<span class="type">Int</span>, <span class="type">String</span>, <span class="type">Double</span>)) { }</div></pre></td></tr></table></figure><p>起初这还挺直接容易理解的,但如果把这个闭包传到其它函数中去呢?我们还要记住这个闭包的签名,而且在每一个它出现的地方都要确保它的签名是正确的。如果有的地方写错了,编译器就会报错。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">dance</span><span class="params">(<span class="keyword">do</span>: <span class="params">(Int, String, Double)</span></span></span> -> (<span class="type">Int</span>, <span class="type">String</span>, <span class="type">Double</span>)) { }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">sing</span><span class="params">(<span class="keyword">do</span>: <span class="params">(Int, String, Double)</span></span></span> -> (<span class="type">Int</span>, <span class="type">String</span>, <span class="type">Double</span>)) { }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">act</span><span class="params">(<span class="keyword">do</span>: <span class="params">(Int, String, Double)</span></span></span> -> (<span class="type">Int</span>, <span class="type">String</span>, <span class="type">Double</span>)) { }</div></pre></td></tr></table></figure><p>如果我们修改了这个闭包的签名,问题就出现了。比如交换了其中参数或者返回值的顺序。我们就得把每一个用到的地方都改一下。这个时候 <code>typealias</code> 就会比较有帮助了。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">typealias</span> <span class="type">TripleThreat</span> = (<span class="type">Int</span>, <span class="type">String</span>, <span class="type">Double</span>) -> (<span class="type">Int</span>, <span class="type">String</span>, <span class="type">Double</span>)</div><div class="line"></div><div class="line">...</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">dance</span><span class="params">(dance: TripleThreat)</span></span> { }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">act</span><span class="params">(act: TripleThreat)</span></span> { }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">sing</span><span class="params">(sing: TripleThreat)</span></span> { }</div></pre></td></tr></table></figure><p>这样就好多了,用 <code>typealias</code> 替代了很多重复的代码,而且如果想更改闭包定义的话,直接改 <code>typealias</code> 就可以了。</p><p><strong>比较「有名的」typealias</strong></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">typealias</span> <span class="type">Void</span> = ()</div><div class="line"></div><div class="line"><span class="keyword">typealias</span> <span class="type">NSTimeInterval</span> = <span class="type">Double</span></div></pre></td></tr></table></figure><h3 id="简写参数名"><a href="#简写参数名" class="headerlink" title="简写参数名"></a>简写参数名</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$<span class="number">0</span>, $<span class="number">1</span>, $<span class="number">2</span>...</div></pre></td></tr></table></figure><p>如果一个闭包有一个或多个参数,Swift 允许我们给它们定义变量名,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">say</span><span class="params">(<span class="number">_</span> message: String, completion: <span class="params">(<span class="number">_</span> goodbye: String)</span></span></span> -> <span class="type">Void</span>) {</div><div class="line"> <span class="built_in">print</span>(message)</div><div class="line"> completion(<span class="string">"Goodbye"</span>)</div><div class="line">}</div><div class="line"></div><div class="line">...</div><div class="line"></div><div class="line">say(<span class="string">"Hi"</span>) { (goodbye: <span class="type">String</span>) -> <span class="type">Void</span> <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(goodbye)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// prints: "Hi"</span></div><div class="line"><span class="comment">// prints: "Goodbye"</span></div></pre></td></tr></table></figure><p>在这个例子中,尾随闭包有一个名为 <code>goodbype</code> 类型是 <code>String</code> 的参数,Xcode 会自动把它放在一个元组里,接着是 <code>-></code> 、返回值、还有 <code>in</code>,我们的代码在下一行。但是这个闭包很小,写这么多代码是没有必要的。来分析一下,如何才能写更少的代码。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">(goodbye: <span class="type">String</span>) -> <span class="type">Void</span> <span class="keyword">in</span></div></pre></td></tr></table></figure><p>上面的其实都没有什么存在的必要性,因为可以使用简化的参数名,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">say(<span class="string">"Hi"</span>) { <span class="built_in">print</span>($<span class="number">0</span>) }</div><div class="line"></div><div class="line"><span class="comment">// prints: "Hi"</span></div><div class="line"><span class="comment">// prints: "Goodbye"</span></div></pre></td></tr></table></figure><p>可以看到,省略了 <code>goodbye:</code> 参数名,<code>Void</code> 返回类型的定义,还有跟在后面的 <code>in</code>。每一个参数依据它们在闭包中定义的顺序而命名。因为语法太简略了,甚至能把所有的代码放到一行。</p><p>如果闭包的参数多于一个,对于每一个后面的参数,增加简写参数的数字就可以了。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">(goodbye: <span class="type">String</span>, name: <span class="type">String</span>, age: <span class="type">Int</span>) -> <span class="type">Void</span> <span class="keyword">in</span></div><div class="line"></div><div class="line"><span class="comment">// $0: goodbye</span></div><div class="line"><span class="comment">// $1: name</span></div><div class="line"><span class="comment">// $2: age</span></div></pre></td></tr></table></figure><h4 id="返回-Self"><a href="#返回-Self" class="headerlink" title="返回 Self"></a>返回 <code>Self</code></h4><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">-> <span class="type">Self</span></div></pre></td></tr></table></figure><p>Swift 2.0 发布的时候,新增了一些方法,比如 <code>map</code> 和 <code>flatMap</code>,更酷的是,可以通过点语法把这些方法串起来,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="literal">nil</span>, <span class="number">5</span>]</div><div class="line"> .flatMap { $<span class="number">0</span> } <span class="comment">// 清除 nil</span></div><div class="line"> .<span class="built_in">filter</span> { $<span class="number">0</span> < <span class="number">3</span> } <span class="comment">// 选出小于 3 的值</span></div><div class="line"> .<span class="built_in">map</span> { $<span class="number">0</span> * <span class="number">100</span> } <span class="comment">// 每个值乘 100</span></div><div class="line"><span class="comment">// [100, 200]</span></div></pre></td></tr></table></figure><p>是不是很酷!这种语法<em>非常</em>优雅,很容易阅读和理解,我们应该尽可能多地使用它们。</p><p>如果存在一个 <code>String</code> 的 extension,在此方法里面我们做了一些对于这个 <code>String</code> 本身的操作,这个时候函数不要返回 <code>Void</code>,而返回 <code>Self</code>,</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// extension UIView</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">with</span><span class="params">(backgroundColor: UIColor)</span></span> -> <span class="type">Self</span> {</div><div class="line"> backgroundColor = color</div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">with</span><span class="params">(cornerRadius: CGFloat)</span></span> -> <span class="type">Self</span> {</div><div class="line"> layer.cornerRadius = <span class="number">3</span></div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span></div><div class="line">}</div><div class="line"></div><div class="line">...</div><div class="line"></div><div class="line"><span class="keyword">let</span> view = <span class="type">UIView</span>(frame: <span class="type">CGRect</span>(x: <span class="number">0</span>, y: <span class="number">0</span>, width: <span class="number">10</span>, height: <span class="number">10</span>))</div><div class="line"> .with(backgroundColor: .black)</div><div class="line"> .with(cornerRadius: <span class="number">3</span>)</div></pre></td></tr></table></figure><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>不管你是在写新的代码还是在阅读旧的代码,你可能会发现在这里学习到的可以应用到你的部分代码里。Xcode 自动补全的代码和你自己写的代码不一定是正确的,所以应当一直保持质疑。</p><p><em>示例代码可以在 GitHub 上找到</em> <a href="https://github.com/andyyhope/Blog_SyntaxCheatCodes" target="_blank" rel="external"><em>playground</em></a>, <a href="https://gist.github.com/andyyhope/7ed96045d3560e8050994662cb97db87" target="_blank" rel="external"><em>Gist</em></a>。</p><p><em>欢迎阅读我的</em><a href="https://medium.com/@AndyyHope" target="_blank" rel="external"><em>全部文章</em></a>, <em>你也可以在</em> <a href="https://twitter.com/AndyyHope" target="_blank" rel="external"><em>Twitter</em></a> <em>上找到我。我在澳大利亚创办了</em> <a href="http://www.playgroundscon.com/" target="_blank" rel="external"><em>Playgrounds Conference</em></a> <em>期待在下次活动中看到你的身影。</em></p>]]></content>
<summary type="html">
<p>原文链接=<a href="https://medium.com/swift-programming/swift-syntax-cheat-codes-9ce4ab4bc82e#.qrmtczdec" target="_blank" rel="external">https
</summary>
<category term="翻译" scheme="http://zhaoxinyu.me/tags/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>读 Apple Sample Code 之 Simple Ping</title>
<link href="http://zhaoxinyu.me/2017-04-12-simple-ping/"/>
<id>http://zhaoxinyu.me/2017-04-12-simple-ping/</id>
<published>2017-04-13T14:59:00.000Z</published>
<updated>2017-05-14T08:03:55.000Z</updated>
<content type="html"><![CDATA[<p>在翻 <a href="https://developer.apple.com/library/content/navigation/" target="_blank" rel="external">Apple Developer Guides and Sample Code</a> 的时候,顺着 Network 那一栏往下翻,看到了一个 <a href="https://en.wikipedia.org/wiki/Ping_(networking_utility" target="_blank" rel="external">ping</a>) 的<a href="https://developer.apple.com/library/content/samplecode/SimplePing/Introduction/Intro.html#//apple_ref/doc/uid/DTS10000716" target="_blank" rel="external">封装实现</a>。ping 是一个非常常用的命令,相信大家或多或少都有用过。</p><p>这个示例的封装实现了最基本的 ping 功能,其中包括 resolve host,create socket(send & recv data), 解析 ICMP 包验证 checksum 等。同时支持 iPv4 和 iPv6。其实就是对 Core Foundation 中网络相关的 C 函数的封装。</p><h2 id="ping"><a href="#ping" class="headerlink" title="ping"></a>ping</h2><p>ping 是通过网络层的 IP 协议发送 ICMP 协议的数据包,然后等待目标回传 ICMP 数据包,通过时间和成功响应的次数估算丢包率和网络时延。</p><p>其实 ping 并不能完全代表你能不能连接上那个 host,像 zhihu.com 就封掉了 ICMP(ping 就没办法工作了,一直提示超时),但是通过浏览器(HTTP)还是能打开知乎的。</p><h2 id="Demo-中-SimplePing-的使用"><a href="#Demo-中-SimplePing-的使用" class="headerlink" title="Demo 中 SimplePing 的使用"></a>Demo 中 SimplePing 的使用</h2><p>下载好源码,发现 demo 是用 Swift 写的!可惜它使用的版本是 2.3(目前的 Swift 版本是 3.1),看来 Apple 的这些 Sample Code 更新的不是很及时。不过还好代码量比较少,Xcode 帮忙升级,自己再 fix 一两处,项目就可以 run 起来了。</p><p>Demo 中使用 SimplePing 的基本流程如下,</p><p><img src="media/2017-04-15-simple-ping-01.png" alt=""></p><p>其实主要的流程也就以下几个部分,</p><ul><li>初始化。包括创建 pinger 对象,设置想要 iPv4 还是 iPv6 的地址,设置 delegate</li><li>调用 <code>start()</code> 方法,开启 ping 的流程。pinger 会把对应的 host 解析成 IP 地址</li><li>在需要的情况下调用 <code>send</code> 方法。探测对应的 host,delegate 中的回调就会得到对应的回应了。 </li></ul><h2 id="SimplePing-的实现原理"><a href="#SimplePing-的实现原理" class="headerlink" title="SimplePing 的实现原理"></a>SimplePing 的实现原理</h2><p>ping 的实现过程并不复杂,一共以下几个步骤,</p><ul><li>解析传入的 host,获取第一个可用 IP 地址</li><li>创建传输/接收数据的 socket</li><li>发送数据,封装一个 ICMP 包 </li><li>解析目标 IP 传回的 ICMP 包</li></ul><p>不要觉得 ICMP 包之类是什么高深高难的词语,其实它就是一个协议。双方规定好一个数据包的前几个字节是什么,后几个字节是什么,双方按照正确的格式封装包的数据发送或者解析就可以了。</p><p><img src="media/2017-04-15-simple-ping-02.png" alt=""></p><p>ICMP 是位于网络层的一个协议,它依靠 IP 来完成工作。如上图所示,ICMP 的包会有 IP 的 header,但这个 header 对于我们实现 ping 是没有什么必要性的,可以看到代码也有一个方法 <code>icmpHeaderOffsetInIPv4Packet:</code> 计算 IP header 的 offset 从而直接跳过 IP 头。</p><p>Simple Ping 也对 ICMPHeader 也做出了定义,</p><figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ICMPHeader</span> {</span></div><div class="line"> <span class="comment">// iPv4 or iPv6</span></div><div class="line"> <span class="keyword">uint8_t</span> type;</div><div class="line"> <span class="comment">// 0</span></div><div class="line"> <span class="keyword">uint8_t</span> code;</div><div class="line"> <span class="comment">// 0</span></div><div class="line"> <span class="keyword">uint16_t</span> checksum;</div><div class="line"> <span class="comment">// 初始化 host 的时候随机生成的 id</span></div><div class="line"> <span class="keyword">uint16_t</span> identifier;</div><div class="line"> <span class="comment">// 类内部维护的,发送 ping 数据的序号</span></div><div class="line"> <span class="keyword">uint16_t</span> sequenceNumber;</div><div class="line"> <span class="comment">// data...</span></div><div class="line">};</div><div class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">ICMPHeader</span> <span class="title">ICMPHeader</span>;</span></div></pre></td></tr></table></figure><p>发送 ping 数据的时候,会对一个 ICMPHeader 结构体进行初始化,除了上述的几个字段,结合上面的那张图看到,还有 payload,如果调用者没有指定发送的 payload,方法内部会添加默认的 payload。</p><p>接收回应的时候,会对 ICMP 包进行校验,也会跳过 IP 头,其中主要验证的字段是 checksum 和 sequenceNumber(iPv6 只需要验证 sequenceNumber)。checksum 的计算根据的是标准的 BSD checksum 生成函数,主要依据是 ICMP 包,计算规则没细看,因为它是一种标准嘛(想看的同学可以看这里 <a href="https://en.wikipedia.org/wiki/IPv4_header_checksum" target="_blank" rel="external">IPv4 header checksum</a>)。</p><p>至此,一次 ping 的完整流程就结束了。</p><h2 id="关于-Core-Foundation"><a href="#关于-Core-Foundation" class="headerlink" title="关于 Core Foundation"></a>关于 Core Foundation</h2><p>上次看了 Reachability,这次看了 Simple Ping,其实都是对 Core Foundation 中的 C 函数进行封装。其中让我觉得比较关键的点就是对 Core Foundation 中异步函数的调用和内存管理问题。</p><h3 id="异步函数的调用"><a href="#异步函数的调用" class="headerlink" title="异步函数的调用"></a>异步函数的调用</h3><p>Core Foundation 中的异步方法设置一般需要以下几个步骤,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)start {</div><div class="line"> Boolean success;</div><div class="line"> <span class="built_in">CFHostClientContext</span> context = {<span class="number">0</span>, (__bridge <span class="keyword">void</span> *)(<span class="keyword">self</span>), <span class="literal">NULL</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>};</div><div class="line"> <span class="built_in">CFStreamError</span> streamError;</div><div class="line"></div><div class="line"> <span class="comment">// 1. 创建相关的 context,一般都是把 self 存到 info 字段中,方便从 callback 中提取出对象然后调用处理的 Objective-C 方法</span></div><div class="line"> <span class="keyword">self</span>.host = (<span class="built_in">CFHostRef</span>) <span class="built_in">CFAutorelease</span>( <span class="built_in">CFHostCreateWithName</span>(<span class="literal">NULL</span>, (__bridge <span class="built_in">CFStringRef</span>) <span class="keyword">self</span>.hostName) );</div><div class="line"> <span class="comment">// 2. 创建一个与 callback 函数指针相同签名的静态函数,处理异步方法回调后的逻辑,设置 context 等相关参数 </span></div><div class="line"> <span class="built_in">CFHostSetClient</span>(<span class="keyword">self</span>.host, HostResolveCallback, &context);</div><div class="line"> <span class="comment">// 3. 设置函数需要执行的 runloop 及 runloop mode</span></div><div class="line"> <span class="built_in">CFHostScheduleWithRunLoop</span>(<span class="keyword">self</span>.host, <span class="built_in">CFRunLoopGetCurrent</span>(), kCFRunLoopDefaultMode);</div><div class="line"> <span class="comment">// 4. 开始执行,如果成功,那我们的 callback 就会被调用,失败会立即返回 nil</span></div><div class="line"> success = <span class="built_in">CFHostStartInfoResolution</span>(<span class="keyword">self</span>.host, kCFHostAddresses, &streamError);</div><div class="line"> <span class="keyword">if</span> (!success) {</div><div class="line"> <span class="comment">// handle error</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// 在 callback 中,就可以取出我们之前传入的 info 等字段(也就是注册回调的 SimplePing 对象)</span></div><div class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">HostResolveCallback</span><span class="params">(CFHostRef theHost, CFHostInfoType typeInfo, <span class="keyword">const</span> CFStreamError *error, <span class="keyword">void</span> *info)</span> </span>{</div><div class="line"> <span class="comment">/// 从 info 中就可以取出 Objective-C 中的 object</span></div><div class="line"> SimplePing *obj = (__bridge SimplePing *) info;</div><div class="line"> <span class="comment">/// .... </span></div><div class="line">}</div></pre></td></tr></table></figure><p>本质上就是从 C 的静态方法到调用 Objective-C 对象方法的过程,多数的异步函数都是这种调用方式,基本都是套路化的,同时也比较原始,需要写很多代码。</p><h3 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h3><p>Core Foundation 并不支持 ARC,它里面的对象都是需要手动管理内存的。</p><p>根据 Apple 的<a href="https://developer.apple.com/library/content/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html" target="_blank" rel="external">文档</a>,</p><blockquote><p>The compiler does not automatically manage the lifetimes of Core Foundation objects. You tell the compiler about the ownership semantics of objects using either a cast (defined in objc/runtime.h) or a Core Foundation-style macro (defined in NSObject.h):</p><ul><li><code>__bridge</code> transfers a pointer between Objective-C and Core Foundation with no transfer of ownership.</li><li><code>__bridge_retained</code> or <code>CFBridgingRetain</code> casts an Objective-C pointer to a Core Foundation pointer and also transfers ownership to you.<br>You are responsible for calling CFRelease or a related function to relinquish ownership of the object.</li><li><code>__bridge_transfer</code> or <code>CFBridgingRelease</code> moves a non-Objective-C pointer to Objective-C and also transfers ownership to ARC.<br>ARC is responsible for relinquishing ownership of the object.</li></ul></blockquote><p>也就是说,</p><ul><li>利用 Core Foundation API 创建出来的对象(一般是指针类型的),最后都需要我们自己释放(CFRelease)</li><li>通过 <code>__bridge</code> 转换 OC 和 CF 对象只涉及类型的变化,内存管理权并不发生改变</li><li>通过 <code>__bridge_retained</code> 或 <code>CFBridgingRetain</code> 把 OC -> CF 对象,此时这个对象的内存需要你手动管理</li><li>通过 <code>__bridge_transfer</code> 或 <code>CFBridgingRelease</code> 把 CF -> OC 对象,它之后的内存就交给 ARC 来管理,你就不需要负责它的内存处理了</li></ul>]]></content>
<summary type="html">
<p>在翻 <a href="https://developer.apple.com/library/content/navigation/" target="_blank" rel="external">Apple Developer Guides and Sample Cod
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Objective-C" scheme="http://zhaoxinyu.me/tags/Objective-C/"/>
</entry>
<entry>
<title>读 AFNetworking 源码之 Reachability</title>
<link href="http://zhaoxinyu.me/2017-04-11-Reachability/"/>
<id>http://zhaoxinyu.me/2017-04-11-Reachability/</id>
<published>2017-04-11T02:59:00.000Z</published>
<updated>2017-04-11T16:46:47.000Z</updated>
<content type="html"><![CDATA[<h2 id="Reachability-是什么?"><a href="#Reachability-是什么?" class="headerlink" title="Reachability 是什么?"></a>Reachability 是什么?</h2><p>根据 Mac 自带的词典,这个单词的英英解释是 <code>able to be contacted</code>,通俗一点就是连接的能力。而放到 App 中就是有没有连接网络的能力。</p><p>相信大家在日常使用应用的时候都看到过。在不同的网络条件下,App 的某些行为会有所不同。比如对于多媒体素材来说,移动网络下通常会请求低质量版本,而 Wi-Fi 下会尽量下载大尺寸甚至原尺寸。还有,在 Wi-Fi 条件下播放视频的时候,如果用户的 Wi-Fi 突然断掉了,那 App 应该自动暂停视频播放及预取缓存,并弹窗提示用户。</p><p>总结一下,当你想知道当前的设备是连着 Wi-Fi 还是连着 4G,或者你想在用户设备网络条件发生变化的时候得到通知,那你就该使用 Reachability 了。</p><h2 id="Reachability-的几个实现"><a href="#Reachability-的几个实现" class="headerlink" title="Reachability 的几个实现"></a>Reachability 的几个实现</h2><p>目前网络上大概有三种实现</p><ul><li><a href="https://developer.apple.com/library/content/samplecode/Reachability/Introduction/Intro.html" target="_blank" rel="external">Apple Sample Code - Reachability</a></li><li><a href="https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFNetworkReachabilityManager.h" target="_blank" rel="external">AFNetworking - AFNetworkReachabilityManager</a></li><li><a href="https://github.com/tonymillion/Reachability" target="_blank" rel="external">tonymillion/Reachability</a></li></ul><p>而它们本质上都是对 SystemConfiguration Framework 中 <code>SCNetworkReachability.h</code> 相关的 C API 进行封装,以实现相同的效果。</p><h2 id="AFNetworkReachabilityManager"><a href="#AFNetworkReachabilityManager" class="headerlink" title="AFNetworkReachabilityManager"></a>AFNetworkReachabilityManager</h2><p>由于三个封装的原理以及实现的功能都大同小异,但在这里会详细介绍一下 AFNetworking(用 Objective-C 开发的同学,项目里应该 95% 以上都会依赖 AFNetworking 吧)版本的使用以及实现。</p><h3 id="使用及注意事项"><a href="#使用及注意事项" class="headerlink" title="使用及注意事项"></a>使用及注意事项</h3><h4 id="1-初始化对象"><a href="#1-初始化对象" class="headerlink" title="1. 初始化对象"></a>1. 初始化对象</h4><p>AFNetworkReachabilityManager 比较常用的几个初始化方法如下,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// 获取全局唯一单例</span></div><div class="line">+ (<span class="keyword">instancetype</span>)sharedManager;</div><div class="line"><span class="comment">/// 实例化一个监听默认地址(0.0.0.0)的 manager</span></div><div class="line">+ (<span class="keyword">instancetype</span>)manager;</div><div class="line"><span class="comment">/// 实例化一个监听给定 domain 的 manager</span></div><div class="line">+ (<span class="keyword">instancetype</span>)managerForDomain:(<span class="built_in">NSString</span> *)domain;</div></pre></td></tr></table></figure><blockquote><p>Note: Reachability cannot tell your application if you can connect to a particular host, only that an interface is available that might allow a connection, and whether that interface is the WWAN.</p></blockquote><p>首先要澄清一个概念,根据 Apple 的文档,Reachability 并不能像 <code>ping</code> 一样,所以在天朝不要看到 Reachability 能够通过 Wi-Fi 连接到 <code>www.youtube.com</code> 就妄想你可以上 YouTube 了,你只是<strong>可能</strong>通过 Wi-Fi 连接到那个 host,而不是<strong>一定</strong>可以连接上。</p><p>这里比较建议调用 <code>+ (instancetype)manager</code> 创建属于自己的 manager,好处我们后面讲。</p><h4 id="2-开启监听"><a href="#2-开启监听" class="headerlink" title="2. 开启监听"></a>2. 开启监听</h4><p>首先需要调用开启监听的方法,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// 这是很容易被忽视的方法,不是初始化了 manager 就默认开始监听的</span></div><div class="line">- (<span class="keyword">void</span>)startMonitoring;</div></pre></td></tr></table></figure><p>如果你只想知道当前的网络条件是什么,通过 manager 的下面三个属性就可以了解了,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">BOOL reachable; // 有无网络连接</div><div class="line">BOOL reachableViaWWAN // 当前是移动网络</div><div class="line">BOOL reachableViaWiFi // 当前是 Wi-Fi</div></pre></td></tr></table></figure><h4 id="3-接收通知"><a href="#3-接收通知" class="headerlink" title="3. 接收通知"></a>3. 接收通知</h4><p>AFNetworking 的 Reachability Manager 提供了两种获取网络条件发生变化的方式,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// 通过设置 manager 的 statusChangeBlock,监听变化</span></div><div class="line">- (<span class="keyword">void</span>)setReachabilityStatusChangeBlock:(<span class="keyword">nullable</span> <span class="keyword">void</span> (^)(AFNetworkReachabilityStatus status))block;</div><div class="line"></div><div class="line"><span class="comment">/// 通过监听一个 manager 发出的 notification,userinfo 中的</span></div><div class="line"><span class="comment">/// `AFNetworkingReachabilityNotificationStatusItem` key 就是当前的 status</span></div><div class="line">FOUNDATION_EXPORT <span class="built_in">NSString</span> * <span class="keyword">const</span> AFNetworkingReachabilityDidChangeNotification;</div></pre></td></tr></table></figure><p>这里就要解释一下上文中提到问题,为什么不建议使用 <code>+ (instancetype)sharedManager;</code> 获得单例</p><p>因为 <code>setReachabilityStatusChangeBlock</code> 的回调通知的方式只会对最新调用的地方才会生效,如果大家都用 <code>sharedManager</code> 获取 manager,然后在自己的业务里设置回调的 block,如果此时多个业务都跑着,那在网络条件发生变化的时候,那也只有在最后被执行到的代码才会获得通知,之前所有设置 block 的地方都会失效,为了避免这种小概率的事情发生,还是应该在自己的业务里创建自己的 manager。</p><h4 id="4-停止监听"><a href="#4-停止监听" class="headerlink" title="4. 停止监听"></a>4. 停止监听</h4><p>不需要监听网络条件发生变化的时候调用此方法,</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)stopMonitoring;</div></pre></td></tr></table></figure><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>先来分析一下 manager 的初始化。它有一个 designated initializer,也就是所有的初始化方法都会最终调用这个方法进行初始化(顺便帮我们复习了一把初始化方法的设计),</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">instancetype</span>)initWithReachability:(<span class="built_in">SCNetworkReachabilityRef</span>)reachability {</div><div class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> init];</div><div class="line"> <span class="keyword">if</span> (!<span class="keyword">self</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> _networkReachability = <span class="built_in">CFRetain</span>(reachability);</div><div class="line"> <span class="keyword">self</span>.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>它接受一个被定义在 <code>SystemConfiguration/SystemConfiguration.h</code> 中 <code>SCNetworkReachabilityRef</code> 的结构体指针为参数。</p><p>看到以 Ref 结尾的对象,就要想到它是 Core Foundation 中的对象类型,这些对象基本上是 C 的指针,编译器不会帮你进行内存管理,所以需要调用 CF 开头的相关 API 自己手动进行内存管理。</p><p>由于 manager 持有一个 <code>SCNetworkReachabilityRef</code> 的对象,所以在这个初始化方法里,它对这个参数进行了 retain 操作,同样地,在 <code>dealloc</code> 中调用了 release 方法对资源进行释放。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)dealloc {</div><div class="line"> [<span class="keyword">self</span> stopMonitoring];</div><div class="line"> </div><div class="line"> <span class="comment">// 注意, C 对象的判空要和 NULL 比较</span></div><div class="line"> <span class="keyword">if</span> (_networkReachability != <span class="literal">NULL</span>) {</div><div class="line"> <span class="built_in">CFRelease</span>(_networkReachability);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>其它的几个 convenience initializers 就是针对不同的需求创建不同的 <code>SCNetworkReachabilityRef</code> 指针。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)startMonitoring {</div><div class="line"> <span class="comment">/// 在启用某个动作之前先关闭这个动作是一种非常好的编程习惯</span></div><div class="line"> <span class="comment">/// 防止多次调用某个方法会发生副作用</span></div><div class="line"> [<span class="keyword">self</span> stopMonitoring];</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (!<span class="keyword">self</span>.networkReachability) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> __<span class="keyword">weak</span> __<span class="keyword">typeof</span>(<span class="keyword">self</span>)weakSelf = <span class="keyword">self</span>;</div><div class="line"> AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {</div><div class="line"> __<span class="keyword">strong</span> __<span class="keyword">typeof</span>(weakSelf)strongSelf = weakSelf;</div><div class="line"> <span class="comment">/// 通知 block 的持有者,当前的网络状态</span></div><div class="line"> strongSelf.networkReachabilityStatus = status;</div><div class="line"> <span class="keyword">if</span> (strongSelf.networkReachabilityStatusBlock) {</div><div class="line"> strongSelf.networkReachabilityStatusBlock(status);</div><div class="line"> }</div><div class="line"></div><div class="line"> };</div><div class="line"></div><div class="line"> <span class="comment">/// 创建需要的 context,这个结构体包含了用户指定的信息以及 callback</span></div><div class="line"> <span class="built_in">SCNetworkReachabilityContext</span> context = {<span class="number">0</span>, (__bridge <span class="keyword">void</span> *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, <span class="literal">NULL</span>};</div><div class="line"> <span class="comment">/// 当 self.networkReachability 的状态发生变化时,将会调用 AFNetworkReachabilityCallback 函数指针</span></div><div class="line"> <span class="comment">/// AFNetworkReachabilityCallback 的第三个参数将会被传入上面创建的 context 结构体中的 callback 信息</span></div><div class="line"> <span class="built_in">SCNetworkReachabilitySetCallback</span>(<span class="keyword">self</span>.networkReachability, AFNetworkReachabilityCallback, &context);</div><div class="line"> <span class="comment">/// 开始监听 self.networkReachability,需要指定 runloop 和 runloop mode</span></div><div class="line"> <span class="comment">/// Apple 使用的是 kCFRunLoopDefaultMode</span></div><div class="line"> <span class="built_in">SCNetworkReachabilityScheduleWithRunLoop</span>(<span class="keyword">self</span>.networkReachability, <span class="built_in">CFRunLoopGetMain</span>(), kCFRunLoopCommonModes);</div><div class="line"></div><div class="line"> <span class="comment">/// 使用后台线程获取当前持有的 SCNetworkReachabilityRef 的网络条件</span></div><div class="line"> <span class="comment">/// 更新当前 manager 的 status</span></div><div class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, <span class="number">0</span>),^{</div><div class="line"> <span class="built_in">SCNetworkReachabilityFlags</span> flags;</div><div class="line"> <span class="keyword">if</span> (<span class="built_in">SCNetworkReachabilityGetFlags</span>(<span class="keyword">self</span>.networkReachability, &flags)) {</div><div class="line"> AFPostReachabilityStatusChange(flags, callback);</div><div class="line"> }</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/// 在 `SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);` 的时候设置上了系统回调的函数指针</span></div><div class="line"><span class="comment">/// 网络条件发生变化时,此函数被调用,同时获取到了那个持有 SCNetworkReachabilityRef 的 manager 的 block</span></div><div class="line"><span class="keyword">static</span> <span class="keyword">void</span> AFNetworkReachabilityCallback(<span class="built_in">SCNetworkReachabilityRef</span> __unused target, <span class="built_in">SCNetworkReachabilityFlags</span> flags, <span class="keyword">void</span> *info) {</div><div class="line"> AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">static</span> <span class="keyword">void</span> AFPostReachabilityStatusChange(<span class="built_in">SCNetworkReachabilityFlags</span> flags, AFNetworkReachabilityStatusBlock block) {</div><div class="line"> AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);</div><div class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{</div><div class="line"> <span class="keyword">if</span> (block) {</div><div class="line"> <span class="comment">/// 调用那个 manager 的 block,实现通知</span></div><div class="line"> block(status);</div><div class="line"> }</div><div class="line"> <span class="comment">/// 通知 notification 监听者</span></div><div class="line"> <span class="built_in">NSNotificationCenter</span> *notificationCenter = [<span class="built_in">NSNotificationCenter</span> defaultCenter];</div><div class="line"> <span class="built_in">NSDictionary</span> *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };</div><div class="line"> [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:<span class="literal">nil</span> userInfo:userInfo];</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>AFNetworking 的 Reachability 封装的比 Apple 好的地方在于,它不光实现了通过 notification 的方式通知网络变化,还实现了 block 的方式,代码干净,文档丰富。一个 iOS 届最知名的开源库绝非浪得虚名。</p>]]></content>
<summary type="html">
<h2 id="Reachability-是什么?"><a href="#Reachability-是什么?" class="headerlink" title="Reachability 是什么?"></a>Reachability 是什么?</h2><p>根据 Mac 自带的
</summary>
<category term="iOS" scheme="http://zhaoxinyu.me/tags/iOS/"/>
<category term="Objective-C" scheme="http://zhaoxinyu.me/tags/Objective-C/"/>
</entry>
<entry>
<title>Express 的 API 版本控制</title>
<link href="http://zhaoxinyu.me/2017-02-01-express-api-versioning/"/>
<id>http://zhaoxinyu.me/2017-02-01-express-api-versioning/</id>
<published>2017-02-01T14:00:00.000Z</published>
<updated>2017-02-01T14:39:31.000Z</updated>
<content type="html"><![CDATA[<p>在<a href="http://x140yu.com/2017-02-01-api-versioning/" target="_blank" rel="external">上一篇博文</a>中,我们介绍了一下后端做 API 版本控制的几种方法。</p><p>最近在用 Node.js 做一个小项目 <a href="https://github.com/DayCache/DayCache" target="_blank" rel="external">DayCache</a>,其中就涉及到了给客户端提供 API 调用这一块,当然就也需要 API 的版本控制了。</p><h2 id="api-v1-post-id"><a href="#api-v1-post-id" class="headerlink" title="/api/v1/post/:id"></a><code>/api/v1/post/:id</code></h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">// Version 1</div><div class="line">@GET /api/v1/post/:id</div><div class="line"></div><div class="line">// Version 2</div><div class="line">@GET /api/v2/post/:id</div></pre></td></tr></table></figure><p>对于这种方式的 API 版本控制我们应该怎么实现呢?</p><p>首先,把目录结构组织如下,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">├── api</div><div class="line">│ └── v1</div><div class="line">│ └── index.js</div><div class="line">│ └── signin.js</div><div class="line">│ └── signup.js</div><div class="line">└── index.js</div></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// index.js</span></div><div class="line"></div><div class="line">...</div><div class="line"></div><div class="line"><span class="comment">/*</span></div><div class="line"><span class="comment"> // API</span></div><div class="line"><span class="comment"> 如果需要新增加一个 API 的版本</span></div><div class="line"><span class="comment"> 1. 把 ./api/v1 目录复制一份,改名为 v2</span></div><div class="line"><span class="comment"> 2. 在下面的 API_VERSIONS 字典中加入一对 kv,如 `'Version 2': '/v2'`</span></div><div class="line"><span class="comment">*/</span></div><div class="line"></div><div class="line"><span class="keyword">var</span> API_VERSIONS = {</div><div class="line"> <span class="string">'Version 1'</span>: <span class="string">'/v1'</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> v <span class="keyword">in</span> API_VERSIONS) {</div><div class="line"> <span class="keyword">var</span> api = <span class="built_in">require</span>(<span class="string">'./api'</span> + API_VERSIONS[v]);</div><div class="line"> api(app);</div><div class="line">}</div><div class="line"></div><div class="line">...</div></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// api/v1/index.js</span></div><div class="line"><span class="comment">// 如果需要加入新的请求路径,只需要在下面加入路径</span></div><div class="line"><span class="comment">// 然后在 `api/v1` 下建立同名文件即可</span></div><div class="line"><span class="keyword">let</span> ROUTE_PATHS = [</div><div class="line"> <span class="string">'/signin'</span>,</div><div class="line"> <span class="string">'/signup'</span>,</div><div class="line">];</div><div class="line"></div><div class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span> (<span class="params">app</span>) </span>{</div><div class="line"></div><div class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> ROUTE_PATHS) {</div><div class="line"> <span class="keyword">let</span> path = ROUTE_PATHS[i];</div><div class="line"> <span class="built_in">console</span>.log(path);</div><div class="line"> app.use(<span class="string">'/api/v1'</span> + path, <span class="built_in">require</span>(<span class="string">'.'</span> + path));</div><div class="line"> }</div><div class="line"></div><div class="line">};</div></pre></td></tr></table></figure><p>这样,API 版本化的工作就已经完成了,而且不同版本的 API 拆分地比较干净,同一个版本不同路径的请求也都在不同的文件中。</p><h2 id="Accept-Version-1-0"><a href="#Accept-Version-1-0" class="headerlink" title="Accept-Version: 1.0"></a><code>Accept-Version: 1.0</code></h2><p>对于这种请求的 API 版本放到 header 中的,也比较好处理,可以看一下 <a href="https://github.com/Prasanna-sr/express-routes-versioning" target="_blank" rel="external">express-routes-versioning</a> 这个中间件(毕竟 Express.js,中间件就是多),它就是比较优雅地处理了这个问题:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// index.js</span></div><div class="line"><span class="keyword">var</span> app = <span class="built_in">require</span>(<span class="string">'express'</span>)();</div><div class="line"><span class="keyword">var</span> routesVersioning = <span class="built_in">require</span>(<span class="string">'express-routes-versioning'</span>)();</div><div class="line"></div><div class="line">app.get(<span class="string">'/test'</span>, routesVersioning({</div><div class="line"> <span class="string">"1.0.0"</span>: respondV1,</div><div class="line"> <span class="string">"~2.2.1"</span>: respondV2</div><div class="line">}));</div><div class="line"></div><div class="line"><span class="comment">// curl -s -H 'accept-version: 1.0.0' localhost:3000/test</span></div><div class="line"><span class="comment">// version 1.0.0 or 1.0 or 1 !</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">respondV1</span>(<span class="params">req, res, next</span>) </span>{</div><div class="line"> res.status(<span class="number">200</span>).send(<span class="string">'ok v1'</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">//curl -s -H 'accept-version: 2.2.0' localhost:3000/test</span></div><div class="line"><span class="comment">//Anything from 2.2.0 to 2.2.9</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">respondV2</span>(<span class="params">req, res, next</span>) </span>{</div><div class="line"> res.status(<span class="number">200</span>).send(<span class="string">'ok v2'</span>);</div><div class="line">}</div></pre></td></tr></table></figure><p>把请求 header 中 <code>Accept-Version</code> 不同的 API 版本挂在到不同的 handler 中,而且还可以使用类似于 <code>~2.2.1</code> 的方式指定版本号。<br>这个中间件的代码没有多少,可以根据你的需要定制一个,比如 custom header 啦,版本号的匹配规则啦。</p>]]></content>
<summary type="html">
<p>在<a href="http://x140yu.com/2017-02-01-api-versioning/" target="_blank" rel="external">上一篇博文</a>中,我们介绍了一下后端做 API 版本控制的几种方法。</p>
<p>最近在用 N
</summary>
<category term="Nodejs" scheme="http://zhaoxinyu.me/tags/Nodejs/"/>
</entry>
<entry>
<title>服务器端的 API 版本控制</title>
<link href="http://zhaoxinyu.me/2017-02-01-api-versioning/"/>
<id>http://zhaoxinyu.me/2017-02-01-api-versioning/</id>
<published>2017-02-01T02:13:00.000Z</published>
<updated>2017-02-01T13:29:13.000Z</updated>
<content type="html"><![CDATA[<h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>发布出去的 API 就像泼出去的水,不进行版本控制,对某些调用者来说一定会是灾难性的。</p><p>举个例子。比如你在一家公司负责后端 API 的开发,同时给公司的 Android 和 iOS 客户端提供接口。<br>在 1 月份的时候,你开发了一个接口,用于获取文章的详情</p><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">@GET /post/:id</div><div class="line">//response</div><div class="line">{</div><div class="line"> <span class="attr">"title"</span>: <span class="string">"xxx"</span>,</div><div class="line"> <span class="attr">"content"</span>: <span class="string">"xxx"</span>,</div><div class="line"> <span class="attr">"author"</span>: {</div><div class="line"> <span class="attr">"id"</span>: <span class="number">123</span>,</div><div class="line"> <span class="attr">"name"</span>: <span class="string">"xxx"</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>开发完成后,你很 happy 地告诉客户端同学,获取文章详情就调我这个接口吧,一点问题都没有。客户端同学也很 happy 地调用了。2 月份,客户端开发完成,顺利发布 1.0 版本。这个时候,还没有遇到任何问题。</p><p>3 月份的时候,设计变了,文章支持了多作者!所以现在那个相同的 API 返回的结果可能是这样的,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">@GET /post/:id</div><div class="line">// response</div><div class="line">{</div><div class="line"> "title": "xxx",</div><div class="line"> "content": "xxx",</div><div class="line"> "author": [</div><div class="line"> {</div><div class="line"> "id": 123,</div><div class="line"> "name": "xxx"</div><div class="line"> },</div><div class="line"> {</div><div class="line"> "id": 456</div><div class="line"> "name": "xxx"</div><div class="line"> }</div><div class="line"> ]</div><div class="line">}</div></pre></td></tr></table></figure><p>Boom! 这下可糟了,如果 API 产生了这么重大的变化,那么已经发出去的 1.0 版本的客户端可怎么办啊?(开发过客户端的同学都知道,同一个接口返回这么「脏」的数据是会 crash 的)。这个时候,就需要 API versioning(版本化)了。</p><h2 id="How"><a href="#How" class="headerlink" title="How?"></a>How?</h2><p>通常来说,API 版本控制有以下几种方式:</p><h3 id="api-v1-xxx"><a href="#api-v1-xxx" class="headerlink" title="/api/v1/xxx"></a><code>/api/v1/xxx</code></h3><p>直接把 API 的版本信息放到 URL 里面,<a href="http://open.weibo.com/wiki/2/users/show" target="_blank" rel="external">新浪</a>是这么做的。</p><p>优点:直观<br>缺点:URL 被污染了,很不优雅</p><h3 id="API-VERSION-3-0"><a href="#API-VERSION-3-0" class="headerlink" title="API-VERSION: 3.0"></a><code>API-VERSION: 3.0</code></h3><p>使用自定义的 header 识别 API 的版本,知乎目前是这么做的。</p><p>优点:URL 变得很干净<br>缺点:不直观,代码不好组织,而且容易产生向后兼容的问题(至少知乎的某些 API 是这样的,一般这种对于 header 的修改都是全局的,某些 API 说好了可以升级到 2.0,但是我调用 2.0 的 <code>/singin</code> 都登录不上)</p><h3 id="Accept-application-vnd-xxx-v2-json"><a href="#Accept-application-vnd-xxx-v2-json" class="headerlink" title="Accept: application/vnd.xxx.v2+json"></a><code>Accept: application/vnd.xxx.v2+json</code></h3><p>修改 header 中的 <code>Accept</code> 字段,<a href="https://developer.github.com/v3/" target="_blank" rel="external">Github</a> 是这么做的。</p><p>优缺点同上</p>]]></content>
<summary type="html">
<h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>发布出去的 API 就像泼出去的水,不进行版本控制,对某些调用者来说一定会是灾难性的。</p>
<p>举个例子。比如你在一家公司
</summary>
<category term="后端" scheme="http://zhaoxinyu.me/tags/%E5%90%8E%E7%AB%AF/"/>
</entry>
<entry>
<title>宇带逛之天坛篇</title>
<link href="http://zhaoxinyu.me/2017-01-14-yu-ge-dai-ni-you-tian-tan/"/>
<id>http://zhaoxinyu.me/2017-01-14-yu-ge-dai-ni-you-tian-tan/</id>
<published>2017-01-14T12:54:00.000Z</published>
<updated>2017-01-14T13:12:58.000Z</updated>
<content type="html"><![CDATA[<p>我这颗躁动的心终于被最近的好空气点燃了。今天去了一直想去但没去(太远了……)的天坛。<br>现在属于淡季,联票 28 元,去的话,请一定要买联票。去了趟天坛如果连祈年殿都没看到的话,那应该是白去了……</p><h2 id=""><a href="#" class="headerlink" title=""></a><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeo3v333j21kw11oe81.jpg" alt=""></h2><p><img src="http://wx1.sinaimg.cn/large/5cc3eefely1fbqeo875c8j21kw11o1kx.jpg" alt=""><br>不知道这叫什么树,但是,它在夏天的样子,一定更美</p><hr><p><img src="http://wx2.sinaimg.cn/large/5cc3eefely1fbqeoau31fj21kw11oh9x.jpg" alt=""><br>这帮大爷大妈一定是买了年票进来以牌会友的,声音超大,有点吵了……</p><hr><p><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeoepczwj21kw11o1kx.jpg" alt=""><br>长廊,不过论精致程度,还是颐和园里的更胜一筹(我都要爱死颐和园了 <3</p><hr><p><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeog300vj21kw11okc7.jpg" alt=""></p><p><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeoine7uj21kw11o4qp.jpg" alt=""><br><img src="http://wx2.sinaimg.cn/large/5cc3eefely1fbqeokwfcjj21kw11o7wh.jpg" alt=""><br>祈年殿台阶上的浮雕</p><hr><p><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeooynnuj21kw2dw1ky.jpg" alt=""><br><img src="http://wx1.sinaimg.cn/large/5cc3eefely1fbqeor5sp9j21kw11o7up.jpg" alt=""><br><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeotcwdjj21kw2dw7wh.jpg" alt=""><br><img src="http://wx1.sinaimg.cn/large/5cc3eefely1fbqep0j9dcj21kw11oqky.jpg" alt=""><br><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqep8qa84j21kw11oqoj.jpg" alt=""><br>各个角度的祈年殿,在蓝天的映衬下美极了有没有</p><hr><p><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeov4p04j21kw11okbm.jpg" alt=""><br><img src="http://wx4.sinaimg.cn/large/5cc3eefely1fbqeoy8e4aj21kw11oh9l.jpg" alt=""><br><img src="http://wx1.sinaimg.cn/large/5cc3eefely1fbqep339cvj21kw0uyh9m.jpg" alt=""><br>和香炉的影子一起卖个萌</p><hr><p><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqep67fwuj21kw2dwnpd.jpg" alt=""><br><img src="http://wx1.sinaimg.cn/large/5cc3eefely1fbqepbm9g2j21kw2dwe81.jpg" alt=""><br><img src="http://wx2.sinaimg.cn/large/5cc3eefely1fbqepcnzg9j21kw11owre.jpg" alt=""><br>回音壁</p><hr><p><img src="http://wx1.sinaimg.cn/large/5cc3eefely1fbqepdazl4j21kw11oqhs.jpg" alt=""><br>萌萌的一家三口</p><hr><p><img src="http://wx4.sinaimg.cn/large/5cc3eefely1fbqepflcfsj21kw11ob29.jpg" alt=""><br><img src="http://wx2.sinaimg.cn/large/5cc3eefely1fbqeph8cs7j21kw11o1kx.jpg" alt=""><br>天坛公园的绿化面积特别大。有很多鸟类在这里生活。而且这里的喜鹊都不怕人的</p><hr><p><img src="http://wx2.sinaimg.cn/large/5cc3eefely1fbqepk3mm2j21kw11okjl.jpg" alt=""><br>七星石(数了数,一共有八块 = =</p><hr><p><img src="http://wx2.sinaimg.cn/large/5cc3eefely1fbqepne8n9j21kw11o1kx.jpg" alt=""><br><img src="http://wx3.sinaimg.cn/large/5cc3eefely1fbqeprbgnqj21kw11oe0g.jpg" alt=""><br>逛完以后,从天坛一路 ofo 到鼓楼,如果北京雾霾没有那么严重的话,这些共享单车的数据一定会更漂亮吧</p><hr>]]></content>
<summary type="html">
<p>我这颗躁动的心终于被最近的好空气点燃了。今天去了一直想去但没去(太远了……)的天坛。<br>现在属于淡季,联票 28 元,去的话,请一定要买联票。去了趟天坛如果连祈年殿都没看到的话,那应该是白去了……</p>
<h2 id=""><a href="#" class="hea
</summary>
<category term="摄影" scheme="http://zhaoxinyu.me/tags/%E6%91%84%E5%BD%B1/"/>
</entry>
<entry>
<title>如何写好 commit message?</title>
<link href="http://zhaoxinyu.me/2017-01-04-how-to-write-good-commit-message/"/>
<id>http://zhaoxinyu.me/2017-01-04-how-to-write-good-commit-message/</id>
<published>2017-01-04T15:54:00.000Z</published>
<updated>2017-01-04T16:04:53.000Z</updated>
<content type="html"><![CDATA[<p>Commit message 是每个开发人员每天都要写的东西,可是大多数人都会以一个 bug fix 或者 update xxx 草草了事。那么什么是好的 commit message 呢?我看到了一篇<a href="http://chris.beams.io/posts/git-commit/" target="_blank" rel="external">好文</a> ,顺手给大家总结了一下</p><p>比如,这是一条 commit message,首先我们要搞清楚 subject 和 body。</p><p><img src="http://ww3.sinaimg.cn/large/5cc3eefejw1fbf1lbtx1dj20ze0l6qeq.jpg" alt=""></p><h3 id="1-Subject-和-body-之间要留一个空行"><a href="#1-Subject-和-body-之间要留一个空行" class="headerlink" title="1. Subject 和 body 之间要留一个空行"></a>1. Subject 和 body 之间要留一个空行</h3><ul><li>这是 <code>git commit</code> 命令的<a href="https://www.kernel.org/pub/software/scm/git/docs/git-commit.html#_discussion" target="_blank" rel="external">推荐做法</a></li></ul><ul><li>很多 git 的命令依赖于 subject 和 body 中的空行,比如 <code>git log --oneline</code>,<code>git shortlog</code></li><li>PS. 如果 commit 内容较少,没必要写 body</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">// good</div><div class="line">Summarize changes in around 50 characters or less</div><div class="line"></div><div class="line">More detailed explanatory text, if necessary. Wrap it to about 72</div><div class="line">characters or so. In some contexts, the first line is treated as the</div><div class="line">...</div><div class="line"></div><div class="line">// bad</div><div class="line">Summarize changes in around 50 characters or less</div><div class="line">More detailed explanatory text, if necessary. Wrap it to about 72</div><div class="line">characters or so. In some contexts, the first line is treated as the</div><div class="line">...</div></pre></td></tr></table></figure><h3 id="2-把-subject-控制在-50-个字符之内"><a href="#2-把-subject-控制在-50-个字符之内" class="headerlink" title="2. 把 subject 控制在 50 个字符之内"></a>2. 把 subject 控制在 50 个字符之内</h3><p>如图,不解释。</p><p><img src="http://ww4.sinaimg.cn/large/5cc3eefejw1fbf1lb459zj21k40icn0j.jpg" alt=""></p><h3 id="3-Subject-的首字母大写"><a href="#3-Subject-的首字母大写" class="headerlink" title="3. Subject 的首字母大写"></a>3. Subject 的首字母大写</h3><p>不觉得开头大写很优美吗?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">// good</div><div class="line">Accelerate to 88 miles per hour</div><div class="line"></div><div class="line">// bad</div><div class="line">accelerate to 88 miles per hour</div></pre></td></tr></table></figure><h3 id="4-Subject-的结尾不要加标点符号"><a href="#4-Subject-的结尾不要加标点符号" class="headerlink" title="4. Subject 的结尾不要加标点符号"></a>4. Subject 的结尾不要加标点符号</h3><p>Subject 尽量简明扼要,标点符号不是必须的。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">// good</div><div class="line">Open the pod bay doors</div><div class="line"></div><div class="line">// bad</div><div class="line">Open the pod bay doors.</div></pre></td></tr></table></figure><h3 id="5-在-subject-中,使用-imperative-的语句"><a href="#5-在-subject-中,使用-imperative-的语句" class="headerlink" title="5. 在 subject 中,使用 imperative 的语句"></a>5. 在 subject 中,使用 imperative 的语句</h3><p>先来看看什么是 imperative 和 indicative</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">// imperative(命令性) VS indicative(陈述性)</div><div class="line">Empty the bin, John. // imperative</div><div class="line">John empties the bin. // indicative</div></pre></td></tr></table></figure><p>Commit 中 的 subject 使用 imperative 的语句,比如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">// good</div><div class="line">- Refactor subsystem X for readability</div><div class="line">- Update getting started documentation</div><div class="line">- Remove deprecated methods</div><div class="line">- Release version 1.0.0</div><div class="line"></div><div class="line">// bad</div><div class="line">- Fixed bug with Y</div><div class="line">- Changing behavior of X</div><div class="line">- More fixes for broken stuff</div><div class="line">- Sweet new API methods</div></pre></td></tr></table></figure><p>有一个简单的做法可以看出你的 subject 是否符合了这条规定,就是把它套进 <code>If applied, this commit will {subject}</code>,如果语句通顺没有语病,说明你的 subject 是没有问题的。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">// good</div><div class="line">- If applied, this commit will refactor subsystem X for readability</div><div class="line">- If applied, this commit will update getting started documentation</div><div class="line">- If applied, this commit will remove deprecated methods</div><div class="line">- If applied, this commit will release version 1.0.0</div><div class="line">- If applied, this commit will merge pull request #123 from user/branch</div><div class="line"></div><div class="line">// bad</div><div class="line">- If applied, this commit will fixed bug with Y</div><div class="line">- If applied, this commit will changing behavior of X</div><div class="line">- If applied, this commit will more fixes for broken stuff</div><div class="line">- If applied, this commit will sweet new API methods</div></pre></td></tr></table></figure><p>在我看来,其实就是以动词原型开头(逃</p><h3 id="6-控制-body-每行的长度在-72-个字符"><a href="#6-控制-body-每行的长度在-72-个字符" class="headerlink" title="6. 控制 body 每行的长度在 72 个字符"></a>6. 控制 body 每行的长度在 72 个字符</h3><p>如果一行太长话,那真是没法看……</p><h3 id="7-用-body-说明这次-commit-的-what-why-how"><a href="#7-用-body-说明这次-commit-的-what-why-how" class="headerlink" title="7. 用 body 说明这次 commit 的 what, why, how"></a>7. 用 body 说明这次 commit 的 what, why, how</h3><p>能做到这一步的人真的很少。</p><p>PS. 我还是建议大家多用英文写 commit message,因为除了写英文代码注释以外,commit message 是唯一能在每日的工作中训练自己写好英语的机会了。好的 commit message 不光自己看着心里舒服,跟你协作开发的人看着也得劲。从这一刻开始,写好每一条 commit message 吧!</p>]]></content>
<summary type="html">
<p>Commit message 是每个开发人员每天都要写的东西,可是大多数人都会以一个 bug fix 或者 update xxx 草草了事。那么什么是好的 commit message 呢?我看到了一篇<a href="http://chris.beams.io/posts
</summary>
<category term="技术" scheme="http://zhaoxinyu.me/tags/%E6%8A%80%E6%9C%AF/"/>
</entry>
<entry>
<title><读>「万万没想到 - 用理工科思维理解世界」</title>
<link href="http://zhaoxinyu.me/2017-01-01-wwmxd/"/>
<id>http://zhaoxinyu.me/2017-01-01-wwmxd/</id>
<published>2017-01-01T12:17:00.000Z</published>
<updated>2017-01-02T14:23:33.000Z</updated>
<content type="html"><![CDATA[<ul><li>Start: 2016-12-31</li><li>Finish: 2017-01-01</li><li>link: <a href="https://book.douban.com/subject/25986341/" target="_blank" rel="external">https://book.douban.com/subject/25986341/</a></li></ul><hr><p>正值 2016-2017 的跨年之际,有了三天难得的假期。本来指望能出去好好玩一玩逛一逛,没成想,北京持续 5 天雾霾给我了一个我一个开年大礼。成,不就是在家呆着吗,这对我这个死宅来说有何难。</p><p>刷微博的时候,发现了 @JailBreakHum 发的一张图片,内容是一本书某页的截屏,讲r 是某家新闻网站的新闻标题其实是靠网友选择出来的。因为在发布的 5 分钟内时候进行了 A/B Test,点击率比较高的标题会被选为这条新闻的标题。虽然作为一个开发人员,我是了解 A/B Test 的,但当时看完这条我还是有点惊讶的。这吸引到了我,恰巧这本书在 Amazon 上特价,只需 0.1 元,于是是入手咯。</p><p>下到书以后,发现这其实是一个博主的博客文集,从目录来看大体分成了三个部分,</p><ol><li>反常识思维 - 主要介绍了一些有悖于我们常识但是确实有科学依据东西</li><li>成功学的解药 - 一些切实可行的成功学方法</li><li>霍金的答案 - 说实话,对这个部分的内容不太感冒,就草草略过了</li></ol><h2 id="反常识思维"><a href="#反常识思维" class="headerlink" title="反常识思维"></a>反常识思维</h2><p>这一部分主要是作者对于生活中某些事情的观点,并以大量的事例以及客观分析加以佐证。其中作者的大部分观点我都是非常认同的。其实他提到的很多问题都可以在<a href="https://zh.wikipedia.org/wiki/%E8%AA%8D%E7%9F%A5%E5%81%8F%E8%AA%A4%E5%88%97%E8%A1%A8" target="_blank" rel="external">认知偏误列表</a>中找到相关的条目,我是非常希望大家可以平时没事多看看这个列表,因为它能够让我们对自己有更加客观、清晰的认识。</p><h2 id="成功学的解药"><a href="#成功学的解药" class="headerlink" title="成功学的解药"></a>成功学的解药</h2><p>这部分应该是这本书的重头戏,因为和一般的成功学书籍不太一样,作者提供了特别多的可以立即上手的方案。</p><h3 id="刻意练习"><a href="#刻意练习" class="headerlink" title="刻意练习"></a>刻意练习</h3><p>大家一定都听过「一万小时定理」,一个人要想在一个领域内成为顶尖高手,只要练习一万小时即可。读完了「练习一万小时成天才」这篇文章后,我了解了,其实对于「成为天才」这件事情来说,<a href="https://en.wikipedia.org/wiki/Practice_(learning_method" target="_blank" rel="external">刻意练习</a>#Deliberate_practice)乘以天赋才能决定最终的结果。何为刻意练习?书中给了很好的总结,</p><ol><li>只在「学习区」内练习</li><li>把训练内容切分成小块,重复练习</li><li>随时获得有效反馈</li><li>注意力高度集中</li></ol><p>何为「学习区」?大家一定都听过「逃离舒适区」,那什么是「舒适区」?舒适区里边的东西其实就是你已经熟练掌握的,这是生活;学习区的东西是还不会,但是通过一定的学习就能掌握的,这是练习;恐慌区中的东西是怎么看都不会的。刻意练习针对的就是学习区内的内容,待续做自己做不好的事情。只要不停地在学习区内练习,那他就会不停地进步。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">|------------------|</div><div class="line">| |</div><div class="line">| |------------| |</div><div class="line">| | | |</div><div class="line">| | |--------| | |</div><div class="line">| | | 舒适区 | | |</div><div class="line">| | |--------| | |</div><div class="line">| | 学习区 | |</div><div class="line">| |------------| |</div><div class="line">| 恐慌区 |</div><div class="line">|------------------|</div></pre></td></tr></table></figure><h3 id="高效「冲浪」的方法"><a href="#高效「冲浪」的方法" class="headerlink" title="高效「冲浪」的方法"></a>高效「冲浪」的方法</h3><p>><br>看完一篇再看下一篇,一篇篇地读下去,那是错误的做法。</p><p>作者提供了可以实施的高效上网方案(其实我也是一直这么做的,逃)大概可以分为以下三步:</p><ol><li>随便翻翻 <br><br>选一个集中的时间段,把想要看的站点全部打开,对于想看的文章,在新的 Tab 中打开,这个时候不去读这些新的标签页。</li><li>略读 <br><br>对于这些已经打开的标签页,粗读一下,把值得读的文章保存到 Pocket 中(或者是其实类似的 Read Later Service)。</li><li>精读 <br><br>仔细读完,对于好到不行的文章可以把它保存到 Evernote 中,进行批注。</li></ol><p>这种方法也引申出了一个心理学的概念,narrow framing 和 broad framing。一条一条接着读就是 narrow framing,而把所有的文章都摆在桌面上集中选择就是 broad framing。你的时间非常有限,broad framing 是一种很好的决策方式。</p><h3 id="读书笔记"><a href="#读书笔记" class="headerlink" title="读书笔记"></a>读书笔记</h3><p>作者对于笔记的重要性的观点我也是很赞同。人人都知道记笔记好,但是怎么记,应用起来又是另一回事了。</p><p>其实写这篇读书笔记是我的第一篇,我也是通过作者提供的方法,一步一步慢慢实现的。</p><ul><li><p>读什么?<br><br>能提升自己对某一领域理解的,能够有一种智力得到提升的感觉(哪怕是错觉)的非小说类书籍。</p></li><li><p>怎么读? <br><br>新书读两遍,第一遍正常通读,不求快,能时不时停下思考是最好。第二遍,开始记笔记,主要是思想脉络,读一章记一章,读完了这本书就可以扔了。</p></li><li><p>怎么记笔记? <br></p><ol><li>清晰地表现每一章的逻辑脉络,如果你喜欢用思维导图记笔记的话,也只不过是完了好的读书笔记的一部分。</li><li>带走书中所有的亮点,把所有你觉得是亮点的都带走,那这本书在记完笔记之后就真的可以扔掉或者送人了。</li><li>有大量的看法和心得</li><li>找到与之前读过的书或文章的联系</li></ol></li></ul><h1 id="书中的一些-Shinning-Things"><a href="#书中的一些-Shinning-Things" class="headerlink" title="书中的一些 Shinning Things"></a>书中的一些 Shinning Things</h1><p>></p><ul><li>每天给自己制定一个更远的目标。</li><li>蒙洛迪诺说,人做判断的时候有两种机制:一种是 “科学家机制”,先有证据再下结论;一种是 “律师机制”,先有了结论再去找证据。世界上科学家很少,你猜绝大多数人使用什么机制思考?每个人都爱看能印证自己已有观念的东西。我们不但不爱看,而且还会直接忽略,那些不符合我们已有观念的证据。</li><li>如果微博上有人发出违背我理念的言论怎么办?我会果断取消对他的关注。我们完全有权这么做,难道有人上微博是为了找气受吗?可是如果人人都只接收符合自己观点的信息,甚至只跟与自己志同道合的人交流,那么就会形成一个“回音室效应(echochambereffect)”。人们的观念将会变得越来越极端。</li><li>管理者有个常见的思维模式,一旦出了事就必须全体反思,制定相关政策以避免类似事故再次发生,但极小概率事故其实是不值得过度反应的。哪怕是因为员工犯了错而引起的也没必要如此。37 signals公司的两位创始人弗莱德(Jason Fried)和汉森(David Heinemeier Hansson)在2010年出了一本书《重来》(Rework),讲公司创业和管理之道。在我看来此书一个亮点就是它强调不要一看有人犯了错就为此大张旗鼓地制定政策来纠正错误。那样只会把错误变成伤疤,而且会让公司越来越官僚主义。正确的办法是告诉犯错的员工这是一个错误,然后就完了。</li><li>有了各种库函数,编程是如此的简单,以至于高中生也会写程序,可是最好的程序员仍然可以把编程从技术上升到艺术的境界。<br>-如果能通过读书来了解一些前辈的经验,掌握一点做事的方法,甚至哪怕仅仅获得一种更乐观向上的精神,其实都是很不错的收获。读书难道不就是为了这些吗?</li><li>自控需要意志力。一般人可能认为意志力是一种美德,应该通过教育的方式提升思想的境界来培养。然而实验表明,意志力其实是一种生理机能。它就好像人的肌肉一样每次使用都需要消耗能量,而且用多了会疲惫。</li><li>人的意志力能量来自血液中的葡萄糖。</li><li>练习的精髓是要持续地做自己做不好的事。</li><li>只要有快速反馈,再经过长时间的训练,你就能培养出专家的直觉,能够“眨眼判断”。可是,如果反馈是中长期的,直觉就不好使了。我们可以再多想想这个问题。也许只有这样的“环境友好”领域,也就是有快速反馈的领域,才能培养出真正的专家。</li><li>在某种程度上,刻意练习是以错误为中心的练习。练习者必须要对错误极度敏感,一旦发现自己错了就会感到非常不舒服,一直练习到改正为止。</li><li>每个人的时间都一样多,因此时间不是金钱。时间是围棋:你走一手,牛人也走一手,牛人获胜并不是因为他走得比你多,而是他每一手都走在最有价值的地方。执行这样的效率,需要钢铁般的意志。谁能做到不看无聊的文章,谁能做到不去刷新网页,谁能做到不看电视新闻?牛人都能做到。</li><li>知识是有等级的。八卦新闻、实效性强的信息、网友对时局的看法,本来就不值得印在纸上浪费树木,在网上看看正好。扫读网页不见得是什么毛病,相反,能够以不同速度读不同等级的内容是最有用的阅读技术。上网的关键态度是要成为网络的主人,而不做各种超链接的奴隶。高效率的上网应该像自闭症患者一样具有很强的目的性,以我为主,不被无关信息左右。就算是纯粹为了娱乐上网也无可厚非,这时候读得快就是优点。一个真正的智者不会让上网占用读书时间,他应该经常能够平静地深入思考,只有电话接线员才随叫随到。</li><li>如果灵魂真的存在,那么就应该无处不在,而不是非得有灵异现象才能证明灵魂存在。一个有灵魂的世界,每个新生命都不是完全“新”的,其灵魂必然已经经历过好几次别的生命;而在一个没有灵魂的世界,每一个新生命都是完全新的。这两个世界的表现如果完全一样,那么也就是灵魂不可测量,那么有没有灵魂这个问题就毫无意义,再用转世轮回学说去劝人向善也没意义(因为反正都一样)。因此我们可以假设,一个有灵魂存在的世界,必然存在某些可观测的量,代表灵魂转世对这个世界的影响。</li><li>打游戏对人脑的认知能力可能有用,但也有研究认为没用。不管是否真的有用,我们都可以想见就算有用其用处也不大。如果你想学好微积分,你最好的办法是找本微积分习题集做,而不是用大脑训练软件去试图把大脑磨快一点再学微积分,那等于缘木求鱼。</li></ul>]]></content>
<summary type="html">
<ul>
<li>Start: 2016-12-31</li>
<li>Finish: 2017-01-01</li>
<li>link: <a href="https://book.douban.com/subject/25986341/" target="_blank" r
</summary>
<category term="读书笔记" scheme="http://zhaoxinyu.me/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title><译> 如何阅读 Swift 标准库中的源码</title>
<link href="http://zhaoxinyu.me/2016-12-04-how-to-read-the-swift-standard-libray-source/"/>
<id>http://zhaoxinyu.me/2016-12-04-how-to-read-the-swift-standard-libray-source/</id>
<published>2016-12-10T12:17:00.000Z</published>
<updated>2016-12-10T12:20:08.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="https://oleb.net/blog/2016/10/swift-stdlib-source/" target="_blank" rel="external">https://oleb.net/blog/2016/10/swift-stdlib-source/</a><br>作者=Ole Begemann<br>译者=X140Yu</p><!--此处开始正文--><p><strong>在进行完 GYP 预处理后,阅读 Swift 标准库源码的<del>最简单的</del>一种方式是执行一次完整的 Swift 编译。(另一种是写一小段 shell 脚本。可以看下面的更新)</strong></p><p>如果你想要开始阅读 Swift 源码,那它的标准库应该是首先开始阅读的地方。标准库中的代码是和每一个使用 Swift 的开发者都息息相关的,如果你也曾经对某个 API 的表现和性能有过怀疑,那么直接阅读对应的源码会是解决问题最快的方式。</p><p>标准库也是 Swift 项目中最容易接触的地方。一点是因为,它是由 Swift 写的,而不是 C++。因为你每天都用它,所以对它的 API 也会非常熟悉。这就意味着,在源码中找到你想要找的那部分代码不是特别困难。如果你只是没有带着目标而泛泛地看,那么在源码中你可能会发现<a href="https://oleb.net/blog/2016/09/playground-print-hook/" title="_playgroundPrintHook" target="_blank" rel="external">一</a>或者<a href="https://oleb.net/blog/2016/10/swift-array-of-c-strings/" title="Passing an Array of Strings from Swift to C" target="_blank" rel="external">两</a>块金子.</p><h1 id="在哪里能找到标准库的源码?"><a href="#在哪里能找到标准库的源码?" class="headerlink" title="在哪里能找到标准库的源码?"></a>在哪里能找到标准库的源码?</h1><p>标准库的代码在 <a href="https://github.com/apple/swift/tree/master/stdlib/public/core" target="_blank" rel="external"><code>stdlib/public/core</code></a>,GitHub 上的 <a href="https://github.com/apple/swift" target="_blank" rel="external">Swift 仓库中</a>。你可以在里面找到所有 public types,protocols 和 functions。你可以在浏览器中或者把代码 clone 到本地的机器上阅读。但是,一个比较复杂的地方在于,大约 1/3 的文件都以 <code>.swift.gyb</code> 结尾,如果你打开其中一个文件,例如:<a href="https://github.com/apple/swift/blob/master/stdlib/public/core/FixedPoint.swift.gyb" target="_blank" rel="external"><code>FixedPoint.swift.gyb</code></a>(Int 类型被定义的地方),你会发现一种和 Swift 混合在一起的模版语言:GYB。</p><blockquote><p>gyb 代表 Generate Your Boilerplate(生成你的样板文件)。 它是一个 Swift 团队开发的预处理的一个东西。如果他们需要编译十个非常相似的 <code>Int</code>,那他们就得把相同的代码复制粘贴十次。如果你打开某个 gyb 文件,你会发现其中大部分都是 Swift 的代码,但是也有一些行是由 Python 构成的。这个 这个预处理器在 Swift 的代码仓库中的 <a href="https://github.com/apple/swift/blob/master/utils/gyb" target="_blank" rel="external"><code>utils/gyb</code></a>,尽管大部分的代码在 <a href="https://github.com/apple/swift/blob/master/utils/gyb.py" target="_blank" rel="external"><code>utils/gyb.py</code></a>。</p><p>— <a href="https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20151207/000226.html" target="_blank" rel="external">Brent Royal-Gordon</a></p></blockquote><p>我们更想要看到 <a href="https://twitter.com/gottesmang/status/787493623533215745" target="_blank" rel="external">更少的 GYB</a>,更多的 Swift,因为它<a href="https://twitter.com/gottesmang/status/787493919072235520" target="_blank" rel="external">更具有表达性</a>,但是现在,我们不得不看到它们混和的一起。</p><h1 id="处理-GYB"><a href="#处理-GYB" class="headerlink" title="处理 GYB"></a>处理 GYB</h1><p>如果你只想要阅读源码(而不是向 Swift 贡献代码),GYB 带来的坏处远比好处要多。那么怎么来预处理这些文件呢?你可以直接运行 <code>gyb</code> 脚本,但是它依赖于一个被 build 脚本创建的特殊环境。最好的方式是执行一次完整的 Swift build。也许对于阅读源码来说,build 一次可能会有点过了,但是我发现 build 以后,源码阅读起来会更容易一些。</p><p><strong>更新:</strong> <a href="https://twitter.com/tonisuter/status/792325666591088668" target="_blank" rel="external">Toni Suter 指出</a> <code>gyb</code> 的脚本只依赖于一个你可以更改的变量(64-bit 和 32-bit 差别),如果你只想要处理 gyb, <a href="https://gist.github.com/tonisuter/e47267a25b3dcc90fe75a24d3ed2063a" target="_blank" rel="external">这个小脚本</a>比完整编译一次 Swift 要好很多。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">#!/bin/bash</div><div class="line">for f in `ls *.gyb`</div><div class="line">do</div><div class="line"> echo "Processing $f"</div><div class="line"> name=${f%.gyb}</div><div class="line"> ../../../utils/gyb -D CMAKE_SIZEOF_VOID_P=8 -o $name $f --line-directive ""</div><div class="line">done</div></pre></td></tr></table></figure><p>它会把所有的 <code>.gyb</code> 文件处理完毕后放到相同的位置并去除 <code>.gyb</code> 后缀。去除 <code>--line-directive ""</code> 在处理完毕的文件中添加 source location 的注释(就像 Swift build 中处理的一样)。</p><h1 id="从源码编译-Swift"><a href="#从源码编译-Swift" class="headerlink" title="从源码编译 Swift"></a>从源码编译 Swift</h1><p>环境的搭建可以阅读 Swift 仓库中的 <a href="https://github.com/apple/swift/blob/master/README.md" target="_blank" rel="external">readme</a>。 如果的 Mac 机器上,按照下面的步骤进行操作(使用 Homebrew 安装各种依赖),但是别忘记检查这些步骤是否还正确:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"># Install build tools</div><div class="line">brew install cmake ninja</div><div class="line"># Create base directory</div><div class="line">mkdir swift-source</div><div class="line">cd swift-source</div><div class="line"># Clone Swift</div><div class="line">git clone https://github.com/apple/swift.git</div><div class="line"># Clone dependencies (LLVM, Clang, etc.)</div><div class="line">./swift/utils/update-checkout --clone</div></pre></td></tr></table></figure><p>最后一句命令会把 build Swift 需要的其它部分的 repo 给 clone 下来,比如 LLVM,Clang,LLDB 等等。就像对于 Linux 的 Foundation 和 libdispatch 模块一样。在这一步过后,你的 <code>swift-source</code> 文件夹子会看起来像这样:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">du -h -d 1</div><div class="line">250M./clang</div><div class="line">4,7M./cmark</div><div class="line"> 47M./compiler-rt</div><div class="line"> 15M./llbuild</div><div class="line">197M./lldb</div><div class="line">523M./llvm</div><div class="line">221M./swift</div><div class="line"> 26M./swift-corelibs-foundation</div><div class="line">7,8M./swift-corelibs-libdispatch</div><div class="line">1,1M./swift-corelibs-xctest</div><div class="line">316K./swift-integration-tests</div><div class="line">960K./swift-xcode-playground-support</div><div class="line">7,0M./swiftpm</div><div class="line">1,3G.</div></pre></td></tr></table></figure><p>现在你就可以开始运行 build 脚本了,它会先开始 build LLVM,然后是 Swift:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">./swift/utils/build-script -x -R</div></pre></td></tr></table></figure><p>参数是很重要的:</p><ul><li><code>-x</code> 会生成一个 Xcode project,你就可以在这个 project 中使用 Xcode 阅读源码了。</li><li><code>-R</code> 代表 release 模式的编译。它会比 debug 模式更快,在我 2.6 GHz 的 core i7 2013 电脑上,25 分 vs 70 分。占用的空间也更少,2GB vs 24GB。</li></ul><h1 id="Orienting-yourself"><a href="#Orienting-yourself" class="headerlink" title="Orienting yourself"></a>Orienting yourself</h1><p>当 build 结束快捷,你可以在 <code>./build/Xcode-ReleaseAssert/swift-macosx-x86_64/</code> 中找到结果,<code>swift-source</code> 的子文件夹中。其中会有一个 <code>Swift.xcodeproj</code> Xcode 项目,已经处理好的标准库代码在 <code>./stdlib/public/code/8/</code> 中。注意,这个文件夹中只有从 .gyb 文件处理过来的文件,原来以 <code>.swift</code> 结尾的文件还在原来的位置。</p><p>不幸的是, 在 Xcode 中使用 <em>Open Quickly</em> (⇧⌘O) <a href="https://twitter.com/UINT_MIN/status/792101106495008768" target="_blank" rel="external">打不开特定的 API</a>. 我通常使用 <em>Find in Project</em> (⇧⌘F) 来进行导航。如果你使用只出现的函数定义的字符串来搜索,那就很容易搜索到了。比如要搜索 <code>print</code> 函数的定义,搜索 “func print(“ 而不是 “print”。</p><p>你也可以运行 <code>swift</code> REPL 或者 <code>swiftc</code> 编译器了。都在 <code>./Release/bin/</code> 中。如果你想要测试一些在之前 release 中有的 bug,但是在当前 master 已经被修复了,就会很方便了。</p><h1 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h1><p>如果你想在以后更新本地的 clone,重新运行 <code>update-checkout</code> 脚本,并且 rebuild:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">./swift/utils/update-checkout</div><div class="line">./swift/utils/build-script -x -R</div></pre></td></tr></table></figure><p>这些都是增量编译,比第一次要快得多。</p><h1 id="切换到指定的版本"><a href="#切换到指定的版本" class="headerlink" title="切换到指定的版本"></a>切换到指定的版本</h1><p>如果你想要验证一个你在生产环境中已经使用的 Swift 特定版本 API 的表现,你就需要查看那个版本的 Swift 代码而不是当前 <em>master</em> 分支。但是简单地切换分支并不能解决问题,因为如果一些依赖的版本对不上的话,编译是会失败的。</p><p><code>update-checkout</code> 脚本能够让你指定一个特定的 tag 或者 branch。它会帮你切换所有依赖的版本:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"># Either</div><div class="line">./swift/utils/update-checkout --tag swift-3.0-RELEASE</div><div class="line"># or</div><div class="line">./swift/utils/update-checkout --scheme swift-3.0-branch</div></pre></td></tr></table></figure><p><em>swift-3.0-RELEASE</em> tag 和 <em>swift-3.0-branch</em> branch 的区别是,tag 相当于一个 mailstone,代表 Swift 的某个特定的 release 分支。然而分支是会伴随着 bug 修复和功能改进不断更新的。现在来看,在官方 release Xcode 8.1 的 Swift 3.0.1 时,<em>swift-3.0-branch</em> 分支已经包含了一些 Swift 3.0.2 中的修复。</p><p>不幸的是,我发现 <code>update-checkout --scheme</code> 的命令非常脆弱(<code>--tag</code> 在我看来能好一些)。这个脚本会对代码进行 <a href="https://git-scm.com/docs/git-rebase" target="_blank" rel="external">rebase</a> 操作,并且切换到指定的分支,这会在子项目中带来合并冲突,然而我并没有对代码作出任何更改。我不明白为什么这个脚本会这样。</p>]]></content>
<summary type="html">
<p>原文链接=<a href="https://oleb.net/blog/2016/10/swift-stdlib-source/" target="_blank" rel="external">https://oleb.net/blog/2016/10/swift-stdl
</summary>
<category term="翻译" scheme="http://zhaoxinyu.me/tags/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>使用 Vagrant 搭建 Node.js 环境</title>
<link href="http://zhaoxinyu.me/2016-11-20-vagrant-setup/"/>
<id>http://zhaoxinyu.me/2016-11-20-vagrant-setup/</id>
<published>2016-11-20T03:22:00.000Z</published>
<updated>2016-11-20T15:47:13.000Z</updated>
<content type="html"><![CDATA[<p>我记得我在知乎 Q3 的个人 OKR 中定下了一个小目标,就是学习 Node.js,可是现在 Q4 都过去一半了,Node 的毛也没学。</p><p>为了圆之前吹过的牛逼,同时也为了之后我将与 classTC 同学一起做的项目,我决定好好学习一下。先从环境搭建做起。</p><p>其实 Node 的环境是很好搭建的,直接在官网在上下载最新 Binary Package 安装就可以了,可是为什么还要用 Vagrant 呢?</p><p>因为我曾经在一期 TeaHour.fm 的节目中听到过,要养成把写代码的环境和生产的环境保持一致的好习惯。而 Vagrant 就可以做到这一点,不知道的同学可以去官网了解一下。</p><ul><li>首先安装 <a href="https://www.vagrantup.com/" target="_blank" rel="external">vagrant</a> + <a href="https://www.virtualbox.org/" target="_blank" rel="external">virtual box</a></li><li>mac 上创建一个目录并进入,比如我的目录是 <code>vagrant</code>,</li><li>执行 <code>vagrant init</code></li><li><p>安装 Ubuntu 14.04 box(当然你也可以选择其它 box,你生产环境用什么,这里就用什么 box)</p><p><code>vagrant box add ubuntu/trusty64</code> (此步骤执行时间取决于网速,做好心理准备)</p><p>如果报错,执行 <code>sudo rm /opt/vagrant/embedded/bin/curl</code> 后再执行一遍上一条命令</p></li><li><code>vagrant up</code> 根据当前 Vagrantfile 启动 vagrant</li><li><p><code>vagrant ssh</code> ssh 进入当前的 vagrant 环境,进行一些 setup 的工作</p><p>在 vagrant 中执行 <code>mkdir helloworld</code> 创建一个和 mac 共享的目录<br><code>logout</code> 退出 ssh</p></li><li>在 mac 上的 vagrant 目录创建一个目录 <code>mkdir helloworld</code> 用于与 vagrant 共享</li><li>编辑 Vagrantfile</li></ul><p>熟悉 CocoaPods 的同学看着下面的文件应该很亲切</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="comment"># -*- mode: ruby -*-</span></div><div class="line"><span class="comment"># vi: set ft=ruby :</span></div><div class="line"><span class="comment"># 这里的 host 指的是我们的电脑 -> 也就是 mac</span></div><div class="line"><span class="comment"># guest 指的是 Ubuntu</span></div><div class="line">Vagrant.configure(<span class="string">"2"</span>) <span class="keyword">do</span> <span class="params">|config|</span></div><div class="line"> <span class="comment"># 指定我们使用的 box</span></div><div class="line"> config.vm.box = <span class="string">"ubuntu/trusty64"</span></div><div class="line"> <span class="comment"># 禁用掉自动更新,当然啦,这行是 optional 的</span></div><div class="line"> config.vm.box_check_update = <span class="literal">false</span></div><div class="line"> <span class="comment"># 假定我们的 node 程序跑在 8080 端口,我们把这个端口转发出来给 mac,</span></div><div class="line"> <span class="comment"># 在 mac 上也就能通过 http://localhost:8080 访问跑在 Ubuntu 上的 node 应用了</span></div><div class="line"> config.vm.network <span class="string">"forwarded_port"</span>, <span class="symbol">guest:</span> <span class="number">8080</span>, <span class="symbol">host:</span> <span class="number">8080</span></div><div class="line"> <span class="comment"># 映射共享文件夹</span></div><div class="line"> config.vm.synced_folder <span class="string">"path-to-vagrant/vagrant/helloworld"</span>, <span class="string">"/home/vagrant/helloworld"</span></div><div class="line"> <span class="comment"># 这下面的命令可以通过 `vagrant provision` 来执行,进行一些 Ubuntu 环境的初始化工作</span></div><div class="line"> <span class="comment"># 不需要每次都执行,有变更了执行就好</span></div><div class="line"> config.vm.provision <span class="string">"shell"</span>, <span class="symbol">inline:</span> <span class="string"><<-SHELL</span></div><div class="line"><span class="string"> apt-get update</span></div><div class="line"><span class="string"> sudo apt-get install nodejs -y</span></div><div class="line"><span class="string"> sudo apt-get install npm -y</span></div><div class="line"><span class="string"> SHELL</span></div><div class="line"><span class="keyword">end</span></div></pre></td></tr></table></figure><ul><li><code>vagrant reload</code> 重启 vagrant</li><li><code>vagrant provision</code> 安装我们在 Vagrantfile 中写好的 provision</li></ul><p>环境安装到此结束</p><hr><p>测试我们安装好的环境:</p><ul><li><code>vagrant ssh</code> ssh 进 vagrant</li><li>在 mac 上的 /path-to-vagrant/vagrant 新建一个 <code>main.js</code> 的文件,写入以下内容:</li></ul><p>这就是我们建立目录映射的原因,可以通过 mac 上的各种编辑器,编辑在 vagrant 中的东西</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">"http"</span>);</div><div class="line"></div><div class="line">http.createServer(<span class="function"><span class="keyword">function</span> (<span class="params">request, response</span>) </span>{</div><div class="line">response.writeHead(<span class="number">200</span>, {<span class="string">'Content-Type'</span>: <span class="string">'text/plain'</span>});</div><div class="line">response.end(<span class="string">'Hello World\n'</span>);</div><div class="line">}).listen(<span class="number">8080</span>);</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(<span class="string">'Sever running at http://127.0.0.1:8080'</span>);</div></pre></td></tr></table></figure><ul><li><code>vagrant ssh</code> 进入 vagrant,<code>cd helloworld</code> 执行 <code>node main.js</code></li><li>在 mac 上用浏览器查看 <code>localhost:8080</code>,如果有 <code>hello world</code> 就说明成功啦~ 可以顺利开始 node.js 的学习了</li></ul><p><del>如果你只是为了学习 Node.js 大可不必像我这么折腾,直接下载 Binary 安装就可以了</del></p>]]></content>
<summary type="html">
<p>我记得我在知乎 Q3 的个人 OKR 中定下了一个小目标,就是学习 Node.js,可是现在 Q4 都过去一半了,Node 的毛也没学。</p>
<p>为了圆之前吹过的牛逼,同时也为了之后我将与 classTC 同学一起做的项目,我决定好好学习一下。先从环境搭建做起。</p
</summary>
<category term="工具" scheme="http://zhaoxinyu.me/tags/%E5%B7%A5%E5%85%B7/"/>
</entry>
</feed>