-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1168 lines (955 loc) · 286 KB
/
atom.xml
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
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Daizi</title>
<link href="/atom.xml" rel="self"/>
<link href="http://lin493369.github.io/"/>
<updated>2019-02-26T06:58:48.984Z</updated>
<id>http://lin493369.github.io/</id>
<author>
<name>小袋子</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>iOS 一对多 delegate 的简单应用</title>
<link href="http://lin493369.github.io/2019/02/26/iOS%20%E4%B8%80%E5%AF%B9%E5%A4%9A%20delegate%20%E7%9A%84%E7%AE%80%E5%8D%95%E5%BA%94%E7%94%A8/"/>
<id>http://lin493369.github.io/2019/02/26/iOS 一对多 delegate 的简单应用/</id>
<published>2019-02-25T16:00:00.000Z</published>
<updated>2019-02-26T06:58:48.984Z</updated>
<content type="html"><![CDATA[<!--此处开始正文-->
<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>在之前我们尝试过 hook webView 的 delegate,可以查看【 <a href="https://www.daizi.me/2017/11/01/iOS%20%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%9C%B0%20hook%20%E7%B3%BB%E7%BB%9F%E7%9A%84%20delegate%20%E6%96%B9%E6%B3%95%EF%BC%9F/" target="_blank" rel="external">iOS 如何优雅地 hook 系统的 delegate 方法</a>】。当时的思路是,由于 delegate 只能是一对一的消息传递,后面设置的 delegate 对象会替换前面设置的 delegate 对象,因此在无入侵的情况下,需要通过 setDelegate 方法找到设置的 delegate 对象,然后 hook 该对象实现的 protocol 方法,这种方法迂回且比较容易出问题(过多操作运行时)。</p>
<p> 现在我们可以通过更简单的方式来实现,在 hook webView 的初始化方法后,使用一对多委托方式,将 webView 的 delegate 设置为我们内部实现相关协议的对象,使得我们的 hook 代码不影响原始代码的 delegate 实现。</p>
<h3 id="启发"><a href="#启发" class="headerlink" title="启发"></a>启发</h3><p>为什么突然想到可以这样来实现功能?其实启发是来源于优秀的开源框架 <a href="https://github.com/marcuswestin/WebViewJavascriptBridge" target="_blank" rel="external">WebViewJavascriptBridge</a> ,WebViewJavascriptBridge 可以简洁优雅地实现原生和 JS 通信。</p>
<p>WebViewJavascriptBridge 在 Native 端实现桥接功能的简化流程是: JS 向 Native 发消息是构造特殊的 url request,由 Native 端拦截并解析 URL 请求来实现通信。那么一定会需要 webView 拦截 URL(<code>webView:decidePolicyForNavigationAction:request:frame:decisionListener:</code>) 的回调来处理。</p>
<p>那么如何在 delegate 只能一对一传递的情况下,既保证原始的 delegate 实现,又能在 WebViewJavascriptBridge 内部实现 delegate 的回调?</p>
<h3 id="走进源码"><a href="#走进源码" class="headerlink" title="走进源码"></a>走进源码</h3><p>我们可以猜想,WebViewJavascriptBridge 一定使用了某种有效方式解决了这个问题。接下来让我们走进源码来看具体的实现:</p>
<p>1、在桥接类的初始化方法中,将 webView 实例对象传入,并设置其 delegate 为桥接类。(这时候如果外部的控制器提前设置了 delegate 会被覆盖失效)</p>
<figure class="highlight smali"><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">WebViewJavascriptBridge*<span class="keyword"> bridge</span>;</div><div class="line">_bridge = [WebViewJavascriptBridge<span class="keyword"> bridge</span>ForWebView:webView];</div></pre></td></tr></table></figure>
<p>2、如果调用方也想作为 delegate 代理,不能通过设置 webView 的 delegate 来实现,这样会覆盖上面桥接类的 delegate,导致桥接类无法正常工作,WebViewJavascriptBridge 的做法是将待设置 delegate 对象传入桥接类,并赋值给 <code>_webViewDelegate</code> (weak 变量,在 self 释放后可以随即设置为 nil,不会造成循环应用)。</p>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="section">[_bridge setWebViewDelegate:self]</span><span class="comment">;</span></div></pre></td></tr></table></figure>
<figure class="highlight erlang"><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="params">(void)</span>setWebViewDelegate:<span class="params">(WVJB_WEBVIEW_DELEGATE_TYPE*)</span>webViewDelegate {</div><div class="line"> _webViewDelegate = webViewDelegate;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>3、在桥接类对应 delegate 方法回调时,调用 _webViewDelegate 相应的实现方法,</p>
<figure class="highlight groovy"><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>)<span class="string">webView:</span>(WebView *)webView <span class="string">decidePolicyForNavigationAction:</span>(NSDictionary *)actionInformation <span class="string">request:</span>(NSURLRequest *)request <span class="string">frame:</span>(WebFrame *)frame <span class="string">decisionListener:</span>(id<WebPolicyDecisionListener>)listener {</div><div class="line"></div><div class="line"><span class="comment">// another handler</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> (_webViewDelegate && [_webViewDelegate <span class="string">respondsToSelector:</span><span class="meta">@selector</span>(<span class="string">webView:</span><span class="string">decidePolicyForNavigationAction:</span><span class="string">request:</span><span class="string">frame:</span><span class="string">decisionListener:</span>)]) {</div><div class="line"> [_webViewDelegate <span class="string">webView:</span>webView <span class="string">decidePolicyForNavigationAction:</span>actionInformation <span class="string">request:</span>request <span class="string">frame:</span>frame <span class="string">decisionListener:</span>listener];</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="扩展及总结"><a href="#扩展及总结" class="headerlink" title="扩展及总结"></a>扩展及总结</h3><p>如果想实现不入侵地实现一对多 delegate,可以通过 hook webView 初始化方法以及 setDelegate 方法,并注意设置 delegate 的时机,防止被覆盖而失效。</p>
<p>另外,WebViewJavascriptBridge 的实现实际上是一对二的代理,虽然对我来说已经够用了。但如果有同学想扩展为一对多 delegate,可以尝试实现一个桩类,将桩类设置为 delegate,并使用数组管理其他想实现 delegate 协议的对象,实现一对多 delegate。</p>
<p>现在回过头看这种实现很简单易懂,但是在之前固定思维的限制下,使用了大量繁杂、迂回且不可靠的方式,所以多看这些优秀框架的源码,可以获得很多思考、设计、解决问题的思路。</p>
]]></content>
<summary type="html">
现在回过头看这种实现很简单易懂,但是在之前固定思维的限制下,使用了大量繁杂、迂回且不可靠的方式,所以多看这些优秀框架的源码,可以获得很多思考、设计、解决问题的思路。
</summary>
<category term="delegate" scheme="http://lin493369.github.io/tags/delegate/"/>
<category term="iOS" scheme="http://lin493369.github.io/tags/iOS/"/>
</entry>
<entry>
<title>构建第一个 Swift 区块链应用</title>
<link href="http://lin493369.github.io/2018/12/08/appcoda-blockchain-introduction/"/>
<id>http://lin493369.github.io/2018/12/08/appcoda-blockchain-introduction/</id>
<published>2018-12-07T16:00:00.000Z</published>
<updated>2019-02-18T03:35:57.408Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="https://appcoda.com/blockchain-introduction/" target="_blank" rel="external">https://appcoda.com/blockchain-introduction/</a><br>作者=Sai Kambampati<br>原文日期=2018-05-31</p>
<!--此处开始正文-->
<p>区块链作为一项革命性的技术,开始受到越来越多追捧。为什么呢?因为区块链是许多加密数字货币的底层技术,比如:比特币(BTC),以太坊(ETH)以及莱特币(LTC)。区块链具体是如何工作的?本篇教程会涵盖所有区块链相关的知识,还会教你如何构建 Swift “区块链”。下面让我们开始吧!</p>
<h3 id="区块链的工作原理"><a href="#区块链的工作原理" class="headerlink" title="区块链的工作原理"></a>区块链的工作原理</h3><p>顾名思义,区块链是一条由不同区块连接组成的链。每一个块包含三个信息:数据、哈希(hash)、以及前置区块的哈希。</p>
<p><strong>1、数据</strong> – 由于应用场景不同,存储在区块中的数据由区块链的类型决定。例如,在比特币区块链中,存储的数据是交易信息:转账金额和交易双方的信息。</p>
<p><strong>2、哈希</strong> – 你可以将哈希看做数字指纹,用来唯一标识一个区块及其数据。哈希的重要之处在于它是一个独特的字母数字代码,通常是 64 个字符。当一个区块被创建时,哈希也随之创建。当一个区块被修改,哈希也随之修改。因此,当你想要查看在区块上所做的任何变更时,哈希就显得非常重要。</p>
<p><strong>3、前置区块的哈希</strong> – 通过存储前置区块的哈希,你可以还原每个区块连接成区块链的过程!这使得区块链安全性特别高。</p>
<p>我们来看下这张图片:</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-explained.png" alt="区块链"></p>
<p>你可以看到,每一个区块包含数据(图片中没有指明)、哈希以及前置区块的哈希。例如,黄色区块包含自身的哈希:H7s6,以及红色区块的哈希:8SD9。这样它们就构成了一条相互连接的链。现在,假如有一个黑客准备恶意篡改红色的区块。请记住,每当块以任何方式被篡改时,该区块的哈希都会改变!当下一个区块检查并发现前置哈希不一致时,黑客将无法访问它,因为他与前置区块的联系被切断了(译者注:即如果黑客想要要篡改一个区块的话,就需要把这个区块后面的所有区块都要改掉,而这个工作量是很难实现的)。</p>
<p>这使得区块链特别安全,几乎不可能回滚或者篡改任何数据。虽然哈希为保密和隐私提供了巨大的保障,但是还有两个更加安全妥当的措施让区块链更加安全:工作量证明(Proof-of-Work)以及智能合约(Smart Contracts)。本文我不会深入讲解,你可以<a href="https://hackernoon.com/what-on-earth-is-a-smart-contract-2c82e5d89d26" target="_blank" rel="external">在这里</a>了解更多相关知识。</p>
<p>区块链最后一个保证自身安全性的方式是基于其定位。和大多数存储在服务器和数据库的数据不同,区块链使用的是点对点(P2P)网络。P2P 是一种允许任何人加入的网络,并且该网络上的数据会分发给每一个接收者。</p>
<p>每当有人加入这个网络,他们就会获得一份区块链的完整拷贝。每当有人新建一个区块,就会广播给全网。在将该块添加到链之前,节点会通过几个复杂的程序确定该块是否被篡改。这样,所有人、所有地方都可以使用这个信息。如果你是 <em>HBO 美剧硅谷</em> 的粉丝,对此应该不会感到陌生。在该剧中,主演(Richard)使用一种相似的技术创造了新型互联网(译者注:有趣的是剧中还发行了区块链数字货币 PiedPaperCoin,感兴趣的童鞋可以刷一下这部剧)。</p>
<p>因为每个人都有区块链或者节点的一份拷贝,他们可以达成一种共识并决定哪部分区块是有效的。因此,如果你想要攻击某个区块,你必须同时攻击网络上 50% 以上的区块(译者:51% 攻击),使得你的区块可以追上并替换原区块链。所以区块链或许是过去十年所创造的最安全的技术之一。</p>
<h3 id="关于示例程序"><a href="#关于示例程序" class="headerlink" title="关于示例程序"></a>关于示例程序</h3><p>现在你已经对区块链的原理有了初步的认识,那么我们就开始写示例程序吧!你可以在这里下载<a href="https://github.com/appcoda/BlockchainDemo/raw/master/BlockchainStarter.zip" target="_blank" rel="external">原始项目</a>。</p>
<p>如你所见,我们有两个比特币钱包。第一个账户 1065 有 500 BTC,而第二个账户 0217 没有 BTC。我们通过 send 按钮可以发送比特币到另外的账户。为了赚取 BTC,我们可以点击 Mine 按钮,可以获得 50 BTC 的奖励。我们主要工作是查看控制台输出,观察两个账户间的交易过程。</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-2.png" alt="这里写图片描述"></p>
<p>在左侧导航栏可以看到两个很重要的类:<code>Block</code> 和 <code>Blockchain</code>。目前这两个类都是空实现,我会带着你们在这两个类中写入相关逻辑。下面让我们开始吧!</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-3.png" alt="这里写图片描述"></p>
<h3 id="在-Swift-中定义区块"><a href="#在-Swift-中定义区块" class="headerlink" title="在 Swift 中定义区块"></a>在 Swift 中定义区块</h3><p>首先打开 <code>Block.swift</code> 并添加定义区块的代码。在此之前,我们需要定义区块是什么。前面我们曾定义过,区块是由三部分组成:哈希、实际记录的数据以及前置区块的哈希。当我们想要构建我们的区块链时,我们必须知道该区块是第一个还是第二个。我们可以很容易地在 Swift 的类中做如下定义:</p>
<figure class="highlight delphi"><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"><span class="keyword">var</span> hash: <span class="keyword">String</span>!</div><div class="line"><span class="keyword">var</span> data: <span class="keyword">String</span>!</div><div class="line"><span class="keyword">var</span> previousHash: <span class="keyword">String</span>!</div><div class="line"><span class="keyword">var</span> <span class="keyword">index</span>: Int!</div></pre></td></tr></table></figure>
<p>现在需要添加最重要的代码。我曾提过区块在被修改的情况下,哈希也会随之变化,这是区块链如此安全的特性之一。因此我们需要创建一个函数去生成哈希,该哈希由随机字母和数字组成。这个函数只需要几行代码:</p>
<figure class="highlight autoit"><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">generateHash</span><span class="params">()</span> -> <span class="title">String</span> {</span></div><div class="line"> <span class="keyword">return</span> NSUUID().uuidString.replacingOccurrences(of: <span class="string">"-"</span>, <span class="keyword">with</span>: <span class="string">""</span>)</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>NSUUID</code>是一个代表通用唯一值的对象,并且可以桥接成 UUID。它可以快速地生成 32 个字符串。本函数生成一个 UUID,删除其中的连接符,然后返回一个 <code>String</code>,最后将结果作为区块的哈希。<code>Block.swift</code>现在就像下面:</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-4.png" alt="这里写图片描述"></p>
<p>现在我们已经定义好了 <code>Block</code> 类,下面来定义 Blockchain 类,首先切换到 <code>Blockchain.swift</code> 。</p>
<h3 id="在-Swift-中定义区块链"><a href="#在-Swift-中定义区块链" class="headerlink" title="在 Swift 中定义区块链"></a>在 Swift 中定义区块链</h3><p>和之前一样,首先分析区块链的基本原理。用非常基础的术语来说,区块链只是由一连串的区块连接而成,也可以说是一个由多个条目组成的列表。这是不是听起来很熟悉呢?其实这就是数组的定义!而且这个数组是由区块组成的!接下来添加以下代码:</p>
<figure class="highlight markdown"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">var chain = [<span class="string">Block</span>](<span class="link"></span>)</div></pre></td></tr></table></figure>
<figure class="highlight armasm"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">快速提示: 这个方法可以应用于计算机科学世界里的任何事物。如果你遇到大难题,可以尝试把它分解成若干个小问题,以此来建立起解决问题的方法,正如我们解决在 <span class="keyword">Swift </span>中如何添加区块和区块链的问题。</div></pre></td></tr></table></figure>
<p>你会注意到数组里面是我们前面定义的 <code>Block</code> 类,这就是区块链所需要的所有变量。为了完成功能,我们还需要在类中添加两个函数。请尝试着根据我之前教过的方法解答这个问题。</p>
<blockquote>
<p>区块链中的两个主要函数是什么?</p>
</blockquote>
<p>我希望你能认真思考并回答这个问题!实际上,区块链的两个主要功能是创建创世区块(最初的始块),以及在其结尾添加新的区块。当然现在我不打算实现分叉区块链和添加智能合约的功能,但你必须了解这两个是基本功能!将以下代码添加到 <code>Blockchain.swift</code>:</p>
<figure class="highlight stylus"><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">func createGenesisBlock(data:String) {</div><div class="line"> let genesisBlock = Block()</div><div class="line"> genesisBlock<span class="selector-class">.hash</span> = genesisBlock.generateHash()</div><div class="line"> genesisBlock<span class="selector-class">.data</span> = data</div><div class="line"> genesisBlock<span class="selector-class">.previousHash</span> = <span class="string">"0000"</span></div><div class="line"> genesisBlock<span class="selector-class">.index</span> = <span class="number">0</span></div><div class="line"> chain.append(genesisBlock)</div><div class="line">}</div><div class="line"></div><div class="line">func createBlock(data:String) {</div><div class="line"> let newBlock = Block()</div><div class="line"> newBlock<span class="selector-class">.hash</span> = newBlock.generateHash()</div><div class="line"> newBlock<span class="selector-class">.data</span> = data</div><div class="line"> newBlock<span class="selector-class">.previousHash</span> = chain[chain.count-<span class="number">1</span>]<span class="selector-class">.hash</span></div><div class="line"> newBlock<span class="selector-class">.index</span> = chain<span class="selector-class">.count</span></div><div class="line"> chain.append(newBlock)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>1、我们添加的第一个函数的作用是创建创世区块。为此,我们创建了一个以区块数据为入参的函数。然后定义了一个类型为 <code>Block</code> 的变量 <code>genesisBlock</code>,它拥有此前在 <code>Block.swift</code> 中定义的所有变量和函数。我们将 <code>generateHash()</code> 赋值给哈希,将输入的 <code>data</code> 参数赋值给数据。由于这是第一个区块,我们将前置区块的哈希设为 0000,这样我们就可以知道这是起始区块。最后我们将 <code>index</code> 设为 0,并将这个区块加入到区块链中。</p>
<p>2、我们创建的第二个函数适用于 <code>genesisBlock</code> 之后的所有区块,并且能创建剩余的区块。你会注意到它与第一个函数非常相似。唯一的区别是,我们将 <code>previousHash</code> 的值设置为前一个区块的哈希值,并将 <code>index</code> 设置为它在区块链中的位置。就这样,区块链已经定义好了!你的代码应该看起来跟下图一样!</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-5.png" alt="这里写图片描述"></p>
<h3 id="钱包后端"><a href="#钱包后端" class="headerlink" title="钱包后端"></a>钱包后端</h3><p>现在切换到 <code>ViewController.swift</code>,我们会发现所有的 outlet 都已经连接好了。我们只需要处理交易,并将其输出到控制台。</p>
<p>然而在此之前,我们需要稍微研究一下比特币的区块链。比特币是由一个总账户产生的,我们将这个账号的编号称为 0000。当你挖到一个 BTC,意味着你解决了数学问题,因此会发行一定数量的比特币作为奖励。这提供了一个发币的高明方法,并且可以激励更多人去挖矿。在我们的应用,让我们把挖矿奖励设为 100 BTC。首先,在视图控制器中添加所需的变量:</p>
<figure class="highlight nix"><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">let</span> <span class="attr">firstAccount</span> = <span class="number">1065</span></div><div class="line"><span class="keyword">let</span> <span class="attr">secondAccount</span> = <span class="number">0217</span></div><div class="line"><span class="keyword">let</span> <span class="attr">bitcoinChain</span> = Blockchain()</div><div class="line"><span class="keyword">let</span> <span class="attr">reward</span> = <span class="number">100</span></div><div class="line">var accounts: [String: Int] = [<span class="string">"0000"</span>: <span class="number">10000000</span>]</div><div class="line"><span class="keyword">let</span> <span class="attr">invalidAlert</span> = UIAlertController(title: <span class="string">"Invalid Transaction"</span>, message: <span class="string">"Please check the details of your transaction as we were unable to process this."</span>, preferredStyle: .alert)</div></pre></td></tr></table></figure>
<p>首先定义号码为 1065 和 0217 的两个账号。然后添加一个名为 <code>bitcoinChain</code> 的变量作为我们的区块链,并将 <code>reward</code> 设为 100。我们需要一个主帐户作为所有比特币的来源:即创世帐户 0000。里面有 1000 万个比特币。你可以把这个账户想象成一个银行,所有因奖励产生的 100 个比特币都经此发放到合法账户中。我们还定义了一个提醒弹窗,每当交易无法完成时就会弹出。</p>
<p>现在,让我们来编写几个运行时需要的通用函数。你能猜出是什么函数吗?</p>
<p>1、第一个函数是用来处理交易的。我们需要确保交易双方的账户,能够接收或扣除正确的金额,并将这些信息记录到我们的区块链中。</p>
<p>2、下一个函数是在控制台中打印整个记录 —— 它将显示每个区块及其中的数据。</p>
<p>3、最后一个是用于验证区块链是否有效的函数,通过校验下一个区块的 <code>previousHash</code>和上一个区块 <code>hash</code> 是否匹配。由于我们不会演示任何黑客方法,因此在我们的示例程序中,区块链是永远有效的。</p>
<h3 id="交易函数"><a href="#交易函数" class="headerlink" title="交易函数"></a>交易函数</h3><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><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></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">transaction</span><span class="params">(from: String, to: String, amount: Int, type: String)</span></span> {</div><div class="line"> <span class="comment">// 1</span></div><div class="line"> <span class="keyword">if</span> accounts[from] == <span class="literal">nil</span> {</div><div class="line"> <span class="keyword">self</span>.present(invalidAlert, animated: <span class="literal">true</span>, completion: <span class="literal">nil</span>)</div><div class="line"> <span class="keyword">return</span></div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> accounts[from]!-amount < <span class="number">0</span> {</div><div class="line"> <span class="keyword">self</span>.present(invalidAlert, animated: <span class="literal">true</span>, completion: <span class="literal">nil</span>)</div><div class="line"> <span class="keyword">return</span></div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> accounts.updateValue(accounts[from]!-amount, forKey: from)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// 2</span></div><div class="line"> <span class="keyword">if</span> accounts[to] == <span class="literal">nil</span> {</div><div class="line"> accounts.updateValue(amount, forKey: to)</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> accounts.updateValue(accounts[to]!+amount, forKey: to)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// 3</span></div><div class="line"> <span class="keyword">if</span> type == <span class="string">"genesis"</span> {</div><div class="line"> bitcoinChain.createGenesisBlock(data: <span class="string">"From: <span class="subst">\(from)</span>; To: <span class="subst">\(to)</span>; Amount: <span class="subst">\(amount)</span>BTC"</span>)</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> type == <span class="string">"normal"</span> {</div><div class="line"> bitcoinChain.createBlock(data: <span class="string">"From: <span class="subst">\(from)</span>; To: <span class="subst">\(to)</span>; Amount: <span class="subst">\(amount)</span>BTC"</span>)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>代码量看起来好像很大,但主要是定义了每个交易需要遵循的一些规则。一开始是函数的四个参数:<br><code>to</code>, <code>from</code>, <code>amount</code>, <code>type</code>。前三个参数不需要再解释了,而 <code>Type</code> 主要用于定义交易的类型。总共有两个类型:正常类型(normal) 和创世类型(genesis)。正常类型的交易会发生在账户 1065 和 2017 之间,而创世类型将会涉及到账户 0000。</p>
<p>1、第一个 <code>if-else</code> 条件语句处理转出账户的信息。如果账户不存在或者余额不足,将会提示交易不合法并返回。</p>
<p>2、第二个 <code>if-else</code> 条件语句处理转入账户的信息。如果账户不存在,则创建新账户并转入相应的比特币。反之,则向该账户转入正确数量的比特币。</p>
<p>3、最后一个 <code>if-else</code>条件语句处理交易类型。如果类型是创世类型,则添加一个创世区块,否则创建一个新的区块存储数据。</p>
<h3 id="打印函数"><a href="#打印函数" class="headerlink" title="打印函数"></a>打印函数</h3><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></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">chainState</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">0</span>...bitcoinChain.chain.<span class="built_in">count</span>-<span class="number">1</span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"\tBlock: <span class="subst">\(bitcoinChain.chain[i].index!)</span>\n\tHash: <span class="subst">\(bitcoinChain.chain[i].hash!)</span>\n\tPreviousHash: <span class="subst">\(bitcoinChain.chain[i].previousHash!)</span>\n\tData: <span class="subst">\(bitcoinChain.chain[i].data!)</span>"</span>)</div><div class="line"> }</div><div class="line"> redLabel.text = <span class="string">"Balance: <span class="subst">\(accounts[String(describing: firstAccount)</span>]!) BTC"</span></div><div class="line"> blueLabel.text = <span class="string">"Balance: <span class="subst">\(accounts[String(describing: secondAccount)</span>]!) BTC"</span></div><div class="line"> <span class="built_in">print</span>(accounts)</div><div class="line"> <span class="built_in">print</span>(chainValidity())</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这是一个简单的循环语句,遍历 <code>bitcoinChain</code> 中的所有区块,并打印区块号码,哈希,前置哈希,以及存储的数据。同时我们更新了界面中的标签(label),这样就可以显示账户中正确的 BTC 数量。最后,打印所有的账户(应该是 3 个),并校验区块链的有效性。</p>
<p>现在你应该会在函数的最后一行发现一个错误。这是由于我们还没有实现 <code>chainValidity()</code> 函数,让我们马上开始吧。</p>
<h3 id="有效性函数"><a href="#有效性函数" class="headerlink" title="有效性函数"></a>有效性函数</h3><p>判断一个链是否有效的标准是:前置区块的哈希与当前区块所表示的是否匹配。我们可以再次用循环语句来遍历所有的区块:</p>
<figure class="highlight stylus"><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">func chainValidity() -> String {</div><div class="line"> <span class="selector-tag">var</span> isChainValid = true</div><div class="line"> <span class="keyword">for</span> <span class="selector-tag">i</span> <span class="keyword">in</span> <span class="number">1</span>..<span class="selector-class">.bitcoinChain</span><span class="selector-class">.chain</span><span class="selector-class">.count-1</span> {</div><div class="line"> <span class="keyword">if</span> bitcoinChain<span class="selector-class">.chain</span>[i]<span class="selector-class">.previousHash</span> != bitcoinChain<span class="selector-class">.chain</span>[i-<span class="number">1</span>]<span class="selector-class">.hash</span> {</div><div class="line"> isChainValid = false</div><div class="line"> }</div><div class="line"> }</div><div class="line"> return <span class="string">"Chain is valid: \(isChainValid)\n"</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>和之前一样,我们遍历了 <code>bitcoinChain</code>中的所有区块,并检查了前置区块的 <code>hash</code> 是否与当前区块的 <code>previousHash</code> 一致。</p>
<p>就酱!我们已经将定义了所有需要的函数!你的 <code>ViewController.swift</code> 应该如下图一样:</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-6.png" alt="这里写图片描述"></p>
<p>收尾工作就是连接按钮和函数啦。让我们马上开始最后的部分吧!</p>
<h3 id="让一切关联起来"><a href="#让一切关联起来" class="headerlink" title="让一切关联起来"></a>让一切关联起来</h3><p>当我们的应用第一次启动时,需要创世账户 0000 发送 50 BTC 到我们的第一个账户。再从第一个账户转账 10 BTC 到第二个账户,这只需要寥寥三行代码。最后 <code>viewDidLoad</code> 中的代码如下:</p>
<figure class="highlight less"><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="selector-tag">override</span> <span class="selector-tag">func</span> <span class="selector-tag">viewDidLoad</span>() {</div><div class="line"> <span class="selector-tag">super</span><span class="selector-class">.viewDidLoad</span>()</div><div class="line"> <span class="selector-tag">transaction</span>(<span class="attribute">from</span>: <span class="string">"0000"</span>, <span class="attribute">to</span>: <span class="string">"\(firstAccount)"</span>, <span class="attribute">amount</span>: <span class="number">50</span>, <span class="attribute">type</span>: <span class="string">"genesis"</span>)</div><div class="line"> <span class="selector-tag">transaction</span>(<span class="attribute">from</span>: <span class="string">"\(firstAccount)"</span>, <span class="attribute">to</span>: <span class="string">"\(secondAccount)"</span>, <span class="attribute">amount</span>: <span class="number">10</span>, <span class="attribute">type</span>: <span class="string">"normal"</span>)</div><div class="line"> <span class="selector-tag">chainState</span>()</div><div class="line"> <span class="selector-tag">self</span><span class="selector-class">.invalidAlert</span><span class="selector-class">.addAction</span>(UIAlertAction(<span class="attribute">title</span>: <span class="string">"OK"</span>, <span class="attribute">style</span>: .default, <span class="attribute">handler</span>: nil))</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们使用已定义好的函数转账,并调用 <code>chainState()</code> 函数。最后,我们还在 <code>invalidAlert</code> 中添加了一个标题为 OK 的 <code>UIAlertAction</code>。</p>
<p>现在让我们来实现剩下的四个函数:<code>ReMeNe()</code>、<code>BrimeMeNe()</code>、<code>ReSdEnter()</code>和<code>BuLeScript()</code>。</p>
<h3 id="挖矿函数"><a href="#挖矿函数" class="headerlink" title="挖矿函数"></a>挖矿函数</h3><p>挖矿函数特别简单,只需要三行代码。添加以下代码:</p>
<figure class="highlight less"><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="variable">@IBAction</span> func redMine(_ <span class="attribute">sender</span>: Any) {</div><div class="line"> <span class="selector-tag">transaction</span>(<span class="attribute">from</span>: <span class="string">"0000"</span>, <span class="attribute">to</span>: <span class="string">"\(firstAccount)"</span>, <span class="attribute">amount</span>: <span class="number">100</span>, <span class="attribute">type</span>: <span class="string">"normal"</span>)</div><div class="line"> <span class="selector-tag">print</span>(<span class="string">"New block mined by: \(firstAccount)"</span>)</div><div class="line"> <span class="selector-tag">chainState</span>()</div><div class="line">}</div><div class="line"> </div><div class="line">@<span class="selector-tag">IBAction</span> <span class="selector-tag">func</span> <span class="selector-tag">blueMine</span>(_ <span class="attribute">sender</span>: Any) {</div><div class="line"> <span class="selector-tag">transaction</span>(<span class="attribute">from</span>: <span class="string">"0000"</span>, <span class="attribute">to</span>: <span class="string">"\(secondAccount)"</span>, <span class="attribute">amount</span>: <span class="number">100</span>, <span class="attribute">type</span>: <span class="string">"normal"</span>)</div><div class="line"> <span class="selector-tag">print</span>(<span class="string">"New block mined by: \(secondAccount)"</span>)</div><div class="line"> <span class="selector-tag">chainState</span>()</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在第一个挖矿函数中,我们使用交易函数从创世账户发送了 100 BTC 到第一个账户。我们打印了挖矿的区块,然后打印了区块链的状态。同样地,在 <code>blueMine</code> 函数中,我们转给了第二个账户 100 BTC。</p>
<h3 id="发送函数"><a href="#发送函数" class="headerlink" title="发送函数"></a>发送函数</h3><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><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="meta">@IBAction</span> <span class="function"><span class="keyword">func</span> <span class="title">redSend</span><span class="params">(<span class="number">_</span> sender: Any)</span></span> {</div><div class="line"> <span class="keyword">if</span> redAmount.text == <span class="string">""</span> {</div><div class="line"> present(invalidAlert, animated: <span class="literal">true</span>, completion: <span class="literal">nil</span>)</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> transaction(from: <span class="string">"<span class="subst">\(firstAccount)</span>"</span>, to: <span class="string">"<span class="subst">\(secondAccount)</span>"</span>, amount: <span class="type">Int</span>(redAmount.text!)!, type: <span class="string">"normal"</span>)</div><div class="line"> <span class="built_in">print</span>(<span class="string">"<span class="subst">\(redAmount.text!)</span> BTC sent from <span class="subst">\(firstAccount)</span> to <span class="subst">\(secondAccount)</span>"</span>)</div><div class="line"> chainState()</div><div class="line"> redAmount.text = <span class="string">""</span></div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="meta">@IBAction</span> <span class="function"><span class="keyword">func</span> <span class="title">blueSend</span><span class="params">(<span class="number">_</span> sender: Any)</span></span> {</div><div class="line"> <span class="keyword">if</span> blueAmount.text == <span class="string">""</span> {</div><div class="line"> present(invalidAlert, animated: <span class="literal">true</span>, completion: <span class="literal">nil</span>)</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> transaction(from: <span class="string">"<span class="subst">\(secondAccount)</span>"</span>, to: <span class="string">"<span class="subst">\(firstAccount)</span>"</span>, amount: <span class="type">Int</span>(blueAmount.text!)!, type: <span class="string">"normal"</span>)</div><div class="line"> <span class="built_in">print</span>(<span class="string">"<span class="subst">\(blueAmount.text!)</span> BTC sent from <span class="subst">\(secondAccount)</span> to <span class="subst">\(firstAccount)</span>"</span>)</div><div class="line"> chainState()</div><div class="line"> blueAmount.text = <span class="string">""</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>首先,我们检查 <code>redAmount</code> 或者 <code>blueAmount</code> 的文本值是否为空。如果为空,则弹出无效交易的提示框。如果不为空,则继续下一步。我们使用交易函数从第一个账户转账到第二个账户(或者反向转账),金额为输入的数量。我们打印转账金额,并调用 <code>chainState()</code> 方法。最后,清空文本框。</p>
<p>就酱,工作完成!请检查你的代码是否和图中一致:</p>
<p><img src="https://appcoda.com/wp-content/uploads/2018/05/blockchain-7.png" alt="这里写图片描述"></p>
<p>现在运行应用并测试一下。从前端看,这就像一个正常的交易应用,但是运行在屏幕背后的可是区块链啊!请尝试使用应用将 BTC 从一个帐户转移到另一个帐户,随意测试,尽情把玩吧!</p>
<h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>在这个教程中,你已经学会了如何使用 Swift 创建区块链,并且创建了你自己的比特币交易系统。请注意,真正加密货币的后端,和我们上面的实现完全不一样,因为它需要用智能合约实现分布式,而本例仅用于学习。</p>
<p>在这个示例中,我们将区块链技术应用于加密货币,然而你能想到区块链的其他应用场景吗?请留言分享给大家!希望你能学到更多新东西!</p>
<p>为了参考,你可以从 GitHub 下载<a href="https://github.com/appcoda/BlockchainDemo" target="_blank" rel="external">完整的示例</a>。</p>
]]></content>
<summary type="html">
构建第一个 Swift 区块链应用
</summary>
<category term="Swift" scheme="http://lin493369.github.io/tags/Swift/"/>
<category term="区块链" scheme="http://lin493369.github.io/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
</entry>
<entry>
<title>使非法状态不可表示</title>
<link href="http://lin493369.github.io/2018/05/08/making-illegal-states-unrepresentable/"/>
<id>http://lin493369.github.io/2018/05/08/making-illegal-states-unrepresentable/</id>
<published>2018-05-07T16:00:00.000Z</published>
<updated>2018-05-09T01:52:57.089Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="https://oleb.net/blog/2018/03/making-illegal-states-unrepresentable/" target="_blank" rel="external">https://oleb.net/blog/2018/03/making-illegal-states-unrepresentable/</a><br>作者=Ole Begemann<br>原文日期=2018-03-27</p>
<blockquote>
<p>你知道 URLSession 能同时返回响应和错误吗?</p>
</blockquote>
<p><a href="https://oleb.net/blog/2015/07/swift-type-system/" target="_blank" rel="external">我之前介绍过</a>,Swift 强类型系统的一个主要优点是天生具备编译器强制遵循的文档规范。</p>
<h2 id="类型是编译器强制遵循的文档规范"><a href="#类型是编译器强制遵循的文档规范" class="headerlink" title="类型是编译器强制遵循的文档规范"></a>类型是编译器强制遵循的文档规范</h2><p>类型为函数的行为设立了一种“界限”,因此一个易用的 API 应该精心选择输入输出类型。</p>
<p>仔细思考以下 Swift 函数声明:<br><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="params">(dividend: Int, divisor: Int)</span></span> -> <span class="type">Int</span></div></pre></td></tr></table></figure></p>
<p>在不阅读任何函数实现的情况下,你就可以推断出这应该是<a href="http://mathworld.wolfram.com/IntegerDivision.html" target="_blank" rel="external">整型除法</a>,因为返回的类型不可能是小数。相较之下,如果函数的返回类型是既可以表示整型,也可以表示浮点型数值的 <a href="https://developer.apple.com/documentation/foundation/nsnumber" target="_blank" rel="external"><code>NSNumber</code></a>,那你就只能祈祷开发者自觉遵循文档只返回整数。</p>
<p>随着类型系统的表现越来越好,这种使用类型来记录函数行为的技巧变得越来越有用。如果 <code>Swift</code> 有一个<a href="#quote1"><code>NonZeroInt</code> 类型</a>代表“除了 0 之外的整型”,那么除法函数可能就会变成下面这样:</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="params">(dividend: Int, divisor: NonZeroInt)</span></span> -> <span class="type">Int</span></div></pre></td></tr></table></figure>
<p>类型检查不允许传入的除数为 0,因此你不用关心函数如何处理除数为 0 的错误。函数会中断吗?会返回一个没有意义的值吗?如果你用的是上一种定义,就必须在文档里单独说明特殊情况的处理方式。</p>
<h2 id="使非法状态成为不可能"><a href="#使非法状态成为不可能" class="headerlink" title="使非法状态成为不可能"></a>使非法状态成为不可能</h2><p>我们可以把这个观点转换为一条通用规则:<strong>使用类型让你的程序无法表现非法状态</strong>。</p>
<p>如果你想学习更多相关知识,可以看看 Brandon Williams 和 Stephen Celis 的最新视频系列 <a href="https://www.pointfree.co/" target="_blank" rel="external">Point-Free</a>。他们讲了很多这方面的知识和相关话题,前八集真的特别棒,我强烈推荐大家去订阅,你会学到很多东西。</p>
<p>在<a href="https://www.pointfree.co/episodes/ep4-algebraic-data-types" target="_blank" rel="external">第四集</a>关于代数数据类型(<a href="https://en.wikipedia.org/wiki/Algebraic_data_type" target="_blank" rel="external">algebraic data types</a>)的视频中,Brandon 和 Stephen 讨论了如何组合 <code>enums</code> 和 <code>structs</code>(或者 <code>tuples</code>)来精确表示期望状态的类型,并且让所有非法状态无法表示。在视频的最后,他们用 Apple 的 <a href="https://developer.apple.com/documentation/foundation/urlsession" target="_blank" rel="external">URLSession</a> API 作为反面教材进行介绍,因为这个 API 没有使用最合适的类型,这就引出了本文的子标题——“你知道 URLSession 能同时返回响应和错误吗?”。</p>
<h2 id="URLSession"><a href="#URLSession" class="headerlink" title="URLSession"></a>URLSession</h2><p>Swift 的类型系统比 Objective-C 更富有表现力。然而,很多 Apple 自己的 API 也没有利用这个优势,可能是因为没空更新老旧的 API,或者是为了维持 Objective-C 的兼容性。</p>
<p>在 iOS 中发起一个<a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" target="_blank" rel="external">网络请求</a>的通用方法:</p>
<figure class="highlight less"><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="selector-tag">class</span> <span class="selector-tag">URLSession</span> {</div><div class="line"> <span class="selector-tag">func</span> <span class="selector-tag">dataTask</span>(with <span class="attribute">url</span>: URL,</div><div class="line"> <span class="attribute">completionHandler</span>: <span class="variable">@escaping</span> (Data?, URLResponse?, Error?) -> Void)</div><div class="line"> <span class="selector-tag">-</span>> <span class="selector-tag">URLSessionDataTask</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>回调函数的参数是三个可选值:<a href="https://developer.apple.com/documentation/foundation/data" target="_blank" rel="external"><code>Data?</code></a>,<a href="https://developer.apple.com/documentation/foundation/urlresponse" target="_blank" rel="external"><code>URLResponse?</code></a> 和 <a href="https://developer.apple.com/documentation/swift/error" target="_blank" rel="external"><code>Error?</code></a>。这将产生 <code>2 × 2 × 2 = 8</code> 种<a href="#quote2">可能的状态</a>,但是其中有多少种是合法的呢?</p>
<p>引述 <a href="https://www.pointfree.co/episodes/ep4-algebraic-data-types" target="_blank" rel="external">Brandon 和 Stephen</a> 的观点:“这里有很多状态毫无意义”。有些组合很明显没有意义,另外我们基本可以确定,这三个值不可能全为 <code>nil</code> 或全为非 <code>nil</code>。</p>
<h2 id="响应和错误能够同时非-nil"><a href="#响应和错误能够同时非-nil" class="headerlink" title="响应和错误能够同时非 nil"></a>响应和错误能够同时非 nil</h2><p>其他状态就很棘手了,在这里 Brandon 和 Stephen 犯了一点小错误:他们认为 API 要么返回一个有效的 <code>Data</code> 和 <code>URLResponse</code>,要么返回一个 <code>Error</code>。毕竟接口不可能同时返回一个非 <code>nil</code> 的响应和错误。看起来很有道理,对不对?</p>
<p>但事实上这是错误的。<code>URLResponse</code> 封装了服务器的 <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html" target="_blank" rel="external">HTTP 响应头部</a>,只要接收到一个有效的响应头部, <code>URLSession</code> API 就会一直给你提供这个值,无论后续的阶段请求是否出错(例如取消和超时)。因而 API 的完成处理中有可能包含一个有效的 <code>URLResponse</code> 和非 <code>nil</code> 的错误值(但是没有 <code>Data</code>)。</p>
<p>如果你对 <code>URLSession</code> 代理(delegate)API 比较熟悉的话,应该不会太惊讶,因为代理方法就是分成 <a href="https://developer.apple.com/documentation/foundation/urlsessiondatadelegate/1410027-urlsession" target="_blank" rel="external"><code>didReceiveResponse</code></a> 和 <a href="https://developer.apple.com/documentation/foundation/urlsessiondatadelegate/1411528-urlsession" target="_blank" rel="external"><code>didReceiveData</code></a>。实际上,<a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" target="_blank" rel="external"><code>dataTask(with:completionHandler:)</code>的文档</a>也提到了这个问题:</p>
<blockquote>
<p>如果收到服务器的响应,那么<strong>无论请求成功或失败</strong>,响应参数都会有值。</p>
</blockquote>
<p>不过,我敢打赌 Cocoa 开发人员普遍对此抱有误解。仅仅在过去的四周,我就看到<a href="https://davedelong.com/blog/2018/03/02/apple-networking-feedback/" target="_blank" rel="external">两</a>篇<a href="https://ruiper.es/2018/03/03/ras-s2e1/" target="_blank" rel="external">文章</a>的作者犯了同样的错误(至少没有领悟其中的真谛)。</p>
<p>说真的,我很喜欢这个充满讽刺意味的事实:Brandon 和 Stephen 试图指出由于类型问题导致的 API 缺陷,但在指出错误的同时,这个类型问题又让他们犯了另一个错误。如果原始 API 使用了更好的类型,那么这两个错误就都能避免,这反而证明了他们的观点:一个有更加严格类型的 API 能够避免错误使用。</p>
<h2 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h2><p>如果你想自己体验一下 <code>URLSession</code> 的功能,你可以复制以下代码到 Swift playground:<br><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></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> Foundation</div><div class="line"><span class="keyword">import</span> PlaygroundSupport</div><div class="line"></div><div class="line"><span class="comment">// 如果返回 404,把 URL 换成随便一个大文件</span></div><div class="line"><span class="keyword">let</span> bigFile = <span class="type">URL</span>(string: <span class="string">"https://speed.hetzner.de/1GB.bin"</span>)!</div><div class="line"></div><div class="line"><span class="keyword">let</span> task = <span class="type">URLSession</span>.shared.dataTask(with: bigFile) { (data, response, error) <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"data:"</span>, data <span class="keyword">as</span> <span class="type">Any</span>)</div><div class="line"> <span class="built_in">print</span>(<span class="string">"response:"</span>, response <span class="keyword">as</span> <span class="type">Any</span>)</div><div class="line"> <span class="built_in">print</span>(<span class="string">"error:"</span>, error <span class="keyword">as</span> <span class="type">Any</span>)</div><div class="line">}</div><div class="line">task.resume()</div><div class="line"></div><div class="line"><span class="comment">// 过几秒之后取消下载</span></div><div class="line"><span class="type">DispatchQueue</span>.main.asyncAfter(deadline: .now() + <span class="number">3</span>) {</div><div class="line"> task.cancel()</div><div class="line">}</div><div class="line"><span class="type">PlaygroundPage</span>.current.needsIndefiniteExecution = <span class="literal">true</span></div></pre></td></tr></table></figure></p>
<p>这段代码首先下载一个大文件,然后在几秒后取消。最后,完成的处理中返回了一个非 <code>nil</code> 的响应和错误。</p>
<p>(这里假设指定的时间间隔内,能够获取到服务器响应的头部,但不能完成下载。如果你的网速非常慢或者非常变态,请自行调整这个时间参数)</p>
<h2 id="正确的类型应该是什么?"><a href="#正确的类型应该是什么?" class="headerlink" title="正确的类型应该是什么?"></a>正确的类型应该是什么?</h2><p>Brandon 和 Stephen 随后在 <a href="https://www.pointfree.co/episodes/ep9-algebraic-data-types-exponents" target="_blank" rel="external">Point-Free 的第九集视频</a>中发布了他们对问题的跟进。他们认为“正确”的参数类型应该是:</p>
<figure class="highlight clojure"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">(<span class="name">URLResponse?</span>, Result<Data, Error>)</div></pre></td></tr></table></figure>
<p>我不同意,因为如果有数据,就一定有响应,不可能只有数据没有响应。我认为应该是这样的:</p>
<figure class="highlight gcode"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Result<<span class="comment">(Data, URLResponse)</span>, <span class="comment">(Error, URLResponse?)</span>></div></pre></td></tr></table></figure>
<p>解读:你将要么得到数据和响应(后者肯定不是 <code>nil</code>),要么得到一个错误和一个可选类型的响应。不可否认,我的建议与一般的 <code>Result</code> 类型定义相悖,因为它将失败参数约束为不能符合 <code>Error</code> 的 <a href="https://developer.apple.com/documentation/swift/error" target="_blank" rel="external">Error</a> 协议—<code>(Error, URLResponse?)</code>。目前 <a href="https://forums.swift.org/t/adding-result-to-the-standard-library/6932/58" target="_blank" rel="external">Swift 论坛正在讨论</a> <code>Error</code> 约束是否有必要。</p>
<h2 id="Result-类型"><a href="#Result-类型" class="headerlink" title="Result 类型"></a>Result 类型</h2><p>由于 <code>URLResponse</code> 参数的非直观行为,<code>URLSession</code> 的API 显得特别棘手。但是 Apple 几乎所有的基于回调的异步 API 都有相同的问题,它们所提供的类型使得非法状态可以表示。</p>
<p>如何解决这个问题呢?</p>
<p>Swift 的通用方案是定义一个 <a href="https://github.com/antitypical/Result/blob/03fba33a0a8b75492480b9b2e458e88651525a2a/Result/Result.swift" target="_blank" rel="external">Result 类型</a>—一个可以代表通用成功值或错误的枚举。最近,又有人试图将 <a href="https://forums.swift.org/t/adding-result-to-the-standard-library/6932/20" target="_blank" rel="external">Result 添加到标准库</a>。</p>
<p>如果 Swift 5 添加了 <code>Result</code>(大胆假设),Apple 可能(更大胆的假设)会自动导入类似这样 <code>completionHandler: (A?, Error?) -> Void as (Result<A>) -> Void</code> 的 Cocoa API,将四个可表现的状态转为两个。在那之前(如果真的会发生的话),我建议你还是先自己<a href="https://oleb.net/blog/2017/01/result-init-helper/" target="_blank" rel="external">实现转换</a>。</p>
<p>长远来看,Swift 终有一天能从语言层面正确支持异步 API。社区和 Swift 团队可能会提出新的解决方案,<a href="https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619#conversion-of-imported-objective-c-apis" target="_blank" rel="external">把现有的 Cocoa API 移植到新系统中</a>,就像把 Objective-C 的 <code>NSError **</code> 参数作为抛出(throwing)函数引入 Swift 一样。不过不要太过期待,Swift 6 之前肯定实现不了。</p>
<hr>
<p><span id="quote1"><br>1、你可以自己定义一个 <code>NonZeroInt</code> 类型,但是没有办法告诉编译器“如果有人尝试用零去初始化这个类型,就引发一个错误”。你必须依赖运行时检查。</span></p>
<p>不过,引入这样的类型通常是个不错的想法,因为类型的用户可以在初始化之后依赖于所声明的不变性。我还没有在其他地方看到一个 <code>NonZeroInt</code> 类型,保证类型为非空集合的自定义类型更受欢迎。<br></p>
<p><span id="quote2"><br>2、我只是把“<code>nil</code>”或“非<code>nil</code>”作为可能的状态。显然,非 <code>nil</code> 数据值可以具有无数种可能的状态,并且对于其他两个参数也是如此。但是这些状态对我们来说并不好玩。<br></span></p>
]]></content>
<summary type="html">
你知道 URLSession 能够同时返回一个响应和错误吗?
</summary>
<category term="Swift 进阶" scheme="http://lin493369.github.io/categories/Swift-%E8%BF%9B%E9%98%B6/"/>
<category term="Swift" scheme="http://lin493369.github.io/tags/Swift/"/>
</entry>
<entry>
<title>iOS 如何优雅地 hook 系统的 delegate 方法?</title>
<link href="http://lin493369.github.io/2017/11/01/iOS%20%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%9C%B0%20hook%20%E7%B3%BB%E7%BB%9F%E7%9A%84%20delegate%20%E6%96%B9%E6%B3%95%EF%BC%9F/"/>
<id>http://lin493369.github.io/2017/11/01/iOS 如何优雅地 hook 系统的 delegate 方法?/</id>
<published>2017-10-31T16:00:00.000Z</published>
<updated>2017-11-01T07:19:23.000Z</updated>
<content type="html"><![CDATA[<p>在 iOS 开发中我们经常需要去 hook 系统方法,来满足一些特定的应用场景。</p>
<p>比如使用 Swizzling 来实现一些 AOP 的日志功能,比较常见的例子是 hook <code>UIViewController</code> 的 <code>viewDidLoad</code> ,动态为其插入日志。</p>
<p>这当然是一个很经典的例子,能让开发者迅速了解这个知识点。不过正如现在的娱乐圈,diss 天 diss 地,如果我们也想 hook 天,hook 地,顺便 hook 一下系统的 delegate 方法,该怎么做呢?</p>
<p>所以就进入今天的主题:<strong>如何优雅地 hook 系统的 delegate 方法?</strong></p>
<h4 id="hook-系统类的实例方法"><a href="#hook-系统类的实例方法" class="headerlink" title="hook 系统类的实例方法"></a>hook 系统类的实例方法</h4><p>首先,我们回想一下 hook <code>UIViewController</code> 的 <code>viewDidLoad</code> 方法,我们需要使用 category,为什么需要 category 呢?因为在 category 里面才能在不入侵源码的情况下,拿到实例方法 <code>viewDidLoad</code> ,并实现替换:</p>
<figure class="highlight objectivec"><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="meta">#import <span class="meta-string">"UIViewController+swizzling.h"</span></span></div><div class="line"><span class="meta">#import <span class="meta-string"><objc/runtime.h></span></span></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">UIViewController</span> (<span class="title">swizzling</span>)</span></div><div class="line"></div><div class="line">+ (<span class="keyword">void</span>)load {</div><div class="line"></div><div class="line"> <span class="comment">// 通过 class_getInstanceMethod() 函数从当前对象中的 method list 获取 method 结构体,如果是类方法就使用 class_getClassMethod() 函数获取.</span></div><div class="line"> Method fromMethod = class_getInstanceMethod([<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(viewDidLoad));</div><div class="line"> Method toMethod = class_getInstanceMethod([<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(swizzlingViewDidLoad));</div><div class="line"> <span class="comment">// 这里直接交换方法,不做判断,因为 UIViewController 的 viewDidLoad 肯定实现了。</span></div><div class="line"> method_exchangeImplementations(fromMethod, toMethod);</div><div class="line">}</div><div class="line"><span class="comment">// 我们自己实现的方法,也就是和self的viewDidLoad方法进行交换的方法。</span></div><div class="line">- (<span class="keyword">void</span>)swizzlingViewDidLoad {</div><div class="line"> <span class="built_in">NSString</span> *str = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%@"</span>, <span class="keyword">self</span>.class];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"日志打点 : %@"</span>, <span class="keyword">self</span>.class);</div><div class="line"> [<span class="keyword">self</span> swizzlingViewDidLoad];</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>这个例子里面,有一个注意点,通常我们创建 <code>ViewController</code> 都是继承于 <code>UIViewController</code>,因此如果想要使用这个日志打点功能,在自定义 <code>ViewController</code> 里面需要调用 <code>[super viewDidLoad]</code>。所以一定需要明白,这个例子是替换 <code>UIViewController</code> 的 <code>viewDidLoad</code>,而不是全部子类的 <code>viewDidLoad</code>。</p>
<figure class="highlight less"><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="variable">@implementation</span> ViewController</div><div class="line">- (void)viewDidLoad {</div><div class="line"> <span class="selector-attr">[super viewDidLoad]</span>;</div><div class="line"> <span class="comment">// </span></div><div class="line">}</div><div class="line"><span class="variable">@end</span></div></pre></td></tr></table></figure>
<h4 id="hook-webView-的-delegate-方法"><a href="#hook-webView-的-delegate-方法" class="headerlink" title="hook webView 的 delegate 方法"></a>hook webView 的 delegate 方法</h4><p>这个需求最初是项目中需要统计所有 <code>webView</code> 相关的数据,因此需要 hook webView 的 <code>delegate</code> 方法,今天也是以此为例,主要是 hook <code>UIWebView</code>(<code>WKWebView</code>类似)。</p>
<p>首先,我们需要明白,调用 delegate 的对象,是继承了 UIWebViewDelegate 协议的对象,因此如果要 hook delegate 方法,我们先要找到这个对象。</p>
<p>因此我们需要 hook [UIWebView setDelegate:<id>delegate] 方法,拿到 delegate 对象,才能动态地替换该方法。这里 swizzling 上场:</id></p>
<figure class="highlight objectivec"><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="class"><span class="keyword">@implementation</span> <span class="title">UIWebView</span>(<span class="title">delegate</span>)</span></div><div class="line"></div><div class="line">+(<span class="keyword">void</span>)load{</div><div class="line"> <span class="comment">// hook UIWebView</span></div><div class="line"> Method originalMethod = class_getInstanceMethod([<span class="built_in">UIWebView</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(setDelegate:));</div><div class="line"> Method swizzledMethod = class_getInstanceMethod([<span class="built_in">UIWebView</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(hook_setDelegate:));</div><div class="line"> method_exchangeImplementations(originalMethod, swizzledMethod);</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)dz_setDelegate:(<span class="keyword">id</span><<span class="built_in">UIWebViewDelegate</span>>)delegate{</div><div class="line"> [<span class="keyword">self</span> dz_setDelegate:delegate];</div><div class="line"> </div><div class="line"> <span class="comment">// 拿到 delegate 对象,在这里做替换 delegate 方法的操作</span></div><div class="line"> </div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>这里有个局限性,源码中需要调用 <code>setDelegate:</code> 方法,这样才会调用 <code>dz_setDelegate:</code>。</p>
<p>接下来就是重点了,我们需要根据两种情况去动态地 hook delegate 方法,以 hook <code>webViewDidFinishLoad:</code> 为例:</p>
<ul>
<li>delegate 对象实现了 <code>webViewDidFinishLoad:</code> 方法。则交换实现。</li>
<li>delegate 对象未实现了 <code>webViewDidFinishLoad:</code> 方法。则动态添加该 delegate 方法。</li>
</ul>
<p>下面是 category 实现的完整代码,实现了以上两种情况下都能正确统计页面加载完成的数据:</p>
<figure class="highlight objectivec"><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></pre></td><td class="code"><pre><div class="line"><span class="keyword">static</span> <span class="keyword">void</span> dz_exchangeMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL orginReplaceSel){</div><div class="line"> <span class="comment">// 原方法</span></div><div class="line"> Method originalMethod = class_getInstanceMethod(originalClass, originalSel);</div><div class="line"> <span class="comment">// 替换方法</span></div><div class="line"> Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);</div><div class="line"> <span class="comment">// 如果没有实现 delegate 方法,则手动动态添加</span></div><div class="line"> <span class="keyword">if</span> (!originalMethod) {</div><div class="line"> Method orginReplaceMethod = class_getInstanceMethod(replacedClass, orginReplaceSel);</div><div class="line"> <span class="built_in">BOOL</span> didAddOriginMethod = class_addMethod(originalClass, originalSel, method_getImplementation(orginReplaceMethod), method_getTypeEncoding(orginReplaceMethod));</div><div class="line"> <span class="keyword">if</span> (didAddOriginMethod) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"did Add Origin Replace Method"</span>);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 向实现 delegate 的类中添加新的方法</span></div><div class="line"> <span class="comment">// 这里是向 originalClass 的 replaceSel(@selector(replace_webViewDidFinishLoad:)) 添加 replaceMethod</span></div><div class="line"> <span class="built_in">BOOL</span> didAddMethod = class_addMethod(originalClass, replacedSel, method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod));</div><div class="line"> <span class="keyword">if</span> (didAddMethod) {</div><div class="line"> <span class="comment">// 添加成功</span></div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"class_addMethod_success --> (%@)"</span>, <span class="built_in">NSStringFromSelector</span>(replacedSel));</div><div class="line"> <span class="comment">// 重新拿到添加被添加的 method,这里是关键(注意这里 originalClass, 不 replacedClass), 因为替换的方法已经添加到原类中了, 应该交换原类中的两个方法</span></div><div class="line"> Method newMethod = class_getInstanceMethod(originalClass, replacedSel);</div><div class="line"> <span class="comment">// 实现交换</span></div><div class="line"> method_exchangeImplementations(originalMethod, newMethod);</div><div class="line"> }<span class="keyword">else</span>{</div><div class="line"> <span class="comment">// 添加失败,则说明已经 hook 过该类的 delegate 方法,防止多次交换。</span></div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"Already hook class --> (%@)"</span>,<span class="built_in">NSStringFromClass</span>(originalClass));</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">UIWebView</span>(<span class="title">delegate</span>)</span></div><div class="line"></div><div class="line">+(<span class="keyword">void</span>)load{</div><div class="line"> <span class="comment">// hook WebView</span></div><div class="line"> Method originalMethod = class_getInstanceMethod([<span class="built_in">UIWebView</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(setDelegate:));</div><div class="line"> Method swizzledMethod = class_getInstanceMethod([<span class="built_in">UIWebView</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(dz_setDelegate:));</div><div class="line"> method_exchangeImplementations(originalMethod, swizzledMethod);</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)dz_setDelegate:(<span class="keyword">id</span><<span class="built_in">UIWebViewDelegate</span>>)delegate{</div><div class="line"> [<span class="keyword">self</span> dz_setDelegate:delegate];</div><div class="line"> <span class="comment">// 获得 delegate 的实际调用类</span></div><div class="line"> <span class="comment">// 传递给 UIWebView 来交换方法</span></div><div class="line"> [<span class="keyword">self</span> exchangeUIWebViewDelegateMethod:delegate];</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">#pragma mark - hook webView delegate 方法</span></div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)exchangeUIWebViewDelegateMethod:(<span class="keyword">id</span>)delegate{</div><div class="line"> dz_exchangeMethod([delegate <span class="keyword">class</span>], <span class="keyword">@selector</span>(webViewDidFinishLoad:), [<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(replace_webViewDidFinishLoad:),<span class="keyword">@selector</span>(oriReplace_webViewDidFinishLoad:));</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 在未添加该 delegate 的情况下,手动添加 delegate 方法。</span></div><div class="line">- (<span class="keyword">void</span>)oriReplace_webViewDidFinishLoad:(<span class="built_in">UIWebView</span> *)webView{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"统计加载完成数据"</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 在添加该 delegate 的情况下,使用 swizzling 交换方法实现。</span></div><div class="line"><span class="comment">// 交换后的具体方法实现</span></div><div class="line">- (<span class="keyword">void</span>)replace_webViewDidFinishLoad:(<span class="built_in">UIWebView</span> *)webView</div><div class="line">{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"统计加载完成数据"</span>);</div><div class="line"> [<span class="keyword">self</span> replace_webViewDidFinishLoad:webView];</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>与 hook 实例方法不相同的地方是,交换的两个类以及方法都不是 <code>[self class]</code>,在实现过程中:</p>
<ol>
<li><p>判断 delegate 对象的 delegate 方法(<code>originalMethod</code>)是否为空,为空则用 <code>class_addMethod</code> 为 delegate 对象添加方法名为 (<code>webViewDidFinishLoad:</code>) ,方法实现为(<code>oriReplace_webViewDidFinishLoad:</code>)的动态方法。</p>
</li>
<li><p>若已实现,则说明该 delegate 对象实现了 <code>webViewDidFinishLoad:</code> 方法,此时不能简单地交换 <code>originalMethod</code> 与 <code>replacedMethod</code>,因为 <code>replaceMethod</code> 是属于 <code>UIWebView</code> 的实例方法,没有实现 delegate 协议,无法在 hook 之后调用原来的 delegate 方法:<code>[self replace_webViewDidFinishLoad:webView];</code>。</p>
<p> 因此,我们也需要将 <code>replace_webViewDidFinishLoad:</code> 方法动态添加到 delegate 对象中,并使用添加后的方法和源方法交换。</p>
</li>
</ol>
<h4 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h4><p>以上,通过动态添加方法并替换的方式,可以在不入侵源码的情况下,优雅地 hook 系统的 delegate 方法。通过合理使用 runtime 期间几个方法的特性,使得 hook 系统未实现的 delegate 方法成为可能。</p>
<p>最后献上:<a href="https://github.com/lin493369/HookDelegateDemo" target="_blank" rel="external">github 源码地址</a></p>
]]></content>
<summary type="html">
hook 系统的 delegate 方法的一些实践心得。
</summary>
<category term="Objective-C" scheme="http://lin493369.github.io/categories/Objective-C/"/>
<category term="Objective-C" scheme="http://lin493369.github.io/tags/Objective-C/"/>
<category term="hook" scheme="http://lin493369.github.io/tags/hook/"/>
<category term="swizzling" scheme="http://lin493369.github.io/tags/swizzling/"/>
<category term="runtime" scheme="http://lin493369.github.io/tags/runtime/"/>
</entry>
<entry>
<title>伪单例模式</title>
<link href="http://lin493369.github.io/2017/04/10/iOS%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
<id>http://lin493369.github.io/2017/04/10/iOS单例模式最佳实践/</id>
<published>2017-04-09T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>本文仅探讨 iOS 中单例的适用场景.</p>
<p>如需单例教程及其定义作用的请访问:<a href="http://www.jianshu.com/p/5226bc8ed784" target="_blank" rel="external">http://www.jianshu.com/p/5226bc8ed784</a>。</p>
<p>最近在做项目的重构工作,翻看了一下源码,发现了各种历史遗留问题。其中随处可见的单例,产生了万物皆单例的现象(说好的万物皆对象呢?)。</p>
<p>在与前开发人员沟通后,对方坚持使用单例的原因如下:</p>
<ul>
<li>代码简洁,不需要声明属性以及创建新的实例对象,需要的时候就可以马上调用。</li>
<li>方便管理对象的生命周期,把对象的创建和销毁时机都掌握在开发人员手中,可以控制对象的销毁时机。</li>
<li>历史遗留,iOS 系统类中随处可见的单例,我们的前辈们也都是这么用的,那就这么干吧。</li>
</ul>
<p>第一点无法反驳,单例确实很好用,写起来有种欲仙欲死的快感。但是,不管副作用的话,毒品产生的快感大概比这更甚吧。作为一个有追求的程序猿,怎么能被普通的感官快感所诱惑,我们的目标是星辰大海好吗。</p>
<p>第二点无法直视,既然是单例为什么要手动销毁呢。这时候就有人说了,比如退出登录后,需要把账户的单例销毁。作为需要全局使用的对象,这样的需求确实无可厚非,那么如果这个单例对象只是在一个地方使用到了呢?需要特地建一个单例并手动去管理单例的释放时机吗?这还是单例吗,这是假单例吧。</p>
<h3 id="真单例"><a href="#真单例" class="headerlink" title="真单例"></a>真单例</h3><p>吐槽完毕。进入正题,单例作为一个变态的全局变量,首先看他的定义:</p>
<blockquote>
<p>保证一个类仅有一个实例,并提供一个访问它的全局访问点。</p>
</blockquote>
<p>那么他的使用场景很简单且很明确:</p>
<blockquote>
<ul>
<li><p>在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在 APP 开发中我们可能在任何地方都要使用用户的信息,那么可以在登录的时候就把用户信息存放在一个文件里面,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。</p>
</li>
<li><p>有的情况下,某个类可能只能有一个实例。比如说你写了一个类用来播放音乐,那么不管任何时候只能有一个该类的实例来播放声音。再比如,一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个,这里就可以通过单例模式来避免两个打印任务同时输出到打印机中,即在整个的打印过程中我只有一个打印程序的实例。</p>
</li>
</ul>
</blockquote>
<p>综上所述,不遵守以上定义的单例都是伪单例,例如用户信息单例就是典型的伪单例。</p>
<h3 id="伪单例"><a href="#伪单例" class="headerlink" title="伪单例"></a>伪单例</h3><p>使用伪单例并没有什么错,我们不需要咬文爵字,只要有合适的应用场景,并承认自己是伪单例,我们也可以开开心心地使用它。</p>
<p>那么我们今天就来好好谈谈伪单例的正确使用姿势(不管是不是你创造的,既然接盘了你就要负责到底)。</p>
<p>首先本文中对伪单例的定义:</p>
<blockquote>
<p>需要管理生命周期,并且长时间不需要销毁的单例对象。</p>
</blockquote>
<p>即在单例对象的基础上,需要对其生命周期进行管理,并且在应用启动期间如没有特殊情况,会一直存活。</p>
<h4 id="伪单例的销毁"><a href="#伪单例的销毁" class="headerlink" title="伪单例的销毁"></a>伪单例的销毁</h4><p>伪单例的销毁要基于其创建的方式,常规的有两种:同步锁、GCD。</p>
<figure class="highlight smali"><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">static InstanceSync *instance = nil;</div><div class="line">@implementation InstanceSync</div><div class="line">// 同步锁方式</div><div class="line">+(instancetype)shareInstance{</div><div class="line"> @synchronized (self) {</div><div class="line"> <span class="built_in"> if </span>(!instance) {</div><div class="line"> <span class="built_in"> instance </span>= [[self alloc]init];</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="built_in"> return </span>instance;</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight smali"><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">static InstanceSync *instance = nil;<span class="keyword"></span></div><div class="line">static dispatch_once_t onceToken;</div><div class="line">@implementation InstanceSync</div><div class="line">// GCD 方式</div><div class="line">+(instancetype)shareInstance{</div><div class="line"> dispatch_once(&onceToken, ^{</div><div class="line"> <span class="built_in"> instance </span>= [[self alloc]init];</div><div class="line"> });</div><div class="line"> <span class="built_in"> return </span>instance;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>首先我们使用同步锁的单例来试验一下,一般我们销毁一个对象是将其置为空,即可以释放,如下:</p>
<figure class="highlight mipsasm"><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">NSLog(@<span class="string">"instanceSync : %@"</span>,[<span class="keyword">InstanceSync </span><span class="keyword">shareInstance]);</span></div><div class="line"><span class="keyword">InstanceSync </span>*<span class="keyword">instanceSync </span>= [<span class="keyword">InstanceSync </span><span class="keyword">shareInstance];</span></div><div class="line"><span class="keyword">instanceSync </span>= nil<span class="comment">;</span></div><div class="line">NSLog(@<span class="string">"instanceSync : %@"</span>,[<span class="keyword">InstanceSync </span><span class="keyword">shareInstance]);</span></div></pre></td></tr></table></figure>
<p>实际上,这样并不能销毁这个对象:</p>
<figure class="highlight css"><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">2017<span class="selector-tag">-04-10</span> 10<span class="selector-pseudo">:54</span><span class="selector-pseudo">:10.449</span> <span class="selector-tag">instanceSync</span> : <<span class="selector-tag">InstanceSync</span>: 0<span class="selector-tag">x600000016ea0</span>></div><div class="line">2017<span class="selector-tag">-04-10</span> 10<span class="selector-pseudo">:54</span><span class="selector-pseudo">:10.449</span> <span class="selector-tag">instanceSync</span> : <<span class="selector-tag">InstanceSync</span>: 0<span class="selector-tag">x600000016ea0</span>></div></pre></td></tr></table></figure>
<p>其实在常规单例的内部都有一个全局静态变量,我们需要对其置空才能释放该单例对象:</p>
<figure class="highlight smali"><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">-(void)destoryInstance{</div><div class="line"> <span class="built_in"> instance </span>= nil;</div><div class="line">}</div><div class="line"></div><div class="line">-(void)dealloc{</div><div class="line"> NSLog(@<span class="string">"%@ occur"</span>,NSStringFromSelector(_cmd));</div><div class="line">}</div></pre></td></tr></table></figure>
<p>那么我们再来尝试一下:</p>
<figure class="highlight mipsasm"><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">NSLog(@<span class="string">"instanceSync : %@"</span>,[<span class="keyword">InstanceSync </span><span class="keyword">shareInstance]);</span></div><div class="line"><span class="keyword">InstanceSync </span>*<span class="keyword">instanceSync </span>= [<span class="keyword">InstanceSync </span><span class="keyword">shareInstance];</span></div><div class="line">[<span class="keyword">instanceSync </span>destoryInstance]<span class="comment">;</span></div><div class="line">NSLog(@<span class="string">"instanceSync : %@"</span>,[<span class="keyword">InstanceSync </span><span class="keyword">shareInstance]);</span></div></pre></td></tr></table></figure>
<figure class="highlight dns"><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="number">2017-04-10</span> <span class="number">11:05:22.112</span> instanceSync : <InstanceSync: <span class="number">0</span>x<span class="number">608000200480</span>></div><div class="line"><span class="number">2017-04-10</span> <span class="number">11:05:22.112</span> instanceSync : <InstanceSync: <span class="number">0</span>x<span class="number">600000200430</span>></div><div class="line"><span class="number">2017-04-10</span> <span class="number">11</span>:<span class="number">05:24.366</span> dealloc occur</div></pre></td></tr></table></figure>
<p>可以看到伪单例对象 <code>[InstanceSync shareInstance]</code> 并没有马上进入 <code>dealloc</code>,而是在打印完第二 log 后才进入 <code>dealloc</code>;因此这里需要注意:</p>
<blockquote>
<p>如果伪单例对象被外部变量所持有,那么在释放单例对象时,需要确保所有持有变量都被释放后,才可以进入单例的释放。因此不建议将单例赋值给外部变量,以免无法在预期内释放单例对象。</p>
</blockquote>
<p>此外再次调用 <code>[InstanceSync shareInstance]</code> 将会产生新的对象,这也是易于理解的,那么如果使用 GCD 的方式能否产生新的对象?</p>
<p>实际上,这就取决于你销毁对象的方式:</p>
<figure class="highlight smali"><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">-(void)destoryInstance{</div><div class="line"> <span class="built_in"> instance </span>= nil; // 销毁静态全局变量</div><div class="line"> onceToken = nil; // 销毁 GCD onceToken</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果只销毁静态全局变量,那么调用该方法后,将不会产生新的对象:</p>
<figure class="highlight dns"><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="number">2017-04-10</span> <span class="number">11</span>:<span class="number">21:37.917</span> instanceGCD : <InstanceGCD: <span class="number">0</span>x600<span class="number">00000d700</span>></div><div class="line"><span class="number">2017-04-10</span> <span class="number">11</span>:<span class="number">21:37.918</span> instanceGCD : (null)</div><div class="line"><span class="number">2017-04-10</span> <span class="number">11</span>:<span class="number">21:37.918</span> dealloc occur</div></pre></td></tr></table></figure>
<p>如果销毁 GCD onceToken ,那么不论销毁静态全局变量,都会产生新的对象。</p>
<h3 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h3><p>实际上,本文讲述的是在明知是伪单例的情况下,如何正确地管理伪单例的生命周期,文中若有不实之处,希望大家提出宝贵的意见。</p>
]]></content>
<summary type="html">
实际上,本文讲述的是在明知是伪单例的情况下,如何正确地管理伪单例的生命周期,文中若有不实之处,希望大家提出宝贵的意见。
</summary>
<category term="Objective-C" scheme="http://lin493369.github.io/categories/Objective-C/"/>
<category term="Objective-C" scheme="http://lin493369.github.io/tags/Objective-C/"/>
<category term="单例" scheme="http://lin493369.github.io/tags/%E5%8D%95%E4%BE%8B/"/>
</entry>
<entry>
<title>iOS 10 添加推送功能注意点及问题汇总</title>
<link href="http://lin493369.github.io/2016/09/22/iOS10%E6%B7%BB%E5%8A%A0%E6%8E%A8%E9%80%81%E5%8A%9F%E8%83%BD%E8%AE%BE%E7%BD%AE%E6%B5%81%E7%A8%8B%E5%8F%8A%E6%B3%A8%E6%84%8F%E7%82%B9/"/>
<id>http://lin493369.github.io/2016/09/22/iOS10添加推送功能设置流程及注意点/</id>
<published>2016-09-21T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>对于 iOS 9 升级到 iOS 10 推送功能不正常的问题,总结了一下要点,亲们可以根据以下步骤,逐步排查问题,也可以逐步实现 iOS 10 的推送功能。</p>
<p>1、在项目 target 中,打开<code>Capabilitie —> Push Notifications</code>,并会自动在项目中生成 .entitlement 文件。(很多同学升级后,获取不到 deviceToken,大概率是由于没开这个选项)</p>
<p><img src="http://upload-images.jianshu.io/upload_images/928928-0c0e9c11b9d9a9eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Capabilitie —> Push Notifications
"></p>
<p><img src="http://upload-images.jianshu.io/upload_images/928928-d9e5b390c8799506.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="自动生成 .entitlement"></p>
<p>2、确保添加了 <code>UserNotifications.framework</code>,并 import 到 <code>AppDelegate</code>,记得实现 <code>UNUserNotificationCenterDelegate</code> 。</p>
<figure class="highlight objectivec"><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"><span class="meta">#import <span class="meta-string"><UserNotifications/UserNotifications.h></span></span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">AppDelegate</span> : <span class="title">UIResponder</span> <<span class="title">UIApplicationDelegate</span>,<span class="title">UNUserNotificationCenterDelegate</span>></span></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>3、在 <code>didFinishLaunchingWithOptions</code> 方法中,首先实现 <code>UNUserNotificationCenter</code> delegate,并使用 <code>UIUserNotificationSettings</code> 请求权限。</p>
<figure class="highlight objectivec"><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">//注意,关于 iOS10 系统版本的判断,可以用下面这个宏来判断。不能再用截取字符的方法。</span></div><div class="line"><span class="meta">#define SYSTEM_VERSION_GRATERTHAN_OR_EQUALTO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)</span></div><div class="line"></div><div class="line">-(<span class="built_in">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application didFinishLaunchingWithOptions:(<span class="built_in">NSDictionary</span> *)launchOptions{</div><div class="line"></div><div class="line"><span class="keyword">if</span>(SYSTEM_VERSION_GRATERTHAN_OR_EQUALTO(<span class="string">@"10.0"</span>)){</div><div class="line"> UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];</div><div class="line"> center.delegate = <span class="keyword">self</span>;</div><div class="line"> [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(<span class="built_in">BOOL</span> granted, <span class="built_in">NSError</span> * _Nullable error){</div><div class="line"> <span class="keyword">if</span>( !error ){</div><div class="line"> [[<span class="built_in">UIApplication</span> sharedApplication] registerForRemoteNotifications];</div><div class="line"> }</div><div class="line"> }]; </div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">return</span> <span class="literal">YES</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>4、最后实现以下两个回调。</p>
<figure class="highlight less"><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="comment">//====================For iOS 10====================</span></div><div class="line"></div><div class="line"><span class="selector-tag">-</span>(void)<span class="selector-tag">userNotificationCenter</span><span class="selector-pseudo">:(UNUserNotificationCenter</span> *)<span class="selector-tag">center</span> <span class="selector-tag">willPresentNotification</span><span class="selector-pseudo">:(UNNotification</span> *)<span class="selector-tag">notification</span> <span class="selector-tag">withCompletionHandler</span><span class="selector-pseudo">:(void</span> (^)(UNNotificationPresentationOptions options))<span class="selector-tag">completionHandler</span>{</div><div class="line"><span class="selector-tag">NSLog</span>(@<span class="string">"Userinfo %@"</span>,notification.request.content.userInfo);</div><div class="line"></div><div class="line"><span class="comment">//功能:可设置是否在应用内弹出通知</span></div><div class="line"><span class="selector-tag">completionHandler</span>(UNNotificationPresentationOptionAlert);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">//点击推送消息后回调</span></div><div class="line"><span class="selector-tag">-</span>(void)<span class="selector-tag">userNotificationCenter</span><span class="selector-pseudo">:(UNUserNotificationCenter</span> *)<span class="selector-tag">center</span> <span class="selector-tag">didReceiveNotificationResponse</span><span class="selector-pseudo">:(UNNotificationResponse</span> *)<span class="selector-tag">response</span> <span class="selector-tag">withCompletionHandler</span><span class="selector-pseudo">:(void(</span>^)())<span class="selector-tag">completionHandler</span>{</div><div class="line"><span class="selector-tag">NSLog</span>(@<span class="string">"Userinfo %@"</span>,response.notification.request.content.userInfo);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>注意:需要根据系统版本号来判断是否使用新的 <code>UserNotifications.framework</code>,因此,不要着急删除 iOS 10 以前的代码。</p>
<p>有问题,敬请留言探讨。</p>
]]></content>
<summary type="html">
很多童鞋从 iOS 9 升级到 iOS 10 后,发现推送功能有很多问题,特此总结.
</summary>
<category term="Notification" scheme="http://lin493369.github.io/categories/Notification/"/>
<category term="iOS10" scheme="http://lin493369.github.io/tags/iOS10/"/>
<category term="Notification" scheme="http://lin493369.github.io/tags/Notification/"/>
<category term="推送" scheme="http://lin493369.github.io/tags/%E6%8E%A8%E9%80%81/"/>
</entry>
<entry>
<title>iOS 10 IDFA 新策略 </title>
<link href="http://lin493369.github.io/2016/08/31/%E8%8E%B7%E5%8F%96IDFA%E8%BF%94%E5%9B%9E%E5%85%A8%E9%9B%B6%E9%94%99%E8%AF%AF/"/>
<id>http://lin493369.github.io/2016/08/31/获取IDFA返回全零错误/</id>
<published>2016-08-30T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>相信很多人将获取 IDFA 作为应用的唯一标识的替代方案,因此对 IDFA 有很大的需求。</p>
<p>但是最近很多同学在获取 IDFA 时发现返回:</p>
<figure class="highlight lsl"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="number">00000000</span><span class="number">-0000</span><span class="number">-0000</span><span class="number">-0000</span><span class="number">-000000000000</span></div></pre></td></tr></table></figure>
<p>很不幸,实际上是由于:这些设备升级到 iOS10,并且用户开启了限制广告跟踪。</p>
<p><img src="http://img.blog.csdn.net/20160831102842189" alt="这里写图片描述"></p>
<blockquote>
<p>在 iOS 10 之前:当用户开启限制广告跟踪,仍然可以将 IDFA 用于不同的用途,除了不能用于投放特定广告目标。</p>
</blockquote>
<p>但是,iOS 10 之后,对 IDFA 做了变更,参照官方文档所述:</p>
<blockquote>
<p>Important</p>
<p>In iOS 10.0 and later, the value of advertisingIdentifier is all zeroes when the user has limited ad tracking.</p>
<p>在 iOS 10.0 以后,当用户开启限制广告跟踪,advertisingIdentifier 的值将是全零。</p>
</blockquote>
<p>在这种情况下,如果你依然使用 IDFA 作为唯一标识符的话,可能会有大危机,推荐一个替代方案 <a href="https://github.com/ylechelle/OpenIDFA" target="_blank" rel="external">OpenIDFA</a>(一个基于可持续、隐私、友好的 identifier 方案)。</p>
<p>以上,有用到 IDFA 并且将其作为标识用户唯一手段的童鞋请悉知,虽是小改动,但对刚需开发者来说还是蛮严重的,特别 iOS 10 正式版放出之后,可能将是大灾难(危言耸听。。)。</p>
]]></content>
<summary type="html">
相信很多人将获取 IDFA 作为应用的唯一标识的替代方案,因此对 IDFA 有很大的需求。。
</summary>
<category term="iOS10" scheme="http://lin493369.github.io/categories/iOS10/"/>
<category term="iOS10" scheme="http://lin493369.github.io/tags/iOS10/"/>
<category term="IDFA" scheme="http://lin493369.github.io/tags/IDFA/"/>
</entry>
<entry>
<title>iOS 10 添加本地推送(Local Notification)</title>
<link href="http://lin493369.github.io/2016/07/01/iOS10%E6%B7%BB%E5%8A%A0%E6%9C%AC%E5%9C%B0%E6%8E%A8%E9%80%81%EF%BC%88LocalNotification)/"/>
<id>http://lin493369.github.io/2016/07/01/iOS10添加本地推送(LocalNotification)/</id>
<published>2016-06-30T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>iOS 10 中废弃了 <code>UILocalNotification</code>( <code>UIKit Framework</code>) 这个类,采用了全新的 <code>UserNotifications Framework</code> 来推送通知,从此推送通知也有了自己的标签 <code>UN</code>(这待遇真是没别人了),以及对推送功能的一系列增强改进(两个 extension 和 界面的体验优化),简直是苹果的亲儿子,因此推送这部分功能也成为开发中的重点。</p>
<p>本文主要查看了 iOS 10 的相关文档,整理出了在 iOS 10 下的本地推送通知,由于都是代码,就不多做讲解,直接看代码及注释,有问题留言讨论哦。</p>
<hr>
<h2 id="新的推送注册机制"><a href="#新的推送注册机制" class="headerlink" title="新的推送注册机制"></a>新的推送注册机制</h2><p>注册通知(<code>Appdelegate.m</code>):</p>
<figure class="highlight objectivec"><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></pre></td><td class="code"><pre><div class="line"><span class="meta">#import <span class="meta-string"><UserNotifications/UserNotifications.h></span></span></div><div class="line"><span class="meta">#import <span class="meta-string">"AppDelegate.h"</span></span></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">AppDelegate</span> ()<<span class="title">UNUserNotificationCenterDelegate</span>></span></div><div class="line"></div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">AppDelegate</span></span></div><div class="line"></div><div class="line">- (<span class="built_in">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application didFinishLaunchingWithOptions:(<span class="built_in">NSDictionary</span> *)launchOptions {</div><div class="line"> <span class="comment">// 使用 UNUserNotificationCenter 来管理通知</span></div><div class="line"> UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];</div><div class="line"> <span class="comment">//监听回调事件</span></div><div class="line"> center.delegate = <span class="keyword">self</span>;</div><div class="line"> </div><div class="line"> <span class="comment">//iOS 10 使用以下方法注册,才能得到授权</span></div><div class="line"> [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)</div><div class="line"> completionHandler:^(<span class="built_in">BOOL</span> granted, <span class="built_in">NSError</span> * _Nullable error) {</div><div class="line"> <span class="comment">// Enable or disable features based on authorization.</span></div><div class="line"> }];</div><div class="line"> </div><div class="line"> <span class="comment">//获取当前的通知设置,UNNotificationSettings 是只读对象,不能直接修改,只能通过以下方法获取</span></div><div class="line"> [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {</div><div class="line"> </div><div class="line"> }];</div><div class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">#pragma mark - UNUserNotificationCenterDelegate</span></div><div class="line"><span class="comment">//在展示通知前进行处理,即有机会在展示通知前再修改通知内容。</span></div><div class="line">-(<span class="keyword">void</span>)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(<span class="keyword">void</span> (^)(UNNotificationPresentationOptions))completionHandler{</div><div class="line"> <span class="comment">//1. 处理通知</span></div><div class="line"> </div><div class="line"> <span class="comment">//2. 处理完成后条用 completionHandler ,用于指示在前台显示通知的形式</span></div><div class="line"> completionHandler(UNNotificationPresentationOptionAlert);</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<h2 id="推送本地通知"><a href="#推送本地通知" class="headerlink" title="推送本地通知"></a>推送本地通知</h2><figure class="highlight groovy"><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></pre></td><td class="code"><pre><div class="line"><span class="comment">//使用 UNNotification 本地通知</span></div><div class="line">+(<span class="keyword">void</span>)<span class="string">registerNotification:</span>(NSInteger )alerTime{</div><div class="line"> </div><div class="line"> <span class="comment">// 使用 UNUserNotificationCenter 来管理通知</span></div><div class="line"> UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];</div><div class="line"> </div><div class="line"> <span class="comment">//需创建一个包含待通知内容的 UNMutableNotificationContent 对象,注意不是 UNNotificationContent ,此对象为不可变对象。</span></div><div class="line"> UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];</div><div class="line"> content.title = [NSString <span class="string">localizedUserNotificationStringForKey:</span>@<span class="string">"Hello!"</span> <span class="string">arguments:</span>nil];</div><div class="line"> content.body = [NSString <span class="string">localizedUserNotificationStringForKey:</span>@<span class="string">"Hello_message_body"</span></div><div class="line"><span class="symbol"> arguments:</span>nil];</div><div class="line"> content.sound = [UNNotificationSound defaultSound];</div><div class="line"> </div><div class="line"> <span class="comment">// 在 alertTime 后推送本地推送</span></div><div class="line"> UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger</div><div class="line"><span class="symbol"> triggerWithTimeInterval:</span>alerTime <span class="string">repeats:</span>NO];</div><div class="line"></div><div class="line"> UNNotificationRequest* request = [UNNotificationRequest <span class="string">requestWithIdentifier:</span>@<span class="string">"FiveSecond"</span></div><div class="line"><span class="symbol"> content:</span>content <span class="string">trigger:</span>trigger];</div><div class="line"> </div><div class="line"> <span class="comment">//添加推送成功后的处理!</span></div><div class="line"> [center <span class="string">addNotificationRequest:</span>request <span class="string">withCompletionHandler:</span>^(NSError * _Nullable error) {</div><div class="line"> UIAlertController *alert = [UIAlertController <span class="string">alertControllerWithTitle:</span>@<span class="string">"本地通知"</span> <span class="string">message:</span>@<span class="string">"成功添加推送"</span> <span class="string">preferredStyle:</span>UIAlertControllerStyleAlert];</div><div class="line"> UIAlertAction *cancelAction = [UIAlertAction <span class="string">actionWithTitle:</span>@<span class="string">"取消"</span> <span class="string">style:</span>UIAlertActionStyleCancel <span class="string">handler:</span>nil];</div><div class="line"> [alert <span class="string">addAction:</span>cancelAction];</div><div class="line"> [[UIApplication sharedApplication].keyWindow.rootViewController <span class="string">presentViewController:</span>alert <span class="string">animated:</span>YES <span class="string">completion:</span>nil];</div><div class="line"> }];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>iOS 10 以前本地推送通知:</p>
<figure class="highlight objectivec"><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></pre></td><td class="code"><pre><div class="line">+ (<span class="keyword">void</span>)registerLocalNotificationInOldWay:(<span class="built_in">NSInteger</span>)alertTime {</div><div class="line"> <span class="comment">// ios8后,需要添加这个注册,才能得到授权</span></div><div class="line"> <span class="comment">// if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {</span></div><div class="line"> <span class="comment">// UIUserNotificationType type = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;</span></div><div class="line"> <span class="comment">// UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type</span></div><div class="line"> <span class="comment">// categories:nil];</span></div><div class="line"> <span class="comment">// [[UIApplication sharedApplication] registerUserNotificationSettings:settings];</span></div><div class="line"> <span class="comment">// // 通知重复提示的单位,可以是天、周、月</span></div><div class="line"> <span class="comment">// }</span></div><div class="line"> </div><div class="line"> <span class="built_in">UILocalNotification</span> *notification = [[<span class="built_in">UILocalNotification</span> alloc] init];</div><div class="line"> <span class="comment">// 设置触发通知的时间</span></div><div class="line"> <span class="built_in">NSDate</span> *fireDate = [<span class="built_in">NSDate</span> dateWithTimeIntervalSinceNow:alertTime];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"fireDate=%@"</span>,fireDate);</div><div class="line"> </div><div class="line"> notification.fireDate = fireDate;</div><div class="line"> <span class="comment">// 时区</span></div><div class="line"> notification.timeZone = [<span class="built_in">NSTimeZone</span> defaultTimeZone];</div><div class="line"> <span class="comment">// 设置重复的间隔</span></div><div class="line"> notification.repeatInterval = kCFCalendarUnitSecond;</div><div class="line"> </div><div class="line"> <span class="comment">// 通知内容</span></div><div class="line"> notification.alertBody = <span class="string">@"该起床了..."</span>;</div><div class="line"> notification.applicationIconBadgeNumber = <span class="number">1</span>;</div><div class="line"> <span class="comment">// 通知被触发时播放的声音</span></div><div class="line"> notification.soundName = <span class="built_in">UILocalNotificationDefaultSoundName</span>;</div><div class="line"> <span class="comment">// 通知参数</span></div><div class="line"> <span class="built_in">NSDictionary</span> *userDict = [<span class="built_in">NSDictionary</span> dictionaryWithObject:<span class="string">@"开始学习iOS开发了"</span> forKey:<span class="string">@"key"</span>];</div><div class="line"> notification.userInfo = userDict;</div><div class="line"> </div><div class="line"> <span class="comment">// ios8后,需要添加这个注册,才能得到授权</span></div><div class="line"> <span class="keyword">if</span> ([[<span class="built_in">UIApplication</span> sharedApplication] respondsToSelector:<span class="keyword">@selector</span>(registerUserNotificationSettings:)]) {</div><div class="line"> <span class="built_in">UIUserNotificationType</span> type = <span class="built_in">UIUserNotificationTypeAlert</span> | <span class="built_in">UIUserNotificationTypeBadge</span> | <span class="built_in">UIUserNotificationTypeSound</span>;</div><div class="line"> <span class="built_in">UIUserNotificationSettings</span> *settings = [<span class="built_in">UIUserNotificationSettings</span> settingsForTypes:type</div><div class="line"> categories:<span class="literal">nil</span>];</div><div class="line"> [[<span class="built_in">UIApplication</span> sharedApplication] registerUserNotificationSettings:settings];</div><div class="line"> <span class="comment">// 通知重复提示的单位,可以是天、周、月</span></div><div class="line"> notification.repeatInterval = <span class="built_in">NSCalendarUnitDay</span>;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 通知重复提示的单位,可以是天、周、月</span></div><div class="line"> notification.repeatInterval = <span class="built_in">NSDayCalendarUnit</span>;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// 执行通知注册</span></div><div class="line"> [[<span class="built_in">UIApplication</span> sharedApplication] scheduleLocalNotification:notification];</div><div class="line">}</div></pre></td></tr></table></figure>]]></content>
<summary type="html">
本文主要查看了 iOS 10 的相关文档,整理出了在 iOS 10 下的本地推送通知.
</summary>
<category term="Notification" scheme="http://lin493369.github.io/categories/Notification/"/>
<category term="iOS10" scheme="http://lin493369.github.io/tags/iOS10/"/>
<category term="Notification" scheme="http://lin493369.github.io/tags/Notification/"/>
<category term="推送" scheme="http://lin493369.github.io/tags/%E6%8E%A8%E9%80%81/"/>
</entry>
<entry>
<title>iOS 简单易懂的 Block 回调使用和解析 </title>
<link href="http://lin493369.github.io/2016/03/11/iOS%E7%AE%80%E5%8D%95%E6%98%93%E6%87%82%E7%9A%84Block%E5%9B%9E%E8%B0%83%E4%BD%BF%E7%94%A8%E5%92%8C%E8%A7%A3%E6%9E%90/"/>
<id>http://lin493369.github.io/2016/03/11/iOS简单易懂的Block回调使用和解析/</id>
<published>2016-03-10T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>老实说在早前我已经学会了如何使用 Block 来做一些方法回调,传递参数的功能,并且用 Block 简单封装了第三方的网络库(AFNetworking)。虽说对 Block 的应用说不上得心应手,但是却是极其地喜欢使用这种设计模式,并且在项目中也大量地使用了。</p>
<p>但是,最近一位即将参加面试的学弟问我,什么是 Block 呢?我蒙圈了,但是毕竟是学长,我假装淡定地反问道:你所理解的 Block 是什么呢?学弟说:是一段封装的代码块,并可以放在任意位置使用,还可以传递数据。我心里暗喜,这孩子还是图样了,于是语重心长地说:Block 的本质是可以截取自动变量的匿名函数。但是说出这句话我就后悔了,这句话他喵的到底是个什么意思?看着学弟满意地走了之后,我就疯狂地上网查资料,万一下次这个熊孩子深究起来可不就破坏了我英明神武的形象了,但是并没有很满意的答案,大多是照文档描述了 Block 的定义以及基本用法,不然就是高深地去探讨 Block 底层的实现机制,显然这些都不适合让一个初学者既能学会使用又能够没有疑惑地使用。</p>
<p>本文主要讲的是 <strong>Block 回调的使用</strong>,以及 <strong>Block 是如何实现这种神奇的回调</strong>两部分来讲的。</p>
<h2 id="Block-回调实现"><a href="#Block-回调实现" class="headerlink" title="Block 回调实现"></a>Block 回调实现</h2><p>不着急,先跟着我实现最简单的 Block 回调传参的使用,如果你能举一反三,基本上可以满足了 OC 中的开发需求。已经实现的同学可以跳到下一节。</p>
<p>首先解释一下我们例子要实现什么功能(其实是烂大街又最形象的例子):<br>有两个视图控制器 A 和 B,现在点击 A 上的按钮跳转到视图 B ,并在 B 中的textfield 输入字符串,点击 B 中的跳转按钮跳转回 A ,并将之前输入的字符串<br>显示在 A 中的 label 上。也就是说 A 视图中需要回调 B 视图中的数据。</p>
<p>想不明白的同学可以看一看最终实现的效果图:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/928928-80838951ad6524a9?imageMogr2/auto-orient/strip" alt="block example"></p>
<p>这里不再对 <a href="http://blog.csdn.net/totogo2010/article/details/7839061" target="_blank" rel="external">Block 的语法</a>做说明了,不了解的同学可以点<a href="http://blog.csdn.net/totogo2010/article/details/7839061" target="_blank" rel="external">传送门</a>。</p>
<p>首先,我们需要定义两个试图控制器 AViewController 和 BViewController,现在我们需要思考一下,Block 应该在哪里定义呢?</p>
<p>我们可以简单地这样思考,需要回调数据的是 A 视图,那么 Block 就应该在 B 中定义,用于获取传入回调数据。</p>
<p>因此我们在 BViewController.h 中定义如下:</p>
<figure class="highlight objectivec"><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="comment">//BViewController.h</span></div><div class="line"><span class="meta">#import <span class="meta-string"><UIKit/UIKit.h></span></span></div><div class="line"></div><div class="line"><span class="keyword">typedef</span> <span class="keyword">void</span>(^CallBackBlcok) (<span class="built_in">NSString</span> *text);<span class="comment">//1</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">BViewController</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">copy</span>)CallBackBlcok callBackBlock;<span class="comment">//2</span></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>在这里,代码 1 用 typedef 定义了 <code>void(^) (NSString *text)</code>的别名为 <code>CallBackBlcok</code> 。这样我们就可以在代码 2 中,使用这个别名定义一个 Block 类型的变量 <code>callBackBlock</code>。</p>
<p>在定义了 <code>callBackBlock</code> 之后,我们可以在 B 中的点击事件中添加 <code>callBackBlock</code> 的传参操作:</p>
<figure class="highlight objectivec"><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">//BViewController.m</span></div><div class="line"></div><div class="line">- (<span class="keyword">IBAction</span>)click:(<span class="keyword">id</span>)sender {</div><div class="line"> <span class="keyword">self</span>.callBackBlock(_textField.text); <span class="comment">//1</span></div><div class="line"> [<span class="keyword">self</span>.navigationController popToRootViewControllerAnimated:<span class="literal">YES</span>];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这样我们就可以在想要获取数据回调的地方,也就 A 的视图中调用 block:</p>
<figure class="highlight objectivec"><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="comment">// AViewController.m</span></div><div class="line">- (<span class="keyword">IBAction</span>)push:(<span class="keyword">id</span>)sender {</div><div class="line"> BViewController *bVC = [<span class="keyword">self</span>.storyboard instantiateViewControllerWithIdentifier:<span class="string">@"BViewController"</span>];</div><div class="line"> </div><div class="line"> bVC.callBackBlock = ^(<span class="built_in">NSString</span> *text){ <span class="comment">// 1</span></div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"text is %@"</span>,text);</div><div class="line"> </div><div class="line"> <span class="keyword">self</span>.label.text = text;</div><div class="line"> </div><div class="line"> };</div><div class="line"> [<span class="keyword">self</span>.navigationController pushViewController:bVC animated:<span class="literal">YES</span>];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>代码 1 中,通过对回调将 B 中的数据传递到代码块中,并赋值给 A<br>中的 label,实现了整个回调过程。</p>
<p>上例是通过将 block 直接赋值给 block 属性,也可以通过方法参数的方式传递 block 块。</p>
<p>由于考虑有的小伙伴翻墙比较困难,完整的示例代码放在 git.oschina.net 上,代码地址:<a href="http://git.oschina.net/xiaodaizi/BlockMagic" target="_blank" rel="external">BlockMagic</a> 。</p>
<h2 id="关于-Block-的疑惑"><a href="#关于-Block-的疑惑" class="headerlink" title="关于 Block 的疑惑"></a>关于 Block 的疑惑</h2><p>到目前为止,一切看起来都很美好(如果你照着上面的例子做的话),功能正常, A 视图中也获取到数据了。但是某些人可能就要说了,你的代码有问题,你的思路有问题,你这是误人子弟。</p>
<p>是的,代码的确还有问题,第一个问题就是循环引用的问题,在 A 视图的block 代码块中:</p>
<figure class="highlight mipsasm"><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"><span class="keyword">bVC.callBackBlock </span>= ^(NSString *text){</div><div class="line"> NSLog(@<span class="string">"text is %@"</span>,text)<span class="comment">; </span></div><div class="line"> self.label<span class="meta">.text</span> = text<span class="comment">; </span></div><div class="line"> }<span class="comment">;</span></div></pre></td></tr></table></figure>
<p>代码 <code>self.label.text = text;</code> ,在 Block 中引用 self ,也就是 A ,而 A 创建并引用了 B ,而 B 引用 <code>callBackBlock</code>,此时就形成了一个循环引用,而编译器也不会报任何错误,我们需要非常小心这个问题(面试百分百问到我会乱说?)。此时我们通常的解决方法是使用弱引用来解除这个循环:</p>
<figure class="highlight objectivec"><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">weak</span> <span class="built_in">AViewController</span> *weakSelf = <span class="keyword">self</span>;</div><div class="line"> bVC.callBackBlock = ^(<span class="built_in">NSString</span> *text){ </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"text is %@"</span>,text); </div><div class="line"><span class="comment">// self.label.text = text; </span></div><div class="line"> weakSelf.label.text = text;</div><div class="line"> };</div></pre></td></tr></table></figure>
<p>第二个问题是我自己对 Block 的理解不到位,我们都知道 Block 能截取自动变量,并且是不能在 Block 块中进行修改的(除非用<code>__block</code>修饰符),但是很明显 <code>weakSelf.label.text</code>的值被修改了,并且没有用<code>__block</code>修饰符, 这是为什么呢?因为 <code>label</code> 是个全局变量,而如果像如下的局部变量 <code>a</code> 是不能修改的,编译器也会报错:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/928928-13f64f2fd46f30fa?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="局部变量"></p>
<p>通过这个小例子发现的两个问题,也算是值得了。</p>
<h2 id="Block-为什么能实现神奇的回调"><a href="#Block-为什么能实现神奇的回调" class="headerlink" title="Block 为什么能实现神奇的回调"></a>Block 为什么能实现神奇的回调</h2><p>在这里我不会说什么实现原理,仅仅是个人对 Block 能实现神奇回调的理解,有错误的地方请大家指出。</p>
<p>在先前使用 Block 的过程中,虽然会使用,但是总是有一个疑惑,简单说来就是:</p>
<p>为什么在 A 中的 block 块能调用到 B 中的数据?</p>
<p>回顾一下我们在 B 中所实现的代码,不外乎定义了一个 Block 变量,并在适当的时候传入参数,那么为什么在调用了 <code>self.callBackBlock(_textField.text)</code> 之后,值就神奇传到了 A 中的 Block 块了呢?</p>
<p>通过整理使用的过程,我发现是我们的思维陷入了误区(可能是我个人),我们认为在 B 中传入 <code>_textField.text</code> 参数之后, A 中的 Block 块就可以获取到值。虽然思路是对的,但其实是不完整,导致我们形成了回调的数据是通过某种底层实现传递过去的错觉,这就使得我们认为这不需要深究。</p>
<p>事实是,通过简单的整理我们可以发现完整的回调流程应该是这样的:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/928928-e4de1ec1692eb173?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="回调流程"></p>
<ol>
<li>block 代码块赋值给 <code>bVC.callBackBlock</code>,此时 <code>callBackBlock</code> 的指针就指向这个代码块。</li>
<li>调用 <code>callBackBlock(NSString *text)</code></li>
<li><p>由于 <code>callBackBlock</code> 的指针是指向 A 中的 block 代码块,因此执行代码块的代码,实现回调。</p>
<p>很显然之前我忽略了代码块赋值给 <code>callBackBlock</code> 的这个操作(羞愧)。</p>
<p>现在再通过一段代码可以更清晰地理解这个原理:</p>
</li>
</ol>
<figure class="highlight arduino"><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"> bVC.callBackBlock = ^(NSString *<span class="built_in">text</span>){ <span class="comment">//1</span></div><div class="line"> NSLog(@<span class="string">"text is %@"</span>,<span class="built_in">text</span>);</div><div class="line"> };</div><div class="line"> bVC.callBackBlock = ^(NSString *<span class="built_in">text</span>){ <span class="comment">//2</span></div><div class="line"> NSLog(@<span class="string">"text b is %@"</span>,<span class="built_in">text</span>);</div><div class="line"> };</div></pre></td></tr></table></figure>
<p>上述代码中,我们对 <code>callBackBlock</code>进行了两次赋值,结果会怎么样呢?</p>
<p><img src="http://upload-images.jianshu.io/upload_images/928928-2788415bb549621d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="two block"></p>
<p>可以看出来,Block 的回调只对代码 2 生效,因为<code>callBackBlock</code>的指针最后指向了代码 2 的代码块。所以并没有什么神奇的魔法,也没什么隐藏的底层机制(这里指的是方便理解的底层)让你可以带着疑惑去使用它。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我这个人学习方法,总结起来就是看到新技术,先在自己的代码里跑一遍,能跑通,并且使用起来没有什么难度,就基本不会深究了(如果遇到某个熊孩子就坑了)。但是自我反思过,这样的学习方法是很不对的,写代码不能不求甚解,如果想要有所突破,不想局限于码农,一定要深入探究一下实现的机制,最起码要保证不带着疑惑去使用。</p>
]]></content>
<summary type="html">
本文主要讲的是 Block 回调的使用,以及 Block 是如何实现这种神奇的回调两部分来讲的。
</summary>
<category term="Objective-C" scheme="http://lin493369.github.io/categories/Objective-C/"/>
<category term="Objective-C" scheme="http://lin493369.github.io/tags/Objective-C/"/>
<category term="block" scheme="http://lin493369.github.io/tags/block/"/>
</entry>
<entry>
<title>在 WordPress 中使用 Github README 标签</title>
<link href="http://lin493369.github.io/2016/02/20/%E5%9C%A8WordPress%E4%B8%AD%E4%BD%BF%E7%94%A8GithubREADME%E6%A0%87%E7%AD%BE/"/>
<id>http://lin493369.github.io/2016/02/20/在WordPress中使用GithubREADME标签/</id>
<published>2016-02-19T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="http://dev.iachieved.it/iachievedit/github-readme-badges-in-wordpress/" target="_blank" rel="external">http://dev.iachieved.it/iachievedit/github-readme-badges-in-wordpress/</a><br>作者=Joe<br>原文日期=2016/01/24</p>
<hr>
<p>Github 上的很多框架和包都在他们的 README 文件中使用 “badges”(标签)记录 repository 的不同属性。</p>
<ul>
<li>一个 repository 的 Travis 构建(译者注:Travis CI 是开源持续集成构建项目)是否通过</li>
<li>一个 release 版本代码的下载次数 </li>
<li>代码支持的平台(为苹果设备开发时尤其有用)</li>
</ul>
<p><img src="http://dev.iachieved.it/iachievedit/wp-content/uploads/2016/01/githubbadges.png" alt="这里写图片描述"></p>
<p>自2014年6月初次发布以来,<a href="https://en.wikipedia.org/wiki/Swift_%28programming_language%29" target="_blank" rel="external">Swift 编程语言</a>已经经历过了一系列的改变和版本。每一个发行版本都包含了破坏性的改变。从这篇文章开始,我已经开始使用标签去指明文章所兼容的 Swift 版本。</p>
<h2 id="添加标签"><a href="#添加标签" class="headerlink" title="添加标签"></a>添加标签</h2><p>你可以在你的 WordPress 文章中通过两种技术使用标签。严格地获取标签的最好方式是使用内联图片设计使之看起来像,好吧,就是像标签。你也可以自己创建图片或者使用类似 <a href="http://shields.io/" target="_blank" rel="external">Shields.io</a> 的服务去链接标签。不管什么方式,为了在你的页面展示标签你都应该使用<code><img/></code>。一个通过 Shields.io 链接标签的描述例子如下:</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><<span class="selector-tag">img</span> src=<span class="string">"https://img.shields.io/badge/Swift-2.2-orange.svg?style=flat"</span> alt=<span class="string">"Swift 2.2"</span> /></div></pre></td></tr></table></figure>
<p>这会出现这样的标签:Swift 2.2</p>
<p>此外你还可以使用 Markdown 语法(如你在 Github 的 README.md 文件所见的那样)。为了在 WordPress 中使用 Markdown,你可以加载 <a href="https://wordpress.org/plugins/jetpack/" target="_blank" rel="external">jetpack</a> 组件,然后激活它的 Markdown 组件。通过 Markdown 的支持激活创建一篇新的文章,并且可以在文章编辑这样的类型:</p>
<p><img src="https://img.shields.io/badge/Swift-2.2-orange.svg?style=flat" alt="Swift 2.2"></p>
<p>这使用了 <a href="https://daringfireball.net/projects/markdown/syntax#img" target="_blank" rel="external">Markdown 的图片语法</a>,并且可以出现这样的标签:Swift 2.2</p>
<h2 id="Shields-io"><a href="#Shields-io" class="headerlink" title="Shields.io"></a>Shields.io</h2><p>Shields.io 的设计理念是: “标签是一个服务”。换句话说,无需担心创建你自己的标签,Shields.io 会为你创建标签。大多数的 Shields.io 标签在语义上和“一些东西”的状态捆绑在一起。例如,URL:<a href="https://img.shields.io/github/downloads/atom/atom/total.svg" target="_blank" rel="external">https://img.shields.io/github/downloads/atom/atom/total.svg</a> 提供了一个标签指明 <a href="https://github.com/atom/atom" target="_blank" rel="external">Atom</a> 程序被下载的所有次数。Shields.io 首先通过联系 Github 的接口获取到真实的下载数量,然后返回生成的图片。</p>
<p>上述 Swift 的例子使用了这个 Shields.io URL:<br><code>https://img.shields.io/badge/<SUBJECT>-<STATUS>-<COLOR>.svg</code>。我们通过提供如下几个选项使用他:</p>
<ul>
<li>SUBJECT 为 Swift </li>
<li>STATUS 为 2.2 </li>
<li>COLOR 为 orange</li>
</ul>
<p>当然,orange 是由于它是Swift的代表色。</p>
<h2 id="准确的评价"><a href="#准确的评价" class="headerlink" title="准确的评价"></a>准确的评价</h2><p>我非常推荐每一个Swift 博主开始使用标签 (或者一些等同的形式)去指明 Swift 语言的版本,如例子所示的那样。例如,尽管 C 风格的循环已经在 Swift 2.2 中废弃了,但是会在 3.0 中产生错误。当某个人看到你的 2.2 版本的文章,但是试图使用 3.0 的编译器运行代码,他们至少应该知道有些代码可能不兼容。</p>
]]></content>
<summary type="html">
Github 上的很多框架和包都在他们的 README 文件中使用 “badges”(标签)记录 repository 的不同属性。
</summary>
<category term="博客技巧" scheme="http://lin493369.github.io/categories/%E5%8D%9A%E5%AE%A2%E6%8A%80%E5%B7%A7/"/>
<category term="博客" scheme="http://lin493369.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="WordPress" scheme="http://lin493369.github.io/tags/WordPress/"/>
<category term="Github" scheme="http://lin493369.github.io/tags/Github/"/>
</entry>
<entry>
<title>如何简单地模拟 NSURLSeesion 的返回数据</title>
<link href="http://lin493369.github.io/2016/01/18/AnEasyWayToStubNSURLSession/"/>
<id>http://lin493369.github.io/2016/01/18/AnEasyWayToStubNSURLSession/</id>
<published>2016-01-17T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="http://swiftandpainless.com/an-easy-way-to-stub-nsurlsession/" target="_blank" rel="external">http://swiftandpainless.com/an-easy-way-to-stub-nsurlsession/</a><br>作者=dom<br>原文日期=2016/01/09</p>
<hr>
<p>如果你熟悉我这个博客的话,你可能知道我检查问题时,最喜欢的方法是模拟 <code>NSURLSeesion</code> 返回的数据。</p>
<p>那么我们到底要做什么呢,其实是模拟方法的回调数据。而这里的 <code>NSURLSession</code>指的是伪造 web API 的响应。这样做有一些好处,例如:</p>
<ol>
<li>我们不需要一个可用的 web API 来开发我们应用程序的网络请求。</li>
<li>能够立马响应,反馈周期更短。</li>
<li>测试程序能在没有网络连接的电脑上运行。</li>
</ol>
<p>一般来说,模拟 <code>NSURLSession</code> 的请求返回数据是通过 <code>NSURLProtocol</code> 来完成的。具体的用例请查看 <a href="https://github.com/AliSoftware/OHHTTPStubs" target="_blank" rel="external">OHHTTPStubs</a> 和 <a href="https://github.com/kylef/Mockingjay" target="_blank" rel="external">Mockingjay</a>。使用 <code>NSURLProtocol</code> 的优势在于,当你使用诸如 <a href="https://github.com/Alamofire/Alamofire" target="_blank" rel="external">Alamofire</a> 这样的网络请求库时,也能正常模拟数据回调。这种方式很棒,但是对我来说代码太多了。我必须去学习和理解这些代码,以在我的测试中获得预期的效果。</p>
<h2 id="一个简单的解决方案"><a href="#一个简单的解决方案" class="headerlink" title="一个简单的解决方案"></a>一个简单的解决方案</h2><p>我将使用 <code>NSURLSession</code> 来做网络请求。下面是如何伪造我的请求返回数据。</p>
<p>为了让它看起来更简单,我已经写了一个 <code>NSURLSession</code> 的替换类和一个协议。整合起来如下所示:<br><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></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> Foundation</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">protocol</span> <span class="title">DHURLSession</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">dataTaskWithURL</span><span class="params">(url: NSURL,</span></span></div><div class="line"> completionHandler: <span class="params">(NSData?, NSURLResponse?, NSError?)</span> -> <span class="type">Void</span>) -> <span class="type">NSURLSessionDataTask</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">dataTaskWithRequest</span><span class="params">(request: NSURLRequest,</span></span></div><div class="line"> completionHandler: <span class="params">(NSData?, NSURLResponse?, NSError?)</span> -> <span class="type">Void</span>) -> <span class="type">NSURLSessionDataTask</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">NSURLSession</span>: <span class="title">DHURLSession</span> </span>{ }</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">URLSessionMock</span> : <span class="title">DHURLSession</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> url: <span class="type">NSURL</span>?</div><div class="line"> <span class="keyword">var</span> request: <span class="type">NSURLRequest</span>?</div><div class="line"> <span class="keyword">private</span> <span class="keyword">let</span> dataTaskMock: <span class="type">URLSessionDataTaskMock</span></div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">init</span>(data: <span class="type">NSData</span>?, response: <span class="type">NSURLResponse</span>?, error: <span class="type">NSError</span>?) {</div><div class="line"> dataTaskMock = <span class="type">URLSessionDataTaskMock</span>()</div><div class="line"> dataTaskMock.taskResponse = (data, response, error)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">func</span> <span class="title">dataTaskWithURL</span><span class="params">(url: NSURL,</span></span></div><div class="line"> completionHandler: <span class="params">(NSData?, NSURLResponse?, NSError?)</span> -> <span class="type">Void</span>) -> <span class="type">NSURLSessionDataTask</span> {</div><div class="line"> <span class="keyword">self</span>.url = url</div><div class="line"> <span class="keyword">self</span>.dataTaskMock.completionHandler = completionHandler</div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span>.dataTaskMock</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">func</span> <span class="title">dataTaskWithRequest</span><span class="params">(request: NSURLRequest,</span></span></div><div class="line"> completionHandler: <span class="params">(NSData?, NSURLResponse?, NSError?)</span> -> <span class="type">Void</span>) -> <span class="type">NSURLSessionDataTask</span> {</div><div class="line"> <span class="keyword">self</span>.request = request</div><div class="line"> <span class="keyword">self</span>.dataTaskMock.completionHandler = completionHandler</div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span>.dataTaskMock</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">final</span> <span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">URLSessionDataTaskMock</span> : <span class="title">NSURLSessionDataTask</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">typealias</span> <span class="type">CompletionHandler</span> = (<span class="type">NSData</span>!, <span class="type">NSURLResponse</span>!, <span class="type">NSError</span>!) -> <span class="type">Void</span></div><div class="line"> <span class="keyword">var</span> completionHandler: <span class="type">CompletionHandler</span>?</div><div class="line"> <span class="keyword">var</span> taskResponse: (<span class="type">NSData</span>?, <span class="type">NSURLResponse</span>?, <span class="type">NSError</span>?)?</div><div class="line"> </div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">resume</span><span class="params">()</span></span> {</div><div class="line"> completionHandler?(taskResponse?.<span class="number">0</span>, taskResponse?.<span class="number">1</span>, taskResponse?.<span class="number">2</span>)</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>如上,用来伪造数据的完整帮助代码是 47 行。并且所有代码清晰易懂,既没有 swizzling,也没有复杂的方法。是不是很棒!</p>
<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>为了能够在测试中使用 <code>NSURLSession</code> 替换类,我们需要在代码中注入依赖。一种可能的方式是使用一个懒属性:<br><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="built_in">lazy</span> <span class="keyword">var</span> session: <span class="type">DHURLSession</span> = <span class="type">NSURLSession</span>.sharedSession()</div></pre></td></tr></table></figure></p>
<p>然后一个示例测试可能会是这样的:<br><figure class="highlight nix"><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">func testFetchingProfile_ReturnsPopulatedUser() {</div><div class="line"> // Arrage</div><div class="line"> <span class="keyword">let</span> <span class="attr">responseString</span> = <span class="string">"{\"</span>login\<span class="string">": \"</span>dasdom\<span class="string">", \"</span>id\<span class="string">": 1234567}"</span></div><div class="line"> <span class="keyword">let</span> <span class="attr">responseData</span> = responseString.dataUsingEncoding(NSUTF8StringEncoding)!</div><div class="line"> <span class="keyword">let</span> <span class="attr">sessionMock</span> = URLSessionMock(data: responseData, response: nil, error: nil)</div><div class="line"> <span class="keyword">let</span> <span class="attr">apiClient</span> = APIClient()</div><div class="line"> apiClient.<span class="attr">session</span> = sessionMock</div><div class="line"> </div><div class="line"> // Act</div><div class="line"> apiClient.fetchProfileWithName(<span class="string">"dasdom"</span>)</div><div class="line"> </div><div class="line"> // Assert</div><div class="line"> <span class="keyword">let</span> <span class="attr">user</span> = apiClient.user</div><div class="line"> <span class="keyword">let</span> <span class="attr">expectedUser</span> = User(name: <span class="string">"dasdom"</span>, id: <span class="number">1234567</span>)</div><div class="line"> XCTAssertEqual(user, expectedUser)</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>我很喜欢这样的解决方案,因为我只要花几分钟时间,通过阅读五十多行代码就能理解替换类。并且没有涉及到 <code>NSURLProtocol</code> 和 <code>swizzling</code>。</p>
<p>这个 <code>NSURLSession</code> 的替换类在 <a href="https://github.com/dasdom/DHURLSessionStub" target="_blank" rel="external">github</a> 上,并且也可以通过CocoaPods 下载。</p>
<p>让我知道你的想法。</p>
]]></content>
<summary type="html">
如果你熟悉我这个博客的话,你可能知道我检查问题时,最喜欢的方法是模拟 `NSURLSeesion` 返回的数据。那么我们到底要做什么呢,其实是模拟方法的回调数据
</summary>
<category term="Test" scheme="http://lin493369.github.io/categories/Test/"/>
<category term="test" scheme="http://lin493369.github.io/tags/test/"/>
<category term="效率" scheme="http://lin493369.github.io/tags/%E6%95%88%E7%8E%87/"/>
<category term="NSURLSeesion" scheme="http://lin493369.github.io/tags/NSURLSeesion/"/>
</entry>
<entry>
<title>Swift:带有私有设置方法的公有属性</title>
<link href="http://lin493369.github.io/2016/01/08/Swift-PublicPropertiesWithPrivateSetters/"/>
<id>http://lin493369.github.io/2016/01/08/Swift-PublicPropertiesWithPrivateSetters/</id>
<published>2016-01-07T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="http://www.thomashanning.com/public-properties-with-private-setters/" target="_blank" rel="external">http://www.thomashanning.com/public-properties-with-private-setters/</a><br>作者=Thomas<br>原文日期=2015/12/24</p>
<hr>
<p>Swift可以很方便地创建带有私有设置方法的公有属性。这可以让你的代码更加安全和简洁。</p>
<h2 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h2><p>封装从根本上意味着类的信息和状态应该对外部类隐藏,只有类自身可以操作。因此,所有的 bug 和 逻辑错误更加不可能发生了。</p>
<p>通常你会使用 setter 以及 getter 来达到封装的目的。然而,有时候你可能不想对外提供类中的设置方法。对于这样的情况,你可以使用带有私有设置方法的属性。</p>
<h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><p>假设我们想要创建一个代表圆的类,那么圆的半径应该是可以改变的。而且,该圆的面积和直径应该可以从圆的实例中获取,而这两个属性不允许被外部类更改。出于性能考虑,面积和直径只能计算一次。</p>
<p>所以这个圆类应该是这样的:<br><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></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Circle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">var</span> area: <span class="type">Double</span> = <span class="number">0</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">var</span> diameter: <span class="type">Double</span> = <span class="number">0</span></div><div class="line"> </div><div class="line"> <span class="keyword">var</span> radius: <span class="type">Double</span> {</div><div class="line"> <span class="keyword">didSet</span> {</div><div class="line"> calculateFigures()</div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">init</span>(radius:<span class="type">Double</span>) {</div><div class="line"> <span class="keyword">self</span>.radius = radius</div><div class="line"> calculateFigures()</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">calculateFigures</span><span class="params">()</span></span> {</div><div class="line"> area = <span class="type">M_PI</span> * radius * radius</div><div class="line"> diameter = <span class="number">2</span> * <span class="type">M_PI</span> * radius</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">getArea</span><span class="params">()</span></span> -> <span class="type">Double</span> {</div><div class="line"> <span class="keyword">return</span> area</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">getDiameter</span><span class="params">()</span></span> -> <span class="type">Double</span> {</div><div class="line"> <span class="keyword">return</span> diameter</div><div class="line"> } </div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>现在所有的需求都满足啦。然而,Swift 提供了一种更好的方式,可以使得这段代码更加简洁:</p>
<h2 id="带有私有设置方法的属性"><a href="#带有私有设置方法的属性" class="headerlink" title="带有私有设置方法的属性"></a>带有私有设置方法的属性</h2><p>通过在属性前面使用 <code>private(set)</code> ,属性就被设置为默认访问等级的 getter 方法,但是 setter 方法是私有的。所以我们可以去掉两个 getter 方法:</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">Circle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span>(<span class="keyword">set</span>) <span class="keyword">var</span> area: <span class="type">Double</span> = <span class="number">0</span></div><div class="line"> <span class="keyword">private</span>(<span class="keyword">set</span>) <span class="keyword">var</span> diameter: <span class="type">Double</span> = <span class="number">0</span></div><div class="line"> </div><div class="line"> <span class="keyword">var</span> radius: <span class="type">Double</span> {</div><div class="line"> <span class="keyword">didSet</span> {</div><div class="line"> calculateFigures()</div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">init</span>(radius:<span class="type">Double</span>) {</div><div class="line"> <span class="keyword">self</span>.radius = radius</div><div class="line"> calculateFigures()</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">calculateFigures</span><span class="params">()</span></span> {</div><div class="line"> area = <span class="type">M_PI</span> * radius * radius</div><div class="line"> diameter = <span class="number">2</span> * <span class="type">M_PI</span> * radius</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>当然也可以为属性设置公有的 getter 方法:</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="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Circle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">private</span>(<span class="keyword">set</span>) <span class="keyword">var</span> area: <span class="type">Double</span> = <span class="number">0</span></div><div class="line"> <span class="keyword">public</span> <span class="keyword">private</span>(<span class="keyword">set</span>) <span class="keyword">var</span> diameter: <span class="type">Double</span> = <span class="number">0</span></div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">var</span> radius: <span class="type">Double</span> {</div><div class="line"> <span class="keyword">didSet</span> {</div><div class="line"> calculateFigures()</div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">init</span>(radius:<span class="type">Double</span>) {</div><div class="line"> <span class="keyword">self</span>.radius = radius</div><div class="line"> calculateFigures()</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">calculateFigures</span><span class="params">()</span></span> {</div><div class="line"> area = <span class="type">M_PI</span> * radius * radius</div><div class="line"> diameter = <span class="number">2</span> * <span class="type">M_PI</span> * radius</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h2><p>在这个例子中,属性只是 <code>Double</code> 值。然而,如果是一个对象,可以通过使用对象的方法来操作!使用私有设置方法只允许设置一个全新的对象,在使用过程中应铭记这一点。</p>
]]></content>
<summary type="html">
Swift可以很方便地创建带有私有设置方法的公有属性。这可以让你的代码更加安全和简洁。
</summary>
<category term="Swift 入门" scheme="http://lin493369.github.io/categories/Swift-%E5%85%A5%E9%97%A8/"/>
<category term="Swift" scheme="http://lin493369.github.io/tags/Swift/"/>
<category term="封装" scheme="http://lin493369.github.io/tags/%E5%B0%81%E8%A3%85/"/>
</entry>
<entry>
<title>iOS 启动时优化</title>
<link href="http://lin493369.github.io/2016/01/05/iOS%E5%90%AF%E5%8A%A8%E6%97%B6%E4%BC%98%E5%8C%96(1)/"/>
<id>http://lin493369.github.io/2016/01/05/iOS启动时优化(1)/</id>
<published>2016-01-04T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<hr>
<p>译者:本文虽是针对 Facebook 应用的启动时优化,文中所说的大部分优化策略对于小型应用来说意义可能并不是很大,但是重要的是,我们应学习Facebook 工程师通过一系列系统的考量寻求优化解决方案的方式。首先通过建立优化的度量指标,明确优化方向,分解优化目标,分步达到优化目的,最后统一测试优化效果。相较于杂乱无章,碰运气式的优化经验,这种清晰有条理的解决方式,着实令人敬佩。</p>
<hr>
<p>提高 Facebook 应用的性能已经成为 Facebook 持续关注的领域。因为我们相信一个高性能的应用能够传递一种吸引人且令人愉悦的体验。每个 Facebook 应用的用户都必须做的一件事是启动应用(我们特指这个动作为 ”应用启动“)。因此,这是一个很好的优化目标。</p>
<h2 id="稳定的度量"><a href="#稳定的度量" class="headerlink" title="稳定的度量"></a>稳定的度量</h2><p>实现最好的性能度量标准和相应的目标,鼓舞我们专注于提升应用的品质,并且我们相信这将会产生很大的影响。度量必须易懂、经得起推敲,并且需要精确地捕捉到将要被优化的体验。对基于性能的度量,我们已经发现在使用应用过程中,最好是使用那些被捕捉到感知的交互。理想情况下,这些度量应该和一个通过基础设施的单一执行通道有一对一的联系。对于应用程序的启动,确定用于衡量的关键位置是一个挑战。这需要采取几次迭代去简化我们的测量和移除边界问题。</p>
<p>应用启动是一个特别不固定的概念,因为现在存在很多种应用启动的方式。应用可以在后台或者前台启动,甚至可以在后台启动,但是在完成初始化之前转换为前台。你可以通过点击一条通知或者通过一个 URL 打开应用。Facebook 应用甚至可以通过其他应用来打开,因为他们需要通过 Facebook 来实现第三方登录。在现实场景中,主要的交互还是最直接的方式:你点击桌面的应用图标,然后跳转启动。因而,我们选择这个作为应用启动的入口。</p>
<p>当启动入口明确之后,我们必须去计算出何时应用启动是完成的。同样地,我们观察用户的使用模式,发现用户喜欢打开应用(首先跳转到新闻摘要),然后等待摘要的加载。我们断定“摘要完成加载”是应用启动一个很好的终点。我们采取了一些微调使得这个终点契合用户的使用情况。我们可以通过重复地观察应用的启动,围绕度量标准来提高应用的性能。</p>
<p>一旦确定了我们认为有代表性的启动入口和终点,我们把启动问题分解成两种类型:</p>
<ol>
<li>冷启动。指的是当应用还没准备好运行时,我们必须加载和构建整个应用。这包括设置屏幕底部的分栏菜单,确保用户是否被合适地登录,以及处理其他更多的事情。“引导”程序是在<code>applicationDidFinishLaunching:withOptions:</code>方法中开始的。</li>
<li>热启动。指的是应用已经运行但是在后台被挂起(比如用户点击了 home 健),我们只需要知道在何时应用进入后台。在这种情况下,我们的应用通过 <code>applicationWillEnterForeground:</code> 接收到前台的事件,紧接着应用恢复。</li>
</ol>
<p>我们决定主要优化冷启动,主要有两个原因。首先,冷启动其实是包括热启动的(冷启动初始化应用并获得摘要;热启动只获得摘要),所以有更多的地方需要优化和微调。第二,冷启动需要做额外的初始化工作,所以相较而言更慢,导致需要更长的启动等待时间。</p>
<h2 id="优化冷启动体验"><a href="#优化冷启动体验" class="headerlink" title="优化冷启动体验"></a>优化冷启动体验</h2><p>我们把冷启动问题分解成三个阶段,进而我们可以有针对性地解决。每个阶段都有一些列变数和挑战。</p>
<ol>
<li>请求时间:从应用启动到摘要请求离开设备(译者:应该是向服务器发送URL请求算结束时间)的时间。</li>
<li>网络时间:从摘要请求离开设备到服务器响应返回的时间。</li>
<li>响应处理时间:从响应返回到新数据展示在屏幕的时间。</li>
</ol>
<p>我们直观上认为冷启动性能主要被网络请求和响应处理影响了。这个结论是由于我们假定我们在客户端花的时间比较少,并且我们设法让请求的获取更加快速。然而,当我们用 instrument 去检测时,我们发现数据非常出人意料。它展现出了完全不同的结果,我们发现摘要请求花了大部分时间。另外,响应的处理时间也非常短。因此,我们重新把优化的焦点放在初始化阶段。</p>
<h2 id="摘要请求发送的初始化"><a href="#摘要请求发送的初始化" class="headerlink" title="摘要请求发送的初始化"></a>摘要请求发送的初始化</h2><p>所以为什么这个阶段花费了那么多时间呢?很多 iOS 应用并没有这样一个问题——他们在那个阶段并没有很多工作需要做,除了初始化视图控制器和发送网络请求。然而,对于 Facebook 来说,大部分时间被用来开始的时候去设置不同功能块。下面是我们应用中的主要功能块的流程概览。<br><img src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfp1/t39.2365-6/12057214_1016971454990542_827610883_n.png" alt="这里写图片描述"></p>
<p>这看起来好像是很复杂的应用启动设置。但需要重视的是,这些功能块对于 Facebook 应用来说是非常重要的提升,可以提高应用体验,并且使得工程师能够在不同的应用规模下更快地开发。</p>
<p>正如我们所关注的这个流程,我们通过优化独立的部分获得了一些主要的成果。然而,由于未来支持新特性的初始化以及额外提供支持的基础设施,这些成果会慢慢地抵消掉。这使得我们重新考虑如何去解决问题。但我们重新开始,我们认为这个阶段的目标是简单地发送摘要的网络请求。但是为什么摘要请求发出去得这么慢?这是由于很多依赖被添加到摘要的初始化中了。然而,他们并不都是必要的 — 对于摘要请求来说,最少的需要一个有效的验证 token 以及摘要光标(新闻摘要的位置)。因此,我们减少了摘要请求的依赖,让它逐渐地更加接近应用的启动。这允许应用的剩余部分在摘要响应的同时进行初始化。由于这些重构,我们获得了显著的收益。</p>
<h2 id="网络和服务器时间"><a href="#网络和服务器时间" class="headerlink" title="网络和服务器时间"></a>网络和服务器时间</h2><p>根据我们在第一阶段的经验,我们继续把这个阶段分解成更小的部分。网络请求/响应看起来像这样:<br><img src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xtp1/t39.2365-6/12056998_991399770918380_262846919_n.png" alt="这里写图片描述"></p>
<p>我们注意到,一旦请求正在排队,发送请求出去之后就有一个时间间隔。这很好解释 — 在冷启动中,网络连接并不是一个开放的、安全的 TCP 连接。一个连接的建立需要三次握手,平均为几百毫秒。当摘要请求第一次发送时,无法避免要花掉这些时间。长远来看,这可以通过缓存 SSL 证书来解决。但是再次强调,我们退回来的目的并不是为了发送 TCP 请求,而是为了从服务器通过任何可能的方式获得请求信息。</p>
<p>我们提出了一个创造性的解决方案 — UDP 启动。本质上,我们在通过 TCP 发送摘要请求时,先发送一个编码过的包含摘要请求的 UDP 包到服务器。这样做的目的是唤醒服务器更早地去获取和缓存数据。当真正的摘要请求通过 TCP 到达时,服务器只需见到地从缓存内容中构造出响应,并发回客户端。这个技术使得我们可以减少几百毫秒的耗时。</p>
<p>当我们持续深入研究服务器端时,我们开始尝试使用 层-取(story-fetching)策略。过去我们已经做了一批摘要请求的 3+7 层。原因很简单:下载次数和被下载的层成正比。因此,把请求分割成两块,允许开始的三层先进来,其余的七个随后进来。通过提升我们的基础设施,我们已经能够升级为 1+1+X 策略,这已经接近于流了。这样就减少了服务器必须处理第一层的时间,并且能够减少下载的时间,使得可以在最快的时间内与用户交互。通过这样的努力,这样我们又减少了几百毫秒的耗时。</p>
<h2 id="摘要响应处理"><a href="#摘要响应处理" class="headerlink" title="摘要响应处理"></a>摘要响应处理</h2><p>正如在前面提到的那样,我们以为在启动时会在这里花费大量的时间。但是这个想法被证明是错误的。更加使人好奇的是,我们注意到时间并没有花在处理和加工层上面。时间被花在运行应用服务和竞争资源上面。我们注意到这是我们优化网络和服务器时间的副作用,因为摘要请求返回得太早了。尽管大多数的服务是不重要的。因此,我们开发出一个简单的机制去序列化这些工作直到应用完成启动,并且使用先进先出的方式去执行。这样可以用更少的连接去处理所有层,大大地减少了获得响应和展示在屏幕之间的时间。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>很难理解我们在过去几个月走了多远。总之,在一对一的比较中,我们发现我们成功地优化了一秒多的耗时。</p>
<p>优化这个特殊的交互是一个长期的过程,需要建立一个稳定的度量,这个度量必须是易懂的、符合真实世界性能特征,此外要不断地重新思考问题,以提出创新的解决方案。我们希望这可以帮助使用 Facebook 的人有更好的、令人愉悦的用户体验。</p>
<p>你也可以看看 <a href="https://youtu.be/ifozUqqC0TY?t=11m5s" target="_blank" rel="external">Greg Moeck在 2015 年的演讲</a> 。</p>
]]></content>
<summary type="html">
Facebook 工程师通过一系列系统的考量寻求优化解决方案的方式。首先通过建立优化的度量指标,明确优化方向,分解优化目标,分步达到优化目的,最后统一测试优化效果。
</summary>
<category term="性能优化" scheme="http://lin493369.github.io/categories/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Facebook" scheme="http://lin493369.github.io/tags/Facebook/"/>
<category term="优化" scheme="http://lin493369.github.io/tags/%E4%BC%98%E5%8C%96/"/>
<category term="启动时" scheme="http://lin493369.github.io/tags/%E5%90%AF%E5%8A%A8%E6%97%B6/"/>
</entry>
<entry>
<title>关于 Swift 演变的趣味探讨</title>
<link href="http://lin493369.github.io/2015/12/27/interesting-discussions-on-swift-evolution/"/>
<id>http://lin493369.github.io/2015/12/27/interesting-discussions-on-swift-evolution/</id>
<published>2015-12-26T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="http://ericasadun.com/2015/12/15/interesting-discussions-on-swift-evolution/" target="_blank" rel="external">http://ericasadun.com/2015/12/15/interesting-discussions-on-swift-evolution/</a><br>作者=Erica Sadun<br>原文日期=2015/12/15</p>
<hr>
<p>记得我曾分享过一些想法和建议,比如:</p>
<h3 id="newtype"><a href="#newtype" class="headerlink" title="newtype"></a>newtype</h3><p>一个是建议 Swift 推出一个 <code>newtype</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">newtype <span class="type">Currency</span> = <span class="type">NSDecimal</span></div></pre></td></tr></table></figure>
<p>这创建了一个拥有所有 <code>NSDecimal</code> 所有行为的 <code>Currency</code> 类型。然而,你不能让一个 <code>NSDecimal</code> 类型的元素和一个 <code>Currency</code> 类型的元素相加,因为 Swift 中有类型检测。此外,你也可以扩展 <code>Currency</code> 类型。这样看起来就更加有针对性,因为不需要子类化或者添加新的存储属性。</p>
<a id="more"></a>
<p><code>newtype</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">newtype <span class="type">Counter</span><<span class="type">A</span>> = <span class="type">Dictionary</span><<span class="type">A</span>, <span class="type">Int</span>></div></pre></td></tr></table></figure>
<p>类型是部分确定的,具体行为可以在扩展中实现,从而能包含键(key)类型不相同但值类型都是 Int 的字典。</p>
<p>期待看到你们的评论。</p>
<h3 id="self"><a href="#self" class="headerlink" title="self"></a>self</h3><p>另外一个提议是将 <code>self</code> 作为强制前缀,取代上下文语境推断。Greg Parker 在回复中写道:</p>
<blockquote>
<p>在 Objective-C 中 <code>self.property</code> 这种写法很不优雅。</p>
<p>第一种方法是只使用 <code>property</code>。但是同名变量(ivar)会产生歧义,Swift 没有这样的问题。</p>
<p>第二种方法是用 <code>property</code> 访问属性,用 <code>self->ivar</code> 去访问同名变量。这是不可行的,因为会和现有的大量代码冲突。Swift 也没有这样的问题。</p>
</blockquote>
<h3 id="前置条件与断言(Precondition-vs-Assert)"><a href="#前置条件与断言(Precondition-vs-Assert)" class="headerlink" title="前置条件与断言(Precondition vs Assert)"></a>前置条件与断言(Precondition vs Assert)</h3><p>Dave Abrahams 提出了一个有关重命名断言和前置条件的建议,我立刻将其中的一些深刻见解记在笔记本上:</p>
<blockquote>
<p>从语言设计层面来说,这两个函数扮演不同的角色:<br>– assert:检查内部的错误代码。<br>– precondition:检查客户端给你的参数是否有效。</p>
<p>两者的区别很大,第二个要求有公共文档,第一个不需要。</p>
<p>例如:在 Swift 的标准库中,我们保证永远不会出现内存错误,除非你调用 (Obj)C 代码或者使用一个明确地标着「unsafe」的结构。我们需要去检验客户端参数,为了避免给了非法的参数引起内存泄露,我们要在参数中文档化这些需求作为前置条件,并且使用(等价的)precondition() 去检验它。我们还有一系列的内部合理检查,用以确定我们代码假定的正确性,而类型系统还不能保证这个代码的假定。由于这些原因,我们使用(等价的)assert(),因为我们不想降低<em>你的</em>代码性能(使用合理的检查)。</p>
<p>下面是几个具体的例子:</p>
</blockquote>
<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></pre></td><td class="code"><pre><div class="line"><span class="comment">/// 一个集合,其中的元素类型为 Element</span></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">struct</span> <span class="title">Repeat</span><<span class="title">Element</span>> : <span class="title">CollectionType</span> </span>{</div><div class="line"> ...</div><div class="line"> <span class="comment">/// 获取 `position` 位置的元素</span></div><div class="line"> <span class="comment">///</span></div><div class="line"> <span class="comment">/// - 要求: `position` 是 `self` 中的有效位置并且 `position != endIndex`.</span></div><div class="line"> <span class="keyword">public</span> <span class="keyword">subscript</span>(position: <span class="type">Int</span>) -> <span class="type">Element</span> {</div><div class="line"> _precondition(position >= <span class="number">0</span> && position < <span class="built_in">count</span>, <span class="string">"Index out of range"</span>)</div><div class="line"> <span class="keyword">return</span> repeatedValue</div><div class="line"> }</div><div class="line">}</div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">String</span>.<span class="title">UTF8View</span> </span>{</div><div class="line"> ...</div><div class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">_encodeSomeContiguousUTF16AsUTF8</span><span class="params">(i: Int)</span></span> -> (<span class="type">Int</span>, <span class="type">UTF8Chunk</span>) {</div><div class="line"> _sanityCheck(elementWidth == <span class="number">2</span>)</div><div class="line"> _sanityCheck(!_baseAddress._isNull)</div><div class="line"> </div><div class="line"> <span class="keyword">let</span> storage = <span class="type">UnsafeBufferPointer</span>(start: startUTF16, <span class="built_in">count</span>: <span class="keyword">self</span>.<span class="built_in">count</span>)</div><div class="line"> <span class="keyword">return</span> _transcodeSomeUTF16AsUTF8(storage, i)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<blockquote>
<p>在第一个例子中,我们有一个判断客户的 collection 没有越界的前置条件。在这个例子中,我们其实可以不做检查,因为越界也不会导致内存错误(因为返回的都是同一个 repeatedValue),但是我们还是加上了这个检查,这样我们的用户可以快速发现他们的 bug 。</p>
<p>第二个例子中是一个私有函数,它只能在我们保证 elementWidth == 2 和 _baseAddress 不为 null 的条件下调用(_sanityCheck 在 stdlib 下等价于 assert)。因为这是私有函数,使用者就是我们自己,所以看起来这个检查可以省略。但是有时候会出意外,比如后续的开发者可能会错误地使用它,因此我们需要添加检查。因为我们在 debug 和 release 的环境下运行我们的测试,并且有较高的测试覆盖率,因此(如果错误使用函数)断言很可能在某处被触发。</p>
<p>读完上面的内容,你可能认为 assert() 只能在私有方法中使用,而 precondition() 只能在公共方法中使用。事实并非如此;你可以内联任何私有方法到继承的公有方法的方法体内,因此合理的检查依然有意义。前置条件检查也会偶尔在私有方法中使用,最简单的例子就是公有方法转私有方法,复制代码的时候可以把原来的前置条件检查提取成一个私有的辅助方法(Helper)。</p>
<p><sup>*</sup>注意,有些前置条件实际上不会被执行,所以你不能指望所有的前置条件都被执行。</p>
</blockquote>
]]></content>
<summary type="html">
记得我曾分享过一些想法和建议,比如:newtype 。一个是建议 Swift 推出一个 newtype 的关键词,它可以添加完全不同于原生的可扩展的派生类型。
</summary>
<category term="Swift 入门" scheme="http://lin493369.github.io/categories/Swift-%E5%85%A5%E9%97%A8/"/>
<category term="Swift" scheme="http://lin493369.github.io/tags/Swift/"/>
</entry>
<entry>
<title>如何简单地为测试切换 App Delegate</title>
<link href="http://lin493369.github.io/2015/12/19/HowtoEasilySwitchYourAppDelegateforTesting/"/>
<id>http://lin493369.github.io/2015/12/19/HowtoEasilySwitchYourAppDelegateforTesting/</id>
<published>2015-12-18T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="http://qualitycoding.org/app-delegate-for-tests/" target="_blank" rel="external">http://qualitycoding.org/app-delegate-for-tests/</a><br>作者=Jon Reid<br>原文日期=2015/03/17</p>
<hr>
<p><a href="http://qualitycoding.org/benefit-of-tdd/" target="_blank" rel="external">测试驱动的开发最大好处是能够有快速反馈</a>(译者:这是作者的另一篇文章,讲述了测试驱动的好处,有兴趣的可以看看)。所以,为了确保你的 TDD 效率,最好的方式就是尽可能快地获得反馈。</p>
<p>但是很多 iOS 开发者会在测试的时候使用生产环境(译者:应用开发中的不同阶段,一般分为开发环境 development,处于产品开发阶段;生产环境 production,即正式上线的环境,更详细的请参照 <a href="https://en.wikipedia.org/wiki/Development,_testing,_acceptance_and_production" target="_blank" rel="external">Development, testing, acceptance and production</a>)的 app delegate。这是一个影响效率的问题。</p>
<p>你的常规 app delegate 在用于测试时是否跟龟速一样?</p>
<p><img src="http://qualitycoding.org/jrwp/wp-content/uploads/2015/03/turtle.jpg" alt="这里写图片描述"></p>
<p>这是因为当你测试运行时,首先要启动你的应用——而这个过程可能做了很多事情,大量耗时的操作。而这些耗时的操作在测试的时候并不是我们所需要的。</p>
<p>我们应该如何避免这个问题?</p>
<h2 id="测试流程"><a href="#测试流程" class="headerlink" title="测试流程"></a>测试流程</h2><p>Apple 习惯将单元测试归为两类:应用测试和逻辑测试。这个区别是非常重要的,因为在以前,应用测试只能在设备上运行,除非你使用完全不同的第三方测试框架。</p>
<p>但是这个差异现在消失了,因为 Apple 允许我们在模拟器上运行应用测试。Apple 花了很多时间来更新文档,直到在他们最新的<a href="https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/testing_with_xcode/Introduction/Introduction.html" target="_blank" rel="external">Xcode测试</a>才更新了这部分说明,Apple 现在称之为 “app tests” 和 “library tests”。这就使事情简化为你是开发一个应用还是一个库。并且 Xcode 为你设置了一个测试用的 target ,这正是你所需要的。</p>
<p>如果我现在开发一个应用(或者一个需要运行应用的库),我总是会运行应用测试,所以我<a href="http://qualitycoding.org/xcode-unit-testing/" target="_blank" rel="external">停止去试图区分这两种类型的测试</a>。但是由于 Xcode 是在一个运行的应用的上下文环境下执行应用测试,测试流程就变成这样:</p>
<ol>
<li>启动模拟器 </li>
<li>在模拟器中,启动应用 </li>
<li>将测试 bundle 注入运行的应用 </li>
<li>运行测试</li>
</ol>
<p>那么我们怎么才能加快这个流程呢?我们可以在第二步中做文章,让应用尽可能快地启动。</p>
<h2 id="普通的-app-delegate"><a href="#普通的-app-delegate" class="headerlink" title="普通的 app delegate"></a>普通的 app delegate</h2><p>在开发环境下,启动应用可能会关闭很多任务。Ole Begemann 在 <a href="http://oleb.net/blog/2012/02/app-launch-sequence-ios-revisited/" target="_blank" rel="external">Revisiting the App Launch Sequence on iOS</a>中进行了详细的解释,但是根本上, <code>UIApplicationMain()</code> 最终会调用 app delegate去执行 <code>application:didFinishLaunchingWithOptions:</code> 。具体的流程一般取决于你的应用,但是很少会像下面这么做:</p>
<ol>
<li>创建 Core Data。</li>
<li>配置根视图控制器</li>
<li>检测网络连通性</li>
<li>向服务器发送一个网络请求去取回最近的配置,例如应该在根视图中展示的东西。</li>
</ol>
<p>因此在开始测试之前要做很多事情。难道不能使我们的测试不受干扰,如果我们想要的只是运行我们的测试程序?</p>
<p>让我们来解决这个问题,下面是具体方案。</p>
<h2 id="改变-main-函数"><a href="#改变-main-函数" class="headerlink" title="改变 main 函数"></a>改变 main 函数</h2><p>让我们改变我们的 main 函数,如下所示:</p>
<figure class="highlight objectivec"><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="meta">#import <span class="meta-string"><UIKit/UIKit.h></span></span></div><div class="line"><span class="meta">#import <span class="meta-string">"AppDelegate.h"</span></span></div><div class="line"></div><div class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</div><div class="line">{</div><div class="line"> <span class="keyword">@autoreleasepool</span> {</div><div class="line"> <span class="keyword">return</span> <span class="built_in">UIApplicationMain</span>(argc, argv, <span class="literal">nil</span>, <span class="built_in">NSStringFromClass</span>([AppDelegate <span class="keyword">class</span>]));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们现在想要去检查是否我们在运行测试代码。如果想要这么做的话,我们想要去使用一个不同的 app delegate。我们可以这么做:</p>
<p>最早的版本<br><figure class="highlight objectivec"><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"><span class="meta">#import <span class="meta-string"><UIKit/UIKit.h></span></span></div><div class="line"><span class="meta">#import <span class="meta-string">"AppDelegate.h"</span></span></div><div class="line"><span class="meta">#import <span class="meta-string">"TestingAppDelegate.h"</span></span></div><div class="line"></div><div class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</div><div class="line">{</div><div class="line"> <span class="keyword">@autoreleasepool</span> {</div><div class="line"> <span class="built_in">BOOL</span> isTesting = <span class="built_in">NSClassFromString</span>(<span class="string">@"XCTestCase"</span>) != Nil;</div><div class="line"> Class appDelegateClass = isTesting ? [TestingAppDelegate <span class="keyword">class</span>] : [AppDelegate <span class="keyword">class</span>];</div><div class="line"> <span class="keyword">return</span> <span class="built_in">UIApplicationMain</span>(argc, argv, <span class="literal">nil</span>, <span class="built_in">NSStringFromClass</span>(appDelegateClass));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>从根本上来说,如果 XCTestCase 链接好了,我们就会使用 <code>TestingAppDelegate</code>。否则,我们退而使用生产环境的 app delegate。然后我们启动应用时可以选择我们想要的 app delegate。(注意:TestingAppDelegate 必须在生产环境的 target 中)</p>
<p>现在这些代码已经实现了来回切换。上述部分的实现从根本上和我原先的文章一致。因为有一段时间,根据评论中的建议,我将代码改为:<br><figure class="highlight objectivec"><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">@autoreleasepool</span> {</div><div class="line">Class appDelegateClass = <span class="built_in">NSClassFromString</span>(@”XYZTestingAppDelegate”);</div><div class="line"><span class="keyword">if</span>( appDelegateClass == <span class="literal">nil</span> ) {</div><div class="line">appDelegateClass = [DOAAppDelegate <span class="keyword">class</span>];</div><div class="line">}</div><div class="line"><span class="keyword">return</span> <span class="built_in">UIApplicationMain</span>(argc, argv, <span class="literal">nil</span>, <span class="built_in">NSStringFromClass</span>(appDelegateClass));</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>但是在Xcode7上不能正常运行,所以我又改回原始版本。</p>
<p>如果你想在单元测试外部使用 XCTest 该怎么办,例如 UI 测试?为了取代为 XCTestCase 做的测试,你可以设置一个环境变量,通过 getenv 来测试。</p>
<h2 id="提供-TestingAppDelegate"><a href="#提供-TestingAppDelegate" class="headerlink" title="提供 TestingAppDelegate"></a>提供 TestingAppDelegate</h2><p>这里需要创建一个 TestingAppDelegate 类。正如下面代码所示:</p>
<p>TestingAppDelegate.h</p>
<figure class="highlight objectivec"><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="meta">#import <span class="meta-string"><UIKit/UIKit.h></span></span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">TestingAppDelegate</span> : <span class="title">UIResponder</span> <<span class="title">UIApplicationDelegate</span>></span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">UIWindow</span> *window;</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>TestingAppDelegate.m</p>
<figure class="highlight objectivec"><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"><span class="meta">#import <span class="meta-string">"TestingAppDelegate.h"</span></span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">TestingAppDelegate</span></span></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>正如你所看到的那样,不要做任何事。</p>
<p>(在早先的 iOS 版本中,我必须添加更多的代码,导致 TestingAppDelegate 会创建一个 window,给这个 window 设置一个不做任何事情的根视图,然后让其可见。现在看来没必要了。)</p>
<h2 id="快速反馈的本质"><a href="#快速反馈的本质" class="headerlink" title="快速反馈的本质"></a>快速反馈的本质</h2><p>最重要的事情是我们已经从本质上减少了测试过程中启动应用的步骤。尽管还有一些不必要的开销,但是并不多。这是实现快速反馈过程中重要的一步,这样我们就可以从 TDD 中获得更多。</p>
<p>甚至当你开始一个新的项目,我推荐尽早使用这样的方法,因为你真正的app delegate最终会变得日益庞大。让我们在襁褓中阻止这种问题,然后保持快速的反馈。</p>
<p>另外一个好处是,通过完全控制哪部分该测试,什么时候测试,我们现在可以编写跟生产环境的app delegate完全不同的单元测试。这显然是双赢的。</p>
]]></content>
<summary type="html">
这是因为当你测试运行时,首先要启动你的应用——而这个过程可能做了很多事情,大量耗时的操作。而这些耗时的操作在测试的时候并不是我们所需要的。我们应该如何避免这个问题?
</summary>
<category term="Test" scheme="http://lin493369.github.io/categories/Test/"/>
<category term="test" scheme="http://lin493369.github.io/tags/test/"/>
<category term="效率" scheme="http://lin493369.github.io/tags/%E6%95%88%E7%8E%87/"/>
<category term="AppDelegate" scheme="http://lin493369.github.io/tags/AppDelegate/"/>
</entry>
<entry>
<title>Core Animation & Facebook's POP</title>
<link href="http://lin493369.github.io/2015/12/17/CoreAnimation&Facebook'sPOP/"/>
<id>http://lin493369.github.io/2015/12/17/CoreAnimation&Facebook'sPOP/</id>
<published>2015-12-16T16:00:00.000Z</published>
<updated>2017-04-11T11:35:38.000Z</updated>
<content type="html"><![CDATA[<hr>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>相信很多人对实现 iOS 中的动画效果都特别头疼,往往懒得动手,功能实现不就得了,何必要那么花哨、装13的东西。但是看到别人的炫酷动效,心中又瘙痒不已,便下定决心学习,于是开始翻看 Core Animation、UIView动画(其实是<a href="http://www.jianshu.com/p/72f4cca98b0e" target="_blank" rel="external">对Core Animation的一种封装</a>)相关资料。不小心看到一群大神正在热烈讨论,钻一进去一看,原来是 <a href="https://github.com/facebook/pop" target="_blank" rel="external">POP</a> (潜意识:Facebook出品必属精品),这还学什么Core Animation,果断pod一个来玩玩,于是你就左手CA,右手 POP 开森地把玩起来了。</p>
<p>此时,你可能已经学会了CA的基本使用方法,也对UIView动画的便捷感到惊喜,但是不满足的你,显然有更高的追求,POP 以其灵活的用法,丰富的动效,完整的API文档,深得很多程序员的喜爱。作为一个有逼格的程序员,这么流行的框架,必然是值得深入学习的,但是你是否考虑过这样的第三方动画框架是否存在什么不足。因此,作为一个有追求的程序员,有必要来稍微深入地探讨一下 Core Animation 和 POP 不同点。</p>
<h2 id="Core-Animation-工作机制"><a href="#Core-Animation-工作机制" class="headerlink" title="Core Animation 工作机制"></a>Core Animation 工作机制</h2><p>首先我们需要了解CA是如何工作的。每当我们创建并添加动画到 layer 时,<a href="https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/QuartzCoreRefCollection/index.html" target="_blank" rel="external">QuartzCore</a> 框架就会把动画的参数打包好,然后通过 IPC (处理器)发送给名为 <a href="https://theiphonewiki.com/wiki/Backboardd" target="_blank" rel="external">backboardd</a> 的后台处理程序。你的应用也会发送当前展示在屏幕上的每一个 layer 的信息。</p>
<p>backboardd 会处理 layer 的结构体系然后通过 OpenGL 绘制出来。它还会处理你已经添加过的动画(也可以是视图,因为视图本质是包裹着 layer的)。你一定要理解的是,backboardd 使得动画的每一帧都可以在你的应用中完全独立。这里唯一的回调是动画的开始和结束(详见<code>CAAnimationDelegate</code> 协议)。你的应用完全不会参与动画的绘制,这些绘制完全独立于你的应用进程(除非你明确地在你的应用中通过<a href="http://www.objc.io/issue-12/animating-custom-layer-properties.html" target="_blank" rel="external">动画通用属性</a>要求绘制动画帧)。这意味着你可以继续在主线程做其他事情,并且不会影响到 <a href="https://developer.apple.com/Library/mac/documentation/GraphicsImaging/Reference/CAAnimation_class/index.html" target="_blank" rel="external">CAAnimation</a> 的性能。如果你阻塞了你的主线程,或者你在调试器中暂停了你的程序,你的动画还是会继续执行。</p>
<p>但是你可能会有这样的疑问:每个 <a href="https://developer.apple.com/library/mac/Documentation/GraphicsImaging/Reference/CALayer_class/index.html" target="_blank" rel="external">CALayer</a> 不是还有一个 presentationLayer 属性吗?</p>
<blockquote>
<p>presentationLayer的官方解释: </p>
<p>“While an animation is in progress, you can retrieve this object and use it to get the current values for those animations.”</p>
</blockquote>
<p>当CAAnimation发生时,你在屏幕上看到的实际上是 presentation layer 的改变。如果你访问 presentation layer,QuartzCore 将会计算现有的帧状态,并且使用这个帧状态去构建 presentation layer 对象。因为动画状态在动画执行期间一直处于改变,因此你将会获得近似值。</p>
<h2 id="POP-工作机制"><a href="#POP-工作机制" class="headerlink" title="POP 工作机制"></a>POP 工作机制</h2><p>现在有很多优秀的第三方动画库,POP 因为其使用灵活、功能强大、文档齐全,所以备受好评,先看一下官方介绍:</p>
<blockquote>
<p>POP是一个在iOS与OS X上通用的极具扩展性的动画引擎 它在基本的静态动画的基础上增加的弹簧动画与衰减动画<br>使之能创造出更真实更具物理性的交互动画 POP的API可以快速的与现有的ObjC代码集成并可以作用于任意对象的任意属性<br>POP是个相当成熟且久经考验的框架 Facebook出品的令人惊叹的Paper应用中的所有动画和效果即出自POP</p>
</blockquote>
<p>更为详细的介绍和使用请查看<a href="https://github.com/facebook/pop" target="_blank" rel="external">官方文档</a>以及里脊串的 <a href="http://adad184.com/2015/03/11/intro-to-pop/" target="_blank" rel="external">POP介绍与使用实践(快速上手动画)</a>。</p>
<p>POP 本质上是基于定时器的动画库,使用每秒 60 频率的定时器,即时钟频率为 1/60 秒(为了匹配 iOS 显示屏帧率),使得动画刷新绘制频率与屏幕刷新频率一致。很多这类动画库都使用 CADisplayLink 做为一个回调源。</p>
<p>一旦定时器刷新,动画库计算动画的进程,这意味着动画库会计算那些活动的东西的状态(通常是layer 属性,如 bound,opactiy,transform 等)。然后动画库提供最新计算的值给有动画的 layer (或者其他对象)。最主要的区别是,layer 的状态将会在这种情况下改变。</p>
<p>由于 layer 的一些参数已经被改变,你的应用必须通过 IPC 通知 backboardd 处理这些变化。当 backboardd 接收到变化通知(同时接收到的还有应用中的 layer 树),它将在屏幕上重绘一切东西。这意味着,你应用中做的每一个动画帧都会传送数据到 backboardd (即通知 backboardd ),因为 backboardd 完全不知道 layer 发生了什么事情。综上,你的应用就是在这种情况下运行动画的。</p>
<h2 id="Core-Animation-和-POP-运行动画对比"><a href="#Core-Animation-和-POP-运行动画对比" class="headerlink" title="Core Animation 和 POP 运行动画对比"></a>Core Animation 和 POP 运行动画对比</h2><p>由于 POP 是基于定时器定时刷新添加动画的原理,那么如果将动画库运行在主线程上,会由于线程阻塞的问题导致动画效果出现卡顿、不流畅的情况。更为关键的是,你不能将动画效果放在子线程,因为你不能将对 view 和 layer 的操作放到主线程之外。</p>
<p>为了验证上述的观点,我做了一个实验,首先用CA动画制作一个可以旋转的 view:</p>
<figure class="highlight mipsasm"><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">UIView *viewCA = [[UIView alloc]initWithFrame:CGRectMake(<span class="number">50</span>,<span class="number">50</span>, <span class="number">100</span>, <span class="number">100</span>)]<span class="comment">;</span></div><div class="line"> viewCA.<span class="keyword">backgroundColor </span>= [UIColor <span class="keyword">blueColor];</span></div><div class="line"> [self.view <span class="keyword">addSubview:viewCA];</span></div><div class="line"> CABasicAnimation *caAnimation = [CABasicAnimation animationWithKeyPath:@<span class="string">"transform.rotation.z"</span>]<span class="comment">;</span></div><div class="line"> caAnimation.toValue = @(M_PI)<span class="comment">;</span></div><div class="line"> caAnimation.duration = <span class="number">2</span>.<span class="number">0</span><span class="comment">;</span></div><div class="line"> caAnimation.repeatCount = <span class="number">500</span><span class="comment">;</span></div><div class="line"> [viewCA.layer <span class="keyword">addAnimation:caAnimation </span>forKey:@<span class="string">"anim"</span>]<span class="comment">;</span></div></pre></td></tr></table></figure>
<p>再创建一个利用 POP 动画库制作的可旋转 view:<br><figure class="highlight armasm"><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="symbol">UIView</span> *viewPOP = [[UIView alloc]initWithFrame:</div><div class="line"><span class="symbol">CGRectMake</span>(CGRectGetWidth(<span class="keyword">self.view.bounds) </span>- <span class="number">100</span> - <span class="number">50</span>, <span class="number">50</span>, <span class="number">100</span>, <span class="number">100</span>)]<span class="comment">;</span></div><div class="line"> viewPOP.<span class="keyword">backgroundColor </span>= [UIColor yellowColor]<span class="comment">;</span></div><div class="line"> [<span class="keyword">self.view </span><span class="keyword">addSubview:viewPOP];</span></div><div class="line"> <span class="keyword">POPBasicAnimation </span>*<span class="keyword">popAnimation </span>= [<span class="keyword">POPBasicAnimation </span>animationWithPropertyNamed:kPOPLayerRotation]<span class="comment">;</span></div><div class="line"> <span class="keyword">popAnimation.toValue </span>= <span class="comment">@(M_PI);</span></div><div class="line"> <span class="keyword">popAnimation.duration </span>= <span class="number">2</span>.<span class="number">0</span><span class="comment">;</span></div><div class="line"> <span class="keyword">popAnimation.repeatCount </span>= <span class="number">500</span><span class="comment">;</span></div><div class="line"> [viewPOP.layer <span class="keyword">pop_addAnimation:popAnimation </span>forKey:<span class="comment">@"rotation"];</span></div></pre></td></tr></table></figure></p>
<p>在没有线程阻塞的情况下,对比两个动画库的运行效果如下:</p>
<p><img src="http://img.blog.csdn.net/20151217112919922" alt="这里写图片描述"></p>
<p>可以看出来虽然在没有线程阻塞,但是 POP 的动画在结束时有一个明显的停止动作,是因为 POP 的动画效果不好吗?</p>
<p>答案是 <code>timingFunction</code>。</p>
<p>CoreAnimation 和 POPBasicAnimation提供同样的四种 <code>timingFunction</code>:</p>
<blockquote>
<p>kCAMediaTimingFunctionLinear<br>kCAMediaTimingFunctionEaseIn<br>kCAMediaTimingFunctionEaseOut<br>kCAMediaTimingFunctionEaseInEaseOut<br>kCAMediaTimingFunctionDefault</p>
</blockquote>
<p>重点说一下:kCAMediaTimingFunctionDefault(引自:<a href="http://www.cocoachina.com/ios/20150105/10829.html" target="_blank" rel="external">iOS-Core-Animation-Advanced-Techniques(五)</a>)</p>
<blockquote>
<p>它和kCAMediaTimingFunctionEaseInEaseOut很类似,但是加速和减速的过程都稍微有些慢。它和kCAMediaTimingFunctionEaseInEaseOut的区别很难察觉,可能是苹果觉得它对于隐式动画来说更适合(然后对UIKit就改变了想法,而是使用kCAMediaTimingFunctionEaseInEaseOut作为默认效果),虽然它的名字说是默认的,但还是要记住当创建显式的CAAnimation它并不是默认选项(换句话说,默认的图层行为动画用kCAMediaTimingFunctionDefault作为它们的计时方法)。</p>
</blockquote>
<p>如果不设置 <code>timingFunction</code> 属性,那么在使用 CA 的情况下, <code>timingFunction</code> 是 <code>kCAMediaTimingFunctionLinear</code> 的,而 POP 却是<code>kCAMediaTimingFunctionEaseOut</code> ,因此我们只要添加这么一行代码:</p>
<figure class="highlight accesslog"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">popAnimation.timingFunction = <span class="string">[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]</span>;</div></pre></td></tr></table></figure>
<p>现在再看效果:</p>
<p><img src="http://img.blog.csdn.net/20151217114608713" alt="这里写图片描述"></p>
<p>可以看出来,在主线程没有阻塞的情况下,两种动画库的表现并无差异(POP 就是🐂)</p>
<p>现在我们来制造一点难度,人工利用线程的 sleep 增加一个主线程阻塞:</p>
<figure class="highlight groovy"><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">void</span>)repeatedlyBlockMainThread</div><div class="line">{</div><div class="line"> NSLog(@<span class="string">"blocking main thread!"</span>);</div><div class="line"> [NSThread <span class="string">sleepForTimeInterval:</span><span class="number">0.25</span>];</div><div class="line"> [self <span class="string">performSelector:</span><span class="meta">@selector</span>(repeatedlyBlockMainThread) <span class="string">withObject:</span>nil <span class="string">afterDelay:</span><span class="number">1</span>];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>然后再 <code>viewDidLoad</code> 里面调用 :</p>
<figure class="highlight groovy"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[self <span class="string">performSelector:</span><span class="meta">@selector</span>(repeatedlyBlockMainThread) <span class="string">withObject:</span>nil <span class="string">afterDelay:</span><span class="number">1</span>];</div></pre></td></tr></table></figure>
<p>现在再来看一下两者的动画效果:</p>
<p><img src="http://img.blog.csdn.net/20151217121603261" alt="这里写图片描述"></p>
<p>很明显,我们可以看出来,由于添加了主线程阻塞,利用 POP 制作的动画视图,在每隔 1s 都会卡顿一下,而 CA 的视图却完全不受主线程阻塞的影响。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过这次简单的对比,我们从工作机制上了解了 CA 和 POP 两个动画库的基本原理,并用简单的动画效果对比,重现了在主线程阻塞的情况下两者的差异,很显然, POP 受主线程阻塞的影响很大,在使用过程中,应避免在有可能发生主线程阻塞的情况下使用 POP ,避免制作卡顿的动画效果,产生不好的用户体验。文中提出了 POP 的这种缺点,但是 POP 毕竟是久经考验的动画技术,本人也正在学习中,有错误的地方吝请指正。</p>
<p>对比系列,是个人比较喜欢的一种学习方式,通过对比,找出不同技术的优缺点,可以更合理地使用这些武器,俗话说:好钢用在刀刃上,大抵如此。</p>
]]></content>
<summary type="html">
对比系列,是个人比较喜欢的一种学习方式,通过对比,找出不同技术的优缺点,可以更合理地使用这些武器,俗话说:好钢用在刀刃上,大抵如此。本文对 CoreAnimation 和 Facebook 的 POP 动画库进行了对比。
</summary>
<category term="Animation" scheme="http://lin493369.github.io/categories/Animation/"/>
<category term="个人" scheme="http://lin493369.github.io/tags/%E4%B8%AA%E4%BA%BA/"/>
<category term="POP" scheme="http://lin493369.github.io/tags/POP/"/>
<category term="CoreAnimation" scheme="http://lin493369.github.io/tags/CoreAnimation/"/>
<category term="Facebook" scheme="http://lin493369.github.io/tags/Facebook/"/>
</entry>
<entry>
<title>UIAlertController 测试的修正</title>
<link href="http://lin493369.github.io/2015/12/02/UIAlertController%E6%B5%8B%E8%AF%95%E7%9A%84%E4%BF%AE%E6%AD%A3/"/>
<id>http://lin493369.github.io/2015/12/02/UIAlertController测试的修正/</id>
<published>2015-12-01T16:00:00.000Z</published>
<updated>2017-04-13T03:32:16.000Z</updated>
<content type="html"><![CDATA[<p>原文链接=<a href="http://swiftandpainless.com/correction-on-testing-uialertcontroller/" target="_blank" rel="external">http://swiftandpainless.com/correction-on-testing-uialertcontroller/</a><br>作者=dom<br>原文日期=2015/11/25</p>
<hr>
<p>两个月前,我曾发布了一篇<a href="http://swiftandpainless.com/how-to-test-uialertcontroller-in-swift/" target="_blank" rel="external"> 如何测试 UIAlertController </a>的文章。一个读者发现测试没有如期地起作用:</p>
<blockquote>
<p><a href="https://twitter.com/dasdom" target="_blank" rel="external">@dasdom</a> 你的测试是正常的,但是在 <code>MockUIAction</code> 中的简便 <code>init</code> 方法没有被调用。 你不能重写 <code>init</code> 方法,看起来像是 iOS 的bug。<br> — Larhythimx (@Larhythmix) <a href="https://twitter.com/Larhythmix/status/669456137041915905" target="_blank" rel="external">25. November 2015</a></p>
</blockquote>
<p>Larhythimx 说的完全正确。模拟程序的初始化方法从来没有调用。为什么我在写这个测试用例的时候没有发觉呢?那是因为 handler 确实被调用了,看起来就像 <code>UIAlertAction</code> 真的把 handler 作为内部变量去存储动作的 handler 闭包。这是非常脆弱的,并且 Larhythimx 在另一个 tweet 指出在他的测试程序中 handler 是 <code>nil</code>。</p>
<p>所以作为黄金通道(即编写不需要改变实现的测试)走不通,那就退而求其次用别的方法。</p>
<p>首先,我们在 <code>UIAlertAction</code> 中添加一个类方法去创建 action 。在 ViewController.swift 中增加如下扩展:</p>
<figure class="highlight less"><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="selector-tag">extension</span> <span class="selector-tag">UIAlertAction</span> {</div><div class="line"> <span class="selector-tag">class</span> <span class="selector-tag">func</span> <span class="selector-tag">makeActionWithTitle</span>(<span class="attribute">title</span>: String?, <span class="attribute">style</span>: UIAlertActionStyle, <span class="attribute">handler</span>: ((UIAlertAction) -> Void)?) <span class="selector-tag">-</span>> <span class="selector-tag">UIAlertAction</span> {</div><div class="line"> <span class="selector-tag">return</span> <span class="selector-tag">UIAlertAction</span>(<span class="attribute">title</span>: title, <span class="attribute">style</span>: style, <span class="attribute">handler</span>: handler)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在 <code>MockAlertAction</code> 中增加这个重写方法:</p>
<figure class="highlight less"><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="selector-tag">override</span> <span class="selector-tag">class</span> <span class="selector-tag">func</span> <span class="selector-tag">makeActionWithTitle</span>(<span class="attribute">title</span>: String?, <span class="attribute">style</span>: UIAlertActionStyle, <span class="attribute">handler</span>: ((UIAlertAction) -> Void)?) <span class="selector-tag">-</span>> <span class="selector-tag">MockAlertAction</span> {</div><div class="line"> <span class="selector-tag">return</span> <span class="selector-tag">MockAlertAction</span>(<span class="attribute">title</span>: title, <span class="attribute">style</span>: style, <span class="attribute">handler</span>: handler)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在实现代码中,我们现在可以使用类方法去创建 alert 动作:</p>
<figure class="highlight livescript"><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">let</span> okAction = Action.makeActionWithTitle<span class="function"><span class="params">(<span class="string">"OK"</span>, style: .Default)</span> { <span class="params">(action)</span> -></span> Void <span class="keyword">in</span></div><div class="line"> self.actionString = <span class="string">"OK"</span></div><div class="line">}</div><div class="line"><span class="keyword">let</span> cancelAction = Action.makeActionWithTitle<span class="function"><span class="params">(<span class="string">"Cancel"</span>, style: .Default)</span> { <span class="params">(action)</span> -></span> Void <span class="keyword">in</span></div><div class="line"> self.actionString = <span class="string">"Cancel"</span></div><div class="line">}</div><div class="line">alertViewController.addAction(cancelAction)</div></pre></td></tr></table></figure>
<p>为了确保我们的测试用例正常,如我们预期地工作,将<code>MockAlertAction</code>的 <code>handler</code> 属性重命名为 <code>mockHandler</code>:<br><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">var</span> mockHandler: <span class="type">Handler</span>?</div></pre></td></tr></table></figure></p>
<p>此外,我们为动作的模拟标题添加测试。为取消动作的测试应该像这样:</p>
<figure class="highlight stylus"><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">func testAlert_FirstActionStoresCancel() {</div><div class="line"> sut<span class="selector-class">.Action</span> = MockAlertAction<span class="selector-class">.self</span></div><div class="line"></div><div class="line"> sut.showAlert(UIButton())</div><div class="line"></div><div class="line"> let alertController = sut<span class="selector-class">.presentedViewController</span> as! UIAlertController</div><div class="line"> let action = alertController<span class="selector-class">.actions</span><span class="selector-class">.first</span> as! MockAlertAction</div><div class="line"> action.mockHandler!(action)</div><div class="line"></div><div class="line"> XCTAssertEqual(sut<span class="selector-class">.actionString</span>, <span class="string">"Cancel"</span>)</div><div class="line"> XCTAssertEqual(action<span class="selector-class">.mockTitle</span>, <span class="string">"Cancel"</span>)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这个测试在此前的版本将会失败,因为初始化方法没有被调用,因此模拟标题也没有得到设置。</p>
<p>你可以在 <a href="https://github.com/dasdom/TestingAlertExperiment" target="_blank" rel="external">github</a> 上找到正确的版本。</p>
<p>再次感谢 Larhythimx 的推特!</p>
]]></content>
<summary type="html">
首先,我们在 UIAlertAction 中添加一个类方法去创建 action 。在 ViewController.swift 中增加如下扩展:
</summary>
<category term="Swift" scheme="http://lin493369.github.io/categories/Swift/"/>
<category term="test" scheme="http://lin493369.github.io/tags/test/"/>
<category term="dom" scheme="http://lin493369.github.io/tags/dom/"/>
</entry>
<entry>
<title>Xcode:用于管理多个 target 配置的 XCConfig 文件</title>
<link href="http://lin493369.github.io/2015/11/24/Xcode-%E7%94%A8%E4%BA%8E%E7%AE%A1%E7%90%86%E5%A4%9A%E4%B8%AAtarget%E9%85%8D%E7%BD%AE%E7%9A%84XCConfig%E6%96%87%E4%BB%B6/"/>
<id>http://lin493369.github.io/2015/11/24/Xcode-用于管理多个target配置的XCConfig文件/</id>
<published>2015-11-23T16:00:00.000Z</published>
<updated>2017-04-13T03:33:18.000Z</updated>
<content type="html"><![CDATA[<p>原文链接:<br><a href="http://szulctomasz.com/xcode-xcconfig-files-for-managing-targets-configurations/" target="_blank" rel="external">http://szulctomasz.com/xcode-xcconfig-files-for-managing-targets-configurations/</a><br>作者:<br>Tomasz Szulc<br>原文日期:<br>2015/11/14</p>
<hr>
<p>让我们来看看 XCConfig 文件如何才能在多个拥有不同配置的 target 中良好地工作。</p>
<p>今天我本计划学习一些新东西,因此我搜索了 <a href="https://github.com/mozilla/firefox-ios" target="_blank" rel="external">mozilla/firefox-ios</a> 库(译者:这是在火狐浏览器在 github 的一个开源项目)的相关信息,接着我发现他们会在项目中使用大量的配置文件。</p>
<p>我曾经在几个项目中使用 XCConfig ,但是我并没有在现在开发的项目中使用它。因为这个项目有多个不同配置的 target,因此我开始思考如何才能有效且简单地管理这些 target 。</p>
<h2 id="用例"><a href="#用例" class="headerlink" title="用例"></a>用例</h2><p>这个项目现在已经被我的团队接手了。客户的团队先开发了大约一年半的时间,最后决定将项目完全外包出去。这个项目一个麻烦的事就是 target 有不同的配置,因此如何更好地解决,是个棘手的问题。</p>
<p>项目由十个应用 target 组成,两个总的 target 做些业务,以及一个测试 target 。每一个 target 使用不同的尾部和不同的 “api keys”,以及其他像用于hockeyapp(HockeyApp 是一个用来分发你的程序并收集应用的崩溃报告的收集框架,类似友盟) token 的键(key)。每一个 target 有自己的预处理宏,如:“TARGET_A”, “TARGET_B”等…(虚构的名字)。然后,token,api keys,后端的 url 被存储在 plist 文件中。因此很自然地,就需要一些类来封装这个文件,并且有语法分析程序以及可以提供给我们适当的键。这个类有超过两百行的代码,对我来说,仅仅阅读这些数据就要花费很多时间。</p>
<p>因此,我想可能可以使用 XCConfig 文件来简化和替代使用语法分析程序和十个个预处理宏(一个 target)去决定从 plist 文件应该返回什么值。你可以在下面找到我的解决方案。可能不是最好的方案,但是此刻应该是最好的。如果你有更好的方案,我很愿意去拜读 :)</p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>核心思想是使用一些有层级的配置文件。第一层是用于存储最普通的数据,第二层用于区分debug和release模式,最后一层用于关联特殊 target 的设置。</p>
<p><img src="http://szulctomasz.com/wp-content/uploads/2015/11/diagram_1.png" alt="这里写图片描述"></p>
<h2 id="Common-xcconfig"><a href="#Common-xcconfig" class="headerlink" title="Common.xcconfig"></a>Common.xcconfig</h2><p>这个文件存储着类似应用名称,应用版本,bundle version,以及其他 debug和 release target 中通用的常见配置。</p>
<figure class="highlight protobuf"><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="comment">//</span></div><div class="line"><span class="comment">// Common.xcconfig</span></div><div class="line"><span class="comment">// <truncated></span></div><div class="line"><span class="comment">//</span></div><div class="line"></div><div class="line">APP_NAME = App</div><div class="line">APP_VERSION = <span class="number">1.6</span></div><div class="line">APP_BUNDLE_ID = <span class="number">153</span></div></pre></td></tr></table></figure>
<p>考虑到为十个 target 改变相应的应用版本和 bundle 可能会消耗很多时间。其他的选项可能会创建聚合的 target ,这样可以在每次 Cmd+B的时候更新Info-plist 文件,但是我会避免这样的情况并且让项目不会比现在更复杂。</p>
<h2 id="Common-debug-和-Common-release"><a href="#Common-debug-和-Common-release" class="headerlink" title="Common.debug 和 Common.release"></a>Common.debug 和 Common.release</h2><p>这个文件能够存储可用于debug和release target的最常用配置。文件包含Common.xcconfig并且能够重写它的变量。如:你可以通过重写一个变量,轻易地把每个debug target 的应用名称改为“App Debug”。对于存储常见的用于开发和发行版本target的 API Key,这里也是很好的地方。</p>
<p><strong><em>提示:使用通用配置文件和 CocoaPods</em></strong></p>
<p>如果你使用 CocoaPods,你应该相应地在你的配置文件之一中包括(include)Pods.debug.xcconfig 或者 Pods.release.xcconfig。我推荐先在项目信息标签中设置你的配置文件然后执行 <code>pod install</code>去让Pod 项目重新配置。在安装之后,你应该及时地把 Pod 配置文件中的其中一个包括(include)到你自己的文件中去。</p>