-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1512 lines (1351 loc) · 513 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># TODO:</title>
<subtitle>学有所获,习有所得</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2018-04-16T13:30:00.835Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>ifelseboyxx</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>iOS 消息发送与转发详解</title>
<link href="http://yoursite.com/2018/04/16/message-send/"/>
<id>http://yoursite.com/2018/04/16/message-send/</id>
<published>2018-04-16T13:29:23.000Z</published>
<updated>2018-04-16T13:30:00.835Z</updated>
<content type="html"><![CDATA[<p>Objective-C 是一门动态语言,它将很多静态语言在编译和链接时期做的事情,放到了运行时来处理。之所以能具备这种特性,离不开 Runtime 这个库。Runtime 很好的解决了如何在运行时期找到调用方法这样的问题。</p>
<h2 id="消息发送"><a href="#消息发送" class="headerlink" title="消息发送"></a>消息发送</h2><p>在 Objective-C 中,方法调用称为<strong>向对象发送消息</strong>:</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></pre></td><td class="code"><pre><div class="line"><span class="comment">// MyClass 类</span></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">MyClass</span>: <span class="title">NSObject</span></span></div><div class="line">- (<span class="keyword">void</span>)printLog;</div><div class="line"><span class="keyword">@end</span></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MyClass</span></span></div><div class="line">- (<span class="keyword">void</span>)printLog {</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"print log !"</span>);</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line">MyClass *myClass = [[MyClass alloc] init];</div><div class="line">[myClass printLog];</div><div class="line"></div><div class="line"><span class="comment">// 输出: print log !</span></div></pre></td></tr></table></figure>
<p>上面代码中的 <code>[myClass printLog]</code> 也可以这么写:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">((<span class="keyword">void</span> (*)(<span class="keyword">id</span>, SEL))(<span class="keyword">void</span> *) objc_msgSend)(myClass, <span class="keyword">@selector</span>(printLog));</div></pre></td></tr></table></figure>
<p><code>[myClass printLog]</code> 经过编译后就是调用 <code>objc_msgSend</code> 方法。</p>
<p>我们看看这个方法的文档定义:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">id</span> objc_msgSend(<span class="keyword">id</span> <span class="keyword">self</span>, SEL op, ...);</div></pre></td></tr></table></figure>
<blockquote>
<p>self:消息的接收者<br>op: 消息的方法名,C 字符串<br>… :参数列表</p>
</blockquote>
<h3 id="Runtime-是如何找到实例方法的具体实现的?"><a href="#Runtime-是如何找到实例方法的具体实现的?" class="headerlink" title="Runtime 是如何找到实例方法的具体实现的?"></a>Runtime 是如何找到实例方法的具体实现的?</h3><h4 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h4><p>讲之前,我们需要先明白一些基础概念:Objective-C 是一门面向对象的语言,对象又分为<strong>实例对象</strong>、<strong>类对象</strong>、<strong>元类对象</strong>以及<strong>根元类对象</strong>。它们是通过一个叫 <code>isa</code> 的指针来关联起来,具体关系如下图:</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/3/13/1621e9a878046e51?w=625&h=656&f=png&s=162531" alt=""></p>
<p>以我们上文的代码为例:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">MyClass *myClass = [[MyClass alloc] init];</div></pre></td></tr></table></figure>
<p>整理下相互间的关系:</p>
<ul>
<li><code>myClass</code> 是实例对象</li>
<li><code>MyClass</code> 是类对象</li>
<li><code>MyClass</code> 的元类就是 <code>NSObject</code> 的元类</li>
<li><code>NSObject</code> 就是 Root class (class)</li>
<li><code>NSObject</code> 的 <code>superclass</code> 为 <code>nil</code></li>
<li><code>NSObject</code> 的元类就是它<strong>自己</strong></li>
<li><code>NSObject</code> 的元类的 <code>superclass</code> 就是 <code>NSObject</code></li>
</ul>
<p>对应上图中的位置关系如下:</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/3/13/1621e9a878bc2d10?w=2500&h=2624&f=jpeg&s=437203" alt=""></p>
<p>接着,我们用代码来验证下上文的关系:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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></pre></td><td class="code"><pre><div class="line">MyClass *myClass = [[MyClass alloc] init];</div><div class="line"></div><div class="line">Class <span class="keyword">class</span> = [myClass <span class="keyword">class</span>];</div><div class="line">Class metaClass = object_getClass(<span class="keyword">class</span>);</div><div class="line">Class metaOfMetaClass = object_getClass(metaClass);</div><div class="line">Class rootMetaClass = object_getClass(metaOfMetaClass);</div><div class="line">Class superclass = class_getSuperclass(<span class="keyword">class</span>);</div><div class="line">Class superOfSuperclass = class_getSuperclass(superclass);</div><div class="line">Class superOfMetaOfSuperclass = class_getSuperclass(object_getClass(superclass));</div><div class="line"></div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 实例对象是:%p"</span>,myClass);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 类对象是:%p"</span>,<span class="keyword">class</span>);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 元类对象是:%p"</span>,metaClass);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 元类对象的元类对象是:%p"</span>,metaOfMetaClass);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 根元类对象是:%p"</span>,rootMetaClass);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 父类是:%@"</span>,class_getSuperclass(<span class="keyword">class</span>));</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 父类的父类是:%@"</span>,superOfSuperclass);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"MyClass 父类的元类的父类是:%@"</span>,superOfMetaOfSuperclass);</div><div class="line"></div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"NSObject 元类对象是:%p"</span>,object_getClass([<span class="built_in">NSObject</span> <span class="keyword">class</span>]));</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"NSObject 父类是:%@"</span>,[[<span class="built_in">NSObject</span> <span class="keyword">class</span>] superclass]);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"NSObject 元类对象的父类是:%@"</span>,[object_getClass([<span class="built_in">NSObject</span> <span class="keyword">class</span>]) superclass]);</div><div class="line"></div><div class="line"><span class="comment">//输出:</span></div><div class="line">MyClass 实例对象是:<span class="number">0x60c00000b8d0</span></div><div class="line">MyClass 类对象是:<span class="number">0x109ae3fd0</span></div><div class="line">MyClass 元类对象是:****<span class="number">0x109ae3fa8</span></div><div class="line">MyClass 元类对象的元类对象是:****<span class="number">0x10ab02e58</span>**</div><div class="line">MyClass 根元类对象是:<span class="number">0x10ab02e58</span></div><div class="line">MyClass 父类是:<span class="built_in">NSObject</span></div><div class="line">MyClass 父类的父类是:(null)</div><div class="line">MyClass 父类的元类的父类是:<span class="built_in">NSObject</span></div><div class="line"><span class="built_in">NSObject</span> 元类对象是:<span class="number">0x10ab02e58</span></div><div class="line"><span class="built_in">NSObject</span> 父类是:(null)</div><div class="line"><span class="built_in">NSObject</span> 元类对象的父类是:<span class="built_in">NSObject</span></div></pre></td></tr></table></figure>
<p>可以发现,输出结果是完全符合我们的结论的!</p>
<p>现在我们能知道各种对象之间的关系:</p>
<blockquote>
<p>实例对象通过 <code>isa</code> 指针,找到类对象 <code>Class</code>;类对象同样通过 <code>isa</code> 指针,找到元类对象;元类对象也是通过 <code>isa</code> 指针,找到根元类对象;最后,根元类对象的 <code>isa</code> 指针,指向自己。可以发现 <code>NSObject</code> 是整个消息机制的核心,绝大数对象都继承自它。</p>
</blockquote>
<h4 id="寻找流程"><a href="#寻找流程" class="headerlink" title="寻找流程"></a>寻找流程</h4><p>上文提到了,一个 Objective-C 方法会被编译成 <code>objc_msgSend</code>,这个函数有两个默认参数,<code>id</code> 类型的 <code>self</code>, <code>SEL</code> 类型的 <code>op</code>。我们先看看 <code>id</code> 的定义:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> objc_object *<span class="keyword">id</span>;</div><div class="line"></div><div class="line"><span class="keyword">struct</span> objc_object {</div><div class="line"> Class _Nonnull isa OBJC_ISA_AVAILABILITY;</div><div class="line">};</div></pre></td></tr></table></figure>
<p> 我们可以看到,在 <code>objc_object</code> 结构体中,只有一个指向 <code>Class</code> 类型的 <code>isa</code> 指针。</p>
<p>我们再看看 <code>Class</code> 的定义:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> objc_class {</div><div class="line"> Class _Nonnull isa OBJC_ISA_AVAILABILITY;</div><div class="line"><span class="meta">#if !__OBJC2__</span></div><div class="line"> Class _Nullable super_class OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">const</span> <span class="keyword">char</span> * _Nonnull name OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">long</span> version OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">long</span> info OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">long</span> instance_size OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;</div><div class="line"><span class="meta">#endif</span></div><div class="line">} OBJC2_UNAVAILABLE;</div></pre></td></tr></table></figure>
<p>里面有很多参数,很显眼的能看到这一行:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;</div></pre></td></tr></table></figure>
<p>看名字也容易理解,这个 <code>methodLists</code> 就是用来存放方法列表的。我们再看看 <code>objc_method_list</code> 这个结构体:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> objc_method_list {</div><div class="line"> <span class="keyword">struct</span> objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;</div><div class="line"> </div><div class="line"> <span class="keyword">int</span> method_count OBJC2_UNAVAILABLE;</div><div class="line"><span class="meta">#ifdef __LP64__</span></div><div class="line"> <span class="keyword">int</span> space OBJC2_UNAVAILABLE;</div><div class="line"><span class="meta">#endif</span></div><div class="line"> <span class="comment">/* variable length structure */</span></div><div class="line"> <span class="keyword">struct</span> objc_method method_list[<span class="number">1</span>] OBJC2_UNAVAILABLE;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>里面的 <code>objc_method</code> ,也就是我们熟悉的 <code>Method</code>:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> objc_method {</div><div class="line"> SEL _Nonnull method_name OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">char</span> * _Nullable method_types OBJC2_UNAVAILABLE;</div><div class="line"> IMP _Nonnull method_imp OBJC2_UNAVAILABLE;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>Method</code> 里面保存了三个参数:</p>
<ul>
<li>方法的名称</li>
<li>方法的类型</li>
<li>方法的具体实现,由 <code>IMP</code> 指针指向</li>
</ul>
<p>经过层层挖掘,我们能明白实例对象调用方法的大致逻辑:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">MyClass *myClass = [[MyClass alloc] init];</div><div class="line">[myClass printLog];</div></pre></td></tr></table></figure>
<ul>
<li>先被编译成 <code>((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));</code></li>
<li>沿着入参 <code>myClass</code> 的 <code>isa</code> 指针,找到 <code>myClass</code> 的类对象(<code>Class</code>),也就是 <code>MyClass</code></li>
<li>接着在 <code>MyClass</code> 的方法列表 <code>methodLists</code> 中,找到对应的 <code>Method</code></li>
<li>最后找到 <code>Method</code> 中的 <code>IMP</code> 指针,执行具体实现</li>
</ul>
<h3 id="类对象的类方法又是怎么找到并执行的?"><a href="#类对象的类方法又是怎么找到并执行的?" class="headerlink" title="类对象的类方法又是怎么找到并执行的?"></a>类对象的类方法又是怎么找到并执行的?</h3><p>由上文,我们已经知道,<strong>实例对象是通过 <code>isa</code> 指针,找到其类对象(<code>Class</code>)中保存的方法列表中的具体实现的</strong>。</p>
<p>比如:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">MyClass *myClass = [[MyClass alloc] init];</div><div class="line">[myClass printLog];</div></pre></td></tr></table></figure>
<p>可以理解为:<code>printLog</code> 方法就是保存在 <code>MyClass</code> 中的。</p>
<blockquote>
<p>那么如果是个<strong>类方法</strong>,又是保存在什么地方的呢?</p>
</blockquote>
<p>我们回顾下 <code>Class</code> 的定义:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> objc_class {</div><div class="line"> Class _Nonnull isa OBJC_ISA_AVAILABILITY;</div><div class="line"><span class="meta">#if !__OBJC2__</span></div><div class="line"> Class _Nullable super_class OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">const</span> <span class="keyword">char</span> * _Nonnull name OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">long</span> version OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">long</span> info OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">long</span> instance_size OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;</div><div class="line"> <span class="keyword">struct</span> objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;</div><div class="line"><span class="meta">#endif</span></div><div class="line">} OBJC2_UNAVAILABLE;</div></pre></td></tr></table></figure>
<p>可以发现到这一行:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Class _Nonnull isa OBJC_ISA_AVAILABILITY;</div></pre></td></tr></table></figure>
<p>这里的 <code>isa</code> 同样是指向一个 <code>Class</code> 的指针。上文中,我们也知道了类对象的 <code>isa</code> 指针是指向元类对象的。那么不难得出:</p>
<blockquote>
<p>类对象的类方法,是保存在元类对象中的!</p>
</blockquote>
<p>类对象和元类对象都是 <code>Class</code> 类型,仅仅服务的对象不同罢了。找到了元类对象,自然就找到了元类对象中的 <code>methodLists</code>,接下来就和实例对象的方法寻找调用一样的流程了。</p>
<h3 id="如何提高方法查找的效率?"><a href="#如何提高方法查找的效率?" class="headerlink" title="如何提高方法查找的效率?"></a>如何提高方法查找的效率?</h3><p>上文中,我们大概知道,方法是通过 <code>isa</code> 指针,查找 <code>Class</code> 中的 <code>methodLists</code> 的。如果子类没实现对应的方法实现,还会沿着父类去查找。整个工程,可能有<strong>成万上亿</strong>个方法,是如何解决性能问题的呢?</p>
<p>例如:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100000</span>; ++i) {</div><div class="line"> MyClass *myObject = myObjects[i];</div><div class="line"> [myObject methodA];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这种高频次的调用 <code>methodA</code>,如果每调用一次都需要遍历,性能是非常差的。所以引入了 Class Cache 机制:</p>
<blockquote>
<p> Class Cache 认为,当一个方法被调用,那么它之后被调用的可能性就越大。</p>
</blockquote>
<p>查找方法时,会先从缓存中查找,找到直接返回 ;找不到,再去 <code>Class</code> 的方法列表中找。</p>
<p>在上文中 <code>Class</code> 的定义中,我们可以发现 <code>cache</code>:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">struct</span> objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;</div></pre></td></tr></table></figure>
<p>说明了<strong>缓存是存在类中的,每个类都有一份方法缓存,而不是每个类的 <code>object</code> 都保存了一份。</strong></p>
<h3 id="关于父类(superclass)"><a href="#关于父类(superclass)" class="headerlink" title="关于父类(superclass)"></a>关于父类(superclass)</h3><p>在 Objective-C 中,子类调用一个方法,如果没有子类没有实现,父类实现了,会去调用父类的实现。上文中,找到 <code>methodLists</code> 后,寻找 <code>Method</code> 的大致过程如下:</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/3/15/162279fe6e420501?w=858&h=758&f=png&s=50302" alt=""></p>
<blockquote>
<p>ps: 其实这里的寻找过程远没有这么简单,可能会遍历很多遍,因为我们可能会在运行时动态的添加方法(比如 <code>category</code>)。<strong>遍历的过程中同样不时的去查询缓存表。</strong></p>
</blockquote>
<h2 id="消息转发"><a href="#消息转发" class="headerlink" title="消息转发"></a>消息转发</h2><p>如果方法列表(<code>methodLists</code>)没找到对应的 <code>selector</code> 呢?</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// ViewController.m 中 (未实现 myTestPrint 方法)</span></div><div class="line"></div><div class="line">[<span class="keyword">self</span> performSelector:<span class="keyword">@selector</span>(myTestPrint:) withObject:<span class="string">@",你好 !"</span>];</div></pre></td></tr></table></figure>
<p>系统会提供<strong>三次</strong>补救的机会。</p>
<h3 id="第一次"><a href="#第一次" class="headerlink" title="第一次"></a>第一次</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">+ (<span class="built_in">BOOL</span>)resolveInstanceMethod:(SEL)sel {} (实例方法)</div><div class="line">+ (<span class="built_in">BOOL</span>)resolveClassMethod:(SEL)sel {} (类方法)</div></pre></td></tr></table></figure>
<p>这两个方法,一个针对实例方法;一个针对类方法。返回值都是 <code>Bool</code>。</p>
<p>使用示例:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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">// ViewController.m 中</span></div><div class="line"></div><div class="line"><span class="keyword">void</span> myMethod(<span class="keyword">id</span> <span class="keyword">self</span>, SEL _cmd,<span class="built_in">NSString</span> *nub) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"ifelseboyxx%@"</span>,nub);</div><div class="line">}</div><div class="line"></div><div class="line">+ (<span class="built_in">BOOL</span>)resolveInstanceMethod:(SEL)sel {</div><div class="line"><span class="meta">#pragma clang diagnostic push</span></div><div class="line"><span class="meta">#pragma clang diagnostic ignored <span class="meta-string">"-Wundeclared-selector"</span></span></div><div class="line"> <span class="keyword">if</span> (sel == <span class="keyword">@selector</span>(myTestPrint:)) {</div><div class="line"><span class="meta">#pragma clang diagnostic pop</span></div><div class="line"> class_addMethod([<span class="keyword">self</span> <span class="keyword">class</span>],sel,(IMP)myMethod,<span class="string">"v@:@"</span>);</div><div class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</div><div class="line"> }<span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> [<span class="keyword">super</span> resolveInstanceMethod:sel];</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们只需要在 <code>resolveInstanceMethod:</code> 方法中,利用 <code>class_addMethod</code> 方法,将未实现的 <code>myTestPrint:</code> 绑定到 <code>myMethod</code> 上就能完成转发,最后返回 <code>YES</code>。</p>
<h3 id="第二次"><a href="#第二次" class="headerlink" title="第二次"></a>第二次</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">id</span>)forwardingTargetForSelector:(SEL)aSelector {}</div></pre></td></tr></table></figure>
<p>这个方法要求返回一个 <code>id</code>。使用场景一般是将 A 类的某个方法,转发到 B 类的实现中去。</p>
<p>使用示例:</p>
<p>想转发到 <code>Person</code> 类中的 <code>-myTestPrint:</code> 方法中:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Person</span> : <span class="title">NSObject</span></span></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">Person</span></span></div><div class="line">- (<span class="keyword">void</span>)myTestPrint:(<span class="built_in">NSString</span> *)str {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"ifelseboyxx%@"</span>,str);</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">// ViewController.m 中</div><div class="line"></div><div class="line">- (id)forwardingTargetForSelector:(SEL)aSelector {</div><div class="line">#pragma clang diagnostic push</div><div class="line">#pragma clang diagnostic ignored "-Wundeclared-selector"</div><div class="line"> if (aSelector == @selector(myTestPrint:)) {</div><div class="line">#pragma clang diagnostic pop</div><div class="line"> return [Person new];</div><div class="line"> }else{</div><div class="line"> return [super forwardingTargetForSelector:aSelector];</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="第三次"><a href="#第三次" class="headerlink" title="第三次"></a>第三次</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">- (<span class="built_in">NSMethodSignature</span> *)methodSignatureForSelector:(SEL)aSelector {}</div><div class="line">- (<span class="keyword">void</span>)forwardInvocation:(<span class="built_in">NSInvocation</span> *)anInvocation {}</div></pre></td></tr></table></figure>
<p>第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。</p>
<p>这次的转发作用和第二次的比较类似,都是将 A 类的某个方法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第二次更加<strong>灵活</strong>,<code>forwardingTargetForSelector:</code> 只能固定的转发到一个对象;<code>forwardInvocation:</code> <strong>可以让我们转发到多个对象中去</strong>。</p>
<p>使用实例:</p>
<p>想转发到 <code>Person</code> 类以及 <code>Animal</code> 类中的 <code>-myTestPrint:</code> 方法中:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Person</span> : <span class="title">NSObject</span></span></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">Person</span></span></div><div class="line">- (<span class="keyword">void</span>)myTestPrint:(<span class="built_in">NSString</span> *)str {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"ifelseboyxx%@"</span>,str);</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Animal</span> : <span class="title">NSObject</span></span></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">Animal</span></span></div><div class="line">- (<span class="keyword">void</span>)myTestPrint:(<span class="built_in">NSString</span> *)str {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"tiger%@"</span>,str);</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><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">// ViewController.m 中</span></div><div class="line"></div><div class="line">- (<span class="built_in">NSMethodSignature</span> *)methodSignatureForSelector:(SEL)aSelector {</div><div class="line"> <span class="meta">#pragma clang diagnostic push</span></div><div class="line"> <span class="meta">#pragma clang diagnostic ignored <span class="meta-string">"-Wundeclared-selector"</span></span></div><div class="line"> <span class="keyword">if</span> (aSelector == <span class="keyword">@selector</span>(myTestPrint:)) {</div><div class="line"> <span class="meta">#pragma clang diagnostic pop</span></div><div class="line"> <span class="keyword">return</span> [<span class="built_in">NSMethodSignature</span> signatureWithObjCTypes:<span class="string">"v@:@"</span>];</div><div class="line">}</div><div class="line"> <span class="keyword">return</span> [<span class="keyword">super</span> methodSignatureForSelector:aSelector];</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)forwardInvocation:(<span class="built_in">NSInvocation</span> *)anInvocation {</div><div class="line"> Person *person = [Person new];</div><div class="line"> Animal *animal = [Animal new];</div><div class="line"> <span class="keyword">if</span> ([person respondsToSelector:anInvocation.selector]) {</div><div class="line"> [anInvocation invokeWithTarget:person];</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> ([animal respondsToSelector:anInvocation.selector]) {</div><div class="line"> [anInvocation invokeWithTarget:animal];</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>⚠️ 如果到了第三次机会,还没找到对应的实现,就会 crash:</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">unrecognized selector sent to instance <span class="number">0x7f9f817072b0</span></div></pre></td></tr></table></figure>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>到这里,我们大概能了解消息发送与转发的过程了。整理了下大致的流程,有问题欢迎大家积极提出来:</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/3/15/162279c7ab3eedfc?w=1117&h=1014&f=png&s=119757" alt=""></p>
]]></content>
<summary type="html">
<p>Objective-C 是一门动态语言,它将很多静态语言在编译和链接时期做的事情,放到了运行时来处理。之所以能具备这种特性,离不开 Runtime 这个库。Runtime 很好的解决了如何在运行时期找到调用方法这样的问题。</p>
<h2 id="消息发送"><a href
</summary>
<category term="Objc" scheme="http://yoursite.com/tags/Objc/"/>
</entry>
<entry>
<title>聊聊 iOS 中的自释放</title>
<link href="http://yoursite.com/2018/03/06/self_release_iOS/"/>
<id>http://yoursite.com/2018/03/06/self_release_iOS/</id>
<published>2018-03-06T15:19:01.000Z</published>
<updated>2018-03-06T15:21:33.506Z</updated>
<content type="html"><![CDATA[<p>什么叫自释放?可以简单的理解为:对象在生命周期结束后,自动清理回收与其相关的资源。这个清理不仅仅包括对象内存的回收,还包括对象解耦及附属事件的清理等等,例如定时器的停止、通知以及 KVO 对象的监听移除。</p>
<h2 id="对象内存的回收"><a href="#对象内存的回收" class="headerlink" title="对象内存的回收"></a>对象内存的回收</h2><p>在开发中,对象管理的基本原则 — 谁创建谁释放。但是在 MRC 中,我们会用 <code>autorelease</code> 来标记一个对象,告诉编辑器,这个对象我不负责释放。<strong>此时,这个对象就变成了自释放的对象,当其不再需要时,系统就会自动回收其内存。</strong> 等到了 ARC 时代,基本上所有对象对于我们来说都是自释放对象,我们不需要再处处留意内存泄漏问题,可以更专注于业务逻辑上。</p>
<h2 id="KVO-的自释放"><a href="#KVO-的自释放" class="headerlink" title="KVO 的自释放"></a>KVO 的自释放</h2><p>iOS 开发中,我们使用 KVO 监听对象某个 <code>keyPath</code> 时,需要在<strong>被监听的对象释放前</strong>移除对应的 <code>keyPath</code> 监听:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">Person *person = [Person new];</div><div class="line"><span class="keyword">self</span>.person = person;</div><div class="line">[<span class="keyword">self</span>.person addObserver:<span class="keyword">self</span> forKeyPath:<span class="string">@"name"</span> options:<span class="built_in">NSKeyValueObservingOptionOld</span> | <span class="built_in">NSKeyValueObservingOptionNew</span> context:<span class="literal">nil</span>];</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)dealloc {</div><div class="line"> [<span class="keyword">self</span>.person removeObserver:<span class="keyword">self</span> forKeyPath:<span class="string">@"name"</span>];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果我们一不小心忘了移除对应的监听,会得到这样的错误:</p>
<figure class="highlight"><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">Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x17000c2c0 of class Person was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x17003c9e0>(</div><div class="line"><NSKeyValueObservance 0x170243de0: Observer: 0x129d053b0, Key path: name, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x170243db0>)'</div></pre></td></tr></table></figure>
<h3 id="FBKVOController"><a href="#FBKVOController" class="headerlink" title="FBKVOController"></a>FBKVOController</h3><p>我们不由的产生疑问: 对象的 <code>dealloc</code> 函数只做了<code>removeObserver:forKeyPath:</code> 一件事,能不能不每次都写呢?<a href="https://github.com/facebook/KVOController" target="_blank" rel="external">FBKVOController</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></pre></td><td class="code"><pre><div class="line">Person *person = [Person new];</div><div class="line"><span class="keyword">self</span>.person = person;</div><div class="line"></div><div class="line">[<span class="keyword">self</span>.KVOController observe:person keyPath:<span class="string">@"name"</span> options:<span class="built_in">NSKeyValueObservingOptionOld</span> | <span class="built_in">NSKeyValueObservingOptionNew</span> block:^(<span class="keyword">id</span> _Nullable observer, <span class="keyword">id</span> _Nonnull object, <span class="built_in">NSDictionary</span><<span class="built_in">NSKeyValueChangeKey</span>,<span class="keyword">id</span>> * _Nonnull change) {</div><div class="line"> <span class="built_in">NSString</span> *new = change[<span class="built_in">NSKeyValueChangeNewKey</span>];</div><div class="line"> <span class="built_in">NSString</span> *old = change[<span class="built_in">NSKeyValueChangeOldKey</span>];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@ %@"</span>,new,old);</div><div class="line">}];</div></pre></td></tr></table></figure>
<p>抛开烦人的 <code>removeObserver:forKeyPath:</code>,更加简明清晰的满足了需求。</p>
<p>那么,<a href="https://github.com/facebook/KVOController" target="_blank" rel="external">FBKVOController</a> 是如何做到自释放的呢?<strong>其内部将观察者绑定到 <code>FBKVOController</code> 这个第三者上,<code>FBKVOController</code> 会随着观察者的释放而释放。最后,<code>FBKVOController</code> 在自己的 <code>dealloc</code> 方法中,通过 <code>_FBKVOSharedController</code> 这个单例来移除监听。</strong></p>
<h3 id="ReactiveCocoa"><a href="#ReactiveCocoa" class="headerlink" title="ReactiveCocoa"></a>ReactiveCocoa</h3><p>除了 FBKVOController,<a href="https://github.com/ReactiveCocoa/ReactiveObjC/tree/3.1.0" target="_blank" rel="external">ReactiveCocoa</a> 也同样支持 KVO 的自释放:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">Person *person = [Person new];</div><div class="line"><span class="keyword">self</span>.person = person;</div><div class="line"></div><div class="line">[[<span class="keyword">self</span>.person rac_valuesAndChangesForKeyPath:<span class="string">@"name"</span> options:<span class="built_in">NSKeyValueObservingOptionOld</span> | <span class="built_in">NSKeyValueObservingOptionNew</span> observer:<span class="keyword">self</span>] subscribeNext:^(RACTwoTuple<<span class="keyword">id</span>,<span class="built_in">NSDictionary</span> *> * _Nullable x) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@ %@"</span>,x.second[<span class="string">@"old"</span>],x.second[<span class="string">@"new"</span>]);</div><div class="line">}];</div></pre></td></tr></table></figure>
<p>ReactiveCocoa 和 FBKVOController 略有不同,ReactiveCocoa 是通过<strong>监听观察者的 <code>dealloc</code> 方法</strong>,并通过 <code>RACKVOTrampoline</code> 这个对象来管理对象 KVO 监听的添加/移除。</p>
<blockquote>
<p>⚠️ 经测试,在 iOS 11 中,系统已经帮我们做了 KVO 的 <code>keyPath</code> 移除操作。遗憾的是,iOS 11 以下,不移除仍然存在问题!</p>
</blockquote>
<h2 id="NSNotification-的自释放"><a href="#NSNotification-的自释放" class="headerlink" title="NSNotification 的自释放"></a>NSNotification 的自释放</h2><p>通常,我们使用通知时是这样的:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 添加</span></div><div class="line">[[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserver:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(respondsToNotification:) name:<span class="string">@"test0"</span> object:<span class="literal">nil</span>];</div><div class="line"><span class="comment">// 发送</span></div><div class="line">[[<span class="built_in">NSNotificationCenter</span> defaultCenter] postNotificationName:<span class="string">@"test0"</span> object:<span class="literal">nil</span>];</div><div class="line"><span class="comment">// 移除</span></div><div class="line">[[<span class="built_in">NSNotificationCenter</span> defaultCenter] removeObserver:<span class="keyword">self</span> name:<span class="string">@"test0"</span> object:<span class="literal">nil</span>];</div></pre></td></tr></table></figure>
<p>关于移除操作,根据不同的业务场景,有的是放在 <code>dealloc</code> 方法中,有的是 <code>viewWillDisappear:</code> 方法中。然而,在 iOS 8 及以上版本中,我们已经不需要再手动移除通知了,大家可以用以下代码测试下:</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="class"><span class="keyword">@implementation</span> <span class="title">NSNotificationCenter</span> (<span class="title">NS</span>)</span></div><div class="line"></div><div class="line">+ (<span class="keyword">void</span>)load {</div><div class="line"> Method origin = class_getInstanceMethod([<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(removeObserver:));</div><div class="line"> Method current = class_getInstanceMethod([<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(_removeObserver:));</div><div class="line"> method_exchangeImplementations(origin, current);</div><div class="line">}</div><div class="line">- (<span class="keyword">void</span>)_removeObserver:(<span class="keyword">id</span>)observer {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"调用移除通知方法: %@"</span>, observer);</div><div class="line">}</div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>这应该是苹果在 iOS 11 中的一次优化。</p>
<h2 id="NSTimer-的自释放"><a href="#NSTimer-的自释放" class="headerlink" title="NSTimer 的自释放"></a>NSTimer 的自释放</h2><p>通常我们是这样使用定时器:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>) <span class="built_in">NSTimer</span> *timer;</div><div class="line"></div><div class="line"><span class="keyword">self</span>.timer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:<span class="number">1.0</span>f target:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(timerTest) userInfo:<span class="literal">nil</span> repeats:<span class="literal">YES</span>];</div></pre></td></tr></table></figure>
<p>定时器<strong>内部</strong>会 <code>strong</code> <code>target</code>,而 <code>self</code> 也就是 <code>target</code> 又 <code>strong</code> 了定时器,这样就造成了循环引用,导致 <code>self</code> 无法释放。想要打破,我们只有<strong>主动</strong>调用 <code>invalidate</code> 方法。目前解决这种问题的方法有两种方式:</p>
<ul>
<li>使用 <code>weak proxy</code>,持有弱引用 <code>target</code> ,转发消息到 <code>target</code>。<a href="https://github.com/ibireme/YYKit/blob/master/YYKit/Utility/YYWeakProxy.h" target="_blank" rel="external">YYWeakProxy</a> 是个不错的选择。</li>
<li>使用 <code>dispatch_source</code> 自己实现一个定时器。<a href="https://github.com/ibireme/YYKit/blob/master/YYKit/Utility/YYTimer.h" target="_blank" rel="external">YYTimer</a> 是个不错的选择。</li>
</ul>
<h3 id="YYWeakProxy"><a href="#YYWeakProxy" class="headerlink" title="YYWeakProxy"></a>YYWeakProxy</h3><p>YYWeakProxy 是 NSProxy 的子类,其内持有了 <code>weak</code> <code>target</code>,利用消息转发机制,将消息转发到传进来的 <code>target</code>:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">@property</span> (<span class="keyword">nullable</span>, <span class="keyword">nonatomic</span>, <span class="keyword">weak</span>, <span class="keyword">readonly</span>) <span class="keyword">id</span> target;</div></pre></td></tr></table></figure>
<p>这样,当 <code>self</code> 引用计数为 <code>0</code> 时,<code>target</code> 将为 <code>nil</code>,这样就打破了 <code>self</code> 和 <code>NSTimer</code> 之间的循环引用,<code>self</code> 也就得以释放。</p>
<p>然而,虽然 <code>self</code> 和 <code>NSTimer</code> 之间循环引用打破了,却又造成了 <code>YYWeakProxy</code> 和 <code>NSTimer</code> 之间的循环引用,导致 <code>YYWeakProxy</code> 的内存泄漏。按照作者的意思,与其泄漏一个可能很重的 <code>self</code>,不如泄漏一个轻量的 <code>YYWeakProxy</code>。</p>
<h3 id="YYTimer"><a href="#YYTimer" class="headerlink" title="YYTimer"></a>YYTimer</h3><p>YYTimer 可以彻底的解决内存泄漏问题,缺点是实现相对复杂。 其内部是使用 GCD 的 <code>dispatch_source</code> 来实现的,关于 <code>dispatch_source</code> 使用如下:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></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_get_main_queue();</div><div class="line"><span class="comment">// 创建 dispatch_source</span></div><div class="line">dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, <span class="number">0</span>, <span class="number">0</span>, queue);</div><div class="line"><span class="comment">// 声明成员变量</span></div><div class="line"><span class="keyword">self</span>.timer = timer;</div><div class="line"><span class="comment">// 设置两秒后触发</span></div><div class="line">dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, <span class="number">3.0</span> * <span class="built_in">NSEC_PER_SEC</span>);</div><div class="line"><span class="comment">// 设置下次触发事件为 DISPATCH_TIME_FOREVER</span></div><div class="line">dispatch_time_t nextTime = DISPATCH_TIME_FOREVER;</div><div class="line"><span class="comment">// 设置精确度</span></div><div class="line">dispatch_time_t leeway = <span class="number">0.1</span> * <span class="built_in">NSEC_PER_SEC</span>;</div><div class="line"><span class="comment">// 配置时间</span></div><div class="line">dispatch_source_set_timer(timer, startTime, nextTime, leeway);</div><div class="line"><span class="comment">// 回调</span></div><div class="line">dispatch_source_set_event_handler(timer, ^{</div><div class="line"> <span class="comment">// ...</span></div><div class="line">});</div><div class="line"></div><div class="line"><span class="comment">// 激活</span></div><div class="line">dispatch_resume(timer);</div></pre></td></tr></table></figure>
<p>需要取消的话:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">dispatch_source_cancel(<span class="keyword">self</span>.timer);</div></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<p>什么叫自释放?可以简单的理解为:对象在生命周期结束后,自动清理回收与其相关的资源。这个清理不仅仅包括对象内存的回收,还包括对象解耦及附属事件的清理等等,例如定时器的停止、通知以及 KVO 对象的监听移除。</p>
<h2 id="对象内存的回收"><a href="#对象内
</summary>
<category term="Objc" scheme="http://yoursite.com/tags/Objc/"/>
</entry>
<entry>
<title>Objective-C 中延迟执行和取消</title>
<link href="http://yoursite.com/2018/01/08/objc_delay/"/>
<id>http://yoursite.com/2018/01/08/objc_delay/</id>
<published>2018-01-08T09:00:00.000Z</published>
<updated>2018-02-13T10:07:11.359Z</updated>
<content type="html"><![CDATA[<p>在 Objective-C 中延迟执行还是很常见的需求,通常有如下几种方式可供选择:</p>
<h2 id="performSelector:"><a href="#performSelector:" class="headerlink" title="performSelector:"></a>performSelector:</h2><p>想要延迟调用某个方法:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[<span class="keyword">self</span> performSelector:<span class="keyword">@selector</span>(delay) withObject:<span class="literal">nil</span> afterDelay:<span class="number">3.0</span>];</div></pre></td></tr></table></figure>
<p>取消延迟的方法:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[<span class="built_in">NSObject</span> cancelPreviousPerformRequestsWithTarget:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(delay) object:<span class="literal">nil</span>];</div></pre></td></tr></table></figure>
<blockquote>
<p>这里需要注意参数需要保持一致,否则取消失败。</p>
</blockquote>
<h2 id="NSTimer"><a href="#NSTimer" class="headerlink" title="NSTimer"></a>NSTimer</h2><p>想要延迟调用某个方法:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">self</span>.timer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:<span class="number">2.0</span> target:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(delay) userInfo:<span class="literal">nil</span> repeats:<span class="literal">NO</span>];</div></pre></td></tr></table></figure>
<p>取消延迟的方法:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[<span class="keyword">self</span>.timer invalidate];</div></pre></td></tr></table></figure>
<h2 id="GCD"><a href="#GCD" class="headerlink" title="GCD"></a>GCD</h2><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<span class="number">2.0</span> * <span class="built_in">NSEC_PER_SEC</span>)), dispatch_get_main_queue(), ^{</div><div class="line"> <span class="comment">// ...</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p><code>dispatch_after</code> 是比较常用的方法,但是 Objective-C 中并没有提供取消执行的相关 API。我们只能自己实现这个取消的逻辑:</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="keyword">typedef</span> <span class="keyword">void</span> (^Task)(<span class="built_in">BOOL</span> cancel);</div><div class="line">Task delay(<span class="built_in">NSTimeInterval</span> time,<span class="keyword">void</span> (^task)()) {</div><div class="line"> __block <span class="keyword">void</span> (^closure)() = task;</div><div class="line"> __block Task result;</div><div class="line"> Task delayedClosure = ^(<span class="built_in">BOOL</span> cancel){</div><div class="line"> <span class="keyword">if</span> (closure) {</div><div class="line"> <span class="keyword">void</span> (^internalClosure)() = closure;</div><div class="line"> <span class="keyword">if</span> (!cancel) {</div><div class="line"> <span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), internalClosure);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> closure = <span class="literal">nil</span>;</div><div class="line"> result = <span class="literal">nil</span>;</div><div class="line"> };</div><div class="line"> </div><div class="line"> result = delayedClosure;</div><div class="line"> </div><div class="line"> dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * <span class="built_in">NSEC_PER_SEC</span>)), dispatch_get_main_queue(), ^{</div><div class="line"> <span class="keyword">if</span> (delayedClosure) {</div><div class="line"> delayedClosure(<span class="literal">NO</span>);</div><div class="line"> }</div><div class="line"> });</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>使用的话可以这样:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">delay(<span class="number">60</span>, ^{</div><div class="line"> <span class="comment">// ...</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p>如果想要延迟,可以先声明成成员变量并赋值:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">@property</span> (<span class="keyword">copy</span>, <span class="keyword">nonatomic</span>) Task task;</div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">self</span>.task = delay(<span class="number">60</span>, ^{</div><div class="line"> <span class="comment">// ...</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p>最后在需要的地方取消就行:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">self</span>.task(<span class="literal">YES</span>);</div></pre></td></tr></table></figure>
<blockquote>
<p>这种写法的核心思想是根据传入的 <code>Bool</code> 值,来控制 <code>dispatch_after</code> 回调 <code>block</code> 中的方法是否需要执行。看起来是取消了,但实际上还是被 GCD 放到 RunLoop 里去占用主线程资源了。</p>
</blockquote>
<h2 id="dispatch-source"><a href="#dispatch-source" class="headerlink" title="dispatch_source"></a>dispatch_source</h2><p>我们还可以利用 <code>dispatch_source</code> 中的定时器,来实现延时/取消操作:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>) dispatch_source_t timer;</div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><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">dispatch_queue_t</span> queue = dispatch_get_main_queue();</div><div class="line"><span class="comment">// 创建 dispatch_source</span></div><div class="line">dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, <span class="number">0</span>, <span class="number">0</span>, queue);</div><div class="line"><span class="comment">// 声明成员变量</span></div><div class="line"><span class="keyword">self</span>.timer = timer;</div><div class="line"><span class="comment">// 设置两秒后触发</span></div><div class="line">dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, <span class="number">3.0</span> * <span class="built_in">NSEC_PER_SEC</span>);</div><div class="line"><span class="comment">// 设置下次触发事件为 DISPATCH_TIME_FOREVER</span></div><div class="line">dispatch_time_t nextTime = DISPATCH_TIME_FOREVER;</div><div class="line"><span class="comment">// 设置精确度</span></div><div class="line">dispatch_time_t leeway = <span class="number">0.1</span> * <span class="built_in">NSEC_PER_SEC</span>;</div><div class="line"><span class="comment">// 配置时间</span></div><div class="line">dispatch_source_set_timer(timer, startTime, nextTime, leeway);</div><div class="line"><span class="comment">// 回调</span></div><div class="line">dispatch_source_set_event_handler(timer, ^{</div><div class="line"> <span class="comment">// ...</span></div><div class="line">});</div><div class="line"><span class="comment">// 激活</span></div><div class="line">dispatch_resume(timer);</div></pre></td></tr></table></figure>
<p>需要取消的话:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">dispatch_source_cancel(<span class="keyword">self</span>.timer);</div></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<p>在 Objective-C 中延迟执行还是很常见的需求,通常有如下几种方式可供选择:</p>
<h2 id="performSelector:"><a href="#performSelector:" class="headerlink" title="performSel
</summary>
<category term="Objc" scheme="http://yoursite.com/tags/Objc/"/>
</entry>
<entry>
<title>RxSwift 入坑-需要知道的基础概念</title>
<link href="http://yoursite.com/2018/01/02/RxSwift_1/"/>
<id>http://yoursite.com/2018/01/02/RxSwift_1/</id>
<published>2018-01-02T13:55:23.000Z</published>
<updated>2018-02-13T10:06:43.544Z</updated>
<content type="html"><![CDATA[<p>环境:</p>
<blockquote>
<p>Xcode 9.1<br>Swift 4.0<br>RxSwift 4.0 </p>
</blockquote>
<p>目录:</p>
<blockquote>
<p><a href="#1.0">什么是 RxSwift</a><br><a href="#1.1">响应式编程</a><br><a href="#1.2">RxSwift 核心概念</a><br><a href="#1.3">Hot and Cold Observables</a></p>
</blockquote>
<h2 id="1.0"> 什么是 RxSwift </h2>
<p><a href="https://github.com/ReactiveX/RxSwift" target="_blank" rel="external">RxSwift</a> 是 <a href="http://reactivex.io/" target="_blank" rel="external">ReactiveX</a> 的 Swift 版本,全称 Reactive Extensions Swift,是一个响应式编程的基础框架。</p>
<h2 id="1.1"> 响应式编程 </h2>
<p>在面向对象时代,大多数程序都像这样运行:你的代码告诉你的程序需要做什么,并且有很多方法来监听变化–但同时你又必须主动告诉系统什么时候发生的变化。</p>
<p>响应式编程的基本思想是:你的程序可以对底层数据的变化做出响应,而不需要你直接告诉它。这样,你可以更专注于所需要处理的业务逻辑,而不需要去维护特定的状态。</p>
<p>举个简单的例子:</p>
<blockquote>
<p>a = b + c<br>赋值之后 b 或者 c 的值变化后,a 的值不会跟着变化<br>响应式编程,目标就是,如果 b 或者 c 的数值发生变化,a 的数值会同时发生变化;</p>
</blockquote>
<p>另外推荐看看这篇 <a href="http://blog.mrriddler.com/" target="_blank" rel="external">iOS 响应式架构</a></p>
<h2 id="1.2"> RxSwift 的核心 </h2>
<p>RxSwift 核心概念就是一个观察者(Observer)订阅一个可被观察序列(Observable)。观察者对可被观察序列发射的数据或数据序列作出响应。</p>
<p>举个简单的例子,当别人在跟你说话时,你就是那个观察者(Observer),别人就是那个(Observable),它有几个特点:</p>
<ul>
<li>可能会不断地跟你说话。(<code>onNext</code>)</li>
<li>可能会说错话。(<code>onError</code>)</li>
<li>结束说话。(<code>onCompleted</code>)</li>
</ul>
<p>你在听到对方说的话后,也可以有几种反应:</p>
<ul>
<li>根据说的话,做相应的事,比如对方让你借钱给他。(<code>subscribe</code>)</li>
<li>把对方说的话,加工下再传达给其他人,比如对方说小李好像不太舒服,你传达给其他人时就变成了小李失恋了。(<code>map:</code>)</li>
<li>参考其他人说的话再做处理,比如小李说某家店很好吃,小黄说某家店一般般,你需要结合两个人的意见再做定夺。(<code>zip:</code>)</li>
</ul>
<h3 id="Observable-可被观察的序列"><a href="#Observable-可被观察的序列" class="headerlink" title="Observable - 可被观察的序列"></a>Observable - 可被观察的序列</h3><h4 id="Observable-的三种事件"><a href="#Observable-的三种事件" class="headerlink" title="Observable 的三种事件"></a>Observable 的三种事件</h4><ul>
<li>next - 序列产生了一个新的元素</li>
<li>error - 创建序列时产生了一个错误,导致序列终止</li>
<li>completed - 序列的所有元素都已经成功产生,整个序列已经完成</li>
</ul>
<h4 id="基本创建方式"><a href="#基本创建方式" class="headerlink" title="基本创建方式"></a>基本创建方式</h4><figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">enum</span> <span class="title">MyError</span>: <span class="title">Error</span> </span>{</div><div class="line"> <span class="keyword">case</span> anError</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="keyword">let</span> message: <span class="type">Observable</span><<span class="type">String</span>> = <span class="type">Observable</span><<span class="type">String</span>>.create { (observer) -> <span class="type">Disposable</span> <span class="keyword">in</span></div><div class="line"> </div><div class="line"> observer.onNext(<span class="string">"😄"</span>)</div><div class="line"> observer.onError(<span class="type">MyError</span>.anError)</div><div class="line"> observer.onCompleted()</div><div class="line"> </div><div class="line"> <span class="keyword">return</span> <span class="type">Disposables</span>.create()</div><div class="line">};</div></pre></td></tr></table></figure>
<h4 id="封装好操作符创建方式"><a href="#封装好操作符创建方式" class="headerlink" title="封装好操作符创建方式"></a>封装好操作符创建方式</h4><ul>
<li>just - 将某一个元素转换为 <code>Observable</code> 并发出唯一的一个元素</li>
</ul>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> id = <span class="type">Observable</span>.just(<span class="number">0</span>)</div><div class="line"></div><div class="line">相当于:</div><div class="line"></div><div class="line"><span class="keyword">let</span> id = <span class="type">Observable</span><<span class="type">Int</span>>.create { observer <span class="keyword">in</span></div><div class="line"> observer.onNext(<span class="number">0</span>)</div><div class="line"> observer.onCompleted()</div><div class="line"> <span class="keyword">return</span> <span class="type">Disposables</span>.create()</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>from - 将其他类型或者数据结构转换为 <code>Observable</code> </li>
</ul>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">将一个数组转换为 <span class="type">Observable</span>:</div><div class="line"></div><div class="line"><span class="keyword">let</span> numbers = <span class="type">Observable</span>.from([<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>])</div><div class="line"></div><div class="line">相当于:</div><div class="line"></div><div class="line"><span class="keyword">let</span> numbers = <span class="type">Observable</span><<span class="type">Int</span>>.create { observer <span class="keyword">in</span></div><div class="line"> observer.onNext(<span class="number">0</span>)</div><div class="line"> observer.onNext(<span class="number">1</span>)</div><div class="line"> observer.onNext(<span class="number">2</span>)</div><div class="line"> observer.onCompleted()</div><div class="line"> <span class="keyword">return</span> <span class="type">Disposables</span>.create()</div><div class="line">}</div></pre></td></tr></table></figure>
<p>具体更多的操作符,大家可以看看这个文档的 <a href="https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/decision_tree.html" target="_blank" rel="external">如何选择操作符?</a>。也可以看看官方示例里的 <code>playground</code> :<a href="https://github.com/ReactiveX/RxSwift/tree/master/Rx.playground" target="_blank" rel="external">Rx.playground</a></p>
<h3 id="Observer-观察者"><a href="#Observer-观察者" class="headerlink" title="Observer - 观察者"></a>Observer - 观察者</h3><h4 id="基本创建方式-1"><a href="#基本创建方式-1" class="headerlink" title="基本创建方式"></a>基本创建方式</h4><p>理解观察者的意思后,那我们如何创建呢?对应 Observable 一节中的 “基本创建方式”,我们可以为 <code>message: Observable<String></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></pre></td><td class="code"><pre><div class="line">message.subscribe(onNext: { str <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"观察信息"</span>)</div><div class="line"> }, onError: { error <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"发生错误"</span>)</div><div class="line"> }, onCompleted: {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"完成"</span>)</div><div class="line">}).dispose()</div></pre></td></tr></table></figure>
<p>创建观察者最直接的方法就是在 <code>Observable</code> 的 <code>subscribe</code> 方法后面描述,事件发生时,需要如何做出响应。而观察者就是由后面的 <code>onNext</code>,<code>onError</code>,<code>onCompleted</code> 的这些闭包构建出来的。</p>
<h4 id="一些封装"><a href="#一些封装" class="headerlink" title="一些封装"></a>一些封装</h4><p>RxSwift 也帮我们封装了很多许多常用的观察者(Observer),比如 <code>button</code> 的点击,通知以及代理等等。</p>
<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">import</span> RxCocoa</div></pre></td></tr></table></figure>
<p>原先监听按钮点击需要这么做:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> <span class="keyword">let</span> btn = <span class="type">UIButton</span>.<span class="keyword">init</span>(frame: <span class="type">CGRect</span>(x: <span class="number">100</span>, y: <span class="number">100</span>, width: <span class="number">150</span>, height: <span class="number">50</span>))</div><div class="line"> btn.backgroundColor = <span class="type">UIColor</span>.brown</div><div class="line"> btn.addTarget(<span class="keyword">self</span>, action: #selector(btnClick), <span class="keyword">for</span>: .touchUpInside)</div><div class="line"> view.addSubview(btn)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// 回调监听</span></div><div class="line"> <span class="meta">@objc</span> <span class="function"><span class="keyword">func</span> <span class="title">btnClick</span><span class="params">()</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"btn click !"</span>)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>用 RxSwift 我们可以这么做:</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="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">var</span> disposeBag = <span class="type">DisposeBag</span>()</div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> <span class="keyword">let</span> btn = <span class="type">UIButton</span>.<span class="keyword">init</span>(frame: <span class="type">CGRect</span>(x: <span class="number">100</span>, y: <span class="number">100</span>, width: <span class="number">150</span>, height: <span class="number">50</span>))</div><div class="line"> btn.backgroundColor = <span class="type">UIColor</span>.brown</div><div class="line"> view.addSubview(btn)</div><div class="line"> </div><div class="line"> <span class="comment">// 回调监听</span></div><div class="line"> btn.rx.tap.subscribe(onNext: {</div><div class="line"> </div><div class="line"> <span class="built_in">print</span>(<span class="string">"btn click !"</span>)</div><div class="line"> </div><div class="line"> }).disposed(by: disposeBag)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>再看看代理,原先我们需要这么做:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><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="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@IBOutlet</span> <span class="keyword">weak</span> <span class="keyword">var</span> textView: <span class="type">UITextView</span>!</div><div class="line"></div><div class="line"> <span class="keyword">var</span> disposeBag = <span class="type">DisposeBag</span>()</div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> <span class="comment">// 设置代理</span></div><div class="line"> textView.delegate = <span class="keyword">self</span></div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">UIViewController</span>: <span class="title">UITextViewDelegate</span> </span>{</div><div class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">func</span> <span class="title">scrollViewDidScroll</span><span class="params">(<span class="number">_</span> scrollView: UIScrollView)</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"y坐标:\(offset.y)"</span>)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>用 RxSwift 我们可以这么做:</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></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@IBOutlet</span> <span class="keyword">weak</span> <span class="keyword">var</span> textView: <span class="type">UITextView</span>!</div><div class="line"></div><div class="line"> <span class="keyword">var</span> disposeBag = <span class="type">DisposeBag</span>()</div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> textView.rx.contentOffset.subscribe(onNext: { offset <span class="keyword">in</span></div><div class="line"> </div><div class="line"> <span class="built_in">print</span>(<span class="string">"y坐标:\(offset.y)"</span>)</div><div class="line"> </div><div class="line"> }).disposed(by: disposeBag)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以发现,RxSwift 使一些系统的方法,使用上变得更加方便。</p>
<h3 id="Subjects-既是可被监听的序列也是观察者"><a href="#Subjects-既是可被监听的序列也是观察者" class="headerlink" title="Subjects - 既是可被监听的序列也是观察者"></a>Subjects - 既是可被监听的序列也是观察者</h3><p>Subject 是 observable 和 Observer 之间的桥梁。一个 Subject 既是一个 Obserable 也是一个 Observer,既可以发出事件,也可以监听事件。</p>
<p>例如:<code>UITextField</code> 的当前文本。它可以看成是由用户输入,而产生的一个文本序列。也可以是由外部文本序列,来控制当前显示内容的观察者:</p>
<ul>
<li>作为可被监听的序列(<code>observable</code>)</li>
</ul>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@IBOutlet</span> <span class="keyword">weak</span> <span class="keyword">var</span> tf: <span class="type">UITextField</span>!</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> disposeBag = <span class="type">DisposeBag</span>()</div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> <span class="comment">// 作为可被监听的序列</span></div><div class="line"> <span class="keyword">let</span> observable_tf = tf.rx.text</div><div class="line"> </div><div class="line"> observable_tf.subscribe(onNext:{ text <span class="keyword">in</span></div><div class="line"> <span class="keyword">if</span> <span class="keyword">let</span> t = text {</div><div class="line"> <span class="type">DebugPrint</span>(t)</div><div class="line"> }</div><div class="line"> }).disposed(by: disposeBag)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>作为观察者(<code>Observer</code>)</li>
</ul>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@IBOutlet</span> <span class="keyword">weak</span> <span class="keyword">var</span> tf: <span class="type">UITextField</span>!</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> disposeBag = <span class="type">DisposeBag</span>()</div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> <span class="comment">// 作为观察者</span></div><div class="line"> <span class="keyword">let</span> observer_tf = tf.rx.text</div><div class="line"> <span class="keyword">let</span> text: <span class="type">Observable</span><<span class="type">String</span>?> = <span class="type">Observable</span>.just(<span class="string">"Atom."</span>)</div><div class="line"> </div><div class="line"> text.bind(to: observer_tf).disposed(by: disposeBag)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>RxSwift 中也定义了一些辅助类型,它们既是可被监听的序列也是观察者。这里就不多讲了,具体可以看看这里 <a href="https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/rxswift_core/observable_and_observer.html" target="_blank" rel="external">Observable & Observer 既是可被监听的序列也是观察者</a>。</p>
<h3 id="Disposable-可被清除的资源"><a href="#Disposable-可被清除的资源" class="headerlink" title="Disposable - 可被清除的资源"></a>Disposable - 可被清除的资源</h3><p>当监听一个事件序列的时候,有消息事件来了,我们做某些事情。但是这个事件序列不再发出消息了,我们的监听也就没有什么存在价值了,为了不消耗内存,需要释放这些监听资源。</p>
<p>一个 Observable 被观察订阅后,就会产生一个 <code>Disposable</code> 实例,表示「可扔掉」的,怎么扔掉呢?有下面几种方式:</p>
<ul>
<li>调用 <code>dispose()</code> 显式释放</li>
<li>通过 DisposeBag 也就是 <code>disposed()</code> 隐式释放。</li>
<li>利用 <code>takeUntil</code> 操作符,隐式释放。</li>
</ul>
<h4 id="Dispose"><a href="#Dispose" class="headerlink" title="Dispose"></a>Dispose</h4><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></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> <span class="keyword">let</span> subscription = <span class="type">Observable</span><<span class="type">Int</span>>.interval(<span class="number">1.0</span>, scheduler: <span class="type">SerialDispatchQueueScheduler</span>.<span class="keyword">init</span>(internalSerialQueueName: <span class="string">"serial"</span>))</div><div class="line"> .subscribe { event <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"\(event)"</span>)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="type">Thread</span>.sleep(forTimeInterval: <span class="number">4.0</span>)</div><div class="line"> </div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"手动释放"</span>)</div><div class="line"> subscription.dispose()</div><div class="line"> </div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">16</span>:<span class="number">47</span>:<span class="number">49</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">39</span>]:next(<span class="number">0</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">16</span>:<span class="number">47</span>:<span class="number">50</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">39</span>]:next(<span class="number">1</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">16</span>:<span class="number">47</span>:<span class="number">51</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">39</span>]:next(<span class="number">2</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">16</span>:<span class="number">47</span>:<span class="number">52</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">39</span>]:next(<span class="number">3</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">16</span>:<span class="number">47</span>:<span class="number">52</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">44</span>]:手动释放</div></pre></td></tr></table></figure>
<p>上面的例子类似定时器,每秒会调一次回调。我们 <code>sleep</code> 4 秒后,调下 <code>dispose()</code>,可以发现回调就停止了,因为这个订阅被释放了。</p>
<blockquote>
<p><code>dispose()</code> 这种显示释放资源的方式一般不推荐,下面介绍的 DisposeBag 是比较推荐的方式!</p>
</blockquote>
<h4 id="DisposeBag"><a href="#DisposeBag" class="headerlink" title="DisposeBag"></a>DisposeBag</h4><p>DisposeBag 是比较推荐的方式。看名字也很好理解 -「处理袋」,把需要释放的 <code>Disposable</code> 实例,扔到袋子里。在袋子被回收(<code>deinit</code>)时,会顺便执行一下 <code>Disposable.dispose()</code>,之前创建 <code>Disposable</code> 时申请的资源就会被一并释放掉。听起来有点像 ARC。不过在创建这个「处理袋」时,我们需要保证它不那么快被释放掉,所以一般都是声明成实例的成员变量,和实例绑定起来,实例销毁,它也就销毁。我们具体来看个 🌰:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewControllerTwo</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">// 创建「处理袋,声明成实例的成员变量</span></div><div class="line"> <span class="keyword">var</span> disposeBag = <span class="type">DisposeBag</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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> <span class="keyword">let</span> subscription = <span class="type">Observable</span><<span class="type">Int</span>>.interval(<span class="number">1.0</span>, scheduler: <span class="type">SerialDispatchQueueScheduler</span>.<span class="keyword">init</span>(internalSerialQueueName: <span class="string">"haha"</span>))</div><div class="line"> .subscribe { event <span class="keyword">in</span></div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"\(event)"</span>)</div><div class="line"> }</div><div class="line"> subscription.disposed(by: disposeBag)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">deinit</span> {</div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"释放了"</span>)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">17</span>:<span class="number">57</span>:<span class="number">03</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">23</span>]:next(<span class="number">0</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">17</span>:<span class="number">57</span>:<span class="number">04</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">23</span>]:next(<span class="number">1</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">17</span>:<span class="number">57</span>:<span class="number">05</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">23</span>]:next(<span class="number">2</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">17</span>:<span class="number">57</span>:<span class="number">06</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">23</span>]:next(<span class="number">3</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">17</span>:<span class="number">57</span>:<span class="number">07</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">23</span>]:next(<span class="number">4</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">17</span>:<span class="number">57</span>:<span class="number">08</span> <span class="type">ViewControllerTwo</span>.swift <span class="keyword">deinit</span> <span class="number">29</span>]:释放了</div></pre></td></tr></table></figure>
<p>可以发现,在 <code>ViewControllerTwo</code> 释放(<code>deinit</code>)后,回调也停止了!</p>
<h4 id="takeUntil"><a href="#takeUntil" class="headerlink" title="takeUntil"></a>takeUntil</h4><p>我们还可以利用 <code>takeUntil</code> 操作符,把订阅绑定在 <code>deallocated</code>,来实现自动清理。我们直接看 🌰:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewControllerTwo</span>: <span class="title">UIViewController</span> </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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> <span class="number">_</span> = <span class="type">Observable</span><<span class="type">Int</span>></div><div class="line"> .interval(<span class="number">1.0</span>, scheduler: <span class="type">SerialDispatchQueueScheduler</span>.<span class="keyword">init</span>(internalSerialQueueName: <span class="string">"haha"</span>))</div><div class="line"> .takeUntil(<span class="keyword">self</span>.rx.deallocated) <span class="comment">// 绑定 deallocated</span></div><div class="line"> .subscribe { event <span class="keyword">in</span></div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"\(event)"</span>)</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">deinit</span> {</div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"释放了"</span>)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">29</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:next(<span class="number">0</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">30</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:next(<span class="number">1</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">31</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:next(<span class="number">2</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">32</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:next(<span class="number">3</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">33</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:next(<span class="number">4</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">34</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:next(<span class="number">5</span>)</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">35</span> <span class="type">ViewControllerTwo</span>.swift <span class="keyword">deinit</span> <span class="number">27</span>]:释放了</div><div class="line">[<span class="number">2017</span>-<span class="number">12</span>-<span class="number">29</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">35</span> <span class="type">ViewControllerTwo</span>.swift viewDidLoad() <span class="number">22</span>]:completed</div></pre></td></tr></table></figure>
<p>可以发现在 <code>ViewControllerTwo</code> 释放(<code>deinit</code>)后,回调也停止了!</p>
<h2 id="1.3"> Hot and Cold Observables </h2>
<h3 id="Cold-Observables"><a href="#Cold-Observables" class="headerlink" title="Cold Observables"></a>Cold Observables</h3><p>只有在被订阅的时候才会发射事件。每次有新的订阅者都会把之前所有的事件都重新发射一遍,换句话说,每个订阅者都会独立的收到订阅者发射的数据。</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></pre></td><td class="code"><pre><div class="line"> <span class="keyword">let</span> intSequence = <span class="type">Observable</span><<span class="type">Int</span>>.create { (observer) -> <span class="type">Disposable</span> <span class="keyword">in</span></div><div class="line"></div><div class="line"> observer.onNext(<span class="number">1</span>)</div><div class="line"> observer.onNext(<span class="number">2</span>)</div><div class="line"> observer.onNext(<span class="number">3</span>)</div><div class="line"> </div><div class="line"> <span class="keyword">return</span> <span class="type">Disposables</span>.create()</div><div class="line">}</div></pre></td></tr></table></figure>
<p>上面的 <code>Observable</code> 在没订阅之前,<code>create</code> 是不会被执行的。当我们订阅时才会执行:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">intSequence.subscribe(onNext:{ value <span class="keyword">in</span></div><div class="line"> </div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"subscribe1 : \(value)"</span>);</div><div class="line"> </div><div class="line">}).disposed(by: disposeBag)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">76</span>]:subscribe1 : <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">76</span>]:subscribe1 : <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">76</span>]:subscribe1 : <span class="number">3</span></div></pre></td></tr></table></figure>
<p>如果我们接着上面的代码再多添加几个订阅:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><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></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">2</span>...<span class="number">4</span> {</div><div class="line"> <span class="comment">// 第三个订阅延迟两秒</span></div><div class="line"> <span class="keyword">if</span> i == <span class="number">3</span> {</div><div class="line"> <span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() + <span class="number">2</span>, execute: {</div><div class="line"> intSequence.subscribe(onNext:{ value <span class="keyword">in</span></div><div class="line"> </div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"subscribe\(i) : \(value)"</span>)</div><div class="line"> </div><div class="line"> }).disposed(by: <span class="keyword">self</span>.disposeBag)</div><div class="line"> })</div><div class="line"> <span class="keyword">continue</span></div><div class="line"> }</div><div class="line"> </div><div class="line"> intSequence.subscribe(onNext:{ value <span class="keyword">in</span></div><div class="line"> </div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"subscribe\(i) : \(value)"</span>)</div><div class="line"> </div><div class="line"> }).disposed(by: disposeBag)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">76</span>]:subscribe1 : <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">76</span>]:subscribe1 : <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">76</span>]:subscribe1 : <span class="number">3</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">96</span>]:subscribe2 : <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">96</span>]:subscribe2 : <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">96</span>]:subscribe2 : <span class="number">3</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">96</span>]:subscribe4 : <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">96</span>]:subscribe4 : <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">24</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">96</span>]:subscribe4 : <span class="number">3</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">26</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">86</span>]:subscribe3 : <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">26</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">86</span>]:subscribe3 : <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">17</span>:<span class="number">05</span>:<span class="number">26</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">86</span>]:subscribe3 : <span class="number">3</span></div></pre></td></tr></table></figure>
<p>可以发现每次数据都会重新发射一遍,是独立的,即 Observable 会为每个订阅者单独执行一次发射数据的代码。</p>
<h3 id="Hot-Observables"><a href="#Hot-Observables" class="headerlink" title="Hot Observables"></a>Hot Observables</h3><p>有新的事件它就发射,不考虑是否有订阅者订阅。而新的订阅者并不会接收到订阅前已经发射过的事件。</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></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> tap = button.rx.tap</div><div class="line">tap.subscribe(onNext: {</div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"Tap 1"</span>)</div><div class="line">}).disposed(by: disposeBag)</div><div class="line"></div><div class="line"><span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() + <span class="number">3</span>) {</div><div class="line"> tap.subscribe(onNext: {</div><div class="line"> <span class="type">DebugPrint</span>(<span class="string">"Tap 2"</span>)</div><div class="line"> }).disposed(by: <span class="keyword">self</span>.disposeBag)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>比如 3 秒前,我们点击了三次 Button ,此后又点击了两次,总共五次按钮的点击。输出结果如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">[2018-01-02 18:22:51 ViewController.swift viewDidLoad() 45]:Tap 1</div><div class="line">[2018-01-02 18:22:52 ViewController.swift viewDidLoad() 45]:Tap 1</div><div class="line">[2018-01-02 18:22:52 ViewController.swift viewDidLoad() 45]:Tap 1</div><div class="line">[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 45]:Tap 1</div><div class="line">[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 50]:Tap 2</div><div class="line">[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 45]:Tap 1</div><div class="line">[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 50]:Tap 2</div></pre></td></tr></table></figure>
<p>3 秒前第二个订阅者并没有订阅 tap ,故不会有 Tap 2 的输出。3 秒后第二个订阅者订阅了 tap ,此时点击 Button ,可以看到打印结果多了 Tap 2。但与 Cold Observable 不同的是,第二个订阅者不会收到之前三次的点击事件。</p>
<p>我们如果将 <code>button.rx.tap</code> 替换成 Cold Observables,可以看到打印结果有 5 个 Tap 2 :</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> tap = <span class="type">Observable</span><<span class="type">Int</span>>.create { (observer) -> <span class="type">Disposable</span> <span class="keyword">in</span></div><div class="line"> observer.onNext(<span class="number">1</span>)</div><div class="line"> observer.onNext(<span class="number">2</span>)</div><div class="line"> observer.onNext(<span class="number">3</span>)</div><div class="line"> observer.onNext(<span class="number">4</span>)</div><div class="line"> observer.onNext(<span class="number">5</span>)</div><div class="line"> <span class="keyword">return</span> <span class="type">Disposables</span>.create()</div><div class="line"> }.<span class="built_in">map</span>({<span class="number">_</span> <span class="keyword">in</span>})</div><div class="line"> </div><div class="line"><span class="comment">// 输出:</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">43</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">47</span>]:<span class="type">Tap</span> <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">43</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">47</span>]:<span class="type">Tap</span> <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">43</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">47</span>]:<span class="type">Tap</span> <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">43</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">47</span>]:<span class="type">Tap</span> <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">43</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">47</span>]:<span class="type">Tap</span> <span class="number">1</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">46</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">52</span>]:<span class="type">Tap</span> <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">46</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">52</span>]:<span class="type">Tap</span> <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">46</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">52</span>]:<span class="type">Tap</span> <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">46</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">52</span>]:<span class="type">Tap</span> <span class="number">2</span></div><div class="line">[<span class="number">2018</span>-<span class="number">01</span>-<span class="number">02</span> <span class="number">18</span>:<span class="number">24</span>:<span class="number">46</span> <span class="type">ViewController</span>.swift viewDidLoad() <span class="number">52</span>]:<span class="type">Tap</span> <span class="number">2</span></div></pre></td></tr></table></figure>
<p>参考:</p>
<p><a href="https://beeth0ven.github.io/RxSwift-Chinese-Documentation/" target="_blank" rel="external">https://beeth0ven.github.io/RxSwift-Chinese-Documentation/</a><br><a href="https://medium.com/@DianQK/hot-observable%E5%92%8Ccold-observable-c3ba8d07867b" target="_blank" rel="external">https://medium.com/@DianQK/hot-observable%E5%92%8Ccold-observable-c3ba8d07867b</a><br><a href="https://juejin.im/entry/57f29c46da2f60004f68f663" target="_blank" rel="external">https://juejin.im/entry/57f29c46da2f60004f68f663</a><br><a href="https://github.com/ReactiveX/RxSwift/tree/master/Documentation" target="_blank" rel="external">https://github.com/ReactiveX/RxSwift/tree/master/Documentation</a><br><a href="https://github.com/Joe0708/RxSwift-Tutorial" target="_blank" rel="external">https://github.com/Joe0708/RxSwift-Tutorial</a></p>
]]></content>
<summary type="html">
<p>环境:</p>
<blockquote>
<p>Xcode 9.1<br>Swift 4.0<br>RxSwift 4.0
</summary>
<category term="Swift" scheme="http://yoursite.com/tags/Swift/"/>
</entry>
<entry>
<title>Swift 知识小集</title>
<link href="http://yoursite.com/2017/12/07/swift-tips/"/>
<id>http://yoursite.com/2017/12/07/swift-tips/</id>
<published>2017-12-07T13:28:02.000Z</published>
<updated>2018-02-13T10:06:48.512Z</updated>
<content type="html"><![CDATA[<p>以下内容均是笔者学习过程中<strong>收集</strong>的知识点,顺序比较跳跃,初衷是为了方便查阅,顺便加深记忆。<strong>内容会不断更新</strong>,如果有什么问题或者有好的 Swift 方面的语法糖或者知识点也可以提出来,我会挑选斟酌后收录,欢迎大家关注~</p>
<p><strong>环境:</strong></p>
<blockquote>
<p>Swift 4.0<br>Xcode 9.1</p>
</blockquote>
<p><strong>最近更新:2018.01.09</strong></p>
<blockquote>
<p><a href="#1.4.0">取消 asyncAfter 中延迟的事件</a><br><a href="#1.3.9">for in 和 forEach 的区别</a><br><a href="#1.3.8">访问级别</a><br><a href="#1.3.7">几种遍历方式</a><br><a href="#1.3.6">weakSelf 和 strongSelf</a></p>
</blockquote>
<p><strong>更新:2017.12.13</strong></p>
<blockquote>
<p><a href="#1.3.5">优雅的定义通知名称</a></p>
</blockquote>
<p><strong>更新:2017.12.11</strong></p>
<blockquote>
<p><a href="#1.3.4">标签语句:指定跳出某个条件语句</a><br><a href="#1.3.3">倒序 reversed()</a><br><a href="#1.3.2">作用域:do 语句块</a></p>
</blockquote>
<p><strong>更新:2017.12.08</strong></p>
<blockquote>
<p><a href="#1.3.1">Swift 中的 “readonly”</a><br><a href="#1.3.0">自定义日志输出</a><br><a href="#1.2.9">Swift 中的 “@synchronized”</a></p>
</blockquote>
<p><strong>目录:</strong></p>
<blockquote>
<p><a href="#1.1.1">Associated Object</a><br><a href="#1.1.2">Delegate 声明为 weak</a><br><a href="#1.1.3">可选协议和协议扩展</a><br><a href="#1.1.4">单例</a><br><a href="#1.1.5">输出格式化</a><br><a href="#1.1.6">Selector</a><br><a href="#1.1.7">将 protocol 的方法声明为 mutating</a><br><a href="#1.1.8">数组遍历 enumerate</a><br><a href="#1.1.9">输入输出参数 inout</a><br><a href="#1.2.0">Default 参数</a><br><a href="#1.2.1">延迟加载 lazy</a><br><a href="#1.2.2">编译标记</a><br><a href="#1.2.3">换行符</a><br><a href="#1.2.4">字符串切割 split</a><br><a href="#1.2.5">KVC</a><br><a href="#1.2.6">Swift 中值类型和引用类型注意点</a><br><a href="#1.2.7">KVO</a><br><a href="#1.2.8">Swift UIButton 状态的叠加</a><br><a href="#1.2.9">Swift 中的 “@synchronized”</a><br><a href="#1.3.0">自定义日志输出</a><br><a href="#1.3.1">Swift 中的 “readonly”</a><br><a href="#1.3.2">作用域:do 语句块</a><br><a href="#1.3.3">倒序 reversed()</a><br><a href="#1.3.4">标签语句:指定跳出某个条件语句</a><br><a href="#1.3.5">优雅的定义通知名称</a><br><a href="#1.3.6">weakSelf 和 strongSelf</a><br><a href="#1.3.7">几种遍历方式</a><br><a href="#1.3.8">访问级别</a><br><a href="#1.3.9">for in 和 forEach 的区别</a><br><a href="#1.4.0">取消 asyncAfter 中延迟的事件</a></p>
</blockquote>
<h2 id="1.1.1"> Associated Object </h2>
<p>Objective-C 的 runtime 里的 Associated Object 允许我们在使用 Category 扩展现有的类的功能的时候,直接添加实例变量。在 Swift 中 extension 不能添加<strong>存储属性</strong>,我们可以利用 Associated Object 来实现,比如下面的 <code>title</code> 「实际上」是一个存储属性:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// MyClass.swift</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{}</div><div class="line"></div><div class="line"><span class="comment">// MyClassExtension.swift</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">var</span> key: <span class="type">Void</span>?</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">MyClass</span> </span>{</div><div class="line"> <span class="keyword">var</span> title: <span class="type">String</span>? {</div><div class="line"> <span class="keyword">get</span> {</div><div class="line"> <span class="keyword">return</span> swift_getAssociatedObject(<span class="keyword">self</span>, &key) <span class="keyword">as</span>? <span class="type">String</span></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">set</span> {</div><div class="line"> swift_setAssociatedObject(<span class="keyword">self</span>,</div><div class="line"> &key, newValue,</div><div class="line"> .swift_ASSOCIATION_RETAIN_NONATOMIC)</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 测试</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">printTitle</span><span class="params">(<span class="number">_</span> input: MyClass)</span></span> {</div><div class="line"> <span class="keyword">if</span> <span class="keyword">let</span> title = input.title {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"Title: \(title)"</span>)</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"没有设置"</span>)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">let</span> a = <span class="type">MyClass</span>()</div><div class="line">printTitle(a)</div><div class="line">a.title = <span class="string">"Swifter.tips"</span></div><div class="line">printTitle(a)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 没有设置</span></div><div class="line"><span class="comment">// Title: Swifter.tips”</span></div></pre></td></tr></table></figure>
<h2 id="1.1.2"> Delegate 声明为 weak </h2>
<p>Swift 中 Delegate 需要被声明成 <code>weak</code>,来避免访问到已被回收的内存而导致崩溃,如果我们像下面这样,是编译不过的:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">protocol</span> <span class="title">MyClassDelegate</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">method</span><span class="params">()</span></span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</div><div class="line"> <span class="keyword">weak</span> <span class="keyword">var</span> delegate: <span class="type">MyClassDelegate</span>?</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span>, <span class="title">MyClassDelegate</span> </span>{</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> <span class="keyword">var</span> someInstance: <span class="type">MyClass</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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> someInstance = <span class="type">MyClass</span>()</div><div class="line"> someInstance.delegate = <span class="keyword">self</span></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">method</span><span class="params">()</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"Do something"</span>)</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">//...</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译失败</span></div><div class="line"><span class="comment">// 'weak' may only be applied to class and class-bound protocol types, not 'MyClassDelegate'</span></div></pre></td></tr></table></figure>
<p>这是因为 Swift 的 protocol 是可以被除了 class 以外的其他类型遵守的,而对于像 <code>struct</code> 或是 <code>enum</code> 这样的类型,本身就不通过引用计数来管理内存,所以也不可能用 <code>weak</code> 这样的 ARC 的概念来进行修饰。</p>
<p>想要在 Swift 中使用 weak delegate,我们就需要将 protocol 限制在 class 内:</p>
<ul>
<li>一种做法是将 protocol 声明为 Objective-C 的,这可以通过在 protocol 前面加上 <code>@objc</code> 关键字来达到,Objective-C 的 protocol 都只有类能实现,因此使用 weak 来修饰就合理了:</li>
</ul>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@objc</span> <span class="class"><span class="keyword">protocol</span> <span class="title">MyClassDelegate</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">method</span><span class="params">()</span></span></div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>另一种<strong>可能更好</strong>的办法是在 protocol 声明的名字后面加上 <code>class</code>,这可以为编译器显式地指明这个 protocol 只能由 <code>class</code> 来实现,避免了过多的不必要的 Objective-C 兼容:</li>
</ul>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">protocol</span> <span class="title">MyClassDelegate</span>: <span class="title">class</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">method</span><span class="params">()</span></span></div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="1.1.3"> 可选协议和协议扩展 </h2>
<p>Objective-C 中的 protocol 里存在 <code>@optional</code> 关键字,被这个关键字修饰的方法并非必须要被实现,原生的 Swift protocol 里没有可选项,所有定义的方法都是必须实现的,如果不是实现是无法编译的:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span>,<span class="title">MyProtocol</span> </span>{ }</div><div class="line"></div><div class="line"><span class="comment">// 编译失败</span></div><div class="line"><span class="comment">// Type 'ViewController' does not conform to protocol 'MyProtocol'</span></div></pre></td></tr></table></figure>
<p>如果我们想要像 Objective-C 里那样定义可选的协议方法,就需要将协议本身和可选方法都定义为 Objective-C 的,也即在 protocol 定义之前加上 <code>@objc</code>,方法之前加上 <code>@objc optional</code>:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@objc</span> <span class="class"><span class="keyword">protocol</span> <span class="title">MyProtocol</span> </span>{</div><div class="line"> <span class="meta">@objc</span> <span class="keyword">optional</span> <span class="function"><span class="keyword">func</span> <span class="title">myMethod</span><span class="params">()</span></span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>另外,对于所有的声明,它们的前缀修饰是完全分开的,<strong>也就是说你不能像是在 Objective-C 里那样用一个 <code>@optional</code> 指定接下来的若干个方法都是可选的了,</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></pre></td><td class="code"><pre><div class="line"><span class="meta">@objc</span> <span class="class"><span class="keyword">protocol</span> <span class="title">MyProtocol</span> </span>{</div><div class="line"> <span class="meta">@objc</span> <span class="keyword">optional</span> <span class="function"><span class="keyword">func</span> <span class="title">optionalMethod</span><span class="params">()</span></span> <span class="comment">// 可选</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">necessaryMethod</span><span class="params">()</span></span> <span class="comment">// 必须</span></div><div class="line"> <span class="meta">@objc</span> <span class="keyword">optional</span> <span class="function"><span class="keyword">func</span> <span class="title">anotherOptionalMethod</span><span class="params">()</span></span> <span class="comment">// 可选</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p><strong>一个不可避免的限制是</strong>,使用 <code>@objc</code> 修饰的 protocol 就只能被 <code>class</code> 实现了,也就是说,对于 <code>struct</code> 和 <code>enum</code> 类型,我们是无法令它们所实现的协议中含有可选方法或者属性的。另外,实现它的 <code>class</code> 中的方法还必须也被标注为 <code>@objc</code>,或者整个类就是继承自 <code>NSObject</code>。对于这种问题,在 Swift 2.0 中,我们有了另一种选择,那就是使用 <strong>protocol extension</strong>。我们可以在声明一个 protocol 之后再用 extension 的方式给出部分方法<strong>默认的实现</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><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="class"><span class="keyword">protocol</span> <span class="title">MyProtocol</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">optionalMethod</span><span class="params">()</span></span> <span class="comment">// 可选</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">necessaryMethod</span><span class="params">()</span></span> <span class="comment">// 必须</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">anotherOptionalMethod</span><span class="params">()</span></span> <span class="comment">// 可选</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">MyProtocol</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">//默认的可选实现</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">optionalMethod</span><span class="params">()</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"optionalMethod"</span>)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">//默认的可选实现</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">anotherOptionalMethod</span><span class="params">()</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"anotherOptionalMethod"</span>)</div><div class="line"> } </div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span>,<span class="title">MyProtocol</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">// 必须的实现</span></div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">necessaryMethod</span><span class="params">()</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"necessaryMethod"</span>)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> <span class="keyword">self</span>.optionalMethod();</div><div class="line"> <span class="keyword">self</span>.necessaryMethod();</div><div class="line"> <span class="keyword">self</span>.anotherOptionalMethod();</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// optionalMethod</span></div><div class="line"><span class="comment">// necessaryMethod</span></div><div class="line"><span class="comment">// necessaryMethod</span></div></pre></td></tr></table></figure>
<h2 id="1.1.4"> 单例 </h2>
<p>Swift 中的单例非常简单,Swift 1.2 以及之后:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singleton</span> </span>{</div><div class="line"> <span class="keyword">static</span> <span class="keyword">let</span> sharedInstance = <span class="type">Singleton</span>()</div><div class="line"> <span class="keyword">private</span> <span class="keyword">init</span>() {}</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这种写法不但是线程安全的,也是懒加载的,<code>let</code> 定义的属性本身就是线程安全的,同时 <code>static</code> 定义的是一个 class constant,拥有全局作用域和懒加载特性。</p>
<p>另外,这个类型中加入了一个私有的初始化方法,来覆盖默认的公开初始化方法,这让项目中的其他地方不能够通过 init 来生成自己的 <code>Singleton</code> 实例,也保证了类型单例的唯一性。如果你需要的是类似 default 的形式的单例 (也就是说这个类的使用者可以创建自己的实例) 的话,可以去掉这个私有的 <code>init</code> 方法。</p>
<h2 id="1.1.5"> 输出格式化 </h2>
<p>在 Objective-C 中的 <code>%@</code> 这样的格式在指定的位置设定占位符,然后通过参数的方式将实际要输出的内容补充完整。例如 Objective-C 中常用的向控制台输出的 <code>NSLog</code> 方法就使用了这种格式化方法:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">float a = <span class="number">1.234567</span>;</div><div class="line"><span class="type">NSString</span> *b = @<span class="string">"Helllo"</span>;</div><div class="line"><span class="type">NSLog</span>(@<span class="string">"float:%.2f str:%p"</span>,a,b);</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// float:1.23 str:0x1024a1078</span></div></pre></td></tr></table></figure>
<p>对应 Swift 中我们可以这样:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> a = <span class="number">1.234567</span></div><div class="line"><span class="keyword">let</span> b = <span class="string">"Helllo"</span></div><div class="line"><span class="keyword">let</span> <span class="built_in">c</span> = <span class="type">String</span>(format:<span class="string">"float:%.2f str:%p"</span>,a,b)</div><div class="line"><span class="built_in">print</span>(<span class="built_in">c</span>)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// float:1.23 str:0x604000249e10</span></div></pre></td></tr></table></figure>
<h2 id="1.1.6"> Selector </h2>
<p><code>@selector</code> 是 Objective-C 时代的一个关键字,它可以将一个方法转换并赋值给一个 SEL 类型,它的表现很类似一个动态的函数指针。在 Swift 中没有 <code>@selector</code> 了,取而代之,从 Swift 2.2 开始我们使用 <code>#selector</code> 来从暴露给 Objective-C 的代码中获取一个 <code>selector</code>,并且因为 <code>selector</code> 是 Objective-C runtime 的概念,在 Swift 4 中,默认情况下所有的 Swift 方法在 Objective-C 中都是不可见的,所以你需要在这类方法前面加上 <code>@objc</code> 关键字,将这个方法暴露给 Objective-C,才能进行使用:</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">let</span> btn = <span class="type">UIButton</span>.<span class="keyword">init</span>(type: .system)</div><div class="line">btn.backgroundColor = <span class="type">UIColor</span>.red</div><div class="line">btn.frame = <span class="type">CGRect</span>(x: <span class="number">100</span>, y: <span class="number">100</span>, width: <span class="number">150</span>, height: <span class="number">40</span>)</div><div class="line">btn.setTitle(<span class="string">"Button"</span>, <span class="keyword">for</span>: .normal)</div><div class="line"><span class="comment">//无参数</span></div><div class="line">btn.addTarget(<span class="keyword">self</span>, action: #selector(btnClick), <span class="keyword">for</span>: .touchUpInside)</div><div class="line">view.addSubview(btn)</div><div class="line"></div><div class="line"><span class="meta">@objc</span> <span class="function"><span class="keyword">func</span> <span class="title">btnClick</span><span class="params">()</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"button click !"</span>)</div><div class="line">}</div></pre></td></tr></table></figure>
<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">...</div><div class="line"><span class="comment">//有参数</span></div><div class="line">btn.addTarget(<span class="keyword">self</span>, action: #selector(btnClick(<span class="number">_</span> :)), <span class="keyword">for</span>: .touchUpInside)</div><div class="line">...</div><div class="line"></div><div class="line"><span class="meta">@objc</span> <span class="function"><span class="keyword">func</span> <span class="title">btnClick</span><span class="params">(<span class="number">_</span> button: UIButton)</span></span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"button click !"</span>)</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="1.1.7"> 将 protocol 的方法声明为 mutating </h2>
<p>Swift 的 protocol 不仅可以被 class 类型实现,也适用于 <code>struct</code> 和 <code>enum</code>,因为这个原因,我们在写给别人用的协议时需要多考虑是否使用 <code>mutating</code> 来修饰方法。Swift 的 <code>mutating</code> 关键字修饰方法是为了能在该方法中修改 <code>struct</code> 或是 <code>enum</code> 的变量,所以如果你没在协议方法里写 <code>mutating</code> 的话,别人如果用 <code>struct</code> 或者 <code>enum</code> 来实现这个协议的话,就不能在方法里改变自己的变量了,比如下面的代码是编译不过的:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">protocol</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">changeColor</span><span class="params">()</span></span></div><div class="line"> </div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">struct</span> <span class="title">MyCar</span>: <span class="title">Vehicle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> color = <span class="string">"blue"</span></div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">func</span> <span class="title">changeColor</span><span class="params">()</span></span> {</div><div class="line"> color = <span class="string">"red"</span></div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译失败</span></div><div class="line"><span class="comment">// Cannot assign to property: 'self' is immutable</span></div></pre></td></tr></table></figure>
<p>我们应该加上 <code>mutating</code> 关键字:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="class"><span class="keyword">protocol</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">mutating</span> <span class="function"><span class="keyword">func</span> <span class="title">changeColor</span><span class="params">()</span></span></div><div class="line"> </div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">struct</span> <span class="title">MyCar</span>: <span class="title">Vehicle</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> color = <span class="string">"blue"</span></div><div class="line"> </div><div class="line"> <span class="keyword">mutating</span> <span class="function"><span class="keyword">func</span> <span class="title">changeColor</span><span class="params">()</span></span> {</div><div class="line"> color = <span class="string">"red"</span></div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> <span class="keyword">var</span> car = <span class="type">MyCar</span>()</div><div class="line"> <span class="built_in">print</span>(car.color)</div><div class="line"> car.changeColor()</div><div class="line"> <span class="built_in">print</span>(car.color) </div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// blue</span></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// red</span></div></pre></td></tr></table></figure>
<h2 id="1.1.8"> 数组遍历 enumerate </h2>
<p>使用 NSArray 时一个很常遇见的的需求是在枚举数组内元素的同时也想使用对应的<strong>下标索引</strong>,在 Objective-C 中最方便的方式是使用 NSArray 的 <code>enumerateObjectsUsingBlock:</code> ,在 Swift 中存在一个效率,安全性和可读性都很好的替代,那就是快速枚举某个数组的<code>EnumerateGenerator</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></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> arr = [<span class="string">"a"</span>,<span class="string">"b"</span>,<span class="string">"c"</span>,<span class="string">"d"</span>,<span class="string">"e"</span>]</div><div class="line"><span class="keyword">for</span> (idx, str) <span class="keyword">in</span> arr.enumerated() {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"idx: \(idx) str: \(str)"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line">idx: <span class="number">0</span> str: a</div><div class="line">idx: <span class="number">1</span> str: b</div><div class="line">idx: <span class="number">2</span> str: <span class="built_in">c</span></div><div class="line">idx: <span class="number">3</span> str: d</div><div class="line">idx: <span class="number">4</span> str: e</div></pre></td></tr></table></figure>
<h2 id="1.1.9"> 输入输出参数 inout </h2>
<p>函数参数默认是<strong>常量</strong>,如果试图在函数体中更改参数值将会导致<strong>编译错误</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></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">swapTwoInts</span><span class="params">(<span class="number">_</span> a: Int, <span class="number">_</span> b: Int)</span></span> {</div><div class="line"> <span class="keyword">let</span> temporaryA = a</div><div class="line"> a = b</div><div class="line"> b = temporaryA</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译失败</span></div><div class="line"><span class="comment">// Cannot assign to value: 'a' is a 'let' constant</span></div><div class="line"><span class="comment">// Cannot assign to value: 'b' is a 'let' constant</span></div></pre></td></tr></table></figure>
<p>如果想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters):</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">swapTwoInts</span><span class="params">(<span class="number">_</span> a: <span class="keyword">inout</span> Int, <span class="number">_</span> b: <span class="keyword">inout</span> Int)</span></span> {</div><div class="line"> <span class="keyword">let</span> temporaryA = a</div><div class="line"> a = b</div><div class="line"> b = temporaryA</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="1.2.0"> Default 参数 </h2>
<p>Swift 的方法是支持默认参数的,也就是说在声明方法时,可以给某个参数指定一个默认使用的值。在调用该方法时要是传入了这个参数,则使用传入的值,如果缺少这个输入参数,那么直接使用设定的默认值进行调用。和其他很多语言的默认参数相比较,Swift 中的默认参数限制更少,并没有所谓 <strong>“默认参数之后不能再出现无默认值的参数”</strong>这样的规则,举个例子,下面两种方法的声明在 Swift 里都是合法可用的:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">sayHello1</span><span class="params">(str1: String = <span class="string">"Hello"</span>, str2: String, str3: String)</span></span> {</div><div class="line"> <span class="built_in">print</span>(str1 + str2 + str3)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">sayHello2</span><span class="params">(str1: String, str2: String, str3: String = <span class="string">"World"</span>)</span></span> {</div><div class="line"> <span class="built_in">print</span>(str1 + str2 + str3)</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">sayHello1(str2: <span class="string">" "</span>, str3: <span class="string">"World"</span>)</div><div class="line">sayHello2(str1: <span class="string">"Hello"</span>, str2: <span class="string">" "</span>)</div><div class="line"></div><div class="line"><span class="comment">//输出都是 Hello World</span></div></pre></td></tr></table></figure>
<h2 id="1.2.1"> 延迟加载 lazy </h2>
<p>延时加载或者说延时初始化是很常用的优化方法,在构建和生成新的对象的时候,内存分配会在运行时耗费不少时间,<strong>如果有一些对象的属性和内容非常复杂的话,这个时间更是不可忽略</strong>。另外,有些情况下我们并不会立即用到一个对象的所有属性,而默认情况下初始化时,<strong>那些在特定环境下不被使用的存储属性,也一样要被初始化和赋值</strong>,也是一种浪费。在 Objective-C 中,一个延迟加载一般是这样的:</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="comment">// ClassA.h</span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *testString;</div><div class="line"></div><div class="line"><span class="comment">// ClassA.m</span></div><div class="line">- (<span class="built_in">NSString</span> *)testString {</div><div class="line"> <span class="keyword">if</span> (!_testString) {</div><div class="line"> _testString = <span class="string">@"Hello"</span>;</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"只在首次访问输出"</span>);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> _testString;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>对应在 Swift 中,使用 <code>lazy</code> 作为属性修饰符时,只能声明属性是<strong>变量</strong>,且我们需要显式地指定<strong>属性类型</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><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">ClassA</span> </span>{</div><div class="line"> <span class="built_in">lazy</span> <span class="keyword">let</span> str: <span class="type">String</span> = {</div><div class="line"> <span class="keyword">let</span> str = <span class="string">"Hello"</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"只在首次访问输出"</span>)</div><div class="line"> <span class="keyword">return</span> str</div><div class="line"> }()</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译失败</span></div><div class="line"><span class="comment">// 'lazy' cannot be used on a let</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ClassA</span> </span>{</div><div class="line"> <span class="built_in">lazy</span> <span class="keyword">var</span> str = {</div><div class="line"> <span class="keyword">let</span> str = <span class="string">"Hello"</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"只在首次访问输出"</span>)</div><div class="line"> <span class="keyword">return</span> str</div><div class="line"> }()</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译失败</span></div><div class="line"><span class="comment">// Unable to infer complex closure return type</span></div></pre></td></tr></table></figure>
<p>我们应该声明为 <code>var</code> 并指定好类型:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ClassA</span> </span>{</div><div class="line"> <span class="built_in">lazy</span> <span class="keyword">var</span> str: <span class="type">String</span> = {</div><div class="line"> <span class="keyword">let</span> str = <span class="string">"Hello"</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"只在首次访问输出"</span>)</div><div class="line"> <span class="keyword">return</span> str</div><div class="line"> }()</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> <span class="keyword">let</span> ca = <span class="type">ClassA</span>()</div><div class="line"> <span class="built_in">print</span>(ca.str)</div><div class="line"> <span class="built_in">print</span>(ca.str)</div><div class="line"> <span class="built_in">print</span>(ca.str) </div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 只在首次访问输出</span></div><div class="line"><span class="comment">// Hello</span></div><div class="line"><span class="comment">// Hello</span></div><div class="line"><span class="comment">// Hello</span></div></pre></td></tr></table></figure>
<p>如果不需要做什么额外工作的话,也可以对这个 <code>lazy</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="built_in">lazy</span> <span class="keyword">var</span> str: <span class="type">String</span> = <span class="string">"Hello"</span></div></pre></td></tr></table></figure>
<p>我们还可以利用 <code>lazy</code> 配合像 <code>map</code> 或是 <code>filter</code> 这类接受闭包并进行运行的方法一起,<strong>让整个行为变成延时进行的</strong>。在某些情况下这么做也对性能会有不小的帮助。例如,直接使用 map 时:</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></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> data = <span class="number">1</span>...<span class="number">3</span></div><div class="line"><span class="keyword">let</span> result = data.<span class="built_in">map</span> {</div><div class="line"> (i: <span class="type">Int</span>) -> <span class="type">Int</span> <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"正在处理 \(i)"</span>)</div><div class="line"> <span class="keyword">return</span> i * <span class="number">2</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">print</span>(<span class="string">"准备访问结果"</span>)</div><div class="line"><span class="keyword">for</span> i <span class="keyword">in</span> result {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"操作后结果为 \(i)"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">print</span>(<span class="string">"操作完毕"</span>)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 正在处理 1</span></div><div class="line"><span class="comment">// 正在处理 2</span></div><div class="line"><span class="comment">// 正在处理 3</span></div><div class="line"><span class="comment">// 准备访问结果</span></div><div class="line"><span class="comment">// 操作后结果为 2</span></div><div class="line"><span class="comment">// 操作后结果为 4</span></div><div class="line"><span class="comment">// 操作后结果为 6</span></div><div class="line"><span class="comment">// 操作完毕</span></div></pre></td></tr></table></figure>
<p>而如果我们先进行一次 <code>lazy</code> 操作的话,我们就能得到延时运行版本的容器:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> data = <span class="number">1</span>...<span class="number">3</span></div><div class="line"><span class="keyword">let</span> result = data.<span class="built_in">lazy</span>.<span class="built_in">map</span> {</div><div class="line"> (i: <span class="type">Int</span>) -> <span class="type">Int</span> <span class="keyword">in</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"正在处理 \(i)"</span>)</div><div class="line"> <span class="keyword">return</span> i * <span class="number">2</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">print</span>(<span class="string">"准备访问结果"</span>)</div><div class="line"><span class="keyword">for</span> i <span class="keyword">in</span> result {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"操作后结果为 \(i)"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">print</span>(<span class="string">"操作完毕"</span>)</div><div class="line"></div><div class="line"><span class="comment">// 准备访问结果</span></div><div class="line"><span class="comment">// 正在处理 1</span></div><div class="line"><span class="comment">// 操作后结果为 2</span></div><div class="line"><span class="comment">// 正在处理 2</span></div><div class="line"><span class="comment">// 操作后结果为 4</span></div><div class="line"><span class="comment">// 正在处理 3</span></div><div class="line"><span class="comment">// 操作后结果为 6</span></div><div class="line"><span class="comment">// 操作完毕</span></div></pre></td></tr></table></figure>
<p>对于那些不需要完全运行,可能提前退出的情况,使用 lazy 来进行性能优化效果会非常有效。</p>
<h2 id="1.2.2"> 编译标记 </h2>
<p>在 Objective-C 中,我们经常在代码中插入 <code>#param</code> 符号来标记代码的区间,这样在 Xcode 的导航栏中我们就可以看到组织分块后的方法列表。在 Swift 中我们可以用 <code>MARK:</code> 来代替:</p>
<p><img src="http://p0kmbfoc8.bkt.clouddn.com/Snip20171207_3.png" alt=""></p>
<p>在 Objective-C 中还有一个很常用的编译标记,那就是 <code>#warning</code>,一个 <code>#warning</code> 标记可以在 Xcode 的代码编辑器中显示为明显的黄色警告条,非常适合用来提示代码的维护者和使用者需要对某些东西加以关注。在 Swift 中我们可以用 <code>FIXME:</code> 和 <code>TODO:</code> 配合 <code>shell</code> 来代替:</p>
<p><img src="http://p0kmbfoc8.bkt.clouddn.com/Snip20171207_8.png" alt=""></p>
<p>脚本:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">TAGS="TODO:|FIXME:"</div><div class="line">echo "searching ${SRCROOT} for ${TAGS}"</div><div class="line">find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"</div></pre></td></tr></table></figure>
<p>效果:</p>
<p><img src="http://p0kmbfoc8.bkt.clouddn.com/Snip20171207_7.png" alt=""><br><img src="http://p0kmbfoc8.bkt.clouddn.com/Snip20171207_6.png" alt=""></p>
<h2 id="1.2.3"> 换行符 </h2>
<p>在 Swift 3 中,需要换行时是需要 <code>\n</code>:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> str = <span class="string">"xxxx\nxxx"</span></div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// xxxx</span></div><div class="line"><span class="comment">// xxx</span></div></pre></td></tr></table></figure>
<p>在 swift 4 中,我们可以使用 <code>"""</code>:</p>
<figure class="highlight"><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">let jsonStr = """</div><div class="line"> {</div><div class="line"> "id": 123455,</div><div class="line"> "nickname": "xxxx",</div><div class="line"> "isMale": true,</div><div class="line"> "birthday": "2000年3月24日",</div><div class="line"> "personalURL": "https://xxxxxx.github.io"</div><div class="line"> }</div><div class="line"> """</div><div class="line"> </div><div class="line">// 输出:</div><div class="line">{</div><div class="line"> "id": 123455,</div><div class="line"> "nickname": "xxxx",</div><div class="line"> "isMale": true,</div><div class="line"> "birthday": "2000年3月24日",</div><div class="line"> "personalURL": "https://xxxxxx.github.io"</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="1.2.4"> 字符串切割 split </h2>
<p>我们需要切割某个字符串时可以用 <code>split</code> 方法,需要注意的是,返回的结果是个<strong>数组</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></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> str = <span class="string">"Hello,world !"</span></div><div class="line"><span class="built_in">print</span>(str.<span class="built_in">split</span>(separator: <span class="string">","</span>))</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// ["Hello", "world !"]</span></div></pre></td></tr></table></figure>
<h2 id="1.2.5"> KVC </h2>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</div><div class="line"> <span class="keyword">var</span> name = <span class="string">"ifelseboyxx"</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>Swift 4 中 Apple 引入了新的 KeyPath 的表达方式,现在,对于类型 <code>MyClass</code> 中的<strong>变量</strong> <code>name</code>,对应的 KeyPath 可以写为 <code>\MyClass.name</code>,利用 KVC 修改 <code>name</code> 值的话,我们可以这么操作:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> object = <span class="type">MyClass</span>()</div><div class="line"><span class="built_in">print</span>(<span class="string">"name: \(object.name)"</span>)</div><div class="line"><span class="comment">// set</span></div><div class="line">object[keyPath: \<span class="type">MyClass</span>.name] = <span class="string">"ifelseboy"</span></div><div class="line"><span class="comment">// get</span></div><div class="line"><span class="built_in">print</span>(<span class="string">"name: \(object[keyPath: \MyClass.name])"</span>)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// name: ifelseboyxx</span></div><div class="line"><span class="comment">// name: ifelseboy</span></div></pre></td></tr></table></figure>
<p>另外 Swift 4 中 <code>struct</code> 同样支持 KVC :</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="class"><span class="keyword">struct</span> <span class="title">MyStruct</span> </span>{</div><div class="line"> <span class="keyword">var</span> age: <span class="type">Int</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">var</span> obj = <span class="type">MyStruct</span>(age: <span class="number">18</span>)</div><div class="line"><span class="built_in">print</span>(<span class="string">"我今年 \(obj.age) 岁了"</span>)</div><div class="line">obj[keyPath: \<span class="type">MyStruct</span>.age] = <span class="number">8</span></div><div class="line"><span class="built_in">print</span>(<span class="string">"我今年 \(obj[keyPath: \MyStruct.age]) 岁了"</span>)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 我今年 18 岁了</span></div><div class="line"><span class="comment">// 我今年 8 岁了</span></div></pre></td></tr></table></figure>
<h2 id="1.2.6"> Swift 中值类型和引用类型注意点 </h2>
<p>KVC 一节中代码里有个注意点:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> obj = <span class="type">MyStruct</span>(age: <span class="number">18</span>)</div><div class="line"><span class="comment">//替换为</span></div><div class="line"><span class="keyword">let</span> obj = <span class="type">MyStruct</span>(age: <span class="number">18</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="type">Cannot</span> assign to immutable expression of type '<span class="type">Int'</span></div></pre></td></tr></table></figure>
<p>笔者初次也犯了这样的错误,想当然的认为 <code>MyClass</code> 用 <code>let</code> 声明的是没有问题的,<code>struct</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> object = <span class="type">MyClass</span>()</div></pre></td></tr></table></figure>
<p><strong>其实原因很简单,swift 中 Class 是引用类型的,而 struct 是值类型的:值类型在被赋给一个变量,或被传给函数时,实际是做了一次拷贝。引用类型在被赋给一个变量,或被传给函数时,传递的是引用。</strong></p>
<h2 id="1.2.7"> KVO </h2>
<p>很遗憾,依然只有 <code>NSObject</code> 才能支持 KVO,另外由于 Swift 为了效率,默认禁用了动态派发,因此想用 Swift 来实现 KVO,我们还需要做额外的工作,那就是将想要观测的对象标记为 <code>dynamic</code> 和 <code>@objc</code>,下面的 🌰 是 <code>ViewController</code> 监听 <code>MyClass</code> 的 <code>date</code> 属性:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span>: <span class="title">NSObject</span> </span>{</div><div class="line"> <span class="meta">@objc</span> <span class="keyword">dynamic</span> <span class="keyword">var</span> date = <span class="type">Date</span>()</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> myObject: <span class="type">MyClass</span>!</div><div class="line"> <span class="keyword">var</span> observation: <span class="type">NSKeyValueObservation</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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> myObject = <span class="type">MyClass</span>()</div><div class="line"> <span class="built_in">print</span>(<span class="string">"当前日期:\(myObject.date)"</span>)</div><div class="line"> </div><div class="line"> observation = myObject.observe(\<span class="type">MyClass</span>.date, options: [.old,.new], changeHandler: { (<span class="number">_</span>, change) <span class="keyword">in</span></div><div class="line"> <span class="keyword">if</span> <span class="keyword">let</span> newDate = change.newValue , <span class="keyword">let</span> oldDate = change.oldValue {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"日期发生变化 old:\(oldDate) new:\(newDate) "</span>)</div><div class="line"> }</div><div class="line"> })</div><div class="line"> </div><div class="line"> <span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() + <span class="number">1</span>) {</div><div class="line"> <span class="keyword">self</span>.myObject.date = <span class="type">Date</span>()</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 当前日期:2017-12-07 06:31:26 +0000</span></div><div class="line"><span class="comment">// 日期发生变化 old:2017-12-07 06:31:26 +0000 new:2017-12-07 06:31:27 +0000</span></div></pre></td></tr></table></figure>
<p>在 Objective-C 中我们几乎可以没有限制地对所有满足 KVC 的属性进行监听,<strong>而现在我们需要属性有 <code>dynamic</code> 和 <code>@objc</code> 进行修饰</strong>。大多数情况下,我们想要观察的类包含这两个修饰 (除非这个类的开发者有意为之,否则一般也不会有人愿意多花功夫在属性前加上它们,因为这毕竟要损失一部分性能),并且有时候我们很可能也无法修改想要观察的类的源码。遇到这样的情况的话,<strong>一个可能可行的方案是继承这个类并且将需要观察的属性使用 <code>dynamic</code> 和 <code>@objc</code> 进行重写。</strong>比如刚才我们的 <code>MyClass</code> 中如果 <code>date</code> <strong>没有相应标注的话</strong>,我们可能就需要一个新的 <code>MyChildClass</code>了: </p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span>: <span class="title">NSObject</span> </span>{</div><div class="line"> <span class="keyword">var</span> date = <span class="type">Date</span>()</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyChildClass</span>: <span class="title">MyClass</span> </span>{</div><div class="line"> <span class="meta">@objc</span> <span class="keyword">dynamic</span> <span class="keyword">override</span> <span class="keyword">var</span> date: <span class="type">Date</span> {</div><div class="line"> <span class="keyword">get</span> { <span class="keyword">return</span> <span class="keyword">super</span>.date }</div><div class="line"> <span class="keyword">set</span> { <span class="keyword">super</span>.date = newValue }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">var</span> myObject: <span class="type">MyChildClass</span>!</div><div class="line"> <span class="keyword">var</span> observation: <span class="type">NSKeyValueObservation</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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> myObject = <span class="type">MyChildClass</span>()</div><div class="line"> <span class="built_in">print</span>(<span class="string">"当前日期:\(myObject.date)"</span>)</div><div class="line"> </div><div class="line"> observation = myObject.observe(\<span class="type">MyChildClass</span>.date, options: [.old,.new], changeHandler: { (<span class="number">_</span>, change) <span class="keyword">in</span></div><div class="line"> <span class="keyword">if</span> <span class="keyword">let</span> newDate = change.newValue , <span class="keyword">let</span> oldDate = change.oldValue {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"日期发生变化 old:\(oldDate) new:\(newDate) "</span>)</div><div class="line"> }</div><div class="line"> })</div><div class="line"> </div><div class="line"> <span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() + <span class="number">1</span>) {</div><div class="line"> <span class="keyword">self</span>.myObject.date = <span class="type">Date</span>()</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 当前日期:2017-12-07 06:36:50 +0000</span></div><div class="line"><span class="comment">// 日期发生变化 old:2017-12-07 06:36:50 +0000 new:2017-12-07 06:36:51 +0000</span></div></pre></td></tr></table></figure>
<h2 id="1.2.8"> Swift UIButton 状态的叠加 </h2>
<p>在 Objective-C 中,如果我们想叠加按钮的某个状态,可以这么写:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">UIButton</span> * button = [<span class="built_in">UIButton</span> buttonWithType:<span class="built_in">UIButtonTypeCustom</span>];</div><div class="line">[button setTitle:<span class="string">@"Test"</span> forState:<span class="built_in">UIControlStateNormal</span> | <span class="built_in">UIControlStateSelected</span>];</div></pre></td></tr></table></figure>
<p>对应的 Swift 我们可以这么写:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> btn = <span class="type">UIButton</span>.<span class="keyword">init</span>(type: .custom)</div><div class="line">btn.setTitle(<span class="string">"hehe"</span>, <span class="keyword">for</span>: [.normal ,.selected])</div></pre></td></tr></table></figure>
<p>把需要叠加的状态用个<strong>数组</strong>装起来就行了。</p>
<h2 id="1.2.9"> Swift 中的 “@synchronized” </h2>
<p>在 Objective-C 中,我们可以用 <code>@synchronized</code> 这个关键字可以用来修饰一个变量,并为其自动加上和解除互斥锁。这样,可以保证变量在作用范围内不会被其他线程改变:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)myMethod:(<span class="keyword">id</span>)anObj {</div><div class="line"> <span class="keyword">@synchronized</span>(anObj) {</div><div class="line"> <span class="comment">// 在括号内持有 anObj 锁</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>虽然这个方法很简单好用,但是很不幸的是在 Swift 中它已经 (或者是暂时) 不存在了。其实 <code>@synchronized</code> 在幕后做的事情是调用了 <code>objc_sync</code> 中的 <code>objc_sync_enter</code> 和 <code>objc_sync_exit</code> 方法,并且加入了一些异常判断。因此,在 Swift 中,如果我们忽略掉那些异常的话,我们想要 lock 一个变量的话,可以这样写:</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="function"><span class="keyword">func</span> <span class="title">myMethod</span><span class="params">(anObj: AnyObject!)</span></span> {</div><div class="line"> objc_sync_enter(anObj)</div><div class="line"></div><div class="line"> <span class="comment">// 在 enter 和 exit 之间持有 anObj 锁</span></div><div class="line"></div><div class="line"> objc_sync_exit(anObj)</div><div class="line">}</div><div class="line">``` </div><div class="line">更进一步,如果我们喜欢以前的那种形式,甚至可以写一个全局的方法,并接受一个闭包,来将 `objc_sync_enter` 和 `objc_sync_exit` 封装起来:</div><div class="line"></div><div class="line">```swift</div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">synchronized</span><span class="params">(<span class="number">_</span> lock: AnyObject, closure: <span class="params">()</span></span></span> -> ()) {</div><div class="line"> objc_sync_enter(lock)</div><div class="line"> closure()</div><div class="line"> objc_sync_exit(lock)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 使用:</span></div><div class="line">synchronized(<span class="keyword">self</span>) {</div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure>
<p>这样使用起来就和 Objective-C 中 <code>@synchronized</code> 很像了。</p>
<p>再举个 🌰 ,如果我们想为某个类实现一个线程安全的 <code>setter</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></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Obj</span> </span>{</div><div class="line"> <span class="keyword">var</span> _str = <span class="string">"123"</span></div><div class="line"> <span class="keyword">var</span> str: <span class="type">String</span> {</div><div class="line"> <span class="keyword">get</span> {</div><div class="line"> <span class="keyword">return</span> _str</div><div class="line"> }</div><div class="line"> <span class="keyword">set</span> {</div><div class="line"> synchronized(<span class="keyword">self</span>) {</div><div class="line"> _str = newValue</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="1.3.0"> 自定义日志输出 </h2>
<p>在 Objective-C 中,我们通常会自定义日志输出来完善信息以及避免 <code>release</code> 下的输出,比如下面这种,可以额外提供行数、方法名等信息:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#ifdef DEBUG</span></div><div class="line"><span class="meta">#define XXLog(fmt, ...) NSLog((@<span class="meta-string">"%s [Line %d] "</span> fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)</span></div><div class="line"><span class="meta">#else</span></div><div class="line"><span class="meta">#define XXLog(...)</span></div><div class="line"><span class="meta">#endif</span></div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><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">// Override point for customization after application launch.</span></div><div class="line"> </div><div class="line"> XXLog(<span class="string">@"ifelseboyxx"</span>);</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="comment">// 输出:</span></div><div class="line"><span class="comment">// 2017-12-08 13:32:02.211306+0800 Demo[17902:88775537] -[AppDelegate application:didFinishLaunchingWithOptions:] [Line 28] ifelseboyxx</span></div></pre></td></tr></table></figure>
<p>在 Swift 中,我们可以这样自定义:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">xxprint</span><T><span class="params">(<span class="number">_</span> message: T, filePath: String = #file, line: Int = #line, function: String = #function)</span></span> {</div><div class="line"> #<span class="keyword">if</span> <span class="type">DEBUG</span></div><div class="line"> <span class="keyword">let</span> fileName = (filePath <span class="keyword">as</span> <span class="type">NSString</span>).lastPathComponent.replacingOccurrences(of: <span class="string">".Swift"</span>, with: <span class="string">""</span>)</div><div class="line"> <span class="keyword">let</span> dateFormatter = <span class="type">DateFormatter</span>()</div><div class="line"> dateFormatter.locale = <span class="type">Locale</span>.current</div><div class="line"> dateFormatter.dateFormat = <span class="string">"yyyy-MM-dd HH:mm:ss"</span></div><div class="line"> <span class="built_in">print</span>(<span class="string">"["</span> + dateFormatter.string(from: <span class="type">Date</span>()) + <span class="string">" "</span> + fileName + <span class="string">" "</span> + function + <span class="string">" \(line)"</span> + <span class="string">"]:"</span> + <span class="string">"\(message)"</span>)</div><div class="line"> #endif</div><div class="line">}</div></pre></td></tr></table></figure>
<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></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> </div><div class="line"> xxprint(<span class="string">"ifelseboyxx"</span>)</div><div class="line"> <span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() + <span class="number">1</span>) {</div><div class="line"> xxprint(<span class="string">"ifelseboyxx"</span>)</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// [2017-12-08 13:49:38 ViewController.swift viewDidLoad() 27]:ifelseboyxx</span></div><div class="line"><span class="comment">// [2017-12-08 13:49:39 ViewController.swift viewDidLoad() 29]:ifelseboyxx</span></div></pre></td></tr></table></figure>
<h2 id="1.3.1"> Swift 中的 “readonly” </h2>
<p>在 Objective-C 中,我们通常把属性声明为 <code>readonly</code> 来提醒别人:“不要修改!!”,通常这么写:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Person</span> : <span class="title">NSObject</span></span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *name;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>如果外部尝试修改的话,会编译错误:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)viewDidLoad {</div><div class="line"> [<span class="keyword">super</span> viewDidLoad];</div><div class="line"> </div><div class="line"> Person *p = [Person new];</div><div class="line"> p.name = <span class="string">@"ifelseboyxx"</span>;</div><div class="line"> </div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译错误:</span></div><div class="line"><span class="comment">// Assignment to readonly property</span></div></pre></td></tr></table></figure>
<p>有些情况下,我们希望内部可以点语法访问 <code>name</code> 属性,也就是 <code>self.name</code>,但是因为是 <code>readonly</code> 的,会编译错误:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">Person</span></span></div><div class="line"></div><div class="line">- (<span class="keyword">instancetype</span>)init {</div><div class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> init];</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">self</span>) {</div><div class="line"> <span class="keyword">self</span>.name = <span class="string">@"ifelseboyxx"</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line"><span class="comment">// 编译错误:</span></div><div class="line"><span class="comment">// Assignment to readonly property</span></div></pre></td></tr></table></figure>
<p>这时候我们就会在内部的 <code>extension</code> 重新声明一个 <code>readwrite</code>的同样的属性,也就是<strong>“外部只读,内部可写”</strong>:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">Person</span> ()</span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">readwrite</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *name;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>在 Swift 中,我们可能有同样的场景。这里就不得不提到 <code>private</code> 和 <code>fileprivate</code> 关键字了。<br><code>private</code>表示声明为私有的实体只能在其声明的范围内被访问。比如我在 <code>MyClass</code> 中声明了一个私有的 <code>name</code> 属性,外部访问的话会编译错误:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">var</span> name: <span class="type">String</span> = <span class="string">"Test"</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> <span class="keyword">let</span> only = <span class="type">MyClass</span>()</div><div class="line"> <span class="built_in">print</span>(only.name) </div><div class="line"> only.name = <span class="string">"ifelseboyxxv587"</span> </div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译异常:</span></div><div class="line"><span class="comment">// 'name' is inaccessible due to 'private' protection level</span></div></pre></td></tr></table></figure>
<p>而 <code>fileprivate</code>,看命名我们大概能猜到,就是将对实体的访问权限于它声明的源文件。通俗点讲,比如我上面的代码都是在 <code>ViewController.swift</code> 这个文件里的,我把 <code>private</code> 修改为 <code>fileprivate</code>,就不会编译错误了:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</div><div class="line"> fileprivate <span class="keyword">var</span> name: <span class="type">String</span> = <span class="string">"Test"</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>那么如果非 <code>ViewController.swift</code> 文件,也想访问 <code>MyClass</code> 的 <code>name</code> 属性该怎么办呢?我们可以把 <code>name</code> 属性声明为 <code>fileprivate(set)</code>,就要就达到类似 Objective-C 中的 <code>readonly</code> 效果了 :</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// ViewController.swift 文件</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</div><div class="line"> fileprivate(<span class="keyword">set</span>) <span class="keyword">var</span> name: <span class="type">String</span> = <span class="string">"Test"</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </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">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"></div><div class="line"> <span class="keyword">let</span> only = <span class="type">MyClass</span>()</div><div class="line"> <span class="built_in">print</span>(only.name)</div><div class="line"> only.name = <span class="string">"ifelseboyxxv587"</span></div><div class="line"> <span class="built_in">print</span>(only.name)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译正常,ViewController.swift 文件内可读可写</span></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// Test</span></div><div class="line"><span class="comment">// ifelseboyxxv587</span></div></pre></td></tr></table></figure>
<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="comment">// AppDelegate.swift 文件</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">application</span><span class="params">(<span class="number">_</span> application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)</span></span> -> <span class="type">Bool</span> {</div><div class="line"> <span class="comment">// Override point for customization after application launch.</span></div><div class="line"></div><div class="line"> <span class="keyword">let</span> only = <span class="type">MyClass</span>()</div><div class="line"> <span class="built_in">print</span>(only.name) <span class="comment">//只能读</span></div><div class="line"> only.name = <span class="string">"ifelseboyxxv587"</span> <span class="comment">//这里报错,不能写</span></div><div class="line"> </div><div class="line"> <span class="keyword">return</span> <span class="literal">true</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 编译异常:</span></div><div class="line"><span class="comment">// Cannot assign to property: 'name' setter is inaccessible</span></div></pre></td></tr></table></figure>
<h2 id="1.3.2"> 作用域:do 语句块 </h2>
<p>在 Objective-C 中,我们可以利用 <code>{}</code> 来开辟新的作用域,来避免对象名称重复的问题:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSString</span> *ha = <span class="string">@"测试一"</span>;</div><div class="line"> </div><div class="line">{</div><div class="line"> <span class="built_in">NSString</span> *ha = <span class="string">@"测试二"</span>;</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,ha);</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,ha);</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 2017-12-11 16:55:20.303132+0800 Demo[48418:93027416] 测试二</span></div><div class="line"><span class="comment">// 2017-12-11 16:55:20.303316+0800 Demo[48418:93027416] 测试一</span></div></pre></td></tr></table></figure>
<p>在 Swift 中,取代 <code>{}</code> 的是 <code>do {}</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">let</span> ha = <span class="string">"测试一"</span></div><div class="line"> </div><div class="line"><span class="keyword">do</span> {</div><div class="line"> <span class="keyword">let</span> ha = <span class="string">"测试二"</span></div><div class="line"> <span class="built_in">print</span>(ha)</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="built_in">print</span>(ha)</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 测试二</span></div><div class="line"><span class="comment">// 测试一</span></div></pre></td></tr></table></figure>
<h2 id="1.3.3"> 倒序 reversed() </h2>
<p>在 Objective-C 中,我们如果想倒序数组一般这样:</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">NSArray</span> *array = @[<span class="string">@"1"</span>,<span class="string">@"2"</span>,<span class="string">@"3"</span>];</div><div class="line"> </div><div class="line"><span class="built_in">NSArray</span> *reversedArray = [[array reverseObjectEnumerator] allObjects];</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 2017-12-11 17:39:57.127466+0800 Demo[49004:93210504] (</span></div><div class="line"> <span class="number">3</span>,</div><div class="line"> <span class="number">2</span>,</div><div class="line"> <span class="number">1</span></div><div class="line">)</div></pre></td></tr></table></figure>
<p>在 Swift 中,相对简单点:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> arr:[<span class="type">String</span>] = [<span class="string">"1"</span>,<span class="string">"2"</span>,<span class="string">"3"</span>]</div><div class="line"><span class="keyword">let</span> reversedArr:[<span class="type">String</span>] = arr.reversed()</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// ["3", "2", "1"]</span></div></pre></td></tr></table></figure>
<h2 id="1.3.3"> 标签语句:指定跳出某个条件语句 </h2>
<p>在 Objective-C 中,如果遇到多层嵌套的条件语句,我们如果想要指定跳出某个条件语句是很不方便的。比如有两个循环,<strong>一旦找到它们相同的,就立刻停止循环</strong>,我们可能会这么做:</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></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSArray</span> *arr1 = @[<span class="string">@"1"</span>,<span class="string">@"2"</span>,<span class="string">@"3"</span>,<span class="string">@"4"</span>,<span class="string">@"5"</span>];</div><div class="line"><span class="built_in">NSArray</span> *arr2 = @[<span class="string">@"4"</span>,<span class="string">@"6"</span>,<span class="string">@"8"</span>,<span class="string">@"9"</span>,<span class="string">@"2"</span>];</div><div class="line"></div><div class="line"><span class="built_in">BOOL</span> finded = <span class="literal">NO</span>;</div><div class="line"><span class="keyword">for</span> (<span class="built_in">NSString</span> *x <span class="keyword">in</span> arr1) {</div><div class="line"> <span class="keyword">if</span> (finded) {</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"x:%@"</span>,x);</div><div class="line"> <span class="keyword">for</span> (<span class="built_in">NSString</span> *y <span class="keyword">in</span> arr2) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"y:%@"</span>,y);</div><div class="line"> <span class="keyword">if</span> ([x isEqualToString:y]) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"找到相等的了:%@"</span>,x);</div><div class="line"> finded = <span class="literal">YES</span>;</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们需要借助 <code>finded</code> 这个 <code>BOOL</code>,来方便我们跳出循环。在 Swift 中,我们就可以利用标签语句,来指定具体跳出哪个循环,语法是这样的:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">标签名: 条件语句 {</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>上面的 🌰 我们可以这么写:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><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="keyword">let</span> arr1 = [<span class="string">"1"</span>,<span class="string">"2"</span>,<span class="string">"3"</span>,<span class="string">"4"</span>,<span class="string">"5"</span>]</div><div class="line"><span class="keyword">let</span> arr2 = [<span class="string">"4"</span>,<span class="string">"6"</span>,<span class="string">"8"</span>,<span class="string">"9"</span>,<span class="string">"2"</span>]</div><div class="line"> </div><div class="line">label: <span class="keyword">for</span> x <span class="keyword">in</span> arr1 {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"x: \(x)"</span>)</div><div class="line"> <span class="keyword">for</span> y <span class="keyword">in</span> arr2 {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"y: \(y)"</span>)</div><div class="line"> <span class="keyword">if</span> x == y {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"找到相等的了:\(y)"</span>)</div><div class="line"> <span class="keyword">break</span> label</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>上面代码,我们把第一层循环定义了标签:<code>label</code>。在第二层循环中,一旦条件成立,立刻跳出第一层循环 <code>label</code>。这个特性,可以说十分方便了!</p>
<h2 id="1.3.5"> 优雅的定义通知名称 </h2>
<p>在 Objective-C 中,我们自定义通知时,对于名称的定义一般都有规范:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// xxx.h </span></div><div class="line"><span class="built_in">UIKIT_EXTERN</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> XXXXNotification;</div><div class="line"></div><div class="line"><span class="comment">// xxx.m </span></div><div class="line"><span class="built_in">NSString</span> * <span class="keyword">const</span> XXXXNotification = <span class="string">@"XXXXNotification"</span>;</div></pre></td></tr></table></figure>
<p>在 Swift 中,我们可以参考 <a href="https://github.com/Alamofire/Alamofire" target="_blank" rel="external">Alamofire</a> 的方式,创建个专门存放通知名的文件,扩展 <code>Notification.Name</code> 并以结构体 <code>struct</code> 方式声明:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// XXNotification.swift 文件</span></div><div class="line"></div><div class="line"><span class="keyword">import</span> Foundation</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">extension</span> <span class="title">Notification</span>.<span class="title">Name</span> </span>{</div><div class="line"> <span class="keyword">public</span> <span class="class"><span class="keyword">struct</span> <span class="title">Task</span> </span>{</div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">let</span> 通知名 = <span class="type">Notification</span>.<span class="type">Name</span>(rawValue: <span class="string">"org.target名称.notification.name.task.通知名"</span>)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>然后我们就可以愉快的使用了:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><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">// add</span></div><div class="line"><span class="type">NotificationCenter</span>.<span class="keyword">default</span>.addObserver(<span class="keyword">self</span>, selector: #selector(myNotification(<span class="number">_</span> :)), name: <span class="type">NSNotification</span>.<span class="type">Name</span>.<span class="type">Task</span>.通知名, object: <span class="keyword">self</span>)</div><div class="line"></div><div class="line"><span class="comment">// post</span></div><div class="line"><span class="type">NotificationCenter</span>.<span class="keyword">default</span>.post(name: <span class="type">NSNotification</span>.<span class="type">Name</span>.<span class="type">Task</span>.通知名, object: <span class="keyword">self</span>)</div><div class="line"></div><div class="line"><span class="comment">// remove</span></div><div class="line"><span class="type">NotificationCenter</span>.<span class="keyword">default</span>.removeObserver(<span class="keyword">self</span>, name: <span class="type">NSNotification</span>.<span class="type">Name</span>.<span class="type">Task</span>.通知名, object: <span class="keyword">self</span>)</div></pre></td></tr></table></figure>
<h2 id="1.3.6"> weakSelf 和 strongSelf </h2>
<p>在 Objective-C 中,为了防止 block 循环引用,我们通用利用 <code>__weak</code> 和 <code>__strong</code> 搭配使用:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">__<span class="keyword">weak</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) weakSelf = <span class="keyword">self</span>;</div><div class="line"> </div><div class="line">__<span class="keyword">strong</span> <span class="keyword">typeof</span>(weakSelf) strongSelf = weakSelf;</div></pre></td></tr></table></figure>
<p>对应的 Swift 中我们可以这样:</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">resource.request().onComplete { [<span class="keyword">weak</span> <span class="keyword">self</span>] response <span class="keyword">in</span></div><div class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> strongSelf = <span class="keyword">self</span> <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span></div><div class="line"> }</div><div class="line"> <span class="keyword">let</span> model = strongSelf.updateModel(response)</div><div class="line"> strongSelf.updateUI(model)</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="1.3.7"> 几种遍历方式 </h2>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> <span class="number">_</span> <span class="keyword">in</span> <span class="number">0</span>..<<span class="number">3</span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"Hello three times"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 带索引</span></div><div class="line"><span class="keyword">for</span> (index, person) <span class="keyword">in</span> attendeeList.enumerated() {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"\(person) is at position #\(index)"</span>)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 每隔两个打印</span></div><div class="line"><span class="keyword">for</span> index <span class="keyword">in</span> <span class="built_in">stride</span>(from: <span class="number">0</span>, to: <span class="number">5</span>, by: <span class="number">2</span>) {</div><div class="line"> <span class="built_in">print</span>(index)</div><div class="line">}</div><div class="line"><span class="comment">// 输出: 0 2 4</span></div><div class="line"></div><div class="line"><span class="comment">// 倒叙</span></div><div class="line"><span class="keyword">for</span> index <span class="keyword">in</span> (<span class="number">0</span>...<span class="number">3</span>).reversed() {</div><div class="line"> <span class="built_in">print</span>(index)</div><div class="line">}</div><div class="line"><span class="comment">// 输出:3 2 1 0</span></div></pre></td></tr></table></figure>
<h2 id="1.3.8"> 访问级别</h2>
<p>Swift 中的访问级别有以下<strong>五种</strong>:</p>
<ul>
<li><code>open</code>:公开权限, 最高的权限,可以被其他模块访问,继承及复写。</li>
<li><code>public</code>:公有访问权限,类或者类的公有属性或者公有方法可以从文件或者模块的任何地方进行访问。那么什么样才能成为一个模块呢?一个 App 就是一个模块,一个第三方 API,第三等方框架等都是一个完整的模块,这些模块如果要对外留有访问的属性或者方法,就应该使用 public 的访问权限。public 的权限在 Swift 3.0 后无法在其他模块被复写方法/属性或被继承。</li>
<li><code>fileprivate</code>:文件私有访问权限,被 fileprivate 修饰的类或者类的属性或方法可以在同一个物理文件中访问。如果超出该物理文件,那么有着 fileprivate 访问权限的类,属性和方法就不能被访问。</li>
<li><code>private</code>:私有访问权限,被 private 修饰的类或者类的属性或方法可以在同一个物理文件中的同一个类型(包含 extension)访问。如果超出该物理文件或不属于同一类型,那么有着 private 访问权限的属性和方法就不能被访问。</li>
<li><code>internal</code>: 顾名思义,internal 是内部的意思,即有着 internal 访问权限的属性和方法说明在模块内部可以访问,超出模块内部就不可被访问了。在 Swift 中默认就是 internal 的访问权限。</li>
</ul>
<h2 id="1.3.9"> for in 和 forEach 的区别 </h2>
<p><code>for in</code> 能使用 <code>return</code>、<code>break</code>、<code>continue</code> 关键字,<code>forEach</code> 不能使用 <code>break</code>、<code>continue</code> 关键字。<strong>它们的主要区别在于 <code>return</code> 关键字结果的不同:</strong></p>
<p>在 <code>for in</code> 中,<code>return</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></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> array = [<span class="string">"1"</span>, <span class="string">"2"</span>, <span class="string">"3"</span>, <span class="string">"4"</span>, <span class="string">"5"</span>]</div><div class="line"><span class="keyword">for</span> element <span class="keyword">in</span> array {</div><div class="line"> <span class="keyword">if</span> element == <span class="string">"3"</span> {</div><div class="line"> <span class="keyword">return</span></div><div class="line"> }</div><div class="line"> <span class="built_in">print</span>(element)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 1</span></div><div class="line"><span class="comment">// 2</span></div></pre></td></tr></table></figure>
<p>而 <code>forEach</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></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> array = [<span class="string">"1"</span>, <span class="string">"2"</span>, <span class="string">"3"</span>, <span class="string">"4"</span>, <span class="string">"5"</span>]</div><div class="line">array.forEach { (element) <span class="keyword">in</span></div><div class="line"> <span class="keyword">if</span> element == <span class="string">"3"</span> {</div><div class="line"> <span class="keyword">return</span></div><div class="line"> }</div><div class="line"> <span class="built_in">print</span>(element)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 1</span></div><div class="line"><span class="comment">// 2</span></div><div class="line"><span class="comment">// 4</span></div><div class="line"><span class="comment">// 5</span></div></pre></td></tr></table></figure>
<p><strong>这样看来,<code>forEach</code> 中的 <code>return</code> 关键字倒是有点类似 <code>for in</code> 中的 <code>continue</code> 了!</strong></p>
<h2 id="1.4.0"> 取消 asyncAfter 中延迟的事件 </h2>
<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></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewController</span>: <span class="title">UIViewController</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">let</span> workItem = <span class="type">DispatchWorkItem</span> {</div><div class="line"> <span class="built_in">print</span>(<span class="string">"test"</span>)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">viewDidLoad</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">super</span>.viewDidLoad()</div><div class="line"> <span class="comment">// Do any additional setup after loading the view, typically from a nib.</span></div><div class="line"> </div><div class="line"> <span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() + <span class="number">3</span>, execute: workItem)</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// 这里点击取消之前延迟的事情</span></div><div class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">func</span> <span class="title">touchesBegan</span><span class="params">(<span class="number">_</span> touches: Set<UITouch>, with event: UIEvent?)</span></span> {</div><div class="line"> </div><div class="line"> <span class="built_in">print</span>(workItem.isCancelled ? <span class="string">"取消了"</span> : <span class="string">"未取消"</span>)</div><div class="line"> </div><div class="line"> workItem.cancel()</div><div class="line"></div><div class="line"> <span class="built_in">print</span>(workItem.isCancelled ? <span class="string">"取消了"</span> : <span class="string">"未取消"</span>)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 输出:</span></div><div class="line"><span class="comment">// 未取消</span></div><div class="line"><span class="comment">// 取消了</span></div></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<p>以下内容均是笔者学习过程中<strong>收集</strong>的知识点,顺序比较跳跃,初衷是为了方便查阅,顺便加深记忆。<strong>内容会不断更新</strong>,如果有什么问题或者有好的 Swift 方面的语法糖或者知识点也可以提出来,我会挑选斟酌后收录,欢迎大家
</summary>
<category term="Swift" scheme="http://yoursite.com/tags/Swift/"/>
</entry>
<entry>
<title>Method Swizzling 实战:Hook 系统代理方法</title>
<link href="http://yoursite.com/2017/11/30/hook_system_delegate/"/>
<id>http://yoursite.com/2017/11/30/hook_system_delegate/</id>
<published>2017-11-30T15:41:23.000Z</published>
<updated>2018-02-13T10:07:20.159Z</updated>
<content type="html"><![CDATA[<p>关于 Method Swizzling 基本用法可以先看看博主之前的文章 <a href="http://blog.ifelseboyxx.com/2017/01/25/Method-Swizzling/" target="_blank" rel="external">浅谈 Method Swizzling</a>, 这里就不多做介绍了。本文以 <code>UIWebViewDelegate</code> 为例,介绍如何利用 Method Swizzling 来 Hook 系统的 delegate 方法, 主要有如下几个步骤:</p>
<ul>
<li>新建 UIWebView Category</li>
<li>Hook UIWebView 的 <code>delegate</code> 方法</li>
<li>Hook <code>UIWebViewDelegate</code> 协议中的具体方法</li>
</ul>
<h3 id="Hook-UIWebView-的代理"><a href="#Hook-UIWebView-的代理" class="headerlink" title="Hook UIWebView 的代理"></a>Hook UIWebView 的代理</h3><p>首先,我们应该明白,当我们想 Hook 某个方法函数时,必须知道对应的 <code>class</code> ,我们这里之所以 Hook UIWebView 的 delegate,为的就是找到继承了 <code>UIWebViewDelegate</code> 协议的 <code>class</code>:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><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="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(setDelegate:));</div><div class="line"> Method ownerMethod = class_getInstanceMethod([<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(hook_setDelegate:));</div><div class="line"> method_exchangeImplementations(originalMethod, ownerMethod);</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)hook_setDelegate:(<span class="keyword">id</span><<span class="built_in">UIWebViewDelegate</span>>)delegate {</div><div class="line"></div><div class="line"> [<span class="keyword">self</span> hook_setDelegate:delegate];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里我们需要注意,我们用的是 <code>class_getInstanceMethod</code> 而不是 <code>class_getClassMethod</code> ,因为 <code>setDelegate :</code> 是实例方法。另外我们这里用 <code>method_exchangeImplementations</code> 直接交换了,并没有使用 <code>class_addMethod()</code> 函数判断是因为这里不会出现替换的父类方法的问题。</p>
<h3 id="Hook-协议中的具体方法"><a href="#Hook-协议中的具体方法" class="headerlink" title="Hook 协议中的具体方法"></a>Hook 协议中的具体方法</h3><p>在这之前,假如我们想要 Hook <code>webViewDidStartLoad:</code> 方法,我们需要考虑这几种情况:</p>
<ul>
<li>代理对象实现了 <code>webViewDidStartLoad:</code> 方法,那么我们直接交换就行。</li>
<li>代理对象如果没有实现 <code>webViewDidStartLoad:</code> 方法,而我们又想监听时,就需要我们动态的添加 <code>webViewDidStartLoad:</code> 方法。</li>
<li><code>setDelegate :</code> 万一重复设置了,会导致 <code>webViewDidStartLoad:</code> 多次交换,我们需要预防这种情况。</li>
</ul>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">static</span> <span class="keyword">void</span> Hook_Method(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL noneSel){</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 noneMethod = class_getInstanceMethod(replacedClass, noneSel);</div><div class="line"> <span class="built_in">BOOL</span> didAddNoneMethod = class_addMethod(originalClass, originalSel, method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod));</div><div class="line"> <span class="keyword">if</span> (didAddNoneMethod) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"******** 没有实现 (%@) 方法,手动添加成功!!"</span>,<span class="built_in">NSStringFromSelector</span>(originalSel));</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(owner_webViewDidStartLoad:)) 添加 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">@"******** 实现了 (%@) 方法并成功 Hook 为 --> (%@)"</span>,<span class="built_in">NSStringFromSelector</span>(originalSel) ,<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">@"******** 已替换过,避免多次替换 --> (%@)"</span>,<span class="built_in">NSStringFromClass</span>(originalClass));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><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></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)hook_setDelegate:(<span class="keyword">id</span><<span class="built_in">UIWebViewDelegate</span>>)delegate {</div><div class="line"> </div><div class="line"> [<span class="keyword">self</span> hook_setDelegate:delegate];</div><div class="line"> </div><div class="line"> <span class="comment">//Hook (webViewDidStartLoad:) 方法</span></div><div class="line"> Hook_Method([delegate <span class="keyword">class</span>], <span class="keyword">@selector</span>(webViewDidStartLoad:), [<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(owner_webViewDidStartLoad:), <span class="keyword">@selector</span>(none_webViewDidStartLoad:));</div><div class="line"> </div><div class="line"> <span class="comment">//Hook (webViewDidFinishLoad:) 方法</span></div><div class="line"> Hook_Method([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>(owner_webViewDidFinishLoad:), <span class="keyword">@selector</span>(none_webViewDidFinishLoad:));</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)owner_webViewDidStartLoad:(<span class="built_in">UIWebView</span> *)webView {</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"*********** owner_webViewDidStartLoad:"</span>);</div><div class="line"> </div><div class="line"> [<span class="keyword">self</span> owner_webViewDidStartLoad:webView];</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)none_webViewDidStartLoad:(<span class="built_in">UIWebView</span> *)webView {</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"*********** none_webViewDidStartLoad:"</span>);</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)owner_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">@"*********** owner_webViewDidFinishLoad:"</span>);</div><div class="line"> </div><div class="line"> [<span class="keyword">self</span> owner_webViewDidFinishLoad:webView];</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)none_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">@"*********** none_webViewDidFinishLoad:"</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<blockquote>
<p>另外,不管我们是纯代码设置的 UIWebView delegate,还是通过 IB 设置的,都是没问题~</p>
</blockquote>
<p>完整的代码在这里 <a href="https://github.com/ifelseboyxx/xx_Notes/tree/master/contents/HookSystemDelegate/Hook_Delegate" target="_blank" rel="external">Demo</a></p>
]]></content>
<summary type="html">
<p>关于 Method Swizzling 基本用法可以先看看博主之前的文章 <a href="http://blog.ifelseboyxx.com/2017/01/25/Method-Swizzling/" target="_blank" rel="external">浅谈
</summary>
<category term="Objc" scheme="http://yoursite.com/tags/Objc/"/>
<category term="runtime" scheme="http://yoursite.com/tags/runtime/"/>
</entry>
<entry>
<title>iOS 开发中的锁</title>
<link href="http://yoursite.com/2017/11/15/lock/"/>
<id>http://yoursite.com/2017/11/15/lock/</id>
<published>2017-11-15T03:38:01.000Z</published>
<updated>2018-02-13T10:06:36.514Z</updated>
<content type="html"><![CDATA[<p>这两天翻看 ibireme 大神 <a href="http://blog.ibireme.com/" target="_blank" rel="external">不再安全的 OSSpinLock</a> 这篇文章,看到文中分析各种锁之间的性能的图表,怀着好奇的心态,学习了下锁:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-eb3ef0d444034362.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="lock_benchmark.png"></p>
<h2 id="锁是什么意思?"><a href="#锁是什么意思?" class="headerlink" title="锁是什么意思?"></a>锁是什么意思?</h2><p>我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,<strong>锁</strong>应运而生。</p>
<h3 id="OSSpinLock"><a href="#OSSpinLock" class="headerlink" title="OSSpinLock"></a>OSSpinLock</h3><p>需导入头文件:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#import <span class="meta-string"><libkern/OSAtomic.h></span></span></div></pre></td></tr></table></figure>
<p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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">__block OSSpinLock oslock = OS_SPINLOCK_INIT;</div><div class="line"><span class="comment">//线程1 </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"> <span class="built_in">NSLog</span>(<span class="string">@"线程1 准备上锁"</span>);</div><div class="line"> OSSpinLockLock(&oslock);</div><div class="line"> sleep(<span class="number">4</span>);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> OSSpinLockUnlock(&oslock);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1 解锁成功"</span>);</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">//线程2</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程2 准备上锁"</span>);</div><div class="line"> OSSpinLockLock(&oslock);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> OSSpinLockUnlock(&oslock);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2 解锁成功"</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-da0bbfd046fc7e30.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="OSSpinLock1"></p>
<p>我们来修改一下代码:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">__block OSSpinLock oslock = OS_SPINLOCK_INIT;</div><div class="line"><span class="comment">//线程1 </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="comment">//OSSpinLockUnlock(&oslock);</span></div><div class="line">......</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-5f6aeebcb8d9cb00.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="OSSpinLock2"></p>
<p>在 <code>OSSpinLock1</code> 图中可以发现:当我们锁住线程1时,在同时锁住线程2的情况下,线程2会一直等待<strong>(自旋锁不会让等待的进入睡眠状态)</strong>,直到线程1的任务执行完且解锁完毕,线程2会立即执行;而在 <code>OSSpinLock2</code> 图中,因为我们注释掉了线程1中的解锁代码,会绕过线程1,直到调用了线程2的解锁方法才会继续执行线程1中的任务,<strong>正常情况下,<code>lock</code>和<code>unlock</code>最好成对出现</strong>。</p>
<blockquote>
<p><strong>OS_SPINLOCK_INIT:</strong> 默认值为 <code>0</code>,在 <code>locked</code> 状态时就会大于 <code>0</code>,<code>unlocked</code>状态下为 <code>0</code><br><strong>OSSpinLockLock(&oslock):</strong>上锁,参数为 <code>OSSpinLock</code> 地址<br><strong>OSSpinLockUnlock(&oslock):</strong>解锁,参数为 <code>OSSpinLock</code> 地址<br><strong>OSSpinLockTry(&oslock)</strong>:尝试加锁,可以加锁则<strong>立即加锁</strong>并返回 <code>YES</code>,反之返回 <code>NO</code></p>
</blockquote>
<p>这里顺便提一下 <code>trylock</code> 和 <code>lock</code> 使用场景:</p>
<blockquote>
<p>当前线程锁失败,也可以继续其它任务,用 trylock 合适<br>当前线程只有锁成功后,才会做一些有意义的工作,那就 lock,没必要轮询 trylock</p>
</blockquote>
<h3 id="dispatch-semaphore-信号量"><a href="#dispatch-semaphore-信号量" class="headerlink" title="dispatch_semaphore 信号量"></a>dispatch_semaphore 信号量</h3><p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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">dispatch_semaphore_t signal = dispatch_semaphore_create(<span class="number">1</span>); <span class="comment">//传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句</span></div><div class="line">dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, <span class="number">3.0</span>f * <span class="built_in">NSEC_PER_SEC</span>);</div><div class="line"></div><div class="line"><span class="comment">//线程1</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程1 等待ing"</span>);</div><div class="line"> dispatch_semaphore_wait(signal, overTime); <span class="comment">//signal 值 -1</span></div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> dispatch_semaphore_signal(signal); <span class="comment">//signal 值 +1</span></div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1 发送信号"</span>);</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">//线程2</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程2 等待ing"</span>);</div><div class="line"> dispatch_semaphore_wait(signal, overTime);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> dispatch_semaphore_signal(signal);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2 发送信号"</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<blockquote>
<p><strong>dispatch_semaphore_create(1):</strong> 传入值必须 <code>>=0</code>, 若传入为 <code>0</code> 则阻塞线程并等待timeout,时间到后会执行其后的语句<br><strong>dispatch_semaphore_wait(signal, overTime):</strong>可以理解为 <code>lock</code>,会使得 <code>signal</code> 值 <code>-1</code><br><strong>dispatch_semaphore_signal(signal):</strong>可以理解为 <code>unlock</code>,会使得 <code>signal</code> 值 <code>+1</code></p>
</blockquote>
<p>关于信号量,我们可以用停车来比喻:</p>
<blockquote>
<p>停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。<br><strong>信号量的值(signal)</strong>就相当于剩余车位的数目,<code>dispatch_semaphore_wait</code> 函数就相当于来了一辆车,<code>dispatch_semaphore_signal</code> 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次 dispatch_semaphore_wait 剩余车位就减少一个;当剩余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。</p>
</blockquote>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-e45133dc53c7b53d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="初始信号量大于0"></p>
<p>可以发现,因为我们初始化信号量的时候是大于 <code>0</code> 的,所以并没有阻塞线程,而是直接执行了 线程1 线程2。</p>
<p>我们把 信号量初始值改为 <code>0</code>:</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">dispatch_semaphore_t signal = dispatch_semaphore_create(<span class="number">0</span>);</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-378750ef97bd0959.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="初始信号量为0"></p>
<p>可以看到这时候我们设置的 <code>overTime</code> 生效了。</p>
<h3 id="pthread-mutex"><a href="#pthread-mutex" class="headerlink" title="pthread_mutex"></a>pthread_mutex</h3><p>ibireme 在《<a href="http://blog.ibireme.com/" target="_blank" rel="external">不再安全的 OSSpinLock</a>》这篇文章中提到性能最好的 <code>OSSpinLock</code> 已经不再是线程安全的并把自己开源项目中的 <code>OSSpinLock</code> 都替换成了 <code>pthread_mutex</code>。<br>特意去看了下源码,总结了下常见用法:</p>
<p>使用需导入头文件:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#import <span class="meta-string"><pthread.h></span></span></div></pre></td></tr></table></figure>
<p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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">static</span> pthread_mutex_t pLock;</div><div class="line">pthread_mutex_init(&pLock, <span class="literal">NULL</span>);</div><div class="line"><span class="comment">//1.线程1</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程1 准备上锁"</span>);</div><div class="line"> pthread_mutex_lock(&pLock);</div><div class="line"> sleep(<span class="number">3</span>);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> pthread_mutex_unlock(&pLock);</div><div class="line"> });</div><div class="line"> </div><div class="line"><span class="comment">//1.线程2</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程2 准备上锁"</span>);</div><div class="line"> pthread_mutex_lock(&pLock);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> pthread_mutex_unlock(&pLock);</div><div class="line"> });</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-f051bcdb173e8612.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="pthread_mutex"></p>
<blockquote>
<p>pthread_mutex 中也有个 <code>pthread_mutex_trylock(&pLock)</code>,和上面提到的 <code>OSSpinLockTry(&oslock)</code> 区别在于,前者可以加锁时返回的是 <code>0</code>,否则返回一个错误提示码;后者返回的 <code>YES</code>和 <code>NO</code>。</p>
</blockquote>
<p>这里贴个 <a href="https://github.com/ibireme/YYKit" target="_blank" rel="external">YYKit</a> 中的源码:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-48d96143d13a9371.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="YYKit"></p>
<h3 id="pthread-mutex-recursive"><a href="#pthread-mutex-recursive" class="headerlink" title="pthread_mutex(recursive)"></a>pthread_mutex(recursive)</h3><p>经过上面几种例子,我们可以发现:加锁后只能有一个线程访问该对象,后面的线程需要排队,并且 lock 和 unlock 是对应出现的,同一线程多次 lock 是不允许的,而递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。</p>
<p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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="keyword">static</span> pthread_mutex_t pLock;</div><div class="line">pthread_mutexattr_t attr;</div><div class="line">pthread_mutexattr_init(&attr); <span class="comment">//初始化attr并且给它赋予默认</span></div><div class="line">pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); <span class="comment">//设置锁类型,这边是设置为递归锁</span></div><div class="line">pthread_mutex_init(&pLock, &attr);</div><div class="line">pthread_mutexattr_destroy(&attr); <span class="comment">//销毁一个属性对象,在重新进行初始化之前该结构不能重新使用</span></div><div class="line"></div><div class="line"><span class="comment">//1.线程1</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"> <span class="keyword">static</span> <span class="keyword">void</span> (^RecursiveBlock)(<span class="keyword">int</span>);</div><div class="line"> RecursiveBlock = ^(<span class="keyword">int</span> value) {</div><div class="line"> pthread_mutex_lock(&pLock);</div><div class="line"> <span class="keyword">if</span> (value > <span class="number">0</span>) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"value: %d"</span>, value);</div><div class="line"> RecursiveBlock(value - <span class="number">1</span>);</div><div class="line"> }</div><div class="line"> pthread_mutex_unlock(&pLock);</div><div class="line"> };</div><div class="line"> RecursiveBlock(<span class="number">5</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-ed159b5987a9aaed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="结果"></p>
<blockquote>
<p>上面的代码如果我们用 <code>pthread_mutex_init(&pLock, NULL)</code> 初始化会出现死锁的情况,递归锁能很好的避免这种情况的死锁;</p>
</blockquote>
<h3 id="NSLock"><a href="#NSLock" class="headerlink" title="NSLock"></a>NSLock</h3><p>NSLock API 很少也很简单:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-fd930ccf5d969f6f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="NSLock"></p>
<blockquote>
<p><strong>lock、unlock</strong>:不多做解释,和上面一样<br><strong>trylock</strong>:能加锁返回 YES 并执行<strong>加锁</strong>操作,相当于 lock,反之返回 NO<br><strong> lockBeforeDate:</strong>这个方法表示会在传入的时间内尝试加锁,若能加锁则执行<strong>加锁</strong>操作并返回 YES,反之返回 NO</p>
</blockquote>
<p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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="built_in">NSLock</span> *lock = [<span class="built_in">NSLock</span> new];</div><div class="line"><span class="comment">//线程1</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程1 尝试加速ing..."</span>);</div><div class="line"> [lock lock];</div><div class="line"> sleep(<span class="number">3</span>);<span class="comment">//睡眠5秒</span></div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> [lock unlock];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1解锁成功"</span>);</div><div class="line">});</div><div class="line"><span class="comment">//线程2</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"> <span class="built_in">NSLog</span>(<span class="string">@"线程2 尝试加速ing..."</span>);</div><div class="line"> <span class="built_in">BOOL</span> x = [lock lockBeforeDate:[<span class="built_in">NSDate</span> dateWithTimeIntervalSinceNow:<span class="number">4</span>]];</div><div class="line"> <span class="keyword">if</span> (x) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> [lock unlock];</div><div class="line"> }<span class="keyword">else</span>{</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"失败"</span>);</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-471bcb7fcc0bcfea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="NSLock_result"></p>
<h3 id="NSCondition"><a href="#NSCondition" class="headerlink" title="NSCondition"></a>NSCondition</h3><p>我们先来看看 API:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-231a3255007492fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="NSCondition"></p>
<p>看字面意思很好理解:</p>
<blockquote>
<p><strong>wait</strong>:进入等待状态<br><strong>waitUntilDate:</strong>:让一个线程等待一定的时间<br><strong>signal</strong>:唤醒一个等待的线程<br><strong>broadcast</strong>:唤醒所有等待的线程</p>
</blockquote>
<p>例子:</p>
<ul>
<li><strong>等待2秒</strong></li>
</ul>
<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">NSCondition</span> *cLock = [<span class="built_in">NSCondition</span> new];</div><div class="line"><span class="comment">//线程1</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"> <span class="built_in">NSLog</span>(<span class="string">@"start"</span>);</div><div class="line"> [cLock lock];</div><div class="line"> [cLock waitUntilDate:[<span class="built_in">NSDate</span> dateWithTimeIntervalSinceNow:<span class="number">2</span>]];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> [cLock unlock];</div><div class="line"> });</div></pre></td></tr></table></figure>
<p>结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-7ff9328f53551846.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="waiting 2秒"></p>
<ul>
<li><strong>唤醒一个等待线程</strong></li>
</ul>
<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="built_in">NSCondition</span> *cLock = [<span class="built_in">NSCondition</span> new];</div><div class="line"><span class="comment">//线程1</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"> [cLock lock];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1加锁成功"</span>);</div><div class="line"> [cLock wait];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> [cLock unlock];</div><div class="line">});</div><div class="line"> </div><div class="line"><span class="comment">//线程2</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"> [cLock lock];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2加锁成功"</span>);</div><div class="line"> [cLock wait];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> [cLock unlock];</div><div class="line">});</div><div class="line"></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"> sleep(<span class="number">2</span>);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"唤醒一个等待的线程"</span>);</div><div class="line"> [cLock signal];</div><div class="line">});</div></pre></td></tr></table></figure>
<p>结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-e587d966e6f34c92.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="唤醒一个等待的线程"></p>
<ul>
<li><strong>唤醒所有等待的线程</strong></li>
</ul>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">......... </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"> sleep(<span class="number">2</span>);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"唤醒所有等待的线程"</span>);</div><div class="line"> [cLock broadcast];</div><div class="line"> });</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-f7c07e6e2c031088.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="唤醒所有的线程"></p>
<h3 id="NSRecursiveLock"><a href="#NSRecursiveLock" class="headerlink" title="NSRecursiveLock"></a>NSRecursiveLock</h3><p>上面已经大概介绍过了:<br>递归锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。</p>
<p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSLock</span> *rLock = [<span class="built_in">NSLock</span> new];</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"> <span class="keyword">static</span> <span class="keyword">void</span> (^RecursiveBlock)(<span class="keyword">int</span>);</div><div class="line"> RecursiveBlock = ^(<span class="keyword">int</span> value) {</div><div class="line"> [rLock lock];</div><div class="line"> <span class="keyword">if</span> (value > <span class="number">0</span>) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程%d"</span>, value);</div><div class="line"> RecursiveBlock(value - <span class="number">1</span>);</div><div class="line"> }</div><div class="line"> [rLock unlock];</div><div class="line"> };</div><div class="line"> RecursiveBlock(<span class="number">4</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-9502f58fa9244b5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="错误信息"></p>
<p>这段代码是一个典型的死锁情况。在我们的线程中,<code>RecursiveMethod</code> 是递归调用的。所以每次进入这个 block 时,都会去<strong>加一次锁</strong>,而从第二次开始,由于锁已经被使用了且<strong>没有解锁</strong>,所以它<strong>需要等待锁被解除</strong>,这样就导致了死锁,线程被阻塞住了。</p>
<p>将 NSLock 替换为 NSRecursiveLock:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSRecursiveLock</span> *rLock = [<span class="built_in">NSRecursiveLock</span> new];</div><div class="line">..........</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-58b8575d0ba80cb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="NSRecursiveLock"></p>
<p>NSRecursiveLock 方法里还提供了两个方法,用法和上面介绍的基本没什么差别,这里不过多介绍了:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">- (<span class="built_in">BOOL</span>)tryLock;</div><div class="line">- (<span class="built_in">BOOL</span>)lockBeforeDate:(<span class="built_in">NSDate</span> *)limit;</div></pre></td></tr></table></figure>
<h3 id="synchronized"><a href="#synchronized" class="headerlink" title="@synchronized"></a>@synchronized</h3><p>@synchronized 相信大家应该都熟悉,它的用法应该算这些锁中最简单的:</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></pre></td><td class="code"><pre><div class="line"><span class="comment">//线程1</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"> <span class="keyword">@synchronized</span> (<span class="keyword">self</span>) {</div><div class="line"> sleep(<span class="number">2</span>);</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> }</div><div class="line"> });</div><div class="line"> </div><div class="line"><span class="comment">//线程2</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"> <span class="keyword">@synchronized</span> (<span class="keyword">self</span>) {</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> }</div><div class="line"> });</div></pre></td></tr></table></figure>
<blockquote>
<p>有兴趣可以看一下这篇文章 《 <a href="http://ios.jobbole.com/82826/" target="_blank" rel="external">关于 @synchronized,这儿比你想知道的还要多</a>》</p>
</blockquote>
<h3 id="NSConditionLock-条件锁"><a href="#NSConditionLock-条件锁" class="headerlink" title="NSConditionLock 条件锁"></a>NSConditionLock 条件锁</h3><p>我们先来看看 API :</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-95362ad47a95575c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="NSConditionLock"></p>
<p>相比于 NSLock 多了个 <code>condition</code> 参数,我们可以理解为一个<strong>条件标示</strong>。</p>
<p>例子:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><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></pre></td><td class="code"><pre><div class="line"><span class="built_in">NSConditionLock</span> *cLock = [[<span class="built_in">NSConditionLock</span> alloc] initWithCondition:<span class="number">0</span>];</div><div class="line"><span class="comment">//线程1</span></div><div class="line"></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"> <span class="keyword">if</span>([cLock tryLockWhenCondition:<span class="number">0</span>]){</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程1"</span>);</div><div class="line"> [cLock unlockWithCondition:<span class="number">1</span>];</div><div class="line"> }<span class="keyword">else</span>{</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"> </div><div class="line"><span class="comment">//线程2</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"> [cLock lockWhenCondition:<span class="number">3</span>];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程2"</span>);</div><div class="line"> [cLock unlockWithCondition:<span class="number">2</span>];</div><div class="line">});</div><div class="line"> </div><div class="line"><span class="comment">//线程3</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"> [cLock lockWhenCondition:<span class="number">1</span>];</div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"线程3"</span>);</div><div class="line"> [cLock unlockWithCondition:<span class="number">3</span>];</div><div class="line">});</div></pre></td></tr></table></figure>
<p>运行结果:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1899027-7e410aff9dba4060.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="result"></p>
<ul>
<li>我们在初始化 NSConditionLock 对象时,给了他的标示为 <code>0</code></li>
<li>执行 <code>tryLockWhenCondition:</code> 时,我们传入的条件标示也是 <code>0</code>,所 <strong>以线程1</strong> 加锁成功</li>
<li>执行 <code>unlockWithCondition:</code> 时,<strong>这时候会把 <code>condition</code> 由 <code>0</code> 修改为 <code>1</code> </strong></li>
<li>因为 <code>condition</code> 修改为了 <code>1</code>, 会先走到 <strong>线程3</strong>,然后 <strong>线程3</strong> 又将 <code>condition</code> 修改为 <code>3</code></li>
<li>最后 走了 <strong>线程2</strong> 的流程</li>
</ul>
<blockquote>
<p>从上面的结果我们可以发现,NSConditionLock 还可以实现任务之间的依赖。</p>
</blockquote>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献:"></a>参考文献:</h3><p><a href="http://www.cocoachina.com/ios/20150513/11808.html" target="_blank" rel="external">NSRecursiveLock递归锁的使用</a></p>
<p><a href="http://www.cnblogs.com/snailHL/p/3906112.html" target="_blank" rel="external">关于dispatch_semaphore的使用</a></p>
<p><a href="http://www.cnblogs.com/huangjianwu/p/4575763.html" target="_blank" rel="external">实现锁的多种方式和锁的高级用法</a></p>
]]></content>
<summary type="html">
<p>这两天翻看 ibireme 大神 <a href="http://blog.ibireme.com/" target="_blank" rel="external">不再安全的 OSSpinLock</a> 这篇文章,看到文中分析各种锁之间的性能的图表,怀着好奇的心态,学习
</summary>
<category term="Objc" scheme="http://yoursite.com/tags/Objc/"/>
</entry>
<entry>
<title>iOS 中关于列表滚动流畅方案的一些探讨</title>
<link href="http://yoursite.com/2017/09/06/smooth_tableview/"/>
<id>http://yoursite.com/2017/09/06/smooth_tableview/</id>
<published>2017-09-06T15:30:29.000Z</published>
<updated>2018-02-13T10:07:14.497Z</updated>
<content type="html"><![CDATA[<p>近些年,App 越来越推崇体验至上,随随便便乱写一通的话已经很难让用户买帐了,顺滑的列表便是其中很重要的一点。如果一个 App 的页面滚动起来总是卡顿卡顿的,轻则被当作反面教材来吐槽或者衬托“我们的 App balabala…”,重则直接卸载。正好最近在优化这一块儿,总结记录下。</p>
<p>如果说有什么好的博客文章推荐,ibireme 的 <a href="https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/" target="_blank" rel="external">iOS 保持界面流畅的技巧</a> 这篇堪称经典,墙裂推荐反复阅读。这篇文章中讲解了很多的优化点,我自己总结了下收益最大的两个优化点:</p>
<ul>
<li>避免重复多次计算 cell 行高</li>
<li>文本异步渲染</li>
</ul>
<p><img src="/images/asyn.png" alt=""></p>
<p>大家可以看看上面这张图的对比分析,数据是 iPhone6 的机子用 instruments 抓的,左边的是用 Auto Layout 绘制界面的数据分析,正常如果想平滑滚动的话,fps 至少需要稳定在 55 左右,我们可以发现,在没有缓存行高和异步渲染的情况下 fps 是最低的,可以说是比较卡顿了,至少是能肉眼感觉出来,能满足平滑滚动要求的也只有在缓存行高且异步渲染的情况下;右边的是没用 Auto Layout 直接用 frame 来绘制界面的数据分析,可以发现即使没有异步渲染,也能勉强满足平滑滚动的要求,如果开启异步渲染的话,可以说是相当的丝滑了。</p>
<h3 id="避免重复多次计算-cell-行高"><a href="#避免重复多次计算-cell-行高" class="headerlink" title="避免重复多次计算 cell 行高"></a>避免重复多次计算 cell 行高</h3><p>TableView 行高计算可以说是个老生常谈的问题了,<code>heightForRowAtIndexPath:</code> 是个调用相当频繁的方法,在里面做过多的事情难免会造成卡顿。 在 iOS 8 中,我们可以通过设置下面两个属性来很轻松的实现高度自适应:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">self</span>.tableView.estimatedRowHeight = <span class="number">88</span>;</div><div class="line"><span class="keyword">self</span>.tableView.rowHeight = <span class="built_in">UITableViewAutomaticDimension</span>;</div></pre></td></tr></table></figure>
<p>虽然很方便,不过如果你的页面对性能有一定要求,建议不要这么做,具体可以看看 sunnyxx 的 <a href="http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/" target="_blank" rel="external">优化UITableViewCell高度计算的那些事</a>。文中针对 Auto Layout,提供了个 cell 行高的缓存库 <a href="https://github.com/forkingdog/UITableView-FDTemplateLayoutCell" target="_blank" rel="external">UITableView-FDTemplateLayoutCell</a>,可以很好的帮助我们避免 cell 行高多次计算的问题。</p>
<p>如果不使用 Auto Layout,我们可以在请求完拿到数据后提前计算好页面每个控件的 frame 和 cell 高度,并且缓存在内存中,用的时候直接在 <code>heightForRowAtIndexPath:</code> 取出计算好的值就行,大概流程如下:</p>
<ul>
<li>模拟请求数据回调:</li>
</ul>
<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></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)viewDidLoad {</div><div class="line"> [<span class="keyword">super</span> viewDidLoad];</div><div class="line"> </div><div class="line"> [<span class="keyword">self</span> buildTestDataThen:^(<span class="built_in">NSMutableArray</span> <FDFeedEntity *> *entities) {</div><div class="line"> <span class="keyword">self</span>.data = @[].mutableCopy;</div><div class="line"> <span class="keyword">@autoreleasepool</span> {</div><div class="line"> <span class="keyword">for</span> (FDFeedEntity *entity <span class="keyword">in</span> entities) {</div><div class="line"> FrameModel *frameModel = [FrameModel new];</div><div class="line"> frameModel.entity = entity;</div><div class="line"> [<span class="keyword">self</span>.data addObject:frameModel];</div><div class="line"> }</div><div class="line"> }</div><div class="line"> [<span class="keyword">self</span>.tvFeed reloadData];</div><div class="line"> }];</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>一个简单计算 frame 、cell 行高方式:</li>
</ul>
<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="comment">//FrameModel.h</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">FrameModel</span> : <span class="title">NSObject</span></span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">assign</span>, <span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>) <span class="built_in">CGRect</span> titleFrame;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">assign</span>, <span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>) <span class="built_in">CGFloat</span> cellHeight;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>) FDFeedEntity *entity;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//FrameModel.m</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">FrameModel</span></span></div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)setEntity:(FDFeedEntity *)entity {</div><div class="line"> <span class="keyword">if</span> (!entity) <span class="keyword">return</span>;</div><div class="line"> </div><div class="line"> _entity = entity;</div><div class="line"> </div><div class="line"> <span class="built_in">CGFloat</span> maxLayout = ([<span class="built_in">UIScreen</span> mainScreen].bounds.size.width - <span class="number">20.</span>f);</div><div class="line"> <span class="built_in">CGFloat</span> bottom = <span class="number">4.</span>f;</div><div class="line"> </div><div class="line"> <span class="comment">//title</span></div><div class="line"> <span class="built_in">CGFloat</span> titleX = <span class="number">10.</span>f;</div><div class="line"> <span class="built_in">CGFloat</span> titleY = <span class="number">10.</span>f;</div><div class="line"> <span class="built_in">CGSize</span> titleSize = [entity.title boundingRectWithSize:<span class="built_in">CGSizeMake</span>(maxLayout, <span class="built_in">CGFLOAT_MAX</span>) options:<span class="built_in">NSStringDrawingUsesLineFragmentOrigin</span>|<span class="built_in">NSStringDrawingUsesFontLeading</span> attributes:@{<span class="built_in">NSFontAttributeName</span> : Font(<span class="number">16.</span>f)} context:<span class="literal">nil</span>].size;</div><div class="line"> _titleFrame = <span class="built_in">CGRectMake</span>(titleX, titleY, titleSize.width, titleSize.height);</div><div class="line"> </div><div class="line"> <span class="comment">//cell Height</span></div><div class="line"> _cellHeight = (<span class="built_in">CGRectGetMaxY</span>(_titleFrame) + bottom);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<ul>
<li>行高取值:</li>
</ul>
<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">UITableViewCell</span> *)tableView:(<span class="built_in">UITableView</span> *)tableView cellForRowAtIndexPath:(<span class="built_in">NSIndexPath</span> *)indexPath {</div><div class="line"> FrameFeedCell *cell = [tableView dequeueReusableCellWithIdentifier:FrameFeedCellIdentifier forIndexPath:indexPath];</div><div class="line"> FrameModel *frameModel = <span class="keyword">self</span>.data[indexPath.row];</div><div class="line"> cell.model = frameModel;</div><div class="line"> <span class="keyword">return</span> cell;</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="built_in">CGFloat</span>)tableView:(<span class="built_in">UITableView</span> *)tableView heightForRowAtIndexPath:(<span class="built_in">NSIndexPath</span> *)indexPath {</div><div class="line"> FrameModel *frameModel = <span class="keyword">self</span>.data[indexPath.row];</div><div class="line"> <span class="keyword">return</span> frameModel.cellHeight;</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>控件赋值:</li>
</ul>
<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="keyword">void</span>)setModel:(FrameModel *)model {</div><div class="line"> <span class="keyword">if</span> (!model) <span class="keyword">return</span>;</div><div class="line"> </div><div class="line"> _model = model;</div><div class="line"> </div><div class="line"> FDFeedEntity *entity = model.entity;</div><div class="line"> </div><div class="line"> <span class="keyword">self</span>.titleLabel.frame = model.titleFrame;</div><div class="line"> <span class="keyword">self</span>.titleLabel.text = entity.title;</div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h4><p>缓存行高方式有现成的库简单方便,虽然 UITableView-FDTemplateLayoutCell 已经处理的很好了,但是 Auto Layout 对性能还是会有部分消耗;手动计算 frame 方式所有的位置都需要计算,比较麻烦,而且在数据量很大的情况下,大量的计算对数据展示时间会有部分影响,相应的回报就是性能会更好一些。</p>
<h3 id="文本异步渲染"><a href="#文本异步渲染" class="headerlink" title="文本异步渲染"></a>文本异步渲染</h3><p>当显示大量文本时,CPU 的压力会非常大。对此解决方案只有一个,那就是自定义文本控件,用 TextKit 或最底层的 CoreText 对文本异步绘制。尽管这实现起来非常麻烦,但其带来的优势也非常大,CoreText 对象创建好后,能直接获取文本的宽高等信息,避免了多次计算(调整 UILabel 大小时算一遍、UILabel 绘制时内部再算一遍);CoreText 对象占用内存较少,可以缓存下来以备稍后多次渲染。</p>
<p>幸运的是,想支持文本异步渲染也有现成的库 <a href="https://github.com/ibireme/YYText" target="_blank" rel="external">YYText</a> ,下面来讲讲如何搭配它最大程度满足我们如丝般顺滑的需求:</p>
<h4 id="Frame-搭配异步渲染"><a href="#Frame-搭配异步渲染" class="headerlink" title="Frame 搭配异步渲染"></a>Frame 搭配异步渲染</h4><p>基本思路和计算 frame 类似,只不过把系统的 <code>boundingRectWithSize:</code>、 <code>sizeWithAttributes:</code> 换成 YYText 中的方法:</p>
<ul>
<li>配置 frame model:</li>
</ul>
<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></pre></td><td class="code"><pre><div class="line"><span class="comment">//FrameYYModel.h</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">FrameYYModel</span> : <span class="title">NSObject</span></span></div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">assign</span>, <span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>) <span class="built_in">CGRect</span> titleFrame;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>) YYTextLayout *titleLayout;</div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">assign</span>, <span class="keyword">nonatomic</span>, <span class="keyword">readonly</span>) <span class="built_in">CGFloat</span> cellHeight;</div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>) FDFeedEntity *entity;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><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></pre></td><td class="code"><pre><div class="line"><span class="comment">//FrameYYModel.m</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">FrameYYModel</span></span></div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)setEntity:(FDFeedEntity *)entity {</div><div class="line"> <span class="keyword">if</span> (!entity) <span class="keyword">return</span>;</div><div class="line"> </div><div class="line"> _entity = entity;</div><div class="line"> </div><div class="line"> <span class="built_in">CGFloat</span> maxLayout = ([<span class="built_in">UIScreen</span> mainScreen].bounds.size.width - <span class="number">20.</span>f);</div><div class="line"> <span class="built_in">CGFloat</span> space = <span class="number">10.</span>f;</div><div class="line"> <span class="built_in">CGFloat</span> bottom = <span class="number">4.</span>f;</div><div class="line"> </div><div class="line"> <span class="comment">//title</span></div><div class="line"> <span class="built_in">NSMutableAttributedString</span> *title = [[<span class="built_in">NSMutableAttributedString</span> alloc] initWithString:entity.title];</div><div class="line"> title.yy_font = Font(<span class="number">16.</span>f);</div><div class="line"> title.yy_color = [<span class="built_in">UIColor</span> blackColor];</div><div class="line"> </div><div class="line"> YYTextContainer *titleContainer = [YYTextContainer containerWithSize:<span class="built_in">CGSizeMake</span>(maxLayout, <span class="built_in">CGFLOAT_MAX</span>)];</div><div class="line"> _titleLayout = [YYTextLayout layoutWithContainer:titleContainer text:title];</div><div class="line"> </div><div class="line"> <span class="built_in">CGFloat</span> titleX = <span class="number">10.</span>f;</div><div class="line"> <span class="built_in">CGFloat</span> titleY = <span class="number">10.</span>f;</div><div class="line"> <span class="built_in">CGSize</span> titleSize = _titleLayout.textBoundingSize;</div><div class="line"> _titleFrame = (<span class="built_in">CGRect</span>){titleX,titleY,<span class="built_in">CGSizeMake</span>(titleSize.width, titleSize.height)};</div><div class="line"> </div><div class="line"> <span class="comment">//cell Height</span></div><div class="line"> _cellHeight = (<span class="built_in">CGRectGetMaxY</span>(_titleFrame) + bottom);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>对比上面 frame,可以发现多了个 <code>YYTextLayout</code> 属性,这个属性可以提前配置文本的特性,包括 <code>font</code>、<code>textColor</code> 以及行数、行间距、内间距等等,好处就是可以把一些逻辑提前处理好,比如根据接口字段,动态配置字体颜色,字号等,如果用 Auto Layout,这部分逻辑则不可避免的需要写在 <code>cellForRowAtIndexPath:</code> 方法中。</p>
<ul>
<li>UITableViewCell 处理 :</li>
</ul>
<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></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">instancetype</span>)initWithStyle:(<span class="built_in">UITableViewCellStyle</span>)style reuseIdentifier:(<span class="built_in">NSString</span> *)reuseIdentifier {</div><div class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> initWithStyle:style reuseIdentifier:reuseIdentifier];</div><div class="line"> <span class="keyword">if</span> (!<span class="keyword">self</span>) <span class="keyword">return</span> <span class="literal">nil</span>;</div><div class="line"> </div><div class="line"> YYLabel *title = [YYLabel new];</div><div class="line"> title.displaysAsynchronously = <span class="literal">YES</span>; <span class="comment">//开启异步渲染</span></div><div class="line"> title.ignoreCommonProperties = <span class="literal">YES</span>; <span class="comment">//忽略属性</span></div><div class="line"> title.layer.borderColor = [<span class="built_in">UIColor</span> brownColor].CGColor;</div><div class="line"> title.layer.cornerRadius = <span class="number">1.</span>f;</div><div class="line"> title.layer.borderWidth = <span class="number">1.</span>f;</div><div class="line"> [<span class="keyword">self</span>.contentView addSubview:_titleLabel = title];</div><div class="line"> </div><div class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>赋值:</li>
</ul>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)setModel:(FrameYYModel *)model {</div><div class="line"> <span class="keyword">if</span> (!model) <span class="keyword">return</span>;</div><div class="line"> _model = model;</div><div class="line"> </div><div class="line"> <span class="keyword">self</span>.titleLabel.frame = model.titleFrame;</div><div class="line"> <span class="keyword">self</span>.titleLabel.textLayout = model.titleLayout; <span class="comment">//直接取 YYTextLayout</span></div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="Auto-Layout-搭配异步渲染"><a href="#Auto-Layout-搭配异步渲染" class="headerlink" title="Auto Layout 搭配异步渲染"></a>Auto Layout 搭配异步渲染</h4><p>YYText 非常友好,同样支持 xib,YYText 继承自 <code>UIView</code>,所要做的事情也很简单:</p>
<ul>
<li>在 xib 中配置约束</li>
<li>开启异步属性</li>
</ul>
<p>开启异步属性可以代码里设置,也可以直接在 xib 里设置,分别如下:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">self</span>.titleLabel.displaysAsynchronously = <span class="literal">YES</span>;</div><div class="line"><span class="keyword">self</span>.subTitleLabel.displaysAsynchronously = <span class="literal">YES</span>;</div><div class="line"><span class="keyword">self</span>.contentLabel.displaysAsynchronously = <span class="literal">YES</span>;</div><div class="line"><span class="keyword">self</span>.usernameLabel.displaysAsynchronously = <span class="literal">YES</span>;</div><div class="line"><span class="keyword">self</span>.timeLabel.displaysAsynchronously = <span class="literal">YES</span>;</div></pre></td></tr></table></figure>
<p><img src="/images/ib.png" alt=""></p>
<p>另外需要注意的一点是,多行文本的情况下需要设置最大换行宽:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">CGFloat</span> maxLayout = [<span class="built_in">UIScreen</span> mainScreen].bounds.size.width - <span class="number">20.</span>f;</div><div class="line"><span class="keyword">self</span>.titleLabel.preferredMaxLayoutWidth = maxLayout;</div><div class="line"><span class="keyword">self</span>.subTitleLabel.preferredMaxLayoutWidth = maxLayout;</div><div class="line"><span class="keyword">self</span>.contentLabel.preferredMaxLayoutWidth = maxLayout;</div></pre></td></tr></table></figure>
<h4 id="优缺点-1"><a href="#优缺点-1" class="headerlink" title="优缺点"></a>优缺点</h4><p> YYText 的异步渲染能极大程度的提高列表流畅度,真正达到如丝般顺滑,但是在开启异步时,刷新列表会有闪烁情况,仔细想想觉得也正常,毕竟是异步的,渲染也需要时间,这里作者给出了一些 <a href="https://github.com/ibireme/YYKit/issues/64" target="_blank" rel="external">方案</a>,大家可以看看。</p>
<h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><h4 id="关于圆角"><a href="#关于圆角" class="headerlink" title="关于圆角"></a>关于圆角</h4><p>列表中如果存在很多系统设置的圆角页面导致卡顿:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">label.layer.cornerRadius = <span class="number">5.</span>f;</div><div class="line">label.clipsToBounds = <span class="literal">YES</span>;</div></pre></td></tr></table></figure>
<p>其实据我观察,只要当前屏幕内只要设置圆角的控件个数不要太多(大概十几个算个临界点),就不会引起卡顿。</p>
<p>还有就是只要不设置 <code>clipsToBounds</code> 不管多少个,都不会卡顿,比如你需要圆角的控件是白色背景色的,然后它的父控件也是白色背景色的,而且没有点击后高亮的,就没必要 clipsToBounds 了。</p>
<h4 id="如何定位卡顿原因"><a href="#如何定位卡顿原因" class="headerlink" title="如何定位卡顿原因"></a>如何定位卡顿原因</h4><p>我们可以利用 instruments 中的 Time Profiler 来帮助我们定位问题位置,选中 Xcode,command + control + i 打开:</p>
<p><img src="/images/instruments.gif" alt=""></p>
<p>我们选中主线程,去掉系统的方法,然后操作一下列表,再截取一段调用信息,可以发现我们自己实现的方法并没有消耗多少时间,反而是系统的方法很费时,这也是卡顿的原因之一:</p>
<p><img src="/images/autolayout.png" alt=""></p>
<p>另外有的人 instruments 看不到方法调用栈(右边一对黑色的方法信息),去 Xcode 设置下就行了:</p>
<p><img src="/images/xcode.png" alt=""></p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>YYText 和 UITableView-FDTemplateLayoutCell 搭配可以很大程度的提高列表流畅度:</p>
<ul>
<li><p><strong>如果时间比较紧迫,可以直接采取 Auto Layout + UITableView-FDTemplateLayoutCell + YYText 方式</strong></p>
</li>
<li><p><strong>如果列表中文本不包含富文本,仅仅显示文字,又不想引入这两个库,可以使用系统方式提前计算 Frame</strong></p>
</li>
<li><p><strong>如果想最大程度的流畅度,就需要提前计算 Frame + YYText,具体大家根据自己情况选择合适的方案就行</strong></p>
</li>
</ul>
<p>最后,附上测试 <a href="https://github.com/ifelseboyxx/xx_Notes/tree/master/contents/TVOptimizationDemo/TVOptimizationDemo" target="_blank" rel="external">Demo</a></p>
]]></content>
<summary type="html">
<p>近些年,App 越来越推崇体验至上,随随便便乱写一通的话已经很难让用户买帐了,顺滑的列表便是其中很重要的一点。如果一个 App 的页面滚动起来总是卡顿卡顿的,轻则被当作反面教材来吐槽或者衬托“我们的 App balabala…”,重则直接卸载。正好最近在优化这一块儿,总结记
</summary>
<category term="Objc" scheme="http://yoursite.com/tags/Objc/"/>
</entry>
<entry>
<title>基于 ResponderChain 的对象交互方式</title>
<link href="http://yoursite.com/2017/08/04/ResponderChain/"/>
<id>http://yoursite.com/2017/08/04/ResponderChain/</id>
<published>2017-08-04T02:26:23.000Z</published>
<updated>2018-02-13T10:06:39.321Z</updated>
<content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>Responder Chain ,也就是响应链,关于这方面的知识因为不是本文重点,还不太理解的可以去看看这篇文章:<a href="http://www.jianshu.com/p/2e074db792ba" target="_blank" rel="external">史上最详细的iOS之事件的传递和响应机制-原理篇</a>。</p>
<p>在 iOS 中,对象间的交互模式大概有这几种:直接 property 传值、delegate、KVO、block、protocol、多态、Target-Action 等等,本文介绍的是一种基于 UIResponder 对象交互方式,简而言之,就是 通过在 UIResponder上挂一个 category,使得事件和参数可以沿着 responder chain 逐步传递。对于那种 subviews 特别多,事件又需要层层传递的层级视图特别好用,但是,缺点也很明显,必须依赖于 UIResponder 对象。</p>
<h3 id="具体事例"><a href="#具体事例" class="headerlink" title="具体事例"></a>具体事例</h3><p>我们先来看看下面这种很常见的界面:</p>
<p><img src="/images/08-04.png" alt=""></p>
<p>简单讲解下:最外层是个 UITableView,我们就叫做 SuperTable,每个 cell 里面又嵌套了个 UITableView,叫做 SubTable,然后这个 SubTable 的 cell 里面有一些按钮,我们理一下这个界面的层级:</p>
<blockquote>
<p>UIViewController -> SuperTable -> SuperCell -> SubTable -> SubCell -> UIButton</p>
</blockquote>
<p>如果我们需要在最外层的 UIViewController 里捕获到这些按钮的点击事件,比如点击按钮需要刷新 SuperTable,这时候该怎么实现呢?</p>
<p>方法有很多,最常见的就是 delegate ,但是因为层级太深,导致我们需要一层层的去实现,各种 protocol、delegate 声明,很繁琐,这种时候,基于 Responder Chain 就很方便了。</p>
<h3 id="具体使用"><a href="#具体使用" class="headerlink" title="具体使用"></a>具体使用</h3><p>只需要一个 UIResponder 的 category 就行:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">UIResponder</span> (<span class="title">Router</span>)</span></div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)routerEventWithSelectorName:(<span class="built_in">NSString</span> *)selectorName</div><div class="line"> object:(<span class="keyword">id</span>)object</div><div class="line"> userInfo:(<span class="built_in">NSDictionary</span> *)userInfo;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">UIResponder</span> (<span class="title">Router</span>)</span></div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)routerEventWithSelectorName:(<span class="built_in">NSString</span> *)selectorName</div><div class="line"> object:(<span class="keyword">id</span>)object</div><div class="line"> userInfo:(<span class="built_in">NSDictionary</span> *)userInfo {</div><div class="line"> </div><div class="line"> [[<span class="keyword">self</span> nextResponder] routerEventWithSelectorName:selectorName</div><div class="line"> object:object</div><div class="line"> userInfo:userInfo];</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></table></figure>
<p>最里层 UIButton 的点击处理:</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></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">IBAction</span>)btnClick1:(<span class="built_in">UIButton</span> *)sender {</div><div class="line"> </div><div class="line"> [<span class="keyword">self</span> routerEventWithSelectorName:<span class="string">@"btnClick1:userInfo:"</span> object:sender userInfo:@{<span class="string">@"key"</span>:<span class="string">@"蓝色按钮"</span>}]; </div><div class="line">}</div></pre></td></tr></table></figure>
<p>外层 UIViewController 的接收:</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></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)routerEventWithSelectorName:(<span class="built_in">NSString</span> *)selectorName</div><div class="line"> object:(<span class="keyword">id</span>)object</div><div class="line"> userInfo:(<span class="built_in">NSDictionary</span> *)userInfo {</div><div class="line"> </div><div class="line"> SEL action = <span class="built_in">NSSelectorFromString</span>(selectorName);</div><div class="line"> </div><div class="line"> <span class="built_in">NSMutableArray</span> *arr = [<span class="built_in">NSMutableArray</span> array];</div><div class="line"> <span class="keyword">if</span>(object) {[arr addObject:object];};</div><div class="line"> <span class="keyword">if</span>(userInfo) {[arr addObject:userInfo];};</div><div class="line"> </div><div class="line"> [<span class="keyword">self</span> performSelector:action withObjects:arr];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>事件响应:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)btnClick1:(<span class="built_in">UIButton</span> *)btn userInfo:(<span class="built_in">NSDictionary</span> *)userInfo {</div><div class="line"> </div><div class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%@ %@"</span>,btn,userInfo); </div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果想在传递过程中新增参数,比如想在 SuperCell 这一层加点参数,只需要在对应的地方实现方法就行:</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)routerEventWithSelectorName:(<span class="built_in">NSString</span> *)selectorName object:(<span class="keyword">id</span>)object userInfo:(<span class="built_in">NSDictionary</span> *)userInfo {</div><div class="line"> </div><div class="line"> <span class="built_in">NSMutableDictionary</span> *mDict = [userInfo mutableCopy];</div><div class="line"> mDict[<span class="string">@"test"</span>] = <span class="string">@"测试"</span>;</div><div class="line"></div><div class="line"> [<span class="keyword">super</span> routerEventWithSelectorName:selectorName object:object userInfo:[mDict <span class="keyword">copy</span>]];</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)routerEventWithSelectorName:(<span class="built_in">NSString</span> *)selectorName</div><div class="line"> object:(<span class="keyword">id</span>)object</div><div class="line"> userInfo:(<span class="built_in">NSDictionary</span> *)userInfo</div></pre></td></tr></table></figure>
<p>细心的可以发现,我这里直接把 <code>SEL</code> 设计成以 <code>NSString</code> 的形式传递了,再在外面通过 <code>NSSelectorFromString(selectorName)</code> 转成对应的 <code>SEL</code>。原文中传的是个用来标识具体是哪个事件的字串,还需要维护专门的 <code>NSDictionary</code> 来找到对应的事件,我觉得太麻烦,但是好处是 <code>@selector(....)</code> 声明和实现在一个地方,可读性高,也不容易出现拼写错误,导致触发不了对应方法的问题,具体怎么设计,大家见仁见智吧~</p>
<p>关于参数的传递,比如我触发 <code>UITableViewDelegate</code> 中的 <code>didSelectRowAtIndexPath:</code> 方法,参数两个以内时,<code>performSelector:</code> 方法也可以满足,但一旦超过两个,就不方便了。这时候我们就可以用 <code>NSInvocation</code> 来实现,我写了个分类,支持传递多个参数,搭配使用很方便:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">@interface NSObject (PerformSelector)</div><div class="line"></div><div class="line">- (id)performSelector:(SEL)aSelector withObjects:(NSArray <id> *)objects;</div><div class="line"></div><div class="line">@end</div></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><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></pre></td><td class="code"><pre><div class="line">@implementation NSObject (PerformSelector)</div><div class="line"></div><div class="line">- (id)performSelector:(SEL)aSelector</div><div class="line"> withObjects:(NSArray <id> *)objects {</div><div class="line"> </div><div class="line"> //创建签名对象</div><div class="line"> NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];</div><div class="line"> </div><div class="line"> //判断传入的方法是否存在</div><div class="line"> if (!signature) { //不存在</div><div class="line"> //抛出异常</div><div class="line"> NSString *info = [NSString stringWithFormat:@"-[%@ %@]:unrecognized selector sent to instance",[self class],NSStringFromSelector(aSelector)];</div><div class="line"> @throw [[NSException alloc] initWithName:@"ifelseboyxx remind:" reason:info userInfo:nil];</div><div class="line"> return nil;</div><div class="line"> }</div><div class="line"> </div><div class="line"> //创建 NSInvocation 对象</div><div class="line"> NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];</div><div class="line"> </div><div class="line"> //保存方法所属的对象</div><div class="line"> invocation.target = self;</div><div class="line"> invocation.selector = aSelector;</div><div class="line"></div><div class="line"> </div><div class="line"> //设置参数</div><div class="line"> //存在默认的 _cmd、target 两个参数,需剔除</div><div class="line"> NSInteger arguments = signature.numberOfArguments - 2;</div><div class="line"> </div><div class="line"> //谁少就遍历谁,防止数组越界</div><div class="line"> NSUInteger objectsCount = objects.count;</div><div class="line"> NSInteger count = MIN(arguments, objectsCount);</div><div class="line"> for (int i = 0; i < count; i++) {</div><div class="line"> id obj = objects[i];</div><div class="line"> //处理参数是 NULL 类型的情况</div><div class="line"> if ([obj isKindOfClass:[NSNull class]]) {obj = nil;}</div><div class="line"> [invocation setArgument:&obj atIndex:i+2];</div><div class="line"> }</div><div class="line"> </div><div class="line"> //调用</div><div class="line"> [invocation invoke];</div><div class="line"> </div><div class="line"> //获取返回值</div><div class="line"> id res = nil;</div><div class="line"> //判断当前方法是否有返回值</div><div class="line"> if (signature.methodReturnLength != 0) {</div><div class="line"> [invocation getReturnValue:&res];</div><div class="line"> }</div><div class="line"> return res;</div><div class="line">}</div><div class="line"></div><div class="line">@end</div></pre></td></tr></table></figure>
<p>最后附上 <a href="https://github.com/ifelseboyxx/xx_Notes/tree/master/contents/ResponderChain/ResponderChainDemo" target="_blank" rel="external">Demo</a></p>
<h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><p><a href="https://casatwy.com/responder_chain_communication.html" target="_blank" rel="external">https://casatwy.com/responder_chain_communication.html</a></p>
]]></content>
<summary type="html">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>Responder Chain ,也就是响应链,关于这方面的知识因为不是本文重点,还不太理解的可以去看看这篇文章:<a href="http