-
Notifications
You must be signed in to change notification settings - Fork 9
/
index.html
2496 lines (1502 loc) · 235 KB
/
index.html
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
<!doctype html>
<html class="theme-next pisces use-motion">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/vendors/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
<link href="/vendors/font-awesome/css/font-awesome.min.css?v=4.4.0" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.0.1" rel="stylesheet" type="text/css" />
<meta name="keywords" content="Hexo, NexT" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.0.1" />
<meta property="og:type" content="website">
<meta property="og:title" content="南峰子的技术博客">
<meta property="og:url" content="http://southpeak.github.io/index.html">
<meta property="og:site_name" content="南峰子的技术博客">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="南峰子的技术博客">
<script type="text/javascript" id="hexo.configuration">
var NexT = window.NexT || {};
var CONFIG = {
scheme: 'Pisces',
sidebar: {"position":"left","display":"post"},
fancybox: true,
motion: true,
duoshuo: {
userId: 0,
author: '博主'
}
};
</script>
<link rel="canonical" href="http://southpeak.github.io/"/>
<title> 南峰子的技术博客 </title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-68856508-1', 'auto');
ga('send', 'pageview');
</script>
<div style="display: none;">
<script src="http://s95.cnzz.com/z_stat.php?id=1000523916&web_id=1000523916" language="JavaScript"></script>
</div>
<div class="container one-collumn sidebar-position-left
page-home
">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">南峰子的技术博客</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle">攀登,一步一个脚印,方能知其乐</p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-techset">
<a href="/categories/techset" rel="section">
<i class="menu-item-icon fa fa-fw fa-fighter-jet"></i> <br />
知识小集
</a>
</li>
<li class="menu-item menu-item-swift">
<a href="/categories/swift" rel="section">
<i class="menu-item-icon fa fa-fw fa-space-shuttle"></i> <br />
Swift
</a>
</li>
<li class="menu-item menu-item-objectivec">
<a href="/categories/objectivec" rel="section">
<i class="menu-item-icon fa fa-fw fa-taxi"></i> <br />
Objective-C
</a>
</li>
<li class="menu-item menu-item-cocoa">
<a href="/categories/cocoa" rel="section">
<i class="menu-item-icon fa fa-fw fa-subway"></i> <br />
Cocoa
</a>
</li>
<li class="menu-item menu-item-translate">
<a href="/categories/translate" rel="section">
<i class="menu-item-icon fa fa-fw fa-rocket"></i> <br />
翻译
</a>
</li>
<li class="menu-item menu-item-sourcecode">
<a href="/categories/sourcecode" rel="section">
<i class="menu-item-icon fa fa-fw fa-train"></i> <br />
源码分析
</a>
</li>
<li class="menu-item menu-item-something">
<a href="/categories/something" rel="section">
<i class="menu-item-icon fa fa-fw fa-bicycle"></i> <br />
杂项
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/01/16/Getting-Started-With-RxSwift-and-RxCocoa/" itemprop="url">
Getting Started With RxSwift and RxCocoa
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2017-01-16T08:13:39+08:00" content="2017-01-16">
2017-01-16
</time>
</span>
<span class="post-category" >
|
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="https://schema.org/Thing">
<a href="/categories/translate/" itemprop="url" rel="index">
<span itemprop="name">翻译</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<blockquote>
<p>本文由Ellen Shapiro发表于raywenderlich,原文地址是<a href="https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa" target="_blank" rel="external">https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa</a></p>
</blockquote>
<hr>
<p>代码能按你的意愿执行总是件很棒的事。在程序中改变一些东西,告诉代码,然后它就照做了。嗯,这是坨好代码!</p>
<p>在面向对象时代,大多数程序都像这样运行:你的代码告诉你的程序需要做什么,并且有很多方法来监听变化–但同时你又必须主动告诉系统什么时候发生的变化。</p>
<p>目前为止还是很不错,不过如果变化发生时,代码能自动响应更新,生活是不是会更美好呢?这就是响应式编程的基本思想:你的程序可以对底层数据的变化做出响应,而不需要你直接告诉它。这样,你可以更专注于所需要处理的业务逻辑,而不需要去维护特定的状态。</p>
<p>在Objective-C和Swift都可以实现这种操作,主要是通过<code>Key-value Observation</code>,在Swift中还可以使用<code>setter</code>或<code>didSet</code>方法。不过,有时这些方法并不那么好使。为了避免这些问题,现在市面上已经有一些Objective-C和Swift的框架,来实现响应式编程。</p>
<blockquote>
<p>如果你想了解更多的信息,可以看看 ‘<a href="https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift" target="_blank" rel="external">ReactiveCocoa vs RxSwift</a>‘ 这篇文章。</p>
</blockquote>
<p>今天我们将使用一个很棒的框架,RxSwift和它的小伙伴RxCocoa,来实现一个购买巧克力的App,并从那令人恼火的命令式编程过渡到相当awesome的响应式编程。</p>
<h2 id="RxSwift和RxCocoa是什么?"><a href="#RxSwift和RxCocoa是什么?" class="headerlink" title="RxSwift和RxCocoa是什么?"></a>RxSwift和RxCocoa是什么?</h2><p>RxSwift和RxCocoa是ReactiveX工具套件的一部分,这个套件包含了多种编程语言和平台。ReactiveX起源于.Net/C#生态体系,不过现在已经非常受Rubyists、JavaScripers、尤其是Java和Android开发人员的欢迎了。</p>
<p>RxSwift是一个用于与Swift语言交互的框架,而RxCocoa是在响应式编程中让Cocoa APIs更容易使用的一个框架。</p>
<p>ReactiveX框架相当于提供了一个词汇表,以方便在不同的语言中描述相同的任务。这让你可以在多语言切换中,专注于语言本身的语法,而不是把时间浪费在将一个普通任务从一种语言转换为另一种新语言。</p>
<h3 id="Observables和Observers"><a href="#Observables和Observers" class="headerlink" title="Observables和Observers"></a>Observables和Observers</h3><p>这里有两个基本的概念:<code>Observable</code>和<code>Observer</code>。</p>
<ul>
<li><code>Observable</code>是发出变化通知的对象;</li>
<li><code>Observer</code>是订阅<code>Observable</code>的对象,以便在<code>Observable</code>变化时接收通知</li>
</ul>
<p>可以有多个<code>Observer</code>监听同一个<code>Observable</code>。这意味着当<code>Observable</code>发生变化时,会通知所有相关的<code>Observer</code>。</p>
<h3 id="DisposeBag"><a href="#DisposeBag" class="headerlink" title="DisposeBag"></a>DisposeBag</h3><p>RxSwift和RxCocoa还有一个额外的工具来辅助处理ARC和内存管理:即<code>DisposeBag</code>。这是<code>Observer</code>对象的一个虚拟”包”,当它们的父对象被释放时,这个虚拟包会被丢弃。</p>
<p>当带有<code>DisposeBag</code>属性的对象调用<code>deinit()</code>时,虚拟包将被清空,且每一个一次性(disposable)<code>Observer</code>会自动取消订阅它所观察的内容。这允许ARC像通常一样回收内存。</p>
<p>如果没有<code>DisposeBag</code>,会有两种结果:或者<code>Observer</code>会产生一个<code>retain cycle</code>,被无限期的绑定到被观察对象上;或者意外地被释放,导致程序崩溃。</p>
<p>所以要成为一个ARC的良民,记得设置<code>Observable</code>对象时,将它们添加到<code>DisposeBag</code>中。这样,它们才能被很好地清理掉。</p>
<h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><p>让我们去买巧克力吧!本教程的初始工程<strong>Chocotastic</strong>可以在<a href="https://koenig-media.raywenderlich.com/uploads/2016/10/Chocotastic-starter-s3-rxs-3b1.zip" target="_blank" rel="external">这里</a>获取。下载zip文件并在Xcode中打开工程。</p>
<blockquote>
<p>该项目使用CocoaPods,因此需要打开Chocotastic.xcworkspace文件。</p>
</blockquote>
<p>构建并运行程序。你会看到以下效果,其中列出了你可以从欧洲购买的几种巧克力,以及各自的价格:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/initial_landing.png" width="300" align="center"></p>
<p>点击单元格,可以将对应的巧克力添加到你的购物车中:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/added_chocolate.png" width="300" align="center"></p>
<p>点击右上角的购物车就可以进入付款或重置购物车的界面:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/checkout.png" width="300" align="center"></p>
<p>如果点击<code>Checkout</code>,将显示一个信用卡输入页面:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/cc_form.png" width="300" align="center"></p>
<p>在本教程的后面,你将使用纯响应式来解决问题。点击<code>Cart</code>按钮将返回购物车清单,点击<code>Reset</code>按钮将返回主界面,并显示空购物车。</p>
<h3 id="起点:非响应式"><a href="#起点:非响应式" class="headerlink" title="起点:非响应式"></a>起点:非响应式</h3><p>你现在已经知道了程序将做什么,现在该来看看是怎么做的了。打开<code>ChocolatesOfTheWorldViewController.swift</code>文件,在这里你可以看到一些标准的<code>UITableViewDelegate</code>和<code>UITableViewDataSource</code>方法。</p>
<p>还有一个<code>updateCartButton()</code>方法,它用购物车中现有的巧克力数量来更新购物车按钮。这个方法在两个地方被调用:</p>
<ul>
<li>在<code>viewWillAppear(_:)</code>中,即视图控制器即将被显示时;</li>
<li>在<code>tableView(_:didSelectRowAt:)</code>中,即新添加一个巧克力到购物车后。</li>
</ul>
<p>这些都是以命令式方法来修改计数:你必须显示调用方法来更新计数。</p>
<p>目前为止,你必须跟踪你要改变值的位置,不过我们要使用响应式技术来重写这些代码。这样,购物车按钮将自已更新,而不关心计数器在哪和怎样被更新。</p>
<h2 id="RxSwift:让购物车按钮响应"><a href="#RxSwift:让购物车按钮响应" class="headerlink" title="RxSwift:让购物车按钮响应"></a>RxSwift:让购物车按钮响应</h2><p>所有引用购物车中的项目的方法都使用<code>ShoppingCart.sharedCart</code>单例。打开<code>ShoppingCart.swift</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"><span class="keyword">var</span> chocolates = [<span class="type">Chocolate</span>]()</div></pre></td></tr></table></figure>
<p>现在,巧克力内容的变更不会被观察到。你可以在它的定义中添加一个<code>didSet</code>闭包,但它只有在整个数组被更新才会被调用,而不是它的元素被更新时。</p>
<p>幸运的是,RxSwift有一个解决方案。使用下面这行代码来替代变量的创建:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> chocolates: <span class="type">Variable</span><[<span class="type">Chocolate</span>]> = <span class="type">Variable</span>([])</div></pre></td></tr></table></figure>
<blockquote>
<p>这个更改暂时会导致一系列编译错误,不过很快会在下面解决这些问题。</p>
</blockquote>
<p>这种语法可能稍微有点难理解,所以下面我们来慢慢了解到底发生了什么。</p>
<p>在这里我们不是将<code>chocolates</code>设置为<code>Chocolate</code>对象的数组,而将其定义为一个RxSwift的<code>Variable</code>对象,其中泛型类型指定为<code>Chocolate</code>数组。</p>
<p><code>Variable</code>是一个类,所以它使用引用语义–即<code>chocolates</code>引用了一个<code>Variable</code>实例。</p>
<p><code>Variable</code>对象有一个<code>value</code>属性。这是你的<code>Chocolate</code>对象数组的存储位置。</p>
<p><code>Variable</code>的魔力来自于它的<code>asObservable()</code>方法。你可以添加一个<code>Observer</code>来观察这个值,而不是每次手动去确认这个值。当值发生变化时,<code>Observer</code>会通知你,以便你对任何更新做出响应。</p>
<p>这个设置有一个缺点,即当你需要访问或更新<code>Chocolates</code>数组中的元素时,你必须使用<code>value</code>属性,而不能直接使用它;这就是编译器提示错误的原因。是时候来解决它们了。</p>
<p>在<code>ShoppingCart.swift</code>中,找到<code>totalCost()</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"><span class="keyword">return</span> chocolates.<span class="built_in">reduce</span>(<span class="number">0</span>) {</div></pre></td></tr></table></figure>
<p>为:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">return</span> chocolates.value.<span class="built_in">reduce</span>(<span class="number">0</span>) {</div></pre></td></tr></table></figure>
<p>在<code>itemCountString()</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"><span class="keyword">guard</span> chocolates.<span class="built_in">count</span> > <span class="number">0</span> <span class="keyword">else</span> {</div></pre></td></tr></table></figure>
<p>为:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">guard</span> chocolates.value.<span class="built_in">count</span> > <span class="number">0</span> <span class="keyword">else</span> {</div></pre></td></tr></table></figure>
<p>并修改:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> setOfChocolates = <span class="type">Set</span><<span class="type">Chocolate</span>>(chocolates)</div></pre></td></tr></table></figure>
<p>为:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> setOfChocolates = <span class="type">Set</span><<span class="type">Chocolate</span>>(chocolates.value)</div></pre></td></tr></table></figure>
<p>最后,修改:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> <span class="built_in">count</span>: <span class="type">Int</span> = chocolates.<span class="built_in">reduce</span>(<span class="number">0</span>) {</div></pre></td></tr></table></figure>
<p>为:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> <span class="built_in">count</span>: <span class="type">Int</span> = chocolates.value.<span class="built_in">reduce</span>(<span class="number">0</span>) {</div></pre></td></tr></table></figure>
<p>在<code>CartViewController.swift</code>中,找到<code>reset()</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"><span class="type">ShoppingCart</span>.sharedCart.chocolates = []</div></pre></td></tr></table></figure>
<p>为:</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="type">ShoppingCart</span>.sharedCart.chocolates.value = []</div></pre></td></tr></table></figure>
<p>回到<code>ChocolatesOfTheWorldViewController.swift</code>中,修改<code>updateCartButton()</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">cartButton.title = <span class="string">"<span class="subst">\(ShoppingCart.sharedCart.chocolates.value.<span class="built_in">count</span>)</span> \u{1f36b}"</span></div></pre></td></tr></table></figure>
<p>及在<code>tableView(_:didSelectRowAt:)</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"><span class="type">ShoppingCart</span>.sharedCart.chocolates.append(chocolate)</div></pre></td></tr></table></figure>
<p>为:</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="type">ShoppingCart</span>.sharedCart.chocolates.value.append(chocolate)</div></pre></td></tr></table></figure>
<p>哇!这回Xcode高兴了,不再报错了。同时<code>chocolates</code>也可以被监听了。</p>
<p>打开<code>ChocolatesOfTheWorldViewController.swift</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"><span class="keyword">let</span> disposeBag = <span class="type">DisposeBag</span>()</div></pre></td></tr></table></figure>
<p>这里创建了一个<code>DisposeBag</code>对象,用于确保设置的<code>Observer</code>在<code>deinit()</code>中被清理掉。</p>
<p>在 <strong>//MARK: Rx Setup</strong> 注释下面添加以下代码:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//MARK: Rx Setup</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">setupCartObserver</span><span class="params">()</span></span> {</div><div class="line"> <span class="comment">//1</span></div><div class="line"> <span class="type">ShoppingCart</span>.sharedCart.chocolates.asObservable()</div><div class="line"> .subscribe(onNext: { <span class="comment">//2</span></div><div class="line"> chocolates <span class="keyword">in</span></div><div class="line"> <span class="keyword">self</span>.cartButton.title = <span class="string">"<span class="subst">\(chocolates.<span class="built_in">count</span>)</span> \u{1f36b}"</span></div><div class="line"> })</div><div class="line"> .addDisposableTo(disposeBag) <span class="comment">//3</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>这将设置一个响应式的<code>Observer</code>来自动更新购物车。如你所见,RxSwift大量使用链式函数,这意味着每一个函数都接受前一个函数的结果。</p>
<p>来解释一下发生的事情:</p>
<ol>
<li>首先,把购物车的<code>chocolates</code>变量作为一个<code>Observable</code>;</li>
<li>在这个<code>Observable</code>上调用<code>subscribe(onNext:)</code>方法,以了解<code>Observable</code>的值的变化。<code>subscribe(onNext:)</code>接受一个闭包作为参数,在每次值改变时,会执行这个闭包。闭包的传入参数是<code>Observable</code>的新值。你将会接受到变更通知,直到你取消订阅或者你的订阅被丢弃。从这个方法得到的是一个实现了<code>Disposable</code>的<code>Observer</code>对象;</li>
<li>将上一步得到的<code>Observer</code>对象添加到<code>disposeBag</code>中,以确保在订阅对象被释放时你的订阅被丢弃。</li>
</ol>
<p>最后,删除命令式的<code>updateCartButton()</code>方法。这将导致在<code>viewWillAppear(_:)</code>和<code>tableView(_:didSelectRowAt:)</code>中报错。</p>
<p>要修复它们,可以删除整个<code>viewWillAppear(_:)</code>方法(因为这里除了调用super外,调用<code>updateCartButton()</code>方法是它唯一做的事情),并删除<code>tableView(_:didSelectRowAt:)</code>中<code>updateCartButton()</code>方法的调用。</p>
<p>构建并运行。你将看到下面的列表:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/Simulator-Screen-Shot-Jul-4-2016-9.26.45-PM.png" width="300" align="center"></p>
<p>但是注意,购物车的按钮只显示了<code>'Item'</code>。当你开始点击列表时,什么都没有发生。这是咋回事?</p>
<p>你创建了一个函数来设置你的<code>Rx Observers</code>,但是现在并没有实际调用它,所以也并没有设置<code>Observers</code>。要解决这个问题,可以在<code>viewDidLoad()</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">setupCartObserver()</div></pre></td></tr></table></figure>
<p>编译并运行程序,可以看到下面的列表:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/initial_landing.png" width="300" align="center"></p>
<p>点击单元格,购物车中的数量现在可以自动更新了!</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/added_chocolate.png" width="300" align="center"></p>
<p>哈哈,现在所有的巧克力都可以添加到购物车了。</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/rage_chocolate1-1.png" width="300" align="center"></p>
<h2 id="RxCocoa-让TableView响应"><a href="#RxCocoa-让TableView响应" class="headerlink" title="RxCocoa: 让TableView响应"></a>RxCocoa: 让TableView响应</h2><p>现在,你已经使用RxSwift让购物车能响应了,现在将使用RxCocoa让<code>UITableView</code>也能响应。</p>
<p>RxCocoa扩展了UI元素以支持响应式API。这让你可以设置<code>UITableView</code>等视图,而不需要直接重写<code>delegate</code>或<code>data source</code>的方法。</p>
<p>为了演示如何工作,可以删除代码中所有的<code>UITableViewDataSource</code>和<code>UITableViewDelegate</code>协议及所有它们的方法。然后,在<code>viewDidLoad()</code>中删除对<code>tableView.dataSource</code>和<code>tableView.delegate</code>的设置。</p>
<p>编译并运行程序,你可以看到原本让人快乐的小列表没有了,巧克力也没有了:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/no_chocolate_for_you.png" width="300" align="center"></p>
<p>一点都不好玩。现在来把巧克力找回来吧!</p>
<p>首先,为了获得一个响应式的<code>table view</code>,你需要一些让<code>table view</code>响应的东西。在<code>ChocolatesOfTheWorldViewController.swift</code>文件中,更新<code>europeanChocolates</code>属性,让其作为一个<code>Observable</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"><span class="keyword">let</span> europeanChocolates = <span class="type">Observable</span>.just(<span class="type">Chocolate</span>.ofEurope)</div></pre></td></tr></table></figure>
<p><code>just(_:)</code>方法表示不会对<code>Observable</code>对象的底层值做任何修改,但你仍然需要以<code>Observable</code>值的方式来访问它。</p>
<blockquote>
<p>有时,调用<code>just(_:)</code>可能是过度地使用响应式编程了 – 毕竟,如果一个值从不改变,为什么要使用响应式技术呢?在这个例子中,你将使用它来设置将要改变的单元格的响应,不过经常思考如何使用Rx总是件好事。因为虽然你有一个锤子,但不意味着每一个问题都是一个钉子。</p>
</blockquote>
<p>现在你已经让<code>europeanChocolates</code>成为一个<code>Observable</code>,然后添加以下代码:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">setupCellConfiguration</span><span class="params">()</span></span> {</div><div class="line"> <span class="comment">//1</span></div><div class="line"> europeanChocolates</div><div class="line"> .bindTo(tableView</div><div class="line"> .rx <span class="comment">//2</span></div><div class="line"> .items(cellIdentifier: <span class="type">ChocolateCell</span>.<span class="type">Identifier</span>,</div><div class="line"> cellType: <span class="type">ChocolateCell</span>.<span class="keyword">self</span>)) { <span class="comment">// 3</span></div><div class="line"> row, chocolate, cell <span class="keyword">in</span></div><div class="line"> cell.configureWithChocolate(chocolate: chocolate) <span class="comment">//4</span></div><div class="line"> }</div><div class="line"> .addDisposableTo(disposeBag) <span class="comment">//5</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>解释一下:</p>
<ol>
<li>调用<code>bindTo(_:)</code>将<code>europeanChocolates</code> <code>observable</code>关联到应该为<code>table view</code>每一行执行的代码;</li>
<li>调用<code>rx</code>,你可以访问任何类的RxCocoa扩展 – 在这里是UITableView;</li>
<li>调用Rx的<code>items(cellIdentifier:cellType:)</code>方法,传入单元格标识符及要使用的单元格类型。这让Rx框架可以调用出列方法(<code>dequeuing methods</code>),如果你的<code>table view</code>仍然有原始的代理,这些方法也会被正常调用;</li>
<li>传入一个为每个单元格执行的闭包。闭包的参数包括行信息、行对应的<code>chocolate</code>及单元格对象,这样配置单元格就非常容易了;</li>
<li>获取到<code>bindTo(_:)</code>返回的<code>Disposable</code>,然后添加到<code>disposeBag</code>。</li>
</ol>
<p>通常由<code>tableView(_:numberOfRowsInSection:)</code>和<code>numberOfSections(in:)</code>生成的值,现在基于被观察的数据自动计算。<code>tableView(_:cellForRowAt:)</code>方法被闭包所取代。在<code>viewDidLoad()</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">setupCellConfiguration()</div></pre></td></tr></table></figure>
<p>编译并运行程序,瞧,巧克力又回来了。</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/no_chocolate_for_you.png" width="300" align="center"></p>
<p>但是,当点击每一个巧克力时,他们并没有被添加到购物车。又是哪不对了?</p>
<p>也木有,之前只是删除了<code>tableView(_:didSelectRowAt:)</code>,这样就没办法识别单元格点击操作了。</p>
<p>为了解决这个问题,需要使用RxCocoa提供的另一个<code>UITableView</code>的扩展方法:<code>modelSelected(_:)</code>,它返回一个<code>Observable</code>,可以观察模型对象什么时候被选中了。</p>
<p>添加以下代码:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">setupCellTapHandling</span><span class="params">()</span></span> {</div><div class="line"> tableView</div><div class="line"> .rx</div><div class="line"> .modelSelected(<span class="type">Chocolate</span>.<span class="keyword">self</span>) <span class="comment">//1</span></div><div class="line"> .subscribe(onNext: { <span class="comment">//2</span></div><div class="line"> chocolate <span class="keyword">in</span></div><div class="line"> <span class="type">ShoppingCart</span>.sharedCart.chocolates.value.append(chocolate) <span class="comment">//3</span></div><div class="line"> </div><div class="line"> <span class="keyword">if</span> <span class="keyword">let</span> selectedRowIndexPath = <span class="keyword">self</span>.tableView.indexPathForSelectedRow {</div><div class="line"> <span class="keyword">self</span>.tableView.deselectRow(at: selectedRowIndexPath, animated: <span class="literal">true</span>)</div><div class="line"> } <span class="comment">//4</span></div><div class="line"> })</div><div class="line"> .addDisposableTo(disposeBag) <span class="comment">//5</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>再来解释一下:</p>
<ol>
<li>调用<code>table view</code>的响应式方法<code>modelSelected(_:)</code>,传入<code>Chocolate</code>模型以获取项目的正确类型。这个方法返回一个<code>Observable</code>;</li>
<li>获取到<code>Observable</code>后,调用<code>subscribe(onNext:)</code>方法,传入一个尾随闭包,在模型被选中时会调用这个闭包;</li>
<li>在尾随闭包中,将选中的巧克力添加到购物车中;</li>
<li>同样在闭包中,确保当前被点击的单元格选中状态被取消;</li>
<li><code>subscribe(onNext:)</code>返回一个<code>Disposable</code>,添加这个<code>Disposable</code>到<code>disposeBag</code>中。</li>
</ol>
<p>最后,在<code>viewDidLoad()</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">setupCellTapHandling()</div></pre></td></tr></table></figure>
<p>编译并运行,可以看到熟悉的场景:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/initial_landing.png" width="300" align="center"></p>
<p>不过现在你可以往购物车添加巧克力了!</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/added_chocolate.png" width="300" align="center"></p>
<h2 id="RxSwift和Direct-Text-Input"><a href="#RxSwift和Direct-Text-Input" class="headerlink" title="RxSwift和Direct Text Input"></a>RxSwift和Direct Text Input</h2><p>RxSwift另一个功能是能够获取并响应用户的直接文本输入(<code>Direct Text Input</code>)。</p>
<p>为了尝试一下响应式处理文本输入,你将在信用卡输入表单中添加一些简单的验证和卡类型检测。</p>
<p>非响应式的信用卡处理是由一串<code>UITextFieldDelegate</code>方法来实现的,通常每个方法包含一串的<code>if/else</code>语句,用于区分哪个<code>text field</code>正在被编辑,应该执行什么操作和逻辑处理。</p>
<p>响应式编程将处理操作和逻辑操作直接连接到每个<code>text field</code>。</p>
<p>在<code>BillingInfoViewController.swift</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"><span class="keyword">private</span> <span class="keyword">let</span> disposeBag = <span class="type">DisposeBag</span>()</div></pre></td></tr></table></figure>
<p>和前面一样,这里定义了一个<code>DisposeBag</code>以确保在类的实例被释放时,你的<code>Observables</code>能被正确处理。</p>
<p>对于信用卡号的输入,一个体验比较好的方式是给用户显示信用卡的类型。</p>
<p>为了实现这一操作,可以在<strong>//MARK: - Rx Setup</strong>下面添加以下代码:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//MARK: - Rx Setup</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">setupCardImageDisplay</span><span class="params">()</span></span> {</div><div class="line"> cardType</div><div class="line"> .asObservable()</div><div class="line"> .subscribe(onNext: {</div><div class="line"> cardType <span class="keyword">in</span></div><div class="line"> <span class="keyword">self</span>.creditCardImageView.image = cardType.image</div><div class="line"> })</div><div class="line"> .addDisposableTo(disposeBag)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>稍后,你将依此根据卡类型的更改来更新卡图片。它为变量的值添加了一个<code>Observer</code>,并附加一个闭包在值改变时执行,同时确保<code>Observer</code>添加到<code>disposeBag</code>中以被正确处理。</p>
<p>现在到了最有趣的部分:文本变更处理。</p>
<p>由于用户可能会快速键入,因此你可能不希望每次按键都去验证。这样会导致昂贵的计算和UI卡顿。</p>
<p>一种更好的方式是限制验证的幅度,即只有一定的时间间隔后再去验证用户的输入,而不是每次改变时都去处理。这样,再快的打字速度也不会阻塞整个程序的运行。</p>
<p><code>Throttling</code>是RxSwift的一个特性。因为在一些东西改变时,通常有大量的逻辑操作。而使用<code>Throttling</code>特性,不会产生大量的逻辑操作,而是以一个小的合理的幅度去执行。</p>
<p>首先,在<code>BillingInfoViewController</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"><span class="keyword">private</span> <span class="keyword">let</span> throttleInterval = <span class="number">0.1</span></div></pre></td></tr></table></figure>
<p>这里以秒为单位为抖动(<code>throttle</code>)幅度定义了一个常量。</p>
<p>然后添加以下代码:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">setupTextChangeHandling</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">let</span> creditCardValid = creditCardNumberTextField</div><div class="line"> .rx</div><div class="line"> .text <span class="comment">//1</span></div><div class="line"> .throttle(throttleInterval, scheduler: <span class="type">MainScheduler</span>.instance) <span class="comment">//2</span></div><div class="line"> .<span class="built_in">map</span> { <span class="keyword">self</span>.validate(cardText: $<span class="number">0</span>) } <span class="comment">//3</span></div><div class="line"> </div><div class="line"> creditCardValid</div><div class="line"> .subscribe(onNext: { <span class="keyword">self</span>.creditCardNumberTextField.valid = $<span class="number">0</span> }) <span class="comment">//4</span></div><div class="line"> .addDisposableTo(disposeBag) <span class="comment">//5</span></div><div class="line">}</div></pre></td></tr></table></figure>
<blockquote>
<p>如果在设置<code>creditCardValid</code>时得到一个”<code>Generic parameter R could not be inferred</code>“编译错误,通常可以显式的声明它的类型来解决问题,如<code>let creditCardValid: Observable</code>。理论上,编译器能推导出它的类型,但有时候还是需要一些帮助。</p>
</blockquote>
<p>代码的描述如下:</p>
<ol>
<li><code>text</code>是另一个RxCocoa扩展(在使用之前必须先调用<code>rx</code>),这一次是<code>UITextField</code>的扩展。它将<code>text field</code>的内容作为<code>Observable</code>值返回;</li>
<li>限制输入,以便设置的验证基于设置的时间间隔才运行。<code>scheduler</code>参数是一个更高级的概念,它绑定到一个线程。因为你需要在主线程上执行,所以使用<code>MainScheduler</code>;</li>
<li>将被限制的输入应用于<code>validate(cardText:)</code>来转换它,<code>validate(cardText:)</code>由当前类提供。如果输入的卡有效,则观察到的布尔值的最终值为true;</li>
<li>接受所创建的<code>Observable</code>值并订阅它,根据传入的值来更新<code>text field</code>的验证;</li>
<li>将生成的<code>Disposable</code>添加到<code>disposeBag</code>。</li>
</ol>
<p>将以下代码添加到<code>setupTextChangeHandling()</code>方法下面,以创建有效期和卡安全代码(CVV)的<code>Observable</code>变量:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> expirationValid = expirationDateTextField</div><div class="line"> .rx</div><div class="line"> .text</div><div class="line"> .throttle(throttleInterval, scheduler: <span class="type">MainScheduler</span>.instance)</div><div class="line"> .<span class="built_in">map</span> { <span class="keyword">self</span>.validate(expirationDateText: $<span class="number">0</span>) }</div><div class="line"> </div><div class="line">expirationValid</div><div class="line"> .subscribe(onNext: { <span class="keyword">self</span>.expirationDateTextField.valid = $<span class="number">0</span> })</div><div class="line"> .addDisposableTo(disposeBag)</div><div class="line"> </div><div class="line"><span class="keyword">let</span> cvvValid = cvvTextField</div><div class="line"> .rx</div><div class="line"> .text</div><div class="line"> .<span class="built_in">map</span> { <span class="keyword">self</span>.validate(cvvText: $<span class="number">0</span>) }</div><div class="line"> </div><div class="line">cvvValid</div><div class="line"> .subscribe(onNext: { <span class="keyword">self</span>.cvvTextField.valid = $<span class="number">0</span> })</div><div class="line"> .addDisposableTo(disposeBag)</div></pre></td></tr></table></figure>
<p>现在你已经为三个<code>text field</code>的有效性设置了<code>Observable</code>值,接下来添加以下代码:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> everythingValid = <span class="type">Observable</span></div><div class="line"> .combineLatest(creditCardValid, expirationValid, cvvValid) {</div><div class="line"> $<span class="number">0</span> && $<span class="number">1</span> && $<span class="number">2</span> <span class="comment">//All must be true</span></div><div class="line"> }</div><div class="line"> </div><div class="line">everythingValid</div><div class="line"> .bindTo(purchaseButton.rx.enabled)</div><div class="line"> .addDisposableTo(disposeBag)</div></pre></td></tr></table></figure>
<p>这里使用了<code>Observable</code>的<code>combineLatest(_:)</code>方法将前面创建的三个<code>Observable</code>组合成第四个变量,即<code>everythingValid</code>,其值是否为<code>true</code>取决于前面三个输入是否有效。</p>
<p>然后将<code>everythingValid</code>绑定到<code>UIButton</code>的响应扩展的<code>enabled</code>属性上,这样购买按钮的状态就由<code>everythingValid</code>的值来控制了。</p>
<p>如果所有的输入都有效,那么<code>everythingValid</code>的基础值就为<code>true</code>。如果不是,<code>rx.enabled</code>将会导致基础值被应用于购买按钮,该功能仅在信用卡详细信息有效时启用。</p>
<p>现在你已经创建了<code>setup</code>方法,在<code>viewDidLoad()</code>方法中添加以下代码来调用它:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">setupCardImageDisplay()</div><div class="line">setupTextChangeHandling()</div></pre></td></tr></table></figure>
<p>编译并运行程序。要进入信用卡输入页面,需要选择一个巧克力将其添加到购物车,然后点击购物车按钮以进入购物车界面。只要购物车中至少有一个巧克力,<code>checkout</code>按钮就是可用的:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/checkout.png" width="300" align="center"></p>
<p>点击<code>Checkout</code>按钮,将进入信用卡输入页面:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/cc_form.png" width="300" align="center"></p>
<p>在卡号<code>text field</code>中输入<code>4</code>–你将看到卡图片显示了<code>Visa</code>的图标:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/Simulator-Screen-Shot-Jul-4-2016-7.47.40-PM.png" width="300" align="center"></p>
<p>删除<code>4</code>,卡图片将恢复未知状态。输入<code>55</code>,图片将变成<code>MasterCard</code>:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/Simulator-Screen-Shot-Jul-4-2016-7.47.36-PM.png" width="300" align="center"></p>
<p>这个应用涵盖了美国的四种主要信用卡(Visa, MasterCard, American Express, Discover)。如果有有其中一种卡,你可以输入卡号来看看图片是否正确以及卡号是否有效。</p>
<blockquote>
<p>如果你没有这些信用卡,你可以使用Paypal用于测试其沙盒的测试卡卡号,这些应该可以通过程序的所有本地验证,即使卡号实际上是不可用的。</p>
</blockquote>
<p>一旦输入有效的信用卡卡号,同时有效期和cvv也是有效的,那么<code>Buy Chocolate!</code>按钮将被启用:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/enabled_checkout.png" width="300" align="center"></p>
<p>点击按钮查看购买的内容及如何付款的摘要:</p>
<p><img src="https://koenig-media.raywenderlich.com/uploads/2016/07/success.png" width="300" align="center"></p>
<p>恭喜你!感谢RxSwift和RxCocoa,你可以买到你的巧克力,想要多少要多少,等你的牙医让你离开为止。</p>
<h2 id="下一步做什么"><a href="#下一步做什么" class="headerlink" title="下一步做什么"></a>下一步做什么</h2><p>最终的代码可以在<a href="https://koenig-media.raywenderlich.com/uploads/2016/10/Chocotastic-finished-s3-rxs-3b1.zip" target="_blank" rel="external">这里</a>找到。</p>
<p>如果你想挑战一下,可以尝试添加一些东西,使这个程序更具响应式:</p>
<ul>
<li>更改<code>CartViewController</code>,以使用响应式<code>table view</code>来显示购物车的内容;</li>
<li>允许用户直接从购物车添加或删除巧克力,并自动更新价格。</li>
</ul>
<p>现在你已经尝试了Rx编程,以下是一些资源,以帮助你继续你的旅程:</p>
<ol>
<li><a href="http://slack.rxswift.org/" target="_blank" rel="external">RxSwift Slack</a></li>
<li><a href="https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md" target="_blank" rel="external">RxSwift’s Getting Started guide</a></li>
<li><a href="https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa" target="_blank" rel="external">Max Alexander’s talk on Rx at Realm</a></li>
</ol>
<p>最后,我们的<a href="https://www.raywenderlich.com/u/icanzilb" target="_blank" rel="external">Marin Todorov</a>有一个不错的博客,里面有他的<a href="http://rx-marin.com/" target="_blank" rel="external">rx_marin</a>响应式编程。</p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/09/23/ios-techset-9/" itemprop="url">
iOS知识小集 第9期(2016.09.23)
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2016-09-23T19:06:37+08:00" content="2016-09-23">
2016-09-23
</time>
</span>
<span class="post-category" >
|
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="https://schema.org/Thing">
<a href="/categories/techset/" itemprop="url" rel="index">
<span itemprop="name">知识小集</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>还有一个星期,一个星期……就是十一长假了,想想还是很激动的。可是我的iPhone 7还是没有着落,哎,想想还是很桑梓啊。什么时候能把这事给办了?</p>
<p>这期换个法吧,无规则有主题,发个关于Instruments的合集。Instruments是我们查找问题和调做强不可缺少的工具,也很强大。所有抽时间把文档撸了一遍,写了几条知识小集,不过还有些没发出来。这期先把之前发的整理整理吧,主要有以下5个问题:</p>
<ol>
<li>使用Instruments检测僵尸对象;</li>
<li>Xcode的Debug navigator中打开Instruments;</li>
<li>Instruments无线Profile;</li>
<li>Instruments访问多次运行的跟踪数据</li>
<li>Abandoned Memory和Generational Analysis</li>
</ol>
<h2 id="使用Instruments检测僵尸对象"><a href="#使用Instruments检测僵尸对象" class="headerlink" title="使用Instruments检测僵尸对象"></a>使用Instruments检测僵尸对象</h2><p>Instruments为我们提供了一个检测僵尸对象的工具:Zombies。使用这个工具时,将会自动开启Enable Zombie Objects模式,而不需要我们自己手动去设置。</p>
<p>我们以下图这段简单的代码为例,点击Product->Profile,启动Instrument。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Zombies%20profiling%201.png?raw=true" alt=""></p>
<p>如下图所示,我们可以看到”Zombies”这个工具。基本操作和其它工具一样,启动后点击工具栏上的红色按钮来启动程序。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Zombies%20profiling%202.png?raw=true" alt=""></p>
<p>在程序运行期间,如果定位到僵尸对象,则会弹出一个提示对话框,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Zombies%20profiling%203.png?raw=true" alt=""></p>
<p>我们可以点击对话框右侧的箭头来定位到具体的代码及调用栈,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Zombies%20profiling%204.png?raw=true" alt=""></p>
<p>双击调用栈对应的方法后,还可以查看具体的代码,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Zombies%20profiling%205.png?raw=true" alt=""></p>
<p>实际上,我们用Allocations工具也可以检测僵尸对象,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Zombies%20profiling%206.png?raw=true" alt=""></p>
<p>我们在属性面板中勾选”Enable NSZombie detection”,其效果和单独使用Zombies工具是一样的。</p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/EradicatingZombies.html" target="_blank" rel="external">Find Zombies</a></li>
</ol>
<h2 id="Xcode的Debug-navigator中打开Instruments"><a href="#Xcode的Debug-navigator中打开Instruments" class="headerlink" title="Xcode的Debug navigator中打开Instruments"></a>Xcode的Debug navigator中打开Instruments</h2><p>Xcode的Debug navigator中提供了几个计量器来帮助我们跟踪程序的性能,包括CPU、内存、电量等。如图1和2所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/debug%20navigator%20to%20instruments%201.png?raw=true" alt=""></p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/debug%20navigator%20to%20instruments%202.png?raw=true" alt=""></p>
<p>在每个计量器的详情面板中的右上角,都提供了一个Profile in Instruments按钮,如图2所示(Energy Impact除外,其在面板详情中有几个按钮直接打开Instruments指定的模板,如下图所示),这些按钮可以让我们直接跳转到Instruments中。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/debug%20navigator%20to%20instruments%203.png?raw=true" alt=""></p>
<p>在点击这些按钮时,会弹出一个提示框,提示“Transfer current debug session?”,下面三个按钮,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/debug%20navigator%20to%20instruments%204.png?raw=true" alt=""></p>
<p>Transfer会在程序当前的运行状态中直接切换到Instruments,然后继续跟踪程序的运行状态;而Restart则是关闭当前运行的程序,重新开始一次新的Profile。</p>
<p>不过,这两种情况都会关闭当前的性能分析(profiling),启动Instruments,初始一个新的性能分析。</p>
<h3 id="参考-1"><a href="#参考-1" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="https://developer.apple.com/library/tvos/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Recording,Pausing,andStoppingTraces.html#//apple_ref/doc/uid/TP40004652-CH12-SW1" target="_blank" rel="external">Instruments User Guide</a></li>
</ol>
<h2 id="Instruments无线Profile"><a href="#Instruments无线Profile" class="headerlink" title="Instruments无线Profile"></a>Instruments无线Profile</h2><p>Instruments支持无线的Profile,即设备不需要通过Lighting线连接到Mac电脑,即可进行性能分析。这对于需要使用加速器或者外接配件的应用来说非常有用。如下图所示。</p>
<p><img src="https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Art/instruments_targets_enablewireless_2x.png" alt=""></p>
<p>不过要使用此功能,需要满足两个条件:设备必须是注册过的开发设备;无线网络必须支持Bonjour和多路广播(multicast)。</p>
<p>当然,还需要做一些基本配置,可参考<a href="https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/WorkingwithTargets.html#//apple_ref/doc/uid/TP40004652-CH10-SW1" target="_blank" rel="external">Instruments User Guide:Target Devices and Processes</a>。</p>
<p>上周五捣鼓了一会,木有成功,后来发现是网络不支持。</p>
<h2 id="Instruments访问多次运行的跟踪数据"><a href="#Instruments访问多次运行的跟踪数据" class="headerlink" title="Instruments访问多次运行的跟踪数据"></a>Instruments访问多次运行的跟踪数据</h2><p>Instruments在一次运行期间可以记录App的多次运行记录。以Allocations为例,开启Instruments后,每结束一次Allocations分析,这条分析就会被记录下来,下次再开启分析时,我们仍然可以看到前一次分析的信息,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Access%20Trace%20Data%20for%20Multiple%20Runs.png?raw=true" alt=""></p>
<p>通过这些记录,我们可以对比每次分析的差别。这样我们就可以边修改程序,边用Instruments来对其进行分析,并通过这种对比来观察修改的效果。</p>
<p>当然,关闭Instruments时,如果不保存信息,这些记录会被清理掉。</p>
<h3 id="参考-2"><a href="#参考-2" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/NavigatingtheTimelinePane.html#//apple_ref/doc/uid/TP40004652-CH85-SW1" target="_blank" rel="external">Instruments User Guide:Navigate the Timeline Pane</a></li>
</ol>
<h2 id="Abandoned-Memory和Generational-Analysis"><a href="#Abandoned-Memory和Generational-Analysis" class="headerlink" title="Abandoned Memory和Generational Analysis"></a>Abandoned Memory和Generational Analysis</h2><p>说到内存问题,我们更多的会想到内存泄露和野指针,而实际上还有一类看似不是问题的内存问题:Abandoned Memory(被遗弃的内存)。这类内存可能由于某些原因被分配,但并非一直需要,只是可能在程序运行期的某个时间需要,如内存缓存的图片,还有一个比较普遍的东西–单例。</p>
<p>我们可能会为某个模块创建一个单例对象来维护这个模块所需要的数据,但在退出模块后,这个单例对象依然存在。与内存泄露不同,这些对象从技术上讲依然是有效的。但实际上可能在程序后续的运行中不会再被使用。</p>
<p>使用Instruments定位内存问题,内存泄露和野指针的定位相对来说容易些,内存泄露使用Leaks,野指针则可以使用僵尸对象。而Abandoned Memory则相对不那么明显。Abandoned Memory可以采用所谓的Generational Analysis方法来分析,即反复进入退出某一场景,查看内存的分配与释放情况,以定位哪些对象是属于Abandoned Memory的范畴。</p>
<p>在Allocations工具中,有专门的Generational Analysis设置,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Abandoned%20Memory%201.png?raw=true" alt=""></p>
<p>我们可以在程序运行时,在进入某个模块前标记一个Generation,这样会生成一个快照。然后进入、退出,再标记一个Generation,如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Abandoned%20Memory%202.png?raw=true" alt=""></p>
<p>在详情面板中我们可以看到两个Generation间内存的增长情况,其中就可能存在潜在的被遗弃的对象,如下图所示。定位到问题,即可做相应的优化。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/Abandoned%20Memory%203.png?raw=true" alt=""></p>
<h3 id="参考-3"><a href="#参考-3" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/FindingAbandonedMemory.html#//apple_ref/doc/uid/TP40004652-CH80-SW1" target="_blank" rel="external">Find Abandoned Memory</a></li>
<li><a href="https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/CommonMemoryProblems.html#//apple_ref/doc/uid/TP40004652-CH91-SW1" target="_blank" rel="external">About Memory Analysis</a></li>
</ol>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>Instruments是一个强大的分析工具,其基于DTrace,为我们提供了丰富的功能。在实际开发中,采用正确的姿式来使用Instruments,可以帮我们提高程序的性能、稳定性等。相信大家都经常用三件套:Time Profile、Allocations、Leaks。当然,其它还有很多模板,也可以多去试试。</p>
<hr>
<p>欢迎关注我的微信公众号:iOS知识小集,扫扫左边站点概览里的二维码就OK了,还有微博:<a href="http://weibo.com/touristdiary" target="_blank" rel="external">@南峰子_老驴</a>。</p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/09/20/ios-techset-8/" itemprop="url">
iOS知识小集 第8期(2016.09.20)
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2016-09-20T22:38:43+08:00" content="2016-09-20">
2016-09-20
</time>
</span>
<span class="post-category" >
|
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="https://schema.org/Thing">
<a href="/categories/techset/" itemprop="url" rel="index">
<span itemprop="name">知识小集</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>今年的Apple发布会也开完了,没有什么太出彩的地方。不过广受非议的iPhone 7依然大卖。群里、微信里都是各种讨论外加各种炫,而我只能静静地看着,等着公司的测试机了。</p>
<p>每次都感叹时间过得快,总是有各种事情,这一晃又三个星期了,哎。这期整理了之前的5个问题,无规则无主题,大伙慢慢看:</p>
<ol>
<li>block未判空导致的EXC_BAD_ACCESS崩溃;</li>
<li>多Target开发;</li>
<li>dispatch_sync导致死锁;</li>
<li>makeObjectsPerformSelector:;</li>
<li>NSSetUncaughtExceptionHandler</li>
</ol>
<h2 id="block未判空导致的EXC-BAD-ACCESS崩溃"><a href="#block未判空导致的EXC-BAD-ACCESS崩溃" class="headerlink" title="block未判空导致的EXC_BAD_ACCESS崩溃"></a>block未判空导致的EXC_BAD_ACCESS崩溃</h2><p>我们在调用block时,如果这个block为nil,则程序会崩溃,报类似于EXC_BAD_ACCESS(code=1, address=0xc)异常[32位下的结果,如果是64位,则address=0x10]。如下图所示,这个异常表示程序在试图读取内存地址0xc的信息时出错。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/block.EXC_BAD_ACCESS.png?raw=true" alt=""></p>
<p>在定义一个block时,编译器会在栈上创建一个结构体,类似于图2的结构体。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> Block_layout {</div><div class="line"> <span class="keyword">void</span> *isa;</div><div class="line"> <span class="keyword">int</span> flags;</div><div class="line"> <span class="keyword">int</span> reserved;</div><div class="line"> <span class="keyword">void</span> (*invoke)(<span class="keyword">void</span> *, ...);</div><div class="line"> <span class="keyword">struct</span> Block_descriptor *descriptor;</div><div class="line"> <span class="comment">/* Imported variables. */</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>block就是指向这个结构体的指针。其中的invoke就是指向具体实现的函数指针。当block被调用时,程序最终会跳转到这个函数指针指向的代码区。而当block为nil时,程序就会试图去读取0xc地址的信息,而这个地址什么都不会有(duff address),于是抛出一个segmentation fault。在32位系统下,之所以是0xc,是因为invoke前面的三个成员变量的大小正好是12。</p>
<p>所以我们在使用block时,应该首先去判断block是否为空。一种比较优雅的写法是:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">!block ?: block()</div></pre></td></tr></table></figure>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="http://stackoverflow.com/questions/4145164/why-do-nil-null-blocks-cause-bus-errors-when-run" target="_blank" rel="external">Why do nil / NULL blocks cause bus errors when run?</a></li>
</ol>
<h2 id="多Target开发"><a href="#多Target开发" class="headerlink" title="多Target开发"></a>多Target开发</h2><p>在Xcode中,一个target表示工程中的一个product,target用于组织product所需要的源文件、资源文件、配置信息等。</p>
<p>在一些情况下,我们可以为一个工程设置多个target,如:同时开发Lite版和正式版;开发版本和发布版本需要不同配置;单工程构建多个相似的App等等。如下图所示。</p>
<p><img src="https://github.com/southpeak/Blog-images/blob/master/target.mutiply.png?raw=true" alt=""></p>
<p>这么做的好处是在共用一份代码的情况下,可以为不同的target配置不同的资源、信息等,如不同的Info.plist, Build Setting, Build Phase配置等,最后得到不同的product。</p>
<h3 id="参考-1"><a href="#参考-1" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="https://developer.apple.com/library/ios/featuredarticles/XcodeConcepts/Concept-Targets.html" target="_blank" rel="external">Xcode Target</a></li>
<li><a href="http://blog.devtang.com/2013/10/17/the-tech-detail-of-ape-client-1/" target="_blank" rel="external">猿题库iOS客户端的技术细节(一):使用多target来构建大量相似App</a></li>
</ol>
<h2 id="dispatch-sync导致死锁"><a href="#dispatch-sync导致死锁" class="headerlink" title="dispatch_sync导致死锁"></a>dispatch_sync导致死锁</h2><p>dispatch_sync函数用于将一个block提交到队列中同步执行,直到block执行完后,这个函数才会返回。</p>
<p>这里有一个潜在的问题,如果我们在某个串行队列中调用dispatch_sync,并将其block提交到这个串行队列中执行,则会引发死锁。如下代码所示。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 死锁</span></div><div class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.apple.test"</span>, <span class="literal">NULL</span>);</div><div class="line"><span class="built_in">dispatch_async</span>(queue, ^{</div><div class="line"> </div><div class="line"> <span class="built_in">dispatch_sync</span>(queue, ^{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"B"</span>);</div><div class="line"> });</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"A"</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>其实还是很好理解,在com.apple.test这个串行队列中,我们执行一个task A,在这个task A中,我们又向队列提交了一个同步的task B。由于是串行队列,task A在task B之前,所以task B的执行依赖于task A的完成,而task B又包含在task A中,task A的完成依赖于task B的完成。这样就成了一个死锁。</p>
<p>所以,千万不要在主队列中这样调用dispatch_sync,否则会导致主线程卡死。</p>
<p>当然,如果在并行队列中这样使用是没有问题的,如下代码所示,可以正常打印出B,A。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>);</div><div class="line"><span class="built_in">dispatch_async</span>(queue, ^{</div><div class="line"> </div><div class="line"> <span class="built_in">dispatch_sync</span>(queue, ^{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"B"</span>);</div><div class="line"> });</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"A"</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>又或是dispatch_sync的目标队列不是当前队列,如下代码所示,也可以正常打印出B,A。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">dispatch_queue_t</span> queue1 = dispatch_queue_create(<span class="string">"com.apple.test1"</span>, <span class="literal">NULL</span>);</div><div class="line"><span class="built_in">dispatch_queue_t</span> queue2 = dispatch_queue_create(<span class="string">"com.apple.test2"</span>, <span class="literal">NULL</span>);</div><div class="line"><span class="built_in">dispatch_async</span>(queue1, ^{</div><div class="line"> </div><div class="line"> <span class="built_in">dispatch_sync</span>(queue2, ^{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"B"</span>);</div><div class="line"> });</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"A"</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>我们在使用dispatch_sync提交task时,可以看到大部分情况下task是在dispatch_sync所在的上下文线程中执行,而不管dispatch_sync指定的队列是什么【串行或并行】,如下代码所示:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 串行队列</span></div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"%@"</span>, [<span class="built_in">NSThread</span> currentThread]); <span class="comment">// <NSThread: 0x100303310>{number = 1, name = main}</span></div><div class="line"> </div><div class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.apple.test"</span>, <span class="literal">NULL</span>);</div><div class="line"> </div><div class="line"><span class="built_in">dispatch_sync</span>(queue, ^{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>, [<span class="built_in">NSThread</span> currentThread]); <span class="comment">// <NSThread: 0x100303310>{number = 1, name = main}</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">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^{</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>, [<span class="built_in">NSThread</span> currentThread]); <span class="comment">// <NSThread: 0x100505ea0>{number = 2, name = (null)}</span></div><div class="line"> </div><div class="line"> <span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">"com.apple.test"</span>, <span class="literal">NULL</span>);</div><div class="line"> </div><div class="line"> <span class="built_in">dispatch_sync</span>(queue, ^{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>, [<span class="built_in">NSThread</span> currentThread]); <span class="comment">// <NSThread: 0x100505ea0>{number = 2, name = (null)}</span></div><div class="line"> });</div><div class="line">});</div></pre></td></tr></table></figure>
<p>官方文档给我们的解释是这么做的目的是为了优化性能:</p>
<blockquote>
<p>As an optimization, this function invokes the block on the current thread when possible。</p>
</blockquote>
<p>我们需要了解的是队列和线程并不是一回事。我们将任务以block的形式提交到队列,然后由GCD来决定将队列中的block分发到系统管理的线程池中的某个线程中去执行。</p>
<h3 id="参考-2"><a href="#参考-2" class="headerlink" title="参考"></a>参考</h3><ol>
<li><a href="https://developer.apple.com/reference/dispatch/1452870-dispatch_sync" target="_blank" rel="external">dispatch_sync</a></li>
</ol>
<h2 id="makeObjectsPerformSelector"><a href="#makeObjectsPerformSelector" class="headerlink" title="makeObjectsPerformSelector:"></a>makeObjectsPerformSelector:</h2><p>遍历一个数组的方法有几种,for, forin, enumerateObjectsUsingBlock:方法。现在用得比较多的可能是enumerateObjectsUsingBlock:,它能很方便地让我们获取到数组中的元素及对应的索引,然后根据这些信息做一些操作,如下代码所示:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSMutableArray</span> *array = [[<span class="built_in">NSMutableArray</span> alloc] init];</div><div class="line"><span class="keyword">for</span> (<span class="built_in">NSInteger</span> index = <span class="number">0</span>; index < <span class="number">10</span>; index++) {</div><div class="line"> </div><div class="line"> Test *t = [[Test alloc] init];</div><div class="line"> t.index = index;</div><div class="line"> [array addObject:t];</div><div class="line">}</div><div class="line"> </div><div class="line">[array enumerateObjectsUsingBlock:^(Test * _Nonnull obj, <span class="built_in">NSUInteger</span> idx, <span class="built_in">BOOL</span> * _Nonnull stop) {</div><div class="line"> [obj test];</div><div class="line">}];</div></pre></td></tr></table></figure>
<p>不过,如果在循环中,只是想调用元素的某一个方法,则可以考虑使用makeObjectsPerformSelector:或者makeObjectsPerformSelector:withObject:,这两个方法会按元素的顺序向数组中的每个元素发送Selector指定的消息。如下代码所示:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSMutableArray</span> *array = [[<span class="built_in">NSMutableArray</span> alloc] init];</div><div class="line"><span class="keyword">for</span> (<span class="built_in">NSInteger</span> index = <span class="number">0</span>; index < <span class="number">10</span>; index++) {</div><div class="line"> </div><div class="line"> Test *t = [[Test alloc] init];</div><div class="line"> t.index = index;</div><div class="line"> [array addObject:t];</div><div class="line">}</div><div class="line"> </div><div class="line">[array makeObjectsPerformSelector:<span class="keyword">@selector</span>(test)];</div><div class="line">[array makeObjectsPerformSelector:<span class="keyword">@selector</span>(testWithNumber:) withObject:@<span class="number">10</span>];</div></pre></td></tr></table></figure>
<p>当然,Selector不能是NULL,否则会抛NSInvalidArgumentException异常。大家如果熟悉runtime的话,应该知道消息机制是如何处理调用不存在方法的。</p>
<h2 id="NSSetUncaughtExceptionHandler"><a href="#NSSetUncaughtExceptionHandler" class="headerlink" title="NSSetUncaughtExceptionHandler"></a>NSSetUncaughtExceptionHandler</h2><p>Foundation里面提供了一个NSSetUncaughtExceptionHandler函数,可以设置一个顶层异常处理函数,让我们在程序发生异常并终止前,有最后的机会来捕获并输出异常信息,如下代码所示。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">void</span> UncaughtExceptionHandler(<span class="built_in">NSException</span> *exception) {</div><div class="line"> </div><div class="line"> <span class="built_in">NSArray</span> *symbols = [exception callStackSymbols];</div><div class="line"> <span class="built_in">NSString</span> *reason = [exception reason];</div><div class="line"> <span class="built_in">NSString</span> *name = [exception name];</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"reason = %@"</span>, reason);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"name = %@"</span>, name);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"symbols = %@"</span>, symbols);</div><div class="line">}</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"></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="built_in">NSSetUncaughtExceptionHandler</span>(&UncaughtExceptionHandler);</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="keyword">@end</span></div></pre></td></tr></table></figure>
<p>这个函数的参数是一个函数指针,指向的函数其签名是:void NSUncaughtExceptionHandler(NSException *exception)。可以看到这个函数有参数是一个NSException对象,通过这个对象我们就可以获取到异常的信息。假定发生数组越界异常时,会有如下输出。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="number">2016</span><span class="number">-09</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">55</span>:<span class="number">36.719</span> Test111[<span class="number">5548</span>:<span class="number">199035</span>] reason = *** -[__NSSingleObjectArrayI objectAtIndex:]: index <span class="number">10</span> beyond bounds [<span class="number">0</span> .. <span class="number">0</span>]</div><div class="line"><span class="number">2016</span><span class="number">-09</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">55</span>:<span class="number">36.720</span> Test111[<span class="number">5548</span>:<span class="number">199035</span>] name = <span class="built_in">NSRangeException</span></div><div class="line"><span class="number">2016</span><span class="number">-09</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">55</span>:<span class="number">36.720</span> Test111[<span class="number">5548</span>:<span class="number">199035</span>] symbols = (</div><div class="line"> <span class="number">0</span> CoreFoundation <span class="number">0x0000000106cef34b</span> __exceptionPreprocess + <span class="number">171</span></div><div class="line"> <span class="number">1</span> libobjc.A.dylib <span class="number">0x000000010675021e</span> objc_exception_throw + <span class="number">48</span></div><div class="line"> <span class="number">2</span> CoreFoundation <span class="number">0x0000000106d47bdf</span> -[__NSSingleObjectArrayI objectAtIndex:] + <span class="number">111</span></div><div class="line"> <span class="number">3</span> Test111 <span class="number">0x000000010617d87b</span> -[AppDelegate application:didFinishLaunchingWithOptions:] + <span class="number">235</span></div><div class="line"> <span class="number">4</span> <span class="built_in">UIKit</span> <span class="number">0x000000010710968e</span> -[<span class="built_in">UIApplication</span> _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + <span class="number">290</span></div><div class="line"> <span class="number">5</span> <span class="built_in">UIKit</span> <span class="number">0x000000010710b013</span> -[<span class="built_in">UIApplication</span> _callInitializationDelegatesForMainScene:transitionContext:] + <span class="number">4236</span></div><div class="line"> <span class="number">6</span> <span class="built_in">UIKit</span> <span class="number">0x00000001071113b9</span> -[<span class="built_in">UIApplication</span> _runWithMainScene:transitionContext:completion:] + <span class="number">1731</span></div><div class="line"> <span class="number">7</span> <span class="built_in">UIKit</span> <span class="number">0x000000010710e539</span> -[<span class="built_in">UIApplication</span> workspaceDidEndTransaction:] + <span class="number">188</span></div><div class="line"> <span class="number">8</span> FrontBoardServices <span class="number">0x000000010a2ee76b</span> __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + <span class="number">24</span></div><div class="line"> <span class="number">9</span> FrontBoardServices <span class="number">0x000000010a2ee5e4</span> -[FBSSerialQueue _performNext] + <span class="number">189</span></div><div class="line"> <span class="number">10</span> FrontBoardServices <span class="number">0x000000010a2ee96d</span> -[FBSSerialQueue _performNextFromRunLoopSource] + <span class="number">45</span></div><div class="line"> <span class="number">11</span> CoreFoundation <span class="number">0x0000000106c94311</span> __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + <span class="number">17</span></div><div class="line"> <span class="number">12</span> CoreFoundation <span class="number">0x0000000106c7959c</span> __CFRunLoopDoSources0 + <span class="number">556</span></div><div class="line"> <span class="number">13</span> CoreFoundation <span class="number">0x0000000106c78a86</span> __CFRunLoopRun + <span class="number">918</span></div><div class="line"> <span class="number">14</span> CoreFoundation <span class="number">0x0000000106c78494</span> <span class="built_in">CFRunLoopRunSpecific</span> + <span class="number">420</span></div><div class="line"> <span class="number">15</span> <span class="built_in">UIKit</span> <span class="number">0x000000010710cdb6</span> -[<span class="built_in">UIApplication</span> _run] + <span class="number">434</span></div><div class="line"> <span class="number">16</span> <span class="built_in">UIKit</span> <span class="number">0x0000000107112f34</span> <span class="built_in">UIApplicationMain</span> + <span class="number">159</span></div><div class="line"> <span class="number">17</span> Test111 <span class="number">0x000000010617db4f</span> main + <span class="number">111</span></div><div class="line"> <span class="number">18</span> libdyld.dylib <span class="number">0x000000010928a68d</span> start + <span class="number">1</span></div><div class="line"> <span class="number">19</span> ??? <span class="number">0x0000000000000001</span> <span class="number">0x0</span> + <span class="number">1</span></div><div class="line">)</div></pre></td></tr></table></figure>
<p>不过这个函数有效范围局限于异常,还有很多错误是无法处理的,如EXC_BAD_ACCESS内存访问错误,这类错误抛出的是Signal,需要专门做Signal处理。</p>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>Crash始终是我们开发最大最头疼的问题,总会有各种各样的Crash情况出现。看着Fabric里面长长的Crash列表,总是很伤感的。我们的成长史也是一部和Bug战斗的斗争史,自己写的Bug,熬夜也要把它们搞完。继续战斗吧,Bug君。</p>
<hr>
<p>欢迎关注我的微信公众号:iOS知识小集,扫扫左边站点概览里的二维码就OK了。对了,还有微博:<a href="http://weibo.com/touristdiary" target="_blank" rel="external">@南峰子_老驴</a>。</p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">