-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
897 lines (889 loc) · 245 KB
/
search.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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Gitlab+fastlane 持续集成</title>
<url>/2018/06/01/Gitlab-fastlane-%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/</url>
<content><![CDATA[<p>[TOC]</p>
<h5 id="实现步骤"><a href="#实现步骤" class="headerlink" title="实现步骤"></a>实现步骤</h5><ol>
<li>在gitlab新建项目</li>
<li>配置并启动gitlab-runner</li>
<li>安装并配置fastlane</li>
<li>配置.gitlab-ci.yml文件</li>
</ol>
<h5 id="1、在gitlab新建项目"><a href="#1、在gitlab新建项目" class="headerlink" title="1、在gitlab新建项目"></a>1、在gitlab新建项目</h5><p>做开发的都会</p>
<h5 id="2、配置gitlab-runner"><a href="#2、配置gitlab-runner" class="headerlink" title="2、配置gitlab-runner"></a>2、配置gitlab-runner</h5><p><a class="link" href="https://docs.gitlab.com/runner/register/index.html" >gitlab-runner文档<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/c7995ad64f48" >gitlab-runner的配置——for Mac<i class="fas fa-external-link-alt"></i></a></p>
<h5 id="3、安装并配置fastlane"><a href="#3、安装并配置fastlane" class="headerlink" title="3、安装并配置fastlane"></a>3、安装并配置fastlane</h5><h6 id="安装fastlane"><a href="#安装fastlane" class="headerlink" title="安装fastlane"></a>安装fastlane</h6><p><a class="link" href="https://s0docs0fastlane0tools.icopy.site/" >fastlane中文文档<i class="fas fa-external-link-alt"></i></a></p>
<p>注意:任何的安装可能都需要你配置下环境变量</p>
<h6 id="安装firim插件"><a href="#安装firim插件" class="headerlink" title="安装firim插件"></a>安装firim插件</h6><figure class="highlight ebnf"><table><tr><td class="code"><pre><code class="hljs ebnf"><span class="hljs-attribute">fastlane add_plugin firim</span><br></code></pre></td></tr></table></figure>
<p>注意:这个要在工程fastlane文件夹所在目录执行</p>
<h6 id="Appfile"><a href="#Appfile" class="headerlink" title="Appfile"></a>Appfile</h6><p>用于存放 app_identifier(bundle id)、 apple_id(appl开发中账号邮箱地址)、team_id、itc_team_id</p>
<h6 id="Deliverfile"><a href="#Deliverfile" class="headerlink" title="Deliverfile"></a>Deliverfile</h6><p>deliver工具的配置文件</p>
<h6 id="Fastfile"><a href="#Fastfile" class="headerlink" title="Fastfile"></a>Fastfile</h6><p>用于管理你所创建的 lane,lane 则会调用 action。</p>
<h6 id="Gymfile"><a href="#Gymfile" class="headerlink" title="Gymfile"></a>Gymfile</h6><p><a class="link" href="http://docs.fastlane.tools/actions/gym/" >官方文档<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/f62c8a980c35" >Fastlane - gym<i class="fas fa-external-link-alt"></i></a></p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p3931" alt="18aa7066fa1a32b15eca900e3670743f.png"></p>
<h6 id="Snapfile"><a href="#Snapfile" class="headerlink" title="Snapfile"></a>Snapfile</h6><p>用于指定需要进行屏幕截图的设备类型和语种。</p>
<h6 id="Matchfile"><a href="#Matchfile" class="headerlink" title="Matchfile"></a>Matchfile</h6><p><a class="link" href="https://www.jianshu.com/p/5962940522a2" >使用Match来管理代码签名<i class="fas fa-external-link-alt"></i></a></p>
<h6 id="Pluginfile"><a href="#Pluginfile" class="headerlink" title="Pluginfile"></a>Pluginfile</h6><p>安装的插件会在这里面显示,如前面提到的插件firim安装之后,Pluginfile中:</p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">gem</span> <span class="hljs-string">'fastlane-plugin-firim'</span><br></code></pre></td></tr></table></figure>
<h5 id="4、配置-gitlab-ci-yml文件"><a href="#4、配置-gitlab-ci-yml文件" class="headerlink" title="4、配置.gitlab-ci.yml文件"></a>4、配置.gitlab-ci.yml文件</h5><p>注意:<br>variables:<br> LC_ALL: “en_US.UTF-8”<br> LANG: “en_US.UTF-8”<br> GIT_STRATEGY: fetch #开发阶段这要设置一下,默认每次都会clone代码,耗时较长。上线版本设置为clone获取最新代码</p>
<p><a class="link" href="https://segmentfault.com/a/1190000019540360" >持续集成之.gitlab-ci.yml篇(详细讲解了各命令的作用)<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://s0docs0gitlab0com.icopy.site/ce/ci/yaml/README.html" >(官方文档)<i class="fas fa-external-link-alt"></i></a> </p>
<h5 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h5><p>在配置.gitlab-ci.yml文件中配置需要执行的脚本,这里配置执行fastlane脚本。</p>
<h5 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h5><p>个人建议,直接看官方文档,除非英文太差,那就找中文资料吧。</p>
]]></content>
<tags>
<tag>fastlane</tag>
</tags>
</entry>
<entry>
<title>NSURLProtocol 研究</title>
<url>/2019/08/06/NSURLProtocol-%E7%A0%94%E7%A9%B6/</url>
<content><![CDATA[<p>[TOC]</p>
<h3 id="API"><a href="#API" class="headerlink" title="API"></a>API</h3><p>// 这个方法是注册NSURLProtocol子类的方法.<br><figure class="highlight gcode"><table><tr><td class="code"><pre><code class="hljs gcode">+ <span class="hljs-comment">(BOOL)</span>registerClass:<span class="hljs-comment">(Class)</span>protocolClass;<br></code></pre></td></tr></table></figure></p>
<p>// 这个方法是注册后,NSURLProtocol就会通过这个方法确定参数request是否需要被处理<br>// return : YES 需要经过这个NSURLProtocol”协议” 的处理, NO 这个 协议request不需要遵守这个NSURLProtocol”协议”<br>// 这个方法的左右 : 1, 筛选Request是否需要遵守这个NSURLRequest , 2, 处理http: , https等URL<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">+ (<span class="hljs-built_in">BOOL</span>)canInitWithRequest:(<span class="hljs-built_in">NSURLRequest</span> *)request;<br></code></pre></td></tr></table></figure></p>
<p>// 这个方法就是返回request,当然这里可以处理的需求有 : 1,规范化请求头的信息 2, 处理DNS劫持,重定向App中所有的请求指向等<br><figure class="highlight lisp"><table><tr><td class="code"><pre><code class="hljs lisp">+ (<span class="hljs-name">NSURLRequest</span> *)canonicalRequestForRequest:(NSURLRequest *)request<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure></p>
<p>// 这个方法主要用来判断两个请求是否是同一个请求,如果是,则可以使用缓存数据,通常只需要调用父类的实现即可,默认为YES,而且一般不在这里做事情<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">+ (<span class="hljs-built_in">BOOL</span>)requestIsCacheEquivalent:(<span class="hljs-built_in">NSURLRequest</span> *)a toRequest:(<span class="hljs-built_in">NSURLRequest</span> *)b;<br></code></pre></td></tr></table></figure></p>
<p>// abstract Initializes an NSURLProtocol given request, cached response, and client.<br>// 开始初始化一个NSURLProtocol抽象对象, 包含请求, cachedResponse , 和建立client<br><figure class="highlight elm"><table><tr><td class="code"><pre><code class="hljs elm">- (instance<span class="hljs-keyword">type</span>)initWithRequest:(<span class="hljs-type">NSURLRequest</span> *)request cachedResponse:(nullable <span class="hljs-type">NSCachedURLResponse</span> *)cachedResponse client:(nullable id <<span class="hljs-type">NSURLProtocolClient</span>>)client <span class="hljs-type">NS_DESIGNATED_INITIALIZER</span>;<br></code></pre></td></tr></table></figure></p>
<p>// 需要在该方法中发起一个请求,对于NSURLConnection来说,就是创建一个NSURLConnection,对于NSURLSession,就是发起一个NSURLSessionTask<br>// 另外一点就是这个方法之后,会回调<NSURLProtocolClient>协议中的方法,<br><figure class="highlight lisp"><table><tr><td class="code"><pre><code class="hljs lisp">- (<span class="hljs-name">void</span>)startLoading<br></code></pre></td></tr></table></figure></p>
<p>// 这个方法是和start是对应的 一般在这个方法中,断开Connection<br>// 另外一点就是当NSURLProtocolClient的协议方法都回调完毕后,就会开始执行这个方法了<br><figure class="highlight lisp"><table><tr><td class="code"><pre><code class="hljs lisp">- (<span class="hljs-name">void</span>)stopLoading<br><br></code></pre></td></tr></table></figure></p>
<h3 id="多个NSURLProtocol注册"><a href="#多个NSURLProtocol注册" class="headerlink" title="多个NSURLProtocol注册"></a>多个NSURLProtocol注册</h3><p>在开发过程中,遇到的一些问题总结一下</p>
<p>如果注册了两个NSURLProtocol,执行顺序是怎样?###</p>
<p>Protocols的遍历是反向的,也就是最后注册的Protocol会被优先判断。<br>如下图, 先注册AAAA,再注册BBBB的话优先判断的是BBBB,</p>
<p><img src="evernotecid://B57F1AED-083F-4A2B-9367-5A171F86DD13/appyinxiangcom/11846024/ENResource/p4242" alt="719b010b9025a0687f94e8dfb5ad23b4.png"></p>
<h3 id="具体流程"><a href="#具体流程" class="headerlink" title="具体流程"></a>具体流程</h3><p>1、发起请求<br>2、-(BOOL)canInitxxx<br> 根据userAgent、Method、白名单、URL System Load 判断是否处理请求<br> 不处理,会默认转给系统的NSURLProtocol处理<br> 处理进入下一步<br>3、canonicalRequestForRequest<br> 添加请求头信息<br>4、startLoading<br> 看自己是否有缓存:<br> 是否过期,<br> 过期去请求数据<br> 没过期,读取缓存,client 回调数据给webview展示<br> 看系统是否自己有缓存:<br> 处理同上<br> 无缓存:<br> 请求数据<br> 等到数据,缓存数据并由client 回调数据给webview展示</p>
<h3 id="填坑"><a href="#填坑" class="headerlink" title="填坑"></a>填坑</h3><p>1、关于私有API<br>因为WKBrowsingContextController和registerSchemeForCustomProtocol应该是私有的所以使用时候需要对字符串做下处理,用加密的方式或者其他就可以了,实测可以过审核的。</p>
<p>2、关于post请求(个人觉得post就过滤不处理好了)<br>大家会发现拦截不了post请求(拦截到的post请求body体为空),这个其实和WKWebview没有关系,这个是苹果为了提高效率加快流畅度所以在NSURLProtocol拦截之后索性就不复制body体内的东西,因为body的大小没有限制,开发者可能会把很大的数据放进去那就不好办了。</p>
<p>网上有人说下面的方式可以处理post请求参数的问题,我动手试了发现并不起作用,因为HTTPBodyStream都是空的。<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> mark -</span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> mark 处理POST请求相关POST 用HTTPBodyStream来处理BODY体</span><br>- (<span class="hljs-built_in">NSMutableURLRequest</span> *)handlePostRequestBodyWithRequest:(<span class="hljs-built_in">NSMutableURLRequest</span> *)request {<br> <span class="hljs-built_in">NSMutableURLRequest</span> * req = [request mutableCopy];<br> <span class="hljs-keyword">if</span> ([request.HTTPMethod isEqualToString:<span class="hljs-string">@"POST"</span>]) {<br> <span class="hljs-keyword">if</span> (!request.HTTPBody) {<br> uint8_t d[<span class="hljs-number">1024</span>] = {<span class="hljs-number">0</span>};<br> <span class="hljs-built_in">NSInputStream</span> *stream = request.HTTPBodyStream;<br> <span class="hljs-built_in">NSMutableData</span> *data = [[<span class="hljs-built_in">NSMutableData</span> alloc] init];<br> [stream open];<br> <span class="hljs-keyword">while</span> ([stream hasBytesAvailable]) {<br> <span class="hljs-built_in">NSInteger</span> len = [stream read:d maxLength:<span class="hljs-number">1024</span>];<br> <span class="hljs-keyword">if</span> (len > <span class="hljs-number">0</span> && stream.streamError == <span class="hljs-literal">nil</span>) {<br> [data appendBytes:(<span class="hljs-keyword">void</span> *)d length:len];<br> }<br> }<br> req.HTTPBody = [data <span class="hljs-keyword">copy</span>];<br> [stream close];<br> }<br> }<br> <span class="hljs-keyword">return</span> req;<br>}<br></code></pre></td></tr></table></figure></p>
<p>目前我的解决办法是:把post请求参数单独保存,利用键值对保存不同post请求的参数,然后拦截post请求修改请求体。 </p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a class="link" href="https://www.jianshu.com/p/ec5d6c204e17" >NSURLProtocol 的使用<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://blog.csdn.net/weixin_37664648/article/details/80021430" >NSURLProtocol拦截网络请求<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/ae5e8f9988d8?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation" >iOS中NSURLProtocol黑魔法的使用<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/02781c0bbca9" >NSURLProtocol全攻略<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/bb20ff351fa2" >WKWebView 那些坑(转自 腾讯Bugly)<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/cd4c1bf1fd5f" >可能是最全的iOS端HttpDns集成方案<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/dabad9068dd5" >iOS-网络优化(一)<i class="fas fa-external-link-alt"></i></a></p>
]]></content>
<tags>
<tag>Foundation</tag>
</tags>
</entry>
<entry>
<title>YBTaskScheduler 研究</title>
<url>/2019/06/06/YBTaskScheduler-%E7%A0%94%E7%A9%B6/</url>
<content><![CDATA[<p>学习<br><a class="link" href="https://github.com/indulgeIn/YBTaskScheduler" >YBTaskScheduler<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/f2a610c77d26" >iOS 任务调度器:为 CPU 和内存减负<i class="fas fa-external-link-alt"></i></a></p>
<p>[TOC]</p>
<h3 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h3><h5 id="可能遇到的问题"><a href="#可能遇到的问题" class="headerlink" title="可能遇到的问题"></a>可能遇到的问题</h5><p>当主线程执行大量的任务会造成卡顿,应该把这些任务移动到子线程异步执行。</p>
<p>但是异步执行的任务量过大,会导致cpu和内存占用率过高,然后引发一系列其他问题。</p>
<h5 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h5><h6 id="延时降频"><a href="#延时降频" class="headerlink" title="延时降频"></a>延时降频</h6><p>降低任务执行频率,或延缓任务执行时机,以时间换空间。</p>
<h6 id="淘汰"><a href="#淘汰" class="headerlink" title="淘汰"></a>淘汰</h6><p>淘汰不必要执行的任务,如已经滑出屏幕的ui异步绘制任务就可以移除,只保留需要显示屏幕可见部分的绘制任务。</p>
<h6 id="优先级调度"><a href="#优先级调度" class="headerlink" title="优先级调度"></a>优先级调度</h6><p>个人理解这个对降低cpu、内存占用率高没什么作用,允许设置优先级应该是一个附加功能,真正起到作用的是使用runloop起到降频(任务到runloop空闲时才执行)的作用。</p>
<h3 id="原理和实现"><a href="#原理和实现" class="headerlink" title="原理和实现"></a>原理和实现</h3><h5 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h5><p><code>YBTaskScheduler</code> 监听runloop的状态,只有在runloop将要进入空闲状态时才执行任务。</p>
<p>设置任务最大数量,在添加任务的时候将超过最大限制的任务淘汰。</p>
<h5 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h5><h6 id="命令模式-runloop"><a href="#命令模式-runloop" class="headerlink" title="命令模式 + runloop"></a>命令模式 + runloop</h6><p>想要管理这些复杂的任务,并且在合适的时机调用它们,自然而然的就想到了命令模式。意味着任务不能直接执行,而是把任务作为一个命令装入容器。</p>
<p>在 Objective-C 中,显然 Block 代码块能解决延迟执行这个问题:</p>
<figure class="highlight clojure"><table><tr><td class="code"><pre><code class="hljs clojure">[_scheduler addTask:<span class="hljs-comment">^{</span><br><span class="hljs-comment"> /* </span><br><span class="hljs-comment"> 具体任务代码</span><br><span class="hljs-comment"> 解压图片、裁剪图片、访问磁盘等 </span><br><span class="hljs-comment"> */</span><br><span class="hljs-comment">}</span>]<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure>
<p>然后组件将这些代码块“装起来”,组件由此“掌握”了所有的任务,可以自由的决定何时调用这些代码块,何时对某些代码块进行淘汰,还可以实现优先级调度。 </p>
<p>既然是命令模式,还差一个 Invoker (调用程序),即何时去触发这些任务。结合 iOS 的技术特点,可以监听 RunLoop 循环周期来实现:</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> addRunLoopObserver() {<br> <span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_once_t</span> onceToken;<br> <span class="hljs-built_in">dispatch_once</span>(&onceToken, ^{<br> taskSchedulers = [<span class="hljs-built_in">NSHashTable</span> weakObjectsHashTable];<br> <span class="hljs-built_in">CFRunLoopObserverRef</span> observer = <span class="hljs-built_in">CFRunLoopObserverCreate</span>(<span class="hljs-built_in">CFAllocatorGetDefault</span>(), kCFRunLoopBeforeWaiting | kCFRunLoopExit, <span class="hljs-literal">true</span>, <span class="hljs-number">0xFFFFFF</span>, runLoopObserverCallBack, <span class="hljs-literal">NULL</span>);<br> <span class="hljs-built_in">CFRunLoopAddObserver</span>(<span class="hljs-built_in">CFRunLoopGetMain</span>(), observer, kCFRunLoopCommonModes);<br> <span class="hljs-built_in">CFRelease</span>(observer);<br> });<br>}<br></code></pre></td></tr></table></figure>
<p>然后在回调函数中进行任务的调度。</p>
<h6 id="策略模式"><a href="#策略模式" class="headerlink" title="策略模式"></a>策略模式</h6><p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2355" alt="d4d2cc7e6fc6db790b737872f1549666.png"></p>
<p>具体策略:</p>
<p>栈:后加入的任务先执行(可以理解为后加入的任务优先级高),优先淘汰先加入的任务。</p>
<p>队列:先加入的任务先执行(可以理解为先加入的任务优先级高),优先淘汰后加入的任务。</p>
<p>优先队列:自定义任务优先级,不支持任务淘汰。</p>
<h6 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h6><p>为了实现淘汰策略和优先级调度,作者直接使用了 C++ 的数据结构:deque 和priority_queue。</p>
<p>要实现任务淘汰,所以使用 deque 双端队列来模拟栈和队列,而不是直接使用 stack 和 queue。</p>
<p>使用 priority_queue 优先队列来处理自定义的优先级调度,它的缺点是不能删除低优先级节点,为了节约时间成本姑且够用。</p>
<h6 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h6><p>任务的调度可能在任意线程,所以必须要做好容器(栈、队列、优先队列)访问的线程安全问题,组件是使用 pthread_mutex_t 和 dispatch_once 来保证线程安全,同时笔者尽量减少临界区来提高性能。</p>
<p>值得注意的是,如果不会存在线程安全的代码就不要去加锁了。</p>
<h3 id="亮点"><a href="#亮点" class="headerlink" title="亮点"></a>亮点</h3><h5 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h5><p>使用策略模式区分不同淘汰策略,以及任务优先级设置。</p>
<p>使用命令模式,将任务添加和任务调度隔离。可以自由的决定何时调用这些代码块,何时对某些代码块进行淘汰,还可以实现优先级调度。</p>
<h5 id="Runloop"><a href="#Runloop" class="headerlink" title="Runloop"></a>Runloop</h5><p>监听runloop回调状态</p>
<p>将任务放在runloop空闲状态才下执行,避免任务集中执行,起到延缓作用。</p>
<h5 id="数据结构-1"><a href="#数据结构-1" class="headerlink" title="数据结构"></a>数据结构</h5><p>使用了 C++ 的数据结构:deque 和priority_queue。</p>
<p>使用 deque 双端队列来模拟栈和队列。</p>
<p>使用 priority_queue 优先队列来处理自定义的优先级调度。</p>
]]></content>
<tags>
<tag>源码学习</tag>
</tags>
</entry>
<entry>
<title>YYWeakProxy|NSProxy</title>
<url>/2017/10/15/YYWeakProxy-NSProxy/</url>
<content><![CDATA[<h5 id="YYWeakProxy解决NSTimer循环引用问题:"><a href="#YYWeakProxy解决NSTimer循环引用问题:" class="headerlink" title="YYWeakProxy解决NSTimer循环引用问题:"></a>YYWeakProxy解决NSTimer循环引用问题:</h5><pre><code><figure class="highlight groovy"><table><tr><td class="code"><pre><code class="hljs groovy">YYWeakProxy *weakProxy = [YYWeakProxy <span class="hljs-attr">proxyWithTarget:</span>self];<br>self.timer = [NSTimer <span class="hljs-attr">scheduledTimerWithTimeInterval:</span><span class="hljs-number">1.0</span> <span class="hljs-attr">target:</span>weakProxy <span class="hljs-attr">selector:</span><span class="hljs-meta">@selector</span>(weakProxyTimer) <span class="hljs-attr">userInfo:</span>nil <span class="hljs-attr">repeats:</span>YES];<br></code></pre></td></tr></table></figure>
</code></pre><h5 id="原理:"><a href="#原理:" class="headerlink" title="原理:"></a>原理:</h5><pre><code>生成一个临时对象,让 displayLink 强引用这个临时对象,在这个临时对象中弱引用 self。
self-强->displayLink-强->YYWeakProxy-弱->self,没有形成循环引用
</code></pre><h5 id="核心代码:"><a href="#核心代码:" class="headerlink" title="核心代码:"></a>核心代码:</h5><pre><code><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">weak</span>, <span class="hljs-keyword">readonly</span>) <span class="hljs-keyword">id</span> target;<br>+ (<span class="hljs-keyword">instancetype</span>)proxyWithTarget:(<span class="hljs-keyword">id</span>)target {<br> <span class="hljs-keyword">return</span> [[YYWeakProxy alloc] initWithTarget:target];<br>}<br><span class="hljs-comment">//将消息接收对象改为 target</span><br>- (<span class="hljs-keyword">id</span>)forwardingTargetForSelector:(SEL)selector {<br> <span class="hljs-keyword">return</span> _target;<br>}<br><span class="hljs-comment">//self 对 target 是弱引用,一旦 target 被释放将调用下面两个方法,如果不实现的话会 crash</span><br>- (<span class="hljs-keyword">void</span>)forwardInvocation:(<span class="hljs-built_in">NSInvocation</span> *)invocation {<br> <span class="hljs-keyword">void</span> *null = <span class="hljs-literal">NULL</span>;<br> [invocation setReturnValue:&null];<br>}<br>- (<span class="hljs-built_in">NSMethodSignature</span> *)methodSignatureForSelector:(SEL)selector {<br> <span class="hljs-keyword">return</span> [<span class="hljs-built_in">NSObject</span> instanceMethodSignatureForSelector:<span class="hljs-keyword">@selector</span>(init)];<br>}<br></code></pre></td></tr></table></figure>
</code></pre><h5 id="YYWeakProxy:"><a href="#YYWeakProxy:" class="headerlink" title="YYWeakProxy:"></a>YYWeakProxy:</h5><pre><code>YYWeakProxy 继承自 NSProxy,NSProxy 是 Foundation 中的一个基类,实现了 NSObject 协议。
NSProxy 做为消息转发的抽象代理类,自身能够处理的方法极少(仅 <NSObject> 接口中定义的部分方法,没有 init 方法,子类必须实现 initWithXXX: forwardInvocation: 和 methodSignatureForSelector: 方法), 专门用于消息转发,相比NSObject类省去了消息查找和动态方法解析流程,直接进行消息转发,所以专门做消息转发的话NSProxy效率更快。
</code></pre><h5 id="NSProxy:"><a href="#NSProxy:" class="headerlink" title="NSProxy:"></a>NSProxy:</h5><pre><code>NSProxy是一个虚类,你可以通过继承它,并重写这两个方法以实现消息转发到另一个实例。
<figure class="highlight erlang"><table><tr><td class="code"><pre><code class="hljs erlang">- <span class="hljs-params">(void)</span>forwardInvocation:<span class="hljs-params">(NSInvocation *)</span>anInvocation;<br>- <span class="hljs-params">(NSMethodSignature *)</span>methodSignatureForSelector:<span class="hljs-params">(SEL)</span>sel; <br></code></pre></td></tr></table></figure>
负责将消息转发到真正的target的代理类。
举个例子,你想要卖一件商品,但是你并不想直接跟卖家接触(直接向target发消息),这时你去找了一个第三方,你告诉这个第三方你要买什么、出多少钱买、什么时候要等(向代理发消息),第三方再去跟卖家接触并把这些信息转告卖家(转发消息给真实的target),最后通过第三方去完成这个交易。
</code></pre>]]></content>
<tags>
<tag>源码学习</tag>
</tags>
</entry>
<entry>
<title>brew 安装软件出现SHA256 mismatch问题解决</title>
<url>/2018/09/06/brew-%E5%AE%89%E8%A3%85%E8%BD%AF%E4%BB%B6%E5%87%BA%E7%8E%B0SHA256-mismatch%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/</url>
<content><![CDATA[<h5 id="报错原因:"><a href="#报错原因:" class="headerlink" title="报错原因:"></a>报错原因:</h5><p>就是sha256期望的值不对,需要去对应的位置修改</p>
<h5 id="修改方式:"><a href="#修改方式:" class="headerlink" title="修改方式:"></a>修改方式:</h5><p>brew edit xxx 要根据自己电脑上报错的文件,去对应文件的sha256值位置修改sha256的值</p>
<h5 id="修改sha256的值文件的位置:"><a href="#修改sha256的值文件的位置:" class="headerlink" title="修改sha256的值文件的位置:"></a>修改sha256的值文件的位置:</h5><figure class="highlight awk"><table><tr><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">/opt/</span>homebrew<span class="hljs-regexp">/Library/</span>Taps<span class="hljs-regexp">/homebrew/</span>homebrew-core/Formula<br></code></pre></td></tr></table></figure>
<p>在这个文件夹中找到与报错文件名最相似的文件,打开文件,修改sha256的值即可</p>
]]></content>
<tags>
<tag>tool</tag>
</tags>
</entry>
<entry>
<title>hexo 使用</title>
<url>/2017/02/26/hexo-%E4%BD%BF%E7%94%A8/</url>
<content><![CDATA[<h3 id="主题配置步骤"><a href="#主题配置步骤" class="headerlink" title="主题配置步骤"></a>主题配置步骤</h3><p>1、cd 跳转到博客所在文件夹<br>2、clone 主题<br>如:<br><figure class="highlight awk"><table><tr><td class="code"><pre><code class="hljs awk">git clone https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/theme-next/</span>hexo-theme-<span class="hljs-keyword">next</span> themes/<span class="hljs-keyword">next</span><br></code></pre></td></tr></table></figure><br>3、将博客文件夹目录下_config.yml里theme的名称landscape修改为next,然后执行cd到你的博客文件夹目录下执行如下命令:</p>
<figure class="highlight awk"><table><tr><td class="code"><pre><code class="hljs awk">hexo clean <span class="hljs-regexp">//</span>清除缓存文件 (db.json) 和已生成的静态文件 (public)<br>hexo g <span class="hljs-regexp">//</span>生成缓存和静态文件<br>hexo d <span class="hljs-regexp">//</span>重新部署到服务器<br></code></pre></td></tr></table></figure>
<p>也可以将3个命令合并一起执行</p>
<figure class="highlight sas"><table><tr><td class="code"><pre><code class="hljs sas">hexo clean <span class="hljs-variable">&&</span> hexo g <span class="hljs-variable">&&</span> hexo d<br></code></pre></td></tr></table></figure>
<p>注意:每次修改_config.yml,都要执行上面3个命令,至于其他修改,<a class="link" href="http://theme-next.iissnan.com" >Next使用文档<i class="fas fa-external-link-alt"></i></a>里有极详细的介绍。</p>
<h3 id="新建"><a href="#新建" class="headerlink" title="新建"></a>新建</h3><h5 id="新建文章"><a href="#新建文章" class="headerlink" title="新建文章"></a>新建文章</h5><figure class="highlight haxe"><table><tr><td class="code"><pre><code class="hljs haxe">hexo <span class="hljs-keyword">new</span> <span class="hljs-type">post</span> <span class="hljs-string">"我的第一篇博客"</span> <span class="hljs-comment">//名字可以自己取</span><br></code></pre></td></tr></table></figure>
<h5 id="图片引用"><a href="#图片引用" class="headerlink" title="图片引用"></a>图片引用</h5><figure class="highlight django"><table><tr><td class="code"><pre><code class="hljs django"><span class="hljs-template-tag">{% <span class="hljs-name">asset_img</span> hexoLocal.png hexo初始的样子 %}</span><br></code></pre></td></tr></table></figure>
<p>hexo初始的样子 是对图片的描述</p>
<h6 id="测试预览"><a href="#测试预览" class="headerlink" title="测试预览"></a>测试预览</h6><figure class="highlight nginx"><table><tr><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">hexo</span> s --<span class="hljs-literal">debug</span><br></code></pre></td></tr></table></figure>
<h6 id="发布"><a href="#发布" class="headerlink" title="发布"></a>发布</h6><p>主题更换后的操作一致</p>
]]></content>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>iOS 高效切圆角方法总结</title>
<url>/2017/07/01/iOS%20%E9%AB%98%E6%95%88%E5%88%87%E5%9C%86%E8%A7%92%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93/</url>
<content><![CDATA[<p>[TOC]</p>
<h3 id="UIView设置圆角"><a href="#UIView设置圆角" class="headerlink" title="UIView设置圆角"></a>UIView设置圆角</h3><h5 id="四个圆角"><a href="#四个圆角" class="headerlink" title="四个圆角"></a>四个圆角</h5><p>对于 contents 无内容或者内容的背景透明(无涉及到圆角以外的区域)的layer,直接设置layer的 backgroundColor 和 cornerRadius 属性来绘制圆角:</p>
<ol>
<li>UIView的contents无内容可以直接通过设置cornerRadius达到效果。</li>
<li>UILable的contents也一样,所以也可通过<br>设置cornerRadius达到效果。不过label不能直接设置backgroundColor,因为这样设置的是contents的backgroundColor,需要设置layer. backgroundColor。 </li>
</ol>
<figure class="highlight stata"><table><tr><td class="code"><pre><code class="hljs stata">UIView *<span class="hljs-keyword">view</span> = [[UIView alloc] init];<br><span class="hljs-keyword">view</span>.backgroundColor = [UIColor blackColor];<br><span class="hljs-keyword">view</span>.layer.cornerRadius = 3.f;<br><span class="hljs-comment">// 以下两行,任写一行</span><br><span class="hljs-keyword">view</span>.layer.masksToBounds = <span class="hljs-keyword">NO</span>;<br><span class="hljs-keyword">view</span>.clipToBounds = <span class="hljs-keyword">NO</span>;<br><span class="hljs-comment">// 以下两行,千万不要加!</span><br><span class="hljs-keyword">view</span>.layer.masksToBounds = YES;<br><span class="hljs-keyword">view</span>.clipToBounds = YES;<br></code></pre></td></tr></table></figure>
<p>注意点:UIView 只要设置图层的 cornerRadius 属性即可(不明白的话,可以看看官方文档里对 cornerRadius 的描述),如果设置 layer.masksToBounds = YES,会造成不必要的离屏渲染。 </p>
<h5 id="单独某个方向的圆角-特殊情况需要设置layer-masksToBounds,就不要通过cornerRadius方式了"><a href="#单独某个方向的圆角-特殊情况需要设置layer-masksToBounds,就不要通过cornerRadius方式了" class="headerlink" title="单独某个方向的圆角/特殊情况需要设置layer.masksToBounds,就不要通过cornerRadius方式了"></a>单独某个方向的圆角/特殊情况需要设置layer.masksToBounds,就不要通过cornerRadius方式了</h5><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-class"><span class="hljs-keyword">@implementation</span> <span class="hljs-title">UIView</span> (<span class="hljs-title">RounderCorner</span>)</span><br><br>- (<span class="hljs-keyword">void</span>)hh_addRounderCornerWithRadius:(<span class="hljs-built_in">CGFloat</span>)radius size:(<span class="hljs-built_in">CGSize</span>)size corners:(<span class="hljs-built_in">UIRectCorner</span>)corner<br>{<br><br> <span class="hljs-comment">//绘制一个圆角图片</span><br> <span class="hljs-built_in">UIImage</span> *image = [<span class="hljs-built_in">UIImage</span> hh_corver:.....];<br> <br> <span class="hljs-built_in">UIImageView</span> *imageView = [[<span class="hljs-built_in">UIImageView</span> alloc] initWithFrame:<span class="hljs-built_in">CGRectMake</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, size.width, size.height)];<br> [imageView setImage:image];<br> [<span class="hljs-keyword">self</span> insertSubview:imageView atIndex:<span class="hljs-number">0</span>];<br>}<br></code></pre></td></tr></table></figure>
<h3 id="文本类视图"><a href="#文本类视图" class="headerlink" title="文本类视图"></a>文本类视图</h3><h5 id="UITextField"><a href="#UITextField" class="headerlink" title="UITextField"></a>UITextField</h5><p>UITextField有两种实现方法</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-comment">// 天然支持设置圆角边框</span><br><span class="hljs-built_in">UITextField</span> *textField = [[<span class="hljs-built_in">UITextField</span> alloc] init];<br>textField.borderStyle = <span class="hljs-built_in">UITextBorderStyleRoundedRect</span>;<br></code></pre></td></tr></table></figure>
<figure class="highlight mel"><table><tr><td class="code"><pre><code class="hljs mel"><span class="hljs-comment">// 与 UIView 类似</span><br>UITextField *<span class="hljs-keyword">textField</span> = [[UITextField alloc] init];<br><span class="hljs-keyword">textField</span>.layer.cornerRadius = cornerRadius;<br></code></pre></td></tr></table></figure>
<h5 id="UITextView"><a href="#UITextView" class="headerlink" title="UITextView"></a>UITextView</h5><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-comment">// 与 UIView 类似</span><br><span class="hljs-built_in">UITextView</span> *textView = [[<span class="hljs-built_in">UITextView</span> alloc] init];<br>textView.layer.cornerRadius = cornerRadius;<br></code></pre></td></tr></table></figure>
<h5 id="UILabel"><a href="#UILabel" class="headerlink" title="UILabel"></a>UILabel</h5><figure class="highlight delphi"><table><tr><td class="code"><pre><code class="hljs delphi">UILabel *<span class="hljs-keyword">label</span> = [[UILabel alloc] init];<br><span class="hljs-comment">// 重点在此!!设置视图的图层背景色,千万不要直接设置 label.backgroundColor</span><br><span class="hljs-keyword">label</span>.layer.backgroundColor = [UIColor grayColor].CGColor;<br><span class="hljs-keyword">label</span>.layer.cornerRadius = cornerRadius;<br></code></pre></td></tr></table></figure>
<h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><h5 id="UIButton"><a href="#UIButton" class="headerlink" title="UIButton"></a>UIButton</h5><p>说明:UIButton 的背景图片,如果是复杂的图片,可以依靠 UI 切图来实现。如果是简单的纯色背景图片,可以利用代码绘制带圆角的图片。</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-built_in">UIButton</span> *button = [<span class="hljs-built_in">UIButton</span> buttonWithType:<span class="hljs-built_in">UIButtonTypeCustom</span>];<br><span class="hljs-comment">// 设置 UIButton 的背景图片。</span><br>[button setBackgroundImage:image forState:<span class="hljs-built_in">UIControlStateNormal</span>];<br></code></pre></td></tr></table></figure>
<p>背景图片绘制方法</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">+ (<span class="hljs-built_in">UIImage</span> *)pureColorImageWithSize:(<span class="hljs-built_in">CGSize</span>)size color:(<span class="hljs-built_in">UIColor</span> *)color cornRadius:(<span class="hljs-built_in">CGFloat</span>)cornRadius {<br> <span class="hljs-built_in">UIView</span> *view = [[<span class="hljs-built_in">UIView</span> alloc] initWithFrame:<span class="hljs-built_in">CGRectMake</span>(<span class="hljs-number">0.0</span>f, <span class="hljs-number">0.0</span>f, size.width, size.height)];<br> view.backgroundColor = color;<br> view.layer.cornerRadius = cornerRadius;<br> <span class="hljs-comment">// 下面方法,第一个参数表示区域大小。第二个参数表示是否是非透明的。如果需要显示半透明效果,需要传NO,否则传YES。第三个参数是屏幕密度</span><br> <span class="hljs-built_in">UIGraphicsBeginImageContextWithOptions</span>(view.bounds.size, <span class="hljs-literal">NO</span>, [<span class="hljs-built_in">UIScreen</span> mainScreen].scale);<br> [view.layer renderInContext:<span class="hljs-built_in">UIGraphicsGetCurrentContext</span>()];<br> <span class="hljs-built_in">UIImage</span> *image = <span class="hljs-built_in">UIGraphicsGetImageFromCurrentImageContext</span>();<br> <span class="hljs-built_in">UIGraphicsEndImageContext</span>();<br><br> <span class="hljs-keyword">return</span> image;<br>}<br></code></pre></td></tr></table></figure>
<h5 id="UIImageView"><a href="#UIImageView" class="headerlink" title="UIImageView"></a>UIImageView</h5><p>UIImageView 有四种方式实现圆角:</p>
<p>1、截取图片方式(性能较好,基本不掉帧)<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">+ (<span class="hljs-built_in">UIImage</span> *)dx_imageByRoundCornerRadius:(<span class="hljs-built_in">CGFloat</span>)radius<br> corners:(<span class="hljs-built_in">UIRectCorner</span>)corners<br> size:(<span class="hljs-built_in">CGSize</span>)size<br> fillColor:(<span class="hljs-built_in">UIColor</span> *)fillColor<br>{<br> <span class="hljs-keyword">if</span> (corners != <span class="hljs-built_in">UIRectCornerAllCorners</span>) {<br> <span class="hljs-built_in">UIRectCorner</span> tmp = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">if</span> (corners & <span class="hljs-built_in">UIRectCornerTopLeft</span>) tmp |= <span class="hljs-built_in">UIRectCornerBottomLeft</span>;<br> <span class="hljs-keyword">if</span> (corners & <span class="hljs-built_in">UIRectCornerTopRight</span>) tmp |= <span class="hljs-built_in">UIRectCornerBottomRight</span>;<br> <span class="hljs-keyword">if</span> (corners & <span class="hljs-built_in">UIRectCornerBottomLeft</span>) tmp |= <span class="hljs-built_in">UIRectCornerTopLeft</span>;<br> <span class="hljs-keyword">if</span> (corners & <span class="hljs-built_in">UIRectCornerBottomRight</span>) tmp |= <span class="hljs-built_in">UIRectCornerTopRight</span>;<br> corners = tmp;<br> }<br><br> <span class="hljs-built_in">UIGraphicsBeginImageContextWithOptions</span>(size, <span class="hljs-literal">NO</span>, [<span class="hljs-built_in">UIScreen</span> mainScreen].scale);<br> <span class="hljs-built_in">CGContextRef</span> context = <span class="hljs-built_in">UIGraphicsGetCurrentContext</span>();<br> <span class="hljs-built_in">CGRect</span> rect = <span class="hljs-built_in">CGRectMake</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, size.width, size.height);<br> <span class="hljs-built_in">CGContextScaleCTM</span>(context, <span class="hljs-number">1</span>, <span class="hljs-number">-1</span>);<br> <span class="hljs-built_in">CGContextTranslateCTM</span>(context, <span class="hljs-number">0</span>, -rect.size.height);<br> <br> <span class="hljs-built_in">UIBezierPath</span> *path = [<span class="hljs-built_in">UIBezierPath</span> bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:<span class="hljs-built_in">CGSizeMake</span>(radius, radius)];<br> [fillColor set];<br> [path fill]; <br> <br> [path addClip];<br> <span class="hljs-built_in">CGContextDrawPath</span>(context, kCGPathFill);<br> <br> <span class="hljs-built_in">UIImage</span> *image = <span class="hljs-built_in">UIGraphicsGetImageFromCurrentImageContext</span>();<br> <span class="hljs-built_in">UIGraphicsEndImageContext</span>();<br> <span class="hljs-keyword">return</span> image;<br>}<br><br></code></pre></td></tr></table></figure></p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">- (<span class="hljs-keyword">void</span>)tx_addCornerWithRadius:(<span class="hljs-built_in">CGFloat</span>)radius corner:(<span class="hljs-built_in">UIRectCorner</span>)corner {<br> <span class="hljs-keyword">self</span>.image = [<span class="hljs-keyword">self</span>.image dx_imageAddCornerWithRadius:radius andSize:<span class="hljs-keyword">self</span>.bounds.size];<br>}<br><br></code></pre></td></tr></table></figure>
<p>实际开发中,网络图片最好截图之后存入缓存,下次直接使用,不用再裁减。YYWebImageView的设计方案即如此。<br><a class="link" href="https://github.com/ibireme/YYWebImage" >YYWebImage<i class="fas fa-external-link-alt"></i></a><br><a class="link" href="https://github.com/walkdianzi/DSImageViewRound/tree/master" >fork SDWebImage库修改实现<i class="fas fa-external-link-alt"></i></a></p>
<p>2、贝塞尔曲线切割圆角(不推荐,掉帧严重)</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">- (<span class="hljs-built_in">UIImageView</span> *)roundedRectImageViewWithCornerRadius:(<span class="hljs-built_in">CGFloat</span>)cornerRadius {<br> <span class="hljs-built_in">UIBezierPath</span> *bezierPath = [<span class="hljs-built_in">UIBezierPath</span> bezierPathWithRoundedRect:<span class="hljs-keyword">self</span>.bounds cornerRadius:cornerRadius];<br> <span class="hljs-built_in">CAShapeLayer</span> *layer = [<span class="hljs-built_in">CAShapeLayer</span> layer];<br> layer.path = bezierPath.CGPath;<br> <span class="hljs-keyword">self</span>.layer.mask = layer;<br><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>;<br>}<br></code></pre></td></tr></table></figure>
<p>3、绘制四个角的遮罩(使用场景受限)</p>
<p>在 UIImageView 上添加一个四个角有内容,其它部分是透明的视图,只对 UIImageView 圆角部分进行遮挡。但要保证被遮挡的部分背景色要与周围背景相同,避免穿帮。所以当 UIImageView 处于一个复杂的背景时,是不适合使用这个方法的。</p>
<p>4、最不推荐做法(当一个页面只有少量圆角图片时才推荐使用)</p>
<figure class="highlight abnf"><table><tr><td class="code"><pre><code class="hljs abnf">UIImageView *imageView = [[UIImageView alloc] init]<span class="hljs-comment">;</span><br>imageView.layer.cornerRadius = <span class="hljs-number">5</span>.f<span class="hljs-comment">;</span><br>imageView.layer.masksToBounds = YES<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure>
<h3 id="扩展:其他会导致离屏渲染的解决方案"><a href="#扩展:其他会导致离屏渲染的解决方案" class="headerlink" title="扩展:其他会导致离屏渲染的解决方案"></a>扩展:其他会导致离屏渲染的解决方案</h3><p>以下离屏渲染操作,按对性能影响等级从高到低进行排序:</p>
<h5 id="1-shadows(阴影)"><a href="#1-shadows(阴影)" class="headerlink" title="1. shadows(阴影)"></a>1. shadows(阴影)</h5><p>方案:在设置完layer的shadow属性之后,设置layer.shadowPath = [UIBezierPath pathWithCGRect:view.bounds].CGPath;</p>
<h5 id="2-圆角(前边已解决过)"><a href="#2-圆角(前边已解决过)" class="headerlink" title="2.圆角(前边已解决过)"></a>2.圆角(前边已解决过)</h5><h5 id="3-mask遮罩"><a href="#3-mask遮罩" class="headerlink" title="3.mask遮罩"></a>3.mask遮罩</h5><p>方案:不用mask</p>
<h5 id="4-allowsGroupOpacity(组不透明)"><a href="#4-allowsGroupOpacity(组不透明)" class="headerlink" title="4. allowsGroupOpacity(组不透明)"></a>4. allowsGroupOpacity(组不透明)</h5><p>开启CALayer的 allowsGroupOpacity 属性后,子 layer 在视觉上的透明度的上限是其父 layer 的 opacity (对应UIView的 alpha ),并且从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。<br>方案:关闭 allowsGroupOpacity 属性,按产品需求自己控制layer透明度。 </p>
<h5 id="5-edge-antialiasing(抗锯齿)"><a href="#5-edge-antialiasing(抗锯齿)" class="headerlink" title="5. edge antialiasing(抗锯齿)"></a>5. edge antialiasing(抗锯齿)</h5><p>方案:不设置 allowsEdgeAntialiasing 属性为YES(默认为NO)</p>
<h5 id="6-shouldRasterize-光栅化"><a href="#6-shouldRasterize-光栅化" class="headerlink" title="6. shouldRasterize(光栅化)"></a>6. shouldRasterize(光栅化)</h5><p>当视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES,此方案最为实用方便。</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">view.layer.shouldRasterize</span> = <span class="hljs-literal">true</span><span class="hljs-comment">;</span><br><span class="hljs-attr">view.layer.rasterizationScale</span> = view.layer.contentsScale<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure>
<p>但当视图内容是动态变化(如后台下载图片完毕后切换到主线程设置)时,使用此方案反而为增加系统负荷。</p>
<h5 id="7-Core-Graphics-API-核心绘图"><a href="#7-Core-Graphics-API-核心绘图" class="headerlink" title="7.Core Graphics API(核心绘图)"></a>7.Core Graphics API(核心绘图)</h5><p>Core Graphics API(核心绘图)的绘制操作会导致CPU的离屏渲染。<br>方案:放到后台线程中进行。</p>
<h3 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h3><p><a class="link" href="https://github.com/Baichenghui/Study/tree/master/CornerDemo" >demo<i class="fas fa-external-link-alt"></i></a></p>
<h3 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h3><p><a class="link" href="https://www.jianshu.com/p/f8a3400836b5" >https://www.jianshu.com/p/f8a3400836b5<i class="fas fa-external-link-alt"></i></a><br><a class="link" href="https://www.jianshu.com/p/e879aeff93f3" >https://www.jianshu.com/p/e879aeff93f3<i class="fas fa-external-link-alt"></i></a><br><a class="link" href="https://www.jianshu.com/p/b9bef82eace1" >https://www.jianshu.com/p/b9bef82eace1<i class="fas fa-external-link-alt"></i></a><br><a class="link" href="https://www.jianshu.com/p/3141c1177d35" >https://www.jianshu.com/p/3141c1177d35<i class="fas fa-external-link-alt"></i></a></p>
]]></content>
<tags>
<tag>UI</tag>
</tags>
</entry>
<entry>
<title>iOS离屏渲染研究</title>
<url>/2018/04/01/iOS%E7%A6%BB%E5%B1%8F%E6%B8%B2%E6%9F%93%E7%A0%94%E7%A9%B6/</url>
<content><![CDATA[<p>[TOC]</p>
<h3 id="离屏渲染"><a href="#离屏渲染" class="headerlink" title="离屏渲染"></a>离屏渲染</h3><h6 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h6><p>如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域,而这也是GPU存储渲染结果的地方。如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。</p>
<p>简单说就是:在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。</p>
<h6 id="离屏渲染消耗性能的原因"><a href="#离屏渲染消耗性能的原因" class="headerlink" title="离屏渲染消耗性能的原因"></a>离屏渲染消耗性能的原因</h6><ol>
<li>需要创建新的缓冲区</li>
<li>离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕</li>
</ol>
<h3 id="离屏渲染是在哪一步进行的?为什么?"><a href="#离屏渲染是在哪一步进行的?为什么?" class="headerlink" title="离屏渲染是在哪一步进行的?为什么?"></a>离屏渲染是在哪一步进行的?为什么?</h3><p>1、CPU”离屏渲染“</p>
<p>在UIView中实现了drawRect方法,就算它的函数体内部实际没有代码,系统也会为这个view申请一块内存区域,等待CoreGraphics可能的绘画操作。</p>
<p>对于类似这种“新开一块CGContext来画图“的操作,有很多文章和视频也称之为“离屏渲染”(因为像素数据是暂时存入了CGContext,而不是直接到了frame buffer)。进一步来说,其实所有CPU进行的光栅化操作(如文字渲染、图片解码),都无法直接绘制到由GPU掌管的frame buffer,只能暂时先放在另一块内存之中,说起来都属于“离屏渲染”。</p>
<p>根据苹果工程师的说法CPU渲染就是俗称的“软件渲染”,而真正的离屏渲染发生在GPU。</p>
<p>2、GPU离屏渲染</p>
<p>对于每一层layer,要么能找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂的、多次的修改/剪裁操作,这时就离屏渲染了。</p>
<h3 id="设置cornerRadius一定会触发离屏渲染吗?"><a href="#设置cornerRadius一定会触发离屏渲染吗?" class="headerlink" title="设置cornerRadius一定会触发离屏渲染吗?"></a>设置cornerRadius一定会触发离屏渲染吗?</h3><p>不会</p>
<h3 id="常见离屏渲染场景"><a href="#常见离屏渲染场景" class="headerlink" title="常见离屏渲染场景"></a>常见离屏渲染场景</h3><h6 id="1、-cornerRadius-clipsToBounds"><a href="#1、-cornerRadius-clipsToBounds" class="headerlink" title="1、 cornerRadius+clipsToBounds"></a>1、 cornerRadius+clipsToBounds</h6><p>1、将一个layer的内容裁剪成圆角,可能不存在一次遍历就能完成的方法<br>2、容器的子layer因为父容器有圆角,那么也会需要被裁剪,而这时它们还在渲染队列中排队,尚未被组合到一块画布上,自然也无法统一裁剪</p>
<p>此时我们就不得不开辟一块独立于frame buffer的空白内存,先把容器以及其所有子layer依次画好,然后把四个角“剪”成圆形,再把结果画到frame buffer中。这就造成了GPU的离屏渲染。</p>
<h6 id="2、shadow"><a href="#2、shadow" class="headerlink" title="2、shadow"></a>2、shadow</h6><p>原因在于,虽然layer本身是一块矩形区域,但是阴影默认是作用在其中”非透明区域“的,而且需要显示在所有layer内容的下方,因此根据画家算法必须被渲染在先。但矛盾在于此时阴影的本体(layer和其子layer)都还没有被组合到一起,怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?这样一来又只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到frame buffer,最后把内容画上去(这只是我的猜测,实际情况可能更复杂)。</p>
<p>不过如果我们能够预先告诉CoreAnimation(通过shadowPath属性)阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。</p>
<h6 id="3、group-opacity"><a href="#3、group-opacity" class="headerlink" title="3、group opacity"></a>3、group opacity</h6><p>alpha并不是分别应用在每一层之上,而是只有到整个layer树画完之后,再统一加上alpha,最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。将一对蓝色和红色layer叠在一起,然后在父layer上设置opacity=0.5,并复制一份在旁边作对比。左边关闭group opacity,右边保持默认(从iOS7开始,如果没有显式指定,group opacity会默认打开),然后打开offscreen rendering的调试,我们会发现右边的那一组确实是离屏渲染了。</p>
<h6 id="4、mask"><a href="#4、mask" class="headerlink" title="4、mask"></a>4、mask</h6><p>我们知道mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity的原理类似,不得不在离屏渲染中完成。</p>
<h6 id="5、UIBlurEffect"><a href="#5、UIBlurEffect" class="headerlink" title="5、UIBlurEffect"></a>5、UIBlurEffect</h6><p>同样无法通过一次遍历完成,其原理在WWDC中提到。</p>
<h6 id="6、shouldRasterize。一旦被设置为true"><a href="#6、shouldRasterize。一旦被设置为true" class="headerlink" title="6、shouldRasterize。一旦被设置为true"></a>6、shouldRasterize。一旦被设置为true</h6><p>Render Server就会强制把layer的渲染结果(包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。</p>
<p>注意:</p>
<ol>
<li>shouldRasterize的主旨在于降低性能损失,但总是至少会触发一次离屏渲染。</li>
<li>离屏渲染缓存有空间上限,最多不超过屏幕总像素的2.5倍大小</li>
<li>一旦缓存超过100ms没有被使用,会自动被丢弃</li>
<li>layer的内容(包括子layer)必须是静态的,因为一旦发生变化(如resize,动画),之前辛苦处理得到的缓存就失效了。</li>
<li>其实除了解决多次离屏渲染的开销,shouldRasterize在另一个场景中也可以使用:如果layer的子结构非常复杂,渲染一次所需时间较长,同样可以打开这个开关,把layer绘制到一块缓存,然后在接下来复用这个结果,这样就不需要每次都重新绘制整个layer树了</li>
</ol>
<h6 id="7、其他"><a href="#7、其他" class="headerlink" title="7、其他"></a>7、其他</h6><p>类似allowsEdgeAntialiasing等等也可能会触发离屏渲染,</p>
<p>原理也都是类似:如果你无法仅仅使用frame buffer来画出最终结果,那就只能另开一块内存空间来储存中间结果。这些原理并不神秘。</p>
<h3 id="什么时候需要CPU渲染"><a href="#什么时候需要CPU渲染" class="headerlink" title="什么时候需要CPU渲染"></a>什么时候需要CPU渲染</h3><p>渲染性能的调优,其实始终是在做一件事:平衡CPU和GPU的负载,让他们尽量做各自最擅长的工作。</p>
<p>绝大多数情况下,得益于GPU针对图形处理的优化,我们都会倾向于让GPU来完成渲染任务,而给CPU留出足够时间处理各种各样复杂的App逻辑。</p>
<p>为此Core Animation做了大量的工作,尽量把渲染工作转换成适合GPU处理的形式(也就是所谓的硬件加速,如layer composition,设置backgroundColor等等)。</p>
<p>但是对于一些情况,如文字(CoreText使用CoreGraphics渲染)和图片(ImageIO)渲染,由于GPU并不擅长做这些工作,不得不先由CPU来处理好以后,再把结果作为texture传给GPU。</p>
<p>一个典型的例子是,我们经常会使用CoreGraphics给图片加上圆角(将图片中圆角以外的部分渲染成透明)。整个过程全部是由CPU完成的。这样一来既然我们已经得到了想要的效果,就不需要再另外给图片容器设置cornerRadius。另一个好处是,我们可以灵活地控制裁剪和缓存的时机,巧妙避开CPU和GPU最繁忙的时段,达到平滑性能波动的目的。</p>
<h3 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h3><p><a class="link" href="https://mp.weixin.qq.com/s?__biz=MzA5NzMwODI0MA==&mid=2647764329&idx=1&sn=7e13a167b7d4df4d071928248d9531b7&chksm=8887d656bff05f40a41e614493103e4ddaecbd7caaf81b6db4019684ffffefd8bdbe1d068179&mpshare=1&scene=23&srcid=1211WQgUISoJ055okBhVGJaJ&sharer_sharetime=1576054080266&sharer_shareid=fc8016a1ffcf846e48e1c7a21f07026a%23rd" >iOS离屏渲染研究<i class="fas fa-external-link-alt"></i></a></p>
]]></content>
<tags>
<tag>离屏渲染</tag>
</tags>
</entry>
<entry>
<title>mac 相关终端命令</title>
<url>/2018/01/13/mac%20%E7%9B%B8%E5%85%B3%E7%BB%88%E7%AB%AF%E5%91%BD%E4%BB%A4/</url>
<content><![CDATA[<p>电池健康程度检测终端命令:</p>
<figure class="highlight 1c"><table><tr><td class="code"><pre><code class="hljs 1c">ioreg -rn AppleSmartBattery <span class="hljs-string">| grep -i capacity</span><br></code></pre></td></tr></table></figure>
<p> 查看</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-variable">$SHELL</span><br></code></pre></td></tr></table></figure>
<p> 切换 bash<br> 要在macOS上更改用户帐户的默认外壳,只需chsh -s在“终端”窗口中运行(更改外壳)命令。<br> 通过运行以下命令将默认Shell更改为Bash:<br> <figure class="highlight awk"><table><tr><td class="code"><pre><code class="hljs awk">chsh -s <span class="hljs-regexp">/ bin /</span> bash<br></code></pre></td></tr></table></figure></p>
<p> 切换 zsh<br> 通过运行以下命令,将默认shell更改回Zsh:<br> <figure class="highlight awk"><table><tr><td class="code"><pre><code class="hljs awk">chsh -s <span class="hljs-regexp">/ bin /</span> zsh<br></code></pre></td></tr></table></figure><br> 出现提示时输入密码。关闭终端窗口并重新打开它之后,您将使用Zsh。</p>
<p>终端前缀修改</p>
<figure class="highlight vim"><table><tr><td class="code"><pre><code class="hljs vim">sudo <span class="hljs-built_in">hostname</span> XxdeMacBookPro15 --自己想要的名字<br>sudo scutil --<span class="hljs-keyword">set</span> LocalHostName $(<span class="hljs-built_in">hostname</span>) <br>sudo scutil --<span class="hljs-keyword">set</span> HostName $(<span class="hljs-built_in">hostname</span>)<br></code></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>command</tag>
</tags>
</entry>
<entry>
<title>socket 研究</title>
<url>/2020/06/08/socket-%E7%A0%94%E7%A9%B6/</url>
<content><![CDATA[<h5 id="什么是socket"><a href="#什么是socket" class="headerlink" title="什么是socket?"></a>什么是socket?</h5><p>网络上两个程序通过一个双向通信连接实现数据交互,这种双向通信的连接叫做Socket.</p>
<p>本质上,Socket 是一组对TCP/UDP协议封装的api接口,处于应用层与传输层之间.</p>
<h5 id="连接过程"><a href="#连接过程" class="headerlink" title="连接过程"></a>连接过程</h5><p>建立Socket连接至少需要一对套接字,分别运行于服务端和客户端。套接字直接的连接需要三个步骤:</p>
<p>1、服务器监听</p>
<p>服务端Socket始终处于等待连接状态,实时监听是否有客户端请求连接。</p>
<p>2、客户端请求</p>
<p>客户端Socket提出连接请求,指定服务端Socket的ip地址和端口号,这时就可以向对应的服务端提出Socket连接请求。</p>
<p>3、连接确认</p>
<p>当服务端Socket监听到客户端Socket提出的连接请求时作出响应,建立一个新的进程,把服务端Socket的描述发送给客户端,该描述得到客户端确认后就可建立起Socket连接。而服务端Socket则继续处于监听状态,继续接收其他客户端Socket的请求。 </p>
<h5 id="CocoaAsyncSocket"><a href="#CocoaAsyncSocket" class="headerlink" title="CocoaAsyncSocket"></a>CocoaAsyncSocket</h5><p>iOS 开发中socket有一个第三方库<code>CocoaAsyncSocket</code>很好用.</p>
<p>使用的时候传递host和端口即可进行连接,</p>
<figure class="highlight groovy"><table><tr><td class="code"><pre><code class="hljs groovy">- (<span class="hljs-keyword">void</span>)<span class="hljs-attr">connectToServerWithCommand:</span>(NSString *)command<br>{<br> _socket = [[GCDAsyncSocket alloc] <span class="hljs-attr">initWithDelegate:</span>self <span class="hljs-attr">delegateQueue:</span>dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="hljs-number">0</span>)];<br> [_socket <span class="hljs-attr">setUserData:</span>command];<br> <br> NSError *error = nil;<br> [_socket <span class="hljs-attr">connectToHost:</span>host <span class="hljs-attr">onPort:</span>端口号 <span class="hljs-attr">error:</span>&error];<br> <span class="hljs-keyword">if</span> (error) {<br> NSLog(@<span class="hljs-string">"连接 error:%@"</span>,error.userInfo);<br> }<br> <br> [_socket <span class="hljs-attr">writeData:</span>[command <span class="hljs-attr">dataUsingEncoding:</span>NSUTF8StringEncoding] <span class="hljs-attr">withTimeout:</span><span class="hljs-number">10.0</span>f <span class="hljs-attr">tag:</span><span class="hljs-number">6</span>];<br>}<br></code></pre></td></tr></table></figure>
<p>然后实现代理</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> mark - Socket Delegate</span><br><br>- (<span class="hljs-keyword">void</span>)socket:(GCDAsyncSocket *)sock didConnectToHost:(<span class="hljs-built_in">NSString</span> *)host port:(uint16_t)port<br>{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"Socket连接成功:%s"</span>,__func__);<br>}<br><br>-(<span class="hljs-keyword">void</span>)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(<span class="hljs-built_in">NSError</span> *)err{<br> <span class="hljs-keyword">if</span> (err) {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"连接失败"</span>);<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"正常断开"</span>);<br> } <br>}<br><br>-(<span class="hljs-keyword">void</span>)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(<span class="hljs-keyword">long</span>)tag {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"数据发送成功:%s"</span>,__func__);<br> <span class="hljs-comment">//发送完数据手动读取,-1不设置超时</span><br> [sock readDataWithTimeout:<span class="hljs-number">-1</span> tag:tag];<br>}<br><br>-(<span class="hljs-keyword">void</span>)socket:(GCDAsyncSocket *)sock didReadData:(<span class="hljs-built_in">NSData</span> *)data withTag:(<span class="hljs-keyword">long</span>)tag {<br> <span class="hljs-built_in">NSString</span> *receiverStr = [[<span class="hljs-built_in">NSString</span> alloc] initWithData:data encoding:<span class="hljs-built_in">NSUTF8StringEncoding</span>];<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"读取数据:%s %@"</span>,__func__,receiverStr);<br>}<br></code></pre></td></tr></table></figure>
<h5 id="TCP粘包、半包"><a href="#TCP粘包、半包" class="headerlink" title="TCP粘包、半包"></a>TCP粘包、半包</h5><h6 id="什么是粘包?"><a href="#什么是粘包?" class="headerlink" title="什么是粘包?"></a>什么是粘包?</h6><p>由于TCP默认会使用优化方法(Nagle算法)。<br>将多次间隔较小且数据量小的数据,合并成一个大的数据块,进行封包,然后依次发送。<br>这么做优点就是为了减少广域网的小分组数目,可以减小网络拥塞的出现,同时也可以节省宽带。</p>
<h6 id="什么是半包?"><a href="#什么是半包?" class="headerlink" title="什么是半包?"></a>什么是半包?</h6><p>当发送一条很大的数据包,类似音视频,大图等,一次发送或者读取数据的缓冲区大小是有限的,所以会分段去发送或者读取数据。当多个包发送的时候,有些包可能就发送失败了,接收方接收数据不全就出现了半包的问题。</p>
<h6 id="怎么处理?"><a href="#怎么处理?" class="headerlink" title="怎么处理?"></a>怎么处理?</h6><p>如果我们要正确解析数据,那么必须要使用一种合理的机制去解包。这个机制的思路其实很简单:</p>
<p>在设计数据结构时记录包长和包的起始位置,一般就是每个包都设计一个包头和包体,包头包含包长等信息,包体就是具体传递的数据。</p>
<h6 id="基于-CocoaAsyncSocket-处理"><a href="#基于-CocoaAsyncSocket-处理" class="headerlink" title="基于 CocoaAsyncSocket 处理"></a>基于 CocoaAsyncSocket 处理</h6><p>首先需要了解几个方法:</p>
<figure class="highlight groovy"><table><tr><td class="code"><pre><code class="hljs groovy"><span class="hljs-comment">//读取数据,有数据就会触发代理</span><br>- (<span class="hljs-keyword">void</span>)<span class="hljs-attr">readDataWithTimeout:</span>(NSTimeInterval)timeout <span class="hljs-attr">tag:</span>(<span class="hljs-keyword">long</span>)tag;<br><span class="hljs-comment">//直到读到这个长度的数据,才会触发代理</span><br>- (<span class="hljs-keyword">void</span>)<span class="hljs-attr">readDataToLength:</span>(NSUInteger)length <span class="hljs-attr">withTimeout:</span>(NSTimeInterval)timeout <span class="hljs-attr">tag:</span>(<span class="hljs-keyword">long</span>)tag;<br><span class="hljs-comment">//直到读到data这个边界,才会触发代理</span><br>- (<span class="hljs-keyword">void</span>)<span class="hljs-attr">readDataToData:</span>(NSData *)data <span class="hljs-attr">withTimeout:</span>(NSTimeInterval)timeout <span class="hljs-attr">tag:</span>(<span class="hljs-keyword">long</span>)tag;<br></code></pre></td></tr></table></figure>
<p>包读取:</p>
<figure class="highlight groovy"><table><tr><td class="code"><pre><code class="hljs groovy"><br><span class="hljs-comment">// socket连接成功</span><br>- (<span class="hljs-keyword">void</span>)<span class="hljs-attr">socket:</span>(GCDAsyncSocket *)sock <span class="hljs-attr">didConnectToHost:</span>(NSString *)host <span class="hljs-attr">port:</span>(uint16_t)port {<br> <span class="hljs-comment">// 非相关代码移除</span><br> ...<br> <br> <span class="hljs-comment">//连接成功就去读取包头的数据,并设置tag,方便解析时使用</span><br> [_socket <span class="hljs-attr">readDataToLength:</span>LENGTH_HEAD <span class="hljs-attr">withTimeout:</span><span class="hljs-number">-1</span> <span class="hljs-attr">tag:</span>TAG_HEAD];<br>}<br><br><br><span class="hljs-comment">// socket读取成功</span><br>- (<span class="hljs-keyword">void</span>)<span class="hljs-attr">socket:</span>(GCDAsyncSocket *)sock <span class="hljs-attr">didReadData:</span>(NSData *)data <span class="hljs-attr">withTag:</span>(<span class="hljs-keyword">long</span>)tag {<br> <span class="hljs-keyword">if</span> (tag == TAG_HEAD) {<span class="hljs-comment">//包头的数据</span><br> <span class="hljs-comment">//保存包头数据,等包体数据接收到时合并</span><br> self.dataHead = data;<br> <br> <span class="hljs-comment">//从包中读取包长度信息</span><br> UInt16 lengthTotal;<br> [data <span class="hljs-attr">getBytes:</span>&lengthTotal <span class="hljs-attr">range:</span>RANGE_PACKET_LENGTH];<br> lengthTotal = CFSwapInt16BigToHost(lengthTotal);<br> <br> <span class="hljs-comment">//包体长度=总长度-包头长度</span><br> UInt16 lengthBody = lengthTotal - LENGTH_HEAD;<br><br> <span class="hljs-comment">//读取指定长度的包体数据</span><br> [sock <span class="hljs-attr">readDataToLength:</span>lengthBody <span class="hljs-attr">withTimeout:</span><span class="hljs-number">-1</span> <span class="hljs-attr">tag:</span>TAG_BODY];<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (tag == TAG_READ_BODY) {<span class="hljs-comment">//包体的数据</span><br> <br> <span class="hljs-comment">//整个包数据=包头+包体</span><br> NSMutableData *mPacketData = [NSMutableData <span class="hljs-attr">dataWithData:</span>self.dataHead];<br> [mPacketData <span class="hljs-attr">appendData:</span>data];<br> <br> <span class="hljs-comment">//清空包体变量</span><br> self.dataHead = nil;<br> <br> <span class="hljs-comment">//解析整个包</span><br> SocketResponse *response = [SocketResponse <span class="hljs-attr">responseFromData:</span>[mPacketData copy]]<br><br> <span class="hljs-comment">//再次读取包头数据,以便后续包的读取</span><br> [sock <span class="hljs-attr">readDataToLength:</span>LENGTH_HEAD <span class="hljs-attr">withTimeout:</span><span class="hljs-number">-1</span> <span class="hljs-attr">tag:</span>TAG_HEAD];<br> } <span class="hljs-keyword">else</span> {<br> NSLog(@<span class="hljs-string">"Socket: DidReadData: withTag: 没有相应的 TAG"</span>);<br> }<br>}<br></code></pre></td></tr></table></figure>
<p>解析:</p>
<p>SocketResponse.m<br><figure class="highlight groovy"><table><tr><td class="code"><pre><code class="hljs groovy">+ (instancetype)<span class="hljs-attr">responseFromData:</span>(NSData *)respData<br>{<br> <span class="hljs-comment">// 解压包头</span><br> SocketPacketHead *currentHead = [[SocketPacketHead alloc] <span class="hljs-attr">initWithData:</span>respData];<br> <br> <span class="hljs-comment">// 包体数据</span><br> NSData *bodyData = [respData <span class="hljs-attr">subdataWithRange:</span>NSMakeRange(LENGTH_HEAD, respData.length - LENGTH_HEAD)];<br> <br> <span class="hljs-comment">//包解析</span><br> SocketResponse *resp = [self <span class="hljs-attr">finalBodyDataWithHead:</span>currentHead <span class="hljs-attr">bodyData:</span>bodyData]; <br> <span class="hljs-keyword">return</span> resp;<br>}<br><br></code></pre></td></tr></table></figure></p>
]]></content>
<tags>
<tag>socket</tag>
</tags>
</entry>
<entry>
<title>translatesAutoresizingMaskIntoConstraints</title>
<url>/2018/05/03/translatesAutoresizingMaskIntoConstraints/</url>
<content><![CDATA[<p>我们查看官方文档,可以看到文档是这样介绍该属性的:<br><figure class="highlight scss"><table><tr><td class="code"><pre><code class="hljs scss">它是一个用来决定,是否将视图的自动调整大小的遮罩(autoresizing <span class="hljs-attribute">mask</span>)转换为 <span class="hljs-attribute">Auto</span> Layout 约束的布尔值。<br></code></pre></td></tr></table></figure><br>通过文档介绍我们可以得知:当该属性为 true 时,系统会自动通过视图的 autoresizing mask 创建一组视图的约束,这些约束是基于你提供的 frame、bounds、center 这些属性。也就是说,当你给视图的 frame 赋值之后,它会为你创建静态的、基于 frame 的 Auto Layout 约束。如下:</p>
<h5 id="属性值为-true且指定-frame"><a href="#属性值为-true且指定-frame" class="headerlink" title="属性值为 true且指定 frame"></a>属性值为 true且指定 frame</h5><figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">let</span> exampleView = <span class="hljs-constructor">UIView(<span class="hljs-params">frame</span>: CGRect(<span class="hljs-params">x</span>: 100, <span class="hljs-params">y</span>: 100, <span class="hljs-params">width</span>: 100, <span class="hljs-params">height</span>: 100)</span>)<br><span class="hljs-comment">// 系统根据你指定的frame 给 exampleView 创建静态的 Auto Layout 约束</span><br>exampleView.backgroundColor = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">UIColor</span>.</span></span>green<br>view.add<span class="hljs-constructor">Subview(<span class="hljs-params">exampleView</span>)</span><br></code></pre></td></tr></table></figure>
<p>复制代码在上述情况中,你是不能给视图添加额外的约束来修改它的位置或大小的,如果添加额外的约束会导致约束冲突。如下:</p>
<h5 id="属性值为-true,指定-frame-且添加额外约束"><a href="#属性值为-true,指定-frame-且添加额外约束" class="headerlink" title="属性值为 true,指定 frame 且添加额外约束"></a>属性值为 true,指定 frame 且添加额外约束</h5><figure class="highlight less"><table><tr><td class="code"><pre><code class="hljs less"><span class="hljs-comment">// 例 1-1面的代码下添加此代码,会导致约束冲突</span><br><span class="hljs-selector-tag">NSLayoutConstraint</span><span class="hljs-selector-class">.activate</span>([<br> exampleView.widthAnchor.constraint(<span class="hljs-attribute">equalToConstant</span>: <span class="hljs-number">50</span>)<br> ])<br> <br></code></pre></td></tr></table></figure>
<p>复制代码错误提示如下图所示:<br><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p3903" alt="3aca4f9fb265f63d5204dcadd60e448a.png"></p>
<h5 id="添加额外约束导致约束冲突"><a href="#添加额外约束导致约束冲突" class="headerlink" title="添加额外约束导致约束冲突"></a>添加额外约束导致约束冲突</h5><p>如果你想使用 Auto Layout 动态计算、改变视图尺寸的话,你必须将该属性值改为 false 。然后你只需提供无歧义、无冲突的约束即可。如例 1-3 代码所示:<br>例 1-3 属性值为 false</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 布局效果等同于 例 1-1</span><br><span class="hljs-keyword">let</span> exampleView = <span class="hljs-constructor">UIView(<span class="hljs-params">frame</span>: .<span class="hljs-params">zero</span>)</span><br>exampleView.backgroundColor = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">UIColor</span>.</span></span>green<br>view.add<span class="hljs-constructor">Subview(<span class="hljs-params">exampleView</span>)</span><br><span class="hljs-comment">// 使用 Auto Layout 时,务必将此属性值设为 false</span><br>exampleView.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span><br><span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NSLayoutConstraint</span>.</span></span>activate(<span class="hljs-literal">[</span><br><span class="hljs-literal"> <span class="hljs-identifier">exampleView</span>.<span class="hljs-identifier">topAnchor</span>.<span class="hljs-identifier">constraint</span>(<span class="hljs-identifier">equalTo</span>: <span class="hljs-identifier">view</span>.<span class="hljs-identifier">topAnchor</span>, <span class="hljs-identifier">constant</span>: <span class="hljs-number">100</span>),</span><br><span class="hljs-literal"> <span class="hljs-identifier">exampleView</span>.<span class="hljs-identifier">leftAnchor</span>.<span class="hljs-identifier">constraint</span>(<span class="hljs-identifier">equalTo</span>: <span class="hljs-identifier">view</span>.<span class="hljs-identifier">leftAnchor</span>, <span class="hljs-identifier">constant</span>: <span class="hljs-number">100</span>),</span><br><span class="hljs-literal"> <span class="hljs-identifier">exampleView</span>.<span class="hljs-identifier">widthAnchor</span>.<span class="hljs-identifier">constraint</span>(<span class="hljs-identifier">equalToConstant</span>: <span class="hljs-number">100</span>),</span><br><span class="hljs-literal"> <span class="hljs-identifier">exampleView</span>.<span class="hljs-identifier">heightAnchor</span>.<span class="hljs-identifier">constraint</span>(<span class="hljs-identifier">equalToConstant</span>: <span class="hljs-number">100</span>)</span><br><span class="hljs-literal"> ]</span>)<br> <br></code></pre></td></tr></table></figure>
<p>复制代码当你代码创建视图时,视图的 translatesAutoresizingMaskIntoConstraints 默认为 true,当你使用 Interface Builder 时,系统会自动将 translatesAutoresizingMaskIntoConstraints 的值设为 false。</p>
<h5 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h5><p>代码创建视图时,视图的 translatesAutoresizingMaskIntoConstraints 属性值默认为 true</p>
<p>Interface Builder 中创建视图时,系统会自动将视图的 translatesAutoresizingMaskIntoConstraints 属性值设为 false</p>
<p>代码创建的视图,且使用 frame 进行布局时,不能添加额外的约束,会导致约束冲突</p>
<p>代码创建的视图,且使用 Auto Layout 进行布局时,需将属性值设为 false</p>
<h5 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h5><p><a class="link" href="https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco" >translatesAutoresizingMaskIntoConstraints<i class="fas fa-external-link-alt"></i></a><br><a class="link" href="https://stackoverflow.com/questions/47800210/when-should-translatesautoresizingmaskintoconstraints-be-set-to-true" >When should translatesAutoresizingMaskIntoConstraints be set to true?<i class="fas fa-external-link-alt"></i></a></p>
]]></content>
<tags>
<tag>UI</tag>
</tags>
</entry>
<entry>
<title>sizeToFit 和 sizeThatFits</title>
<url>/2017/05/01/sizeToFit-%E5%92%8C-sizeThatFits/</url>
<content><![CDATA[<p>[toc]</p>
<h5 id="sizeToFit"><a href="#sizeToFit" class="headerlink" title="sizeToFit"></a>sizeToFit</h5><p>sizeToFit: 会计算出最优的 size 而且会改变自己的size</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml">UILabel *label = <span class="hljs-literal">[[UIL<span class="hljs-identifier">abel</span> <span class="hljs-identifier">alloc</span>]</span> initWithFrame:<span class="hljs-constructor">CGRectMake(100, 100, 0, 0)</span>];<br>label.backgroundColor = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">UIColor</span>.</span></span>grayColor;<br>label.font = <span class="hljs-literal">[UIF<span class="hljs-identifier">ont</span> <span class="hljs-identifier">systemFontOfSize</span>:<span class="hljs-number">20</span>]</span>;<br>label.text = @<span class="hljs-string">"上海欢迎你!!!"</span>;<br><br><span class="hljs-comment">//sizeToFit: 直接改变了label的宽和高, 使它根据上面的字符串的大小做合适的改变</span><br><span class="hljs-literal">[<span class="hljs-identifier">label</span> <span class="hljs-identifier">sizeToFit</span>]</span>;<br><br><span class="hljs-constructor">NSLog(@<span class="hljs-string">"width = %.0f height = %.0f"</span>,<span class="hljs-params">label</span>.<span class="hljs-params">frame</span>.<span class="hljs-params">size</span>.<span class="hljs-params">width</span>, <span class="hljs-params">label</span>.<span class="hljs-params">frame</span>.<span class="hljs-params">size</span>.<span class="hljs-params">height</span>)</span>;<br><span class="hljs-literal">[<span class="hljs-identifier">self</span>.<span class="hljs-identifier">view</span> <span class="hljs-identifier">addSubview</span>:<span class="hljs-identifier">label</span>]</span>;<br><br><span class="hljs-comment">//输出结果:</span><br>width = <span class="hljs-number">119</span> height = <span class="hljs-number">24</span><br><br>注意: 使用sizeToFit时, 一定要设置控件的frame, 并且不可以使用Masonry, 否则sizeToFit将失效<br><br></code></pre></td></tr></table></figure>
<h5 id="sizeThatFits"><a href="#sizeThatFits" class="headerlink" title="sizeThatFits"></a>sizeThatFits</h5><p>sizeThatFits: 会计算出最优的 size 但是不会改变 自己的size</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml">UILabel *label = <span class="hljs-literal">[[UIL<span class="hljs-identifier">abel</span> <span class="hljs-identifier">alloc</span>]</span> initWithFrame:<span class="hljs-constructor">CGRectMake(100, 100, 0, 0)</span>];<br>label.backgroundColor = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">UIColor</span>.</span></span>grayColor;<br>label.font = <span class="hljs-literal">[UIF<span class="hljs-identifier">ont</span> <span class="hljs-identifier">systemFontOfSize</span>:<span class="hljs-number">20</span>]</span>;<br>label.text = @<span class="hljs-string">"上海欢迎你!!!"</span>;<br><br><span class="hljs-comment">//sizeThatFits并没有改变原始label的大小</span><br>CGSize sizeThatFits = <span class="hljs-literal">[<span class="hljs-identifier">label</span> <span class="hljs-identifier">sizeThatFits</span>:CGS<span class="hljs-identifier">izeZero</span>]</span>;<br><span class="hljs-constructor">NSLog(@<span class="hljs-string">"sizeThatFits: width = %.1f height = %.1f"</span>, <span class="hljs-params">sizeThatFits</span>.<span class="hljs-params">width</span>, <span class="hljs-params">sizeThatFits</span>.<span class="hljs-params">height</span>)</span>;<br><br><span class="hljs-constructor">NSLog(@<span class="hljs-string">"label: width = %.1f height = %.1f"</span>,<span class="hljs-params">label</span>.<span class="hljs-params">frame</span>.<span class="hljs-params">size</span>.<span class="hljs-params">width</span>, <span class="hljs-params">label</span>.<span class="hljs-params">frame</span>.<span class="hljs-params">size</span>.<span class="hljs-params">height</span>)</span>;<br><br><span class="hljs-literal">[<span class="hljs-identifier">self</span>.<span class="hljs-identifier">view</span> <span class="hljs-identifier">addSubview</span>:<span class="hljs-identifier">label</span>]</span>;<br><br><br>输出结果:<br>sizeThatFits: width = <span class="hljs-number">119.0</span> height = <span class="hljs-number">24.0</span><br>label: width = <span class="hljs-number">0.0</span> height = <span class="hljs-number">0.0</span><br><br></code></pre></td></tr></table></figure>
<h5 id="iOS-sizeToFit-和-sizeThatFits的联系"><a href="#iOS-sizeToFit-和-sizeThatFits的联系" class="headerlink" title="iOS sizeToFit 和 sizeThatFits的联系"></a>iOS sizeToFit 和 sizeThatFits的联系</h5><p>如果你的控件对尺寸有严格的限定,比如有一个统一的宽高比或者是固定尺寸,那么最好能实现系统给出的约定成俗的接口。<br>sizeToFit 用在基于 frame 布局的情况下,由你的控件去实现 sizeThatFits: 方法:</p>
<figure class="highlight processing"><table><tr><td class="code"><pre><code class="hljs processing">- (CGSize)sizeThatFits:(CGSize)<span class="hljs-built_in">size</span> {<br> CGSize fitSize = [<span class="hljs-keyword">super</span> sizeThatFits:<span class="hljs-built_in">size</span>];<br> fitSize.<span class="hljs-built_in">height</span> += self.label.frame.<span class="hljs-built_in">size</span>.<span class="hljs-built_in">height</span>;<br> <span class="hljs-comment">// 如果是固定尺寸,就像 UISwtich 那样返回一个固定 Size 就 OK 了</span><br> <span class="hljs-keyword">return</span> fitSize;<br>} <br></code></pre></td></tr></table></figure>
<p>然后在外部调用该控件的 sizeToFit 方法,这个方法内部会自动调用 sizeThatFits 并更新自身的 Size:</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[self.customView sizeToFit]</span><span class="hljs-comment">; </span><br></code></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>UI</tag>
</tags>
</entry>
<entry>
<title>常用lldb命令</title>
<url>/2018/12/19/%E5%B8%B8%E7%94%A8lldb%E5%91%BD%E4%BB%A4/</url>
<content><![CDATA[<h6 id="常用"><a href="#常用" class="headerlink" title="常用"></a>常用</h6><p>bt:堆栈信息<br>p:打印对象信息,内容比较全<br>po:打印对象信息<br>breakpoint set -n “-[方法名]”:断点<br>si:下一指令,汇编级别<br>s:下一步,高级源码级别<br>call:方法调用<br>thread return:退出当前线程<br>expression -O — self.view :打印当前对象内容,可以看见属性</p>
<h6 id="打印,修改,方法调用"><a href="#打印,修改,方法调用" class="headerlink" title="打印,修改,方法调用"></a>打印,修改,方法调用</h6><p>p 对象/表达式<br>po 对象<br>expression 表达式<br>call 方法<br>print 对象</p>
<h6 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h6><p>1、流程控制</p>
<p>thread step-in => s<br>thread step-out => n<br>thread step-over => finish<br>thread continue </p>
<p>2、堆栈信息</p>
<p>thread backtrace => bt 打印当前堆栈信息<br>bt 1 只打印一帧</p>
<p>3、跳帧选择N,调到第N帧</p>
<p>frame select N</p>
<p>4、查看帧变量<br>frame variable</p>
<p>5、线程返回</p>
<p>thread return 不带返回值<br>thread return 10 返回值为10</p>
<h6 id="断点"><a href="#断点" class="headerlink" title="断点"></a>断点</h6><p>breakpoint set -n “方法”<br>breakpoint delete 断点编号<br>breakpoint list </p>
<h6 id="image"><a href="#image" class="headerlink" title="image"></a>image</h6><p>image lookup -t 类名<br>image lookup -a 地址</p>
]]></content>
<tags>
<tag>lldb</tag>
</tags>
</entry>
<entry>
<title>技术支持</title>
<url>/2020/12/15/%E6%8A%80%E6%9C%AF%E6%94%AF%E6%8C%81/</url>
<content><![CDATA[<p>开发者QQ:2696437433</p>
<p>开发者邮箱:baichenghui88888@gmail.com </p>
<p>微信号:xiaobailong_b9i_1992</p>
]]></content>
<tags>
<tag>日程日历App</tag>
</tags>
</entry>
<entry>
<title>隐私政策</title>
<url>/2020/12/15/%E6%97%A5%E7%A8%8B%E6%97%A5%E5%8E%86%E9%9A%90%E7%A7%81%E6%94%BF%E7%AD%96/</url>
<content><![CDATA[<p>隐私政策</p>
<p>本应用尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务,本应用会按照本隐私权政策的规定使用和披露您的个人信息。但本应用将以高度的勤勉、审慎义务对待这些信息。除本隐私权政策另有规定外,在未征得您事先许可的情况下,本应用不会将这些信息对外披露或向第三方提供。本应用会不时更新本隐私权政策。 您在同意本应用服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本应用服务使用协议不可分割的一部分。</p>
<ol>
<li>适用范围</li>
</ol>
<p>(a) 在您使用本应用网络服务,或访问本应用平台网页时,本应用自动接收并记录的您的浏览器和计算机上的信息,包括但不限于您的IP地址、浏览器的类型、使用的语言、访问日期和时间、软硬件特征信息及您需求的网页记录等数据;</p>
<p>您了解并同意,以下信息不适用本隐私权政策:</p>
<p>(a) 本应用收集到的您在本应用发布的有关信息数据,包括但不限于参与活动、成交信息及评价详情;</p>
<p>(b) 违反法律规定或违反本应用规则行为及本应用已对您采取的措施。</p>
<ol>
<li>信息使用</li>
</ol>
<p>(a)本应用不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可,或该第三方和本应用(含本应用关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些资料。</p>
<p>(b) 本应用亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何本应用平台用户如从事上述活动,一经发现,本应用有权立即终止与该用户的服务协议。</p>
<ol>
<li>信息披露</li>
</ol>
<p>在如下情况下,本应用将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息:</p>
<p>(a) 经您事先同意,向第三方披露;</p>
<p>(b)为提供您所要求的产品和服务,而必须和第三方分享您的个人信息;</p>
<p>(c) 根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;</p>
<p>(d) 如您出现违反中国有关法律、法规或者本应用服务协议或相关规则的情况,需要向第三方披露;</p>
<p>(e) 如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方处理可能的权利纠纷;</p>
<p>(f) 在本应用平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,本应用有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。</p>
<p>(g) 其它本应用根据法律、法规或者网站政策认为合适的披露。</p>
<ol>
<li>信息存储和交换</li>
</ol>
<p>本应用收集的有关您的信息和资料将保存在本应用及(或)其关联公司的服务器上,这些信息和资料可能传送至您所在国家、地区或本应用收集信息和资料所在地的境外并在境外被访问、存储和展示。</p>
<ol>
<li>Cookie的使用</li>
</ol>
<p>(a) 在您未拒绝接受cookies的情况下,本应用会在您的计算机上设定或取用cookies ,以便您能登录或使用依赖于cookies的本应用平台服务或功能。本应用使用cookies可为您提供更加周到的个性化服务,包括推广服务。</p>
<p>(b) 您有权选择接受或拒绝接受cookies。您可以通过修改浏览器设置的方式拒绝接受cookies。但如果您选择拒绝接受cookies,则您可能无法登录或使用依赖于cookies的本应用网络服务或功能。</p>
<p>(c) 通过本应用所设cookies所取得的有关信息,将适用本政策。</p>
<ol>
<li>信息安全</li>
</ol>
<p>(a) 本应用帐号均有安全保护功能,请妥善保管您的用户名及密码信息。本应用将通过对用户密码进行加密等安全措施确保您的信息不丢失,不被滥用和变造。尽管有前述安全措施,但同时也请您注意在信息网络上不存在“完善的安全措施”。</p>
<p>(b) 在使用本应用网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对</p>
<p>7.本隐私政策的更改</p>
<p>(a)如果决定更改隐私政策,我们会在本政策中、本公司网站中以及我们认为适当的位置发布这些更改,以便您了解我们如何收集、使用您的个人信息,哪些人可以访问这些信息,以及在什么情况下我们会透露这些信息。</p>
<p>(b)该应该作者保留随时修改本政策的权利,因此请经常查看。如对本政策作出重大更改,本公司会通过网站通知的形式告知。</p>
<p>请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,尤其是本应用用户名及密码发生泄露,请您立即联络本应用客服,以便本应用采取相应措施。 </p>
]]></content>
<tags>
<tag>日程日历App</tag>
</tags>
</entry>
<entry>
<title>iOS播放器设计</title>
<url>/2019/01/06/iOS%E6%92%AD%E6%94%BE%E5%99%A8%E8%AE%BE%E8%AE%A1/</url>
<content><![CDATA[<p>[TOC]</p>
<h3 id="需求目标"><a href="#需求目标" class="headerlink" title="需求目标"></a>需求目标</h3><p>1、基础播放器可随时替代(本地、远程播放、暂停、拖动进度、后台播放)<br>2、记录上次播放、播放模式(单曲,顺序,随机)等<br>3、播放单个故事,播放专辑列表<br>4、网络慢,加载进度条和语音提示<br>5、播放失败、播放状态变更时自定义日志log上传bugly</p>
<h3 id="考虑点"><a href="#考虑点" class="headerlink" title="考虑点"></a>考虑点</h3><h5 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h5><p>分层:<br>基础层:给url就播放,开始使用AVPlayer,后续可能采用ffmpeg<br>业务层:播放记录、播放模式、专辑列表播放、播放状态回调、根据故事id播放故事</p>
<p>接口尽可能简洁。</p>
<h5 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h5><p>遵守设计原则:单一原则、迪米特法则、依赖倒置原则、里斯替换原则、接口隔离原则、开放-封闭原则</p>
<p>抽象工厂模式:解决基础播放器快速替换问题</p>
<p>策略模式:实现不同播放模式,资源列表排序算法 </p>
<h5 id="记录音频播放信息"><a href="#记录音频播放信息" class="headerlink" title="记录音频播放信息"></a>记录音频播放信息</h5><p>数据库建表存取</p>
<h5 id="下载组件"><a href="#下载组件" class="headerlink" title="下载组件"></a>下载组件</h5><p>断点下载组件下载音频:</p>
<h5 id="播放日志"><a href="#播放日志" class="headerlink" title="播放日志"></a>播放日志</h5><p>与服务端尽可能少的交互</p>
<p>通过优先级设置改变上传时机:<br> 高优先级日志立即上传<br> 低优先级达到一定数量或一定时长才上传<br> 日志记录需要考虑多线程问题,写入时不能有其他操作,且不能同时多个写入<br> 是否持久化</p>
<h5 id="电量考虑"><a href="#电量考虑" class="headerlink" title="电量考虑"></a>电量考虑</h5><p>日志:尽量少的日志上传次数,传递数据都是必传字段;本地磁盘写入次数尽可能的少;<br>模式切换:播放列表之前一次计算好播放顺序,不要在每次播放器才计算。<br>系统远端控制:锁屏图片在子线程获取和解码</p>
<h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>AVFoundation后台播放模式设置<br><figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">do</span> {<br> <span class="hljs-comment">//keep alive audio at background</span><br> <span class="hljs-keyword">try</span> <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">AVAudioSession</span>.</span></span>shared<span class="hljs-constructor">Instance()</span>.set<span class="hljs-constructor">Category(AVAudioSession.Category.<span class="hljs-params">playback</span>)</span><br>} catch _ { }<br><br><span class="hljs-keyword">do</span> {<br> <span class="hljs-keyword">try</span> <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">AVAudioSession</span>.</span></span>shared<span class="hljs-constructor">Instance()</span>.set<span class="hljs-constructor">Active(<span class="hljs-params">true</span>)</span><br>} catch _ { }<br></code></pre></td></tr></table></figure><br>开启远程控制器后,才会后台自动切歌播放(开启线控,还能支持耳机上的线控操作)<br><figure class="highlight scheme"><table><tr><td class="code"><pre><code class="hljs scheme">[[<span class="hljs-name">UIApplication</span> sharedApplication] beginReceivingRemoteControlEvents]<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><br>系统远端事件捕获不到问题,需要重写下面方法<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">-(<span class="hljs-built_in">BOOL</span>)canBecomeFirstResponder{<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">YES</span>;<br>}<br><br><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> mark - 接收到远程控制时执行的方法</span><br>- (<span class="hljs-keyword">void</span>) remoteControlReceivedWithEvent: (<span class="hljs-built_in">UIEvent</span> *) receivedEvent{<br><br>}<br></code></pre></td></tr></table></figure><br>锁屏播放信息设置<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-built_in">MPNowPlayingInfoCenter</span> *center = [<span class="hljs-built_in">MPNowPlayingInfoCenter</span> defaultCenter];<br><span class="hljs-built_in">NSDictionary</span> *songInfo = @{<br> <span class="hljs-built_in">MPMediaItemPropertyTitle</span>: title,<br> <span class="hljs-built_in">MPMediaItemPropertyArtist</span>: artist<br> <span class="hljs-built_in">MPMediaItemPropertyPlaybackDuration</span> : [<span class="hljs-built_in">NSNumber</span> numberWithFloat:length]<br>};<br>center.nowPlayingInfo = songInfo;<br></code></pre></td></tr></table></figure></p>
<h3 id="播放流程"><a href="#播放流程" class="headerlink" title="播放流程"></a>播放流程</h3><p>1、根据故事id查看是否有播放记录?<br> 有播放记录,获取播放进度<br> 无播放记录,播放进度为初始值</p>
<p>2、查看该故事音频是否已本地缓存?<br> 有缓存,设置播放进度,播放本地<br> 无缓存,设置播放进度,播放远程</p>
<p>3、开始播放,添加播放记录。 </p>
<p>4、播放过程中,定时器定时获取播放进度,设置进度条,同时更播放进度记录;<br> 如果在后台:更新锁屏上的播放信息;否则不刷新锁屏信息(节能)</p>
<p>5、暂停更新播放进度。</p>
<p>6、播放完成更新播放进度。</p>
<p>7、播放状态、网络状态、音频获取状态变换回调,方便打点。</p>
]]></content>
<tags>
<tag>架构设计</tag>
</tags>
</entry>
<entry>
<title>YYAsyncLayer 研究</title>
<url>/2017/11/01/YYAsyncLayer-%E7%A0%94%E7%A9%B6/</url>
<content><![CDATA[<p>[TOC]</p>
<h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>异步绘制是界面流畅度提升的思路,YYAsyncLayer 是 ibireme 写的一个异步绘制的轮子。质量比较高,涉及到很多优化思维,值得学习。</p>
<h3 id="为什么要异步绘制"><a href="#为什么要异步绘制" class="headerlink" title="为什么要异步绘制"></a>为什么要异步绘制</h3><h5 id="屏幕显示图像的原理"><a href="#屏幕显示图像的原理" class="headerlink" title="屏幕显示图像的原理"></a>屏幕显示图像的原理</h5><p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2376" alt="9d500c2270433ab8e444083d5cf650f2.png"></p>
<p>CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2375" alt="eb92699d5650217ec7357a49d5789ca2.png"></p>
<p>过去的CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。</p>
<h5 id="双缓冲机制"><a href="#双缓冲机制" class="headerlink" title="双缓冲机制"></a>双缓冲机制</h5><p>显示系统通常会引入两个缓冲区,即双缓冲机制。在这种情况下,GPU 会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU 会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提升。</p>
<p>双缓冲虽然能解决效率问题,但会引入一个新的问题。当视频控制器还未读取完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象。</p>
<p>为了解决这个问题,GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。</p>
<h5 id="界面卡顿的实质"><a href="#界面卡顿的实质" class="headerlink" title="界面卡顿的实质"></a>界面卡顿的实质</h5><p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2374" alt="5f16df9fe174ef3a6879c2bb1f066d27.png"> </p>
<p>在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。</p>
<h5 id="UIKit-性能瓶颈"><a href="#UIKit-性能瓶颈" class="headerlink" title="UIKit 性能瓶颈"></a>UIKit 性能瓶颈</h5><p>大部分 UIKit 组件的绘制是在主线程进行,需要 CPU 来进行绘制,当同一时刻过多组件需要绘制或者组件元素过于复杂时,必然会给 CPU 带来压力,这个时候就很容易掉帧(主要是文本控件,大量文本内容的计算和绘制过程都相当繁琐)。</p>
<h5 id="UIKit-替代方案:CoreAnimation-或-CoreGraphics"><a href="#UIKit-替代方案:CoreAnimation-或-CoreGraphics" class="headerlink" title="UIKit 替代方案:CoreAnimation 或 CoreGraphics"></a>UIKit 替代方案:CoreAnimation 或 CoreGraphics</h5><h6 id="CoreAnimation"><a href="#CoreAnimation" class="headerlink" title="CoreAnimation"></a>CoreAnimation</h6><p>首选优化方案是 CoreAnimation 框架。CALayer 的大部分属性都是由 GPU 绘制的 (硬件层面),不需要 CPU (软件层面) 做任何绘制。CA 框架下的 CAShapeLayer (多边形绘制)、CATextLayer(文本绘制)、CAGradientLayer (渐变绘制) 等都有较高的效率,非常实用。</p>
<h6 id="CoreGraphics"><a href="#CoreGraphics" class="headerlink" title="CoreGraphics"></a>CoreGraphics</h6><p>其次在适当的地方可以考虑 CoreGraphics 框架。CoreGraphics 依托于 CPU 的软件绘制。在实现 CALayerDelegate 协议的 -drawLayer:inContext: 方法时(等同于UIView 二次封装的 -drawRect:方法),需要分配一个内存占用较高的上下文 context,与此同时,CALayer 或者其子类需要创建一个等大的寄宿图 contents。当基于 CPU 的软件绘制完成,还需要通过 IPC (进程间通信) 传递给设备显示系统。值得注意的是:当重绘时需要抹除这个上下文重新分配内存。</p>
<p>不管是创建上下文、重绘带来的内存重新分配、IPC 都会带来性能上的较大开销。所以 CoreGraphics 的性能比较差,日常开发中要尽量避免直接在主线程使用。通常情况下,直接给 CALayer 的 contents 赋值 CGImage 图片或者使用 CALayer 的衍生类就能实现大部分需求,还能充分利用硬件支持,图像处理交给 GPU 当然更加放心。</p>
<h5 id="多核设备带来的可能性"><a href="#多核设备带来的可能性" class="headerlink" title="多核设备带来的可能性"></a>多核设备带来的可能性</h5><p>通过以上说明,可以了解 CoreGraphics 较为糟糕的性能。然而可喜的是,市面上的设备都已经不是单核了,这就意味着可以通过后台线程处理耗时任务,主线程只需要负责调度显示。</p>
<p>CoreGraphics 框架可以通过图片上下文将绘制内容制作为一张位图,并且这个操作可以在非主线程执行。那么,当有 n 个绘制任务时,可以开辟多个线程在后台异步绘制,绘制成功拿到位图回到主线程赋值给 CALayer 的寄宿图属性。</p>
<p>这就是 YYAsyncLayer 框架的核心思想。</p>
<p>虽然多个线程异步绘制会消耗大量的内存,但是对于性能敏感界面来说,只要工程师控制好内存峰值,可以极大的提高交互流畅度。优化很多时候就是空间换时间,所谓鱼和熊掌不可兼得。这也说明了一个问题,实际开发中要做有针对性的优化,不可盲目跟风。</p>
<h3 id="框架概述"><a href="#框架概述" class="headerlink" title="框架概述"></a>框架概述</h3><p>类文件</p>
<figure class="highlight css"><table><tr><td class="code"><pre><code class="hljs css"><span class="hljs-selector-tag">YYSentinel</span><span class="hljs-selector-class">.h</span> (<span class="hljs-selector-class">.m</span>)<br><span class="hljs-selector-tag">YYTransaction</span><span class="hljs-selector-class">.h</span> (<span class="hljs-selector-class">.m</span>)<br><span class="hljs-selector-tag">YYAsyncLayer</span><span class="hljs-selector-class">.h</span> (<span class="hljs-selector-class">.m</span>)<br></code></pre></td></tr></table></figure>
<h5 id="YYSentinel"><a href="#YYSentinel" class="headerlink" title="YYSentinel"></a>YYSentinel</h5><p>计数的类,自增int32_t类型变量value,是为了记录最新的布局请求标识,便于及时的放弃多余的绘制逻辑以减少开销。</p>
<p>在框架中的应用是,异步绘制操作之前取出value作为局部变量保存</p>
<figure class="highlight ceylon"><table><tr><td class="code"><pre><code class="hljs ceylon">int<span class="hljs-number">32_</span>t <span class="hljs-keyword">value</span> = sentinel.<span class="hljs-keyword">value</span>;<br></code></pre></td></tr></table></figure>
<p>然后定义isCancelled block判断是否取消绘制操作了</p>
<figure class="highlight gauss"><table><tr><td class="code"><pre><code class="hljs gauss"><span class="hljs-keyword">BOOL</span> (^isCancelled)() = ^<span class="hljs-keyword">BOOL</span>() {<br> <span class="hljs-comment">//value产生变化说明取消绘制操作</span><br> <span class="hljs-keyword">return</span> value != sentinel.value;<br>};<br></code></pre></td></tr></table></figure>
<p>取消绘制操作</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><code class="hljs angelscript">- (<span class="hljs-built_in">void</span>)_cancelAsyncDisplay {<br> <span class="hljs-comment">//对 value 进行自增,这样value就产生了变化</span><br><span class="hljs-string"> [_sentinel increase]</span>;<br>}<br></code></pre></td></tr></table></figure>
<h5 id="YYTransaction"><a href="#YYTransaction" class="headerlink" title="YYTransaction"></a>YYTransaction</h5><p>事务类,捕获主线程 runloop 的某个时机回调,用于处理异步绘制事件。</p>
<h5 id="YYAsyncLayer"><a href="#YYAsyncLayer" class="headerlink" title="YYAsyncLayer"></a>YYAsyncLayer</h5><p>继承自 CALayer ,封装了异步绘制的逻辑便于使用。</p>
<h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h5 id="YYSentinel-1"><a href="#YYSentinel-1" class="headerlink" title="YYSentinel"></a>YYSentinel</h5><p>.h<br><figure class="highlight less"><table><tr><td class="code"><pre><code class="hljs less"><span class="hljs-variable">@interface</span> <span class="hljs-attribute">YYSentinel </span>: NSObject <br><span class="hljs-variable">@property</span> (readonly) int32_t value; <br><span class="hljs-selector-tag">-</span> (int32_t)<span class="hljs-selector-tag">increase</span>; <br>@<span class="hljs-selector-tag">end</span><br></code></pre></td></tr></table></figure><br>.m<br><figure class="highlight arduino"><table><tr><td class="code"><pre><code class="hljs arduino"><span class="hljs-meta">#import <span class="hljs-meta-string">"YYSentinel.h"</span></span><br><br><span class="hljs-comment">// 若需要保证整形数值变量的线程安全,可以使用 OSAtomic 框架下的方法,它往往性能比使用各种“锁”更为优越,并且代码优雅。</span><br><span class="hljs-meta">#import <span class="hljs-meta-string"><libkern/OSAtomic.h></span></span><br><br>@implementation YYSentinel {<br> <span class="hljs-keyword">int32_t</span> _value;<br>}<br>- (<span class="hljs-keyword">int32_t</span>)value {<br> <span class="hljs-keyword">return</span> _value;<br>}<br><br><span class="hljs-comment">// 使用 OSAtomicIncrement32() 方法来对 value 执行自增。</span><br><span class="hljs-comment">// OSAtomicIncrement32() 是原子自增方法,线程安全。</span><br>- (<span class="hljs-keyword">int32_t</span>)increase {<br> <span class="hljs-keyword">return</span> OSAtomicIncrement32(&_value);<br>}<br>@<span class="hljs-built_in">end</span> <br></code></pre></td></tr></table></figure></p>
<h5 id="YYTransaction-1"><a href="#YYTransaction-1" class="headerlink" title="YYTransaction"></a>YYTransaction</h5><p>YYTransaction 使用集合来管理任务。</p>
<p>YYTransaction 做的事情就是记录一系列事件,并且在合适的时机调用这些事件。</p>
<h6 id="提交任务"><a href="#提交任务" class="headerlink" title="提交任务"></a>提交任务</h6><p>YYTransaction 有两个属性:<br><figure class="highlight less"><table><tr><td class="code"><pre><code class="hljs less"><span class="hljs-variable">@interface</span> YYTransaction()<br><span class="hljs-variable">@property</span> (nonatomic, strong) id target;<br><span class="hljs-variable">@property</span> (nonatomic, assign) SEL selector;<br><span class="hljs-variable">@end</span><br><br>static NSMutableSet *transactionSet = nil;<br></code></pre></td></tr></table></figure></p>
<p>方法接收者 (target) 和方法 (selector)。<br>一个 YYTransaction就是一个任务,全局区的 transactionSet 集合就是用来存储这些任务。提交方法-commit 不过是初始配置并且将任务装入集合。</p>
<h6 id="合适的回调时机"><a href="#合适的回调时机" class="headerlink" title="合适的回调时机"></a>合适的回调时机</h6><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> YYTransactionSetup() {<br> <span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_once_t</span> onceToken;<br> <span class="hljs-built_in">dispatch_once</span>(&onceToken, ^{<br> transactionSet = [<span class="hljs-built_in">NSMutableSet</span> new];<br> <span class="hljs-built_in">CFRunLoopRef</span> runloop = <span class="hljs-built_in">CFRunLoopGetMain</span>();<br> <span class="hljs-built_in">CFRunLoopObserverRef</span> observer;<br> <br> observer = <span class="hljs-built_in">CFRunLoopObserverCreate</span>(<span class="hljs-built_in">CFAllocatorGetDefault</span>(),<br> kCFRunLoopBeforeWaiting | kCFRunLoopExit,<br> <span class="hljs-literal">true</span>, <span class="hljs-comment">// repeat</span><br> <span class="hljs-number">0xFFFFFF</span>, <span class="hljs-comment">// after CATransaction(2000000)</span><br> YYRunLoopObserverCallBack, <span class="hljs-literal">NULL</span>);<br> <span class="hljs-built_in">CFRunLoopAddObserver</span>(runloop, observer, kCFRunLoopCommonModes);<br> <span class="hljs-built_in">CFRelease</span>(observer);<br> });<br>}<br><br></code></pre></td></tr></table></figure>
<p>在主线程的 RunLoop 中添加了一个 observer 监听,回调的时机是 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit ,即是主线程 RunLoop 循环即将进入休眠或者即将退出的时候。</p>
<p>observer 的优先级是 0xFFFFFF,优先级在 CATransaction 的后面.</p>
<p>回调里面做的事情:</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> YYRunLoopObserverCallBack(<span class="hljs-built_in">CFRunLoopObserverRef</span> observer, <span class="hljs-built_in">CFRunLoopActivity</span> activity, <span class="hljs-keyword">void</span> *info) {<br> <span class="hljs-keyword">if</span> (transactionSet.count == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;<br> <span class="hljs-built_in">NSSet</span> *currentSet = transactionSet;<br> transactionSet = [<span class="hljs-built_in">NSMutableSet</span> new];<br> [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, <span class="hljs-built_in">BOOL</span> *stop) {<br><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> clang diagnostic push</span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> clang diagnostic ignored <span class="hljs-meta-string">"-Warc-performSelector-leaks"</span></span><br> [transaction.target performSelector:transaction.selector];<br><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> clang diagnostic pop</span><br> }];<br>}<br></code></pre></td></tr></table></figure>
<p>只是将集合中的任务遍历执行。</p>
<h6 id="方法重写"><a href="#方法重写" class="headerlink" title="方法重写"></a>方法重写</h6><p>重写 <code>isEqual:</code> 方法</p>
<figure class="highlight kotlin"><table><tr><td class="code"><pre><code class="hljs kotlin">- (BOOL)isEqual:(id)<span class="hljs-keyword">object</span> {<br> <span class="hljs-keyword">if</span> (self == <span class="hljs-keyword">object</span>) <span class="hljs-keyword">return</span> YES;<br> <span class="hljs-keyword">if</span> (![<span class="hljs-keyword">object</span> isMemberOfClass:self.<span class="hljs-keyword">class</span>]) <span class="hljs-keyword">return</span> NO;<br> YYTransaction *other = <span class="hljs-keyword">object</span>;<br> <span class="hljs-keyword">return</span> other.selector == _selector && other.target == _target;<br>}<br></code></pre></td></tr></table></figure>
<p>重写 hash 算法:</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">- (<span class="hljs-built_in">NSUInteger</span>)hash {<br> <span class="hljs-keyword">long</span> v1 = (<span class="hljs-keyword">long</span>)((<span class="hljs-keyword">void</span> *)_selector);<br> <span class="hljs-keyword">long</span> v2 = (<span class="hljs-keyword">long</span>)_target;<br> <span class="hljs-keyword">return</span> v1 ^ v2;<br>}<br></code></pre></td></tr></table></figure>
<p>NSObject 类默认的 hash 值为 10 进制的内存地址,这里作者将 _selector和 _target 的内存地址进行一个位异或处理,意味着只要 _selector 和 _target 地址都相同时,hash 值就相同。</p>
<p>上面定义了<code>static NSMutableSet *transactionSet = nil;</code>,transactionSet是一个集合。</p>
<p>这么做的意义,是因为这里和其他编程语言一样 NSSet 是基于 hash 的集合,它是不能有重复元素的,而判断是否重复毫无疑问是使用 hash。作者通过重写hash算法来重新定义重复元素。</p>
<p>将 YYTransaction 的 hash值依托于 _selector 和 _target 的内存地址,那就意味着两点:<br> 1、同一个 YYTransaction 实例,_selector 和 _target 只要有一个内存地址不同,就会在集合中体现为两个值。<br> 2、不同的 YYTransaction 实例,_selector 和 _target 的内存地址都相同,在集合中的体现为一个值。</p>
<p>在这可以避免重复的方法调用。加入 transactionSet 中的事件会在 Runloop 即将进入休眠或者即将退出时遍历执行,相同的方法接收者 (_target) 和相同的方法 (_selector),可以视为重复调用(这里的主要场景是避免重复绘制影响性能)。</p>
<p>举一个实际的例子:</p>
<p>当使用绘制来制作一个文本时,Font、Text等属性的改变都意味着要重绘,使用 YYTransaction 延迟了绘制的调用时机,并且它们在同一个 RunLoop 循环中,装入 NSSet 将直接合并为一个绘制任务,避免了重复的绘制。</p>
<h5 id="YYAsyncLayer-1"><a href="#YYAsyncLayer-1" class="headerlink" title="YYAsyncLayer"></a>YYAsyncLayer</h5><figure class="highlight less"><table><tr><td class="code"><pre><code class="hljs less"><span class="hljs-variable">@interface</span> <span class="hljs-attribute">YYAsyncLayer </span>: CALayer <br><span class="hljs-variable">@property</span> BOOL displaysAsynchronously;<br><span class="hljs-variable">@end</span><br></code></pre></td></tr></table></figure>
<p>YYAsyncLayer 继承自 CALayer,对外暴露了一个方法可开闭是否异步绘制。</p>
<h6 id="初始化配置"><a href="#初始化配置" class="headerlink" title="初始化配置"></a>初始化配置</h6><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">- (<span class="hljs-keyword">instancetype</span>)init {<br> <span class="hljs-keyword">self</span> = [<span class="hljs-keyword">super</span> init];<br> <span class="hljs-keyword">static</span> <span class="hljs-built_in">CGFloat</span> scale; <span class="hljs-comment">//global</span><br> <span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_once_t</span> onceToken;<br> <span class="hljs-built_in">dispatch_once</span>(&onceToken, ^{<br> scale = [<span class="hljs-built_in">UIScreen</span> mainScreen].scale;<br> });<br> <span class="hljs-keyword">self</span>.contentsScale = scale;<br> _sentinel = [YYSentinel new];<br> _displaysAsynchronously = <span class="hljs-literal">YES</span>;<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>;<br>}<br></code></pre></td></tr></table></figure>
<p>YYAsyncLayer 的 contentsScale 为屏幕的 scale,该属性是 物理像素 / 逻辑像素,这样可以充分利用不同设备的显示器分辨率,绘制更清晰的图像。但是若 contentsGravity 设置了可拉伸的类型,CoreAnimation 将会优先满足,而忽略掉 contentsScale。UIView 和 UIImageView 默认处理了它们内部 CALayer 的 contentsScale,所以除非是直接使用 CALayer 及其衍生类,都不用显式的配置 contentsScale。</p>
<p>同时还创建了一个 YYSentinel 实例。</p>
<p>默认开启异步绘制。</p>
<h6 id="重写绘制方法:"><a href="#重写绘制方法:" class="headerlink" title="重写绘制方法:"></a>重写绘制方法:</h6><figure class="highlight angelscript"><table><tr><td class="code"><pre><code class="hljs angelscript">- (<span class="hljs-built_in">void</span>)setNeedsDisplay {<br><span class="hljs-string"> [self _cancelAsyncDisplay]</span>;<br><span class="hljs-string"> [super setNeedsDisplay]</span>;<br>}<br><br>- (<span class="hljs-built_in">void</span>)display {<br> <span class="hljs-keyword">super</span>.contents = <span class="hljs-keyword">super</span>.contents;<br><span class="hljs-string"> [self _displayAsync:_displaysAsynchronously]</span>;<br>}<br></code></pre></td></tr></table></figure>
<p>标记需要绘制时,会先取消目前的异步绘制。待下一轮runloop到来绘制时,通过 <code>_displayAsync:</code> 方法来进行异步绘制操作。<code>_displayAsync:</code> 方法后面分析。</p>
<h6 id="YYAsyncLayerDisplayTask"><a href="#YYAsyncLayerDisplayTask" class="headerlink" title="YYAsyncLayerDisplayTask"></a>YYAsyncLayerDisplayTask</h6><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-class"><span class="hljs-keyword">@interface</span> <span class="hljs-title">YYAsyncLayerDisplayTask</span> : <span class="hljs-title">NSObject</span></span><br><span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nullable</span>, <span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">copy</span>) <span class="hljs-keyword">void</span> (^willDisplay)(<span class="hljs-built_in">CALayer</span> *layer); <br><span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nullable</span>, <span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">copy</span>) <span class="hljs-keyword">void</span> (^display)(<span class="hljs-built_in">CGContextRef</span> context, <span class="hljs-built_in">CGSize</span> size, <span class="hljs-built_in">BOOL</span>(^isCancelled)(<span class="hljs-keyword">void</span>)); <br><span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nullable</span>, <span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">copy</span>) <span class="hljs-keyword">void</span> (^didDisplay)(<span class="hljs-built_in">CALayer</span> *layer, <span class="hljs-built_in">BOOL</span> finished); <br><span class="hljs-keyword">@end</span><br></code></pre></td></tr></table></figure>
<p><code>YYAsyncLayerDisplayTask</code> 是一个异步绘制任务。</p>
<p>通过 <code>willDisplay</code> 和 <code>didDisplay</code> 回调将要绘制和结束绘制时机,通过实现<code>display</code> 在代码块里面写业务绘制逻辑。</p>
<h6 id="YYAsyncLayerDelegate"><a href="#YYAsyncLayerDelegate" class="headerlink" title="YYAsyncLayerDelegate"></a>YYAsyncLayerDelegate</h6><figure class="highlight less"><table><tr><td class="code"><pre><code class="hljs less"><span class="hljs-variable">@protocol</span> YYAsyncLayerDelegate <NSObject><br><span class="hljs-variable">@required</span> <br>- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask;<br><span class="hljs-variable">@end</span><br></code></pre></td></tr></table></figure>
<p>通过协议提供数据源的方式,获取异步绘制的任务。</p>
<h5 id="异步绘制的核心逻辑"><a href="#异步绘制的核心逻辑" class="headerlink" title="异步绘制的核心逻辑"></a>异步绘制的核心逻辑</h5><p>去掉一些优化代码,主要做的事情如下:</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-built_in">dispatch_async</span>(YYAsyncLayerGetDisplayQueue(), ^{<br> <span class="hljs-built_in">UIGraphicsBeginImageContextWithOptions</span>(size, opaque, scale);<br> <span class="hljs-built_in">CGContextRef</span> context = <span class="hljs-built_in">UIGraphicsGetCurrentContext</span>(); <br> <span class="hljs-built_in">UIImage</span> *image = <span class="hljs-built_in">UIGraphicsGetImageFromCurrentImageContext</span>();<br> <span class="hljs-built_in">UIGraphicsEndImageContext</span>();<br> <span class="hljs-built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{<br> <span class="hljs-keyword">self</span>.contents = (__bridge <span class="hljs-keyword">id</span>)(image.CGImage); <br> });<br> });<br></code></pre></td></tr></table></figure>
<p>其实就是通过 <code>CoreGraphics</code> 在子线程中生成一个位图,然后进入主队列给 YYAsyncLayer 的 contents 赋值 CGImage 由 GPU 渲染过后提交到显示系统。</p>
<h5 id="及时的结束无用的绘制"><a href="#及时的结束无用的绘制" class="headerlink" title="及时的结束无用的绘制"></a>及时的结束无用的绘制</h5><p>前面提到的 <code>YYSentinel</code> 的作用已经说明。</p>
<p>在适当的地方进行自增value,然后在绘制过程中对比value是否发生变化来判断是否取消绘制操作。</p>
<p>很巧妙的实现。</p>
<h5 id="异步线程的管理"><a href="#异步线程的管理" class="headerlink" title="异步线程的管理"></a>异步线程的管理</h5><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_queue_t</span> YYAsyncLayerGetDisplayQueue() {<br><span class="hljs-meta">#<span class="hljs-meta-keyword">ifdef</span> YYDispatchQueuePool_h</span><br> <span class="hljs-keyword">return</span> YYDispatchQueueGetForQOS(<span class="hljs-built_in">NSQualityOfServiceUserInitiated</span>);<br><span class="hljs-meta">#<span class="hljs-meta-keyword">else</span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MAX_QUEUE_COUNT 16</span><br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> queueCount;<br> <span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_queue_t</span> queues[MAX_QUEUE_COUNT];<br> <span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_once_t</span> onceToken;<br> <span class="hljs-keyword">static</span> int32_t counter = <span class="hljs-number">0</span>;<br> <span class="hljs-built_in">dispatch_once</span>(&onceToken, ^{<br> queueCount = (<span class="hljs-keyword">int</span>)[<span class="hljs-built_in">NSProcessInfo</span> processInfo].activeProcessorCount;<br> queueCount = queueCount < <span class="hljs-number">1</span> ? <span class="hljs-number">1</span> : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;<br> <span class="hljs-keyword">if</span> ([<span class="hljs-built_in">UIDevice</span> currentDevice].systemVersion.floatValue >= <span class="hljs-number">8.0</span>) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-built_in">NSUInteger</span> i = <span class="hljs-number">0</span>; i < queueCount; i++) {<br> dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, <span class="hljs-number">0</span>);<br> queues[i] = dispatch_queue_create(<span class="hljs-string">"com.ibireme.yykit.render"</span>, attr);<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-built_in">NSUInteger</span> i = <span class="hljs-number">0</span>; i < queueCount; i++) {<br> queues[i] = dispatch_queue_create(<span class="hljs-string">"com.ibireme.yykit.render"</span>, DISPATCH_QUEUE_SERIAL);<br> dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="hljs-number">0</span>));<br> }<br> }<br> });<br> int32_t cur = OSAtomicIncrement32(&counter);<br> <span class="hljs-keyword">if</span> (cur < <span class="hljs-number">0</span>) cur = -cur;<br> <span class="hljs-keyword">return</span> queues[(cur) % queueCount];<br><span class="hljs-meta">#<span class="hljs-meta-keyword">undef</span> MAX_QUEUE_COUNT</span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span><br>}<br></code></pre></td></tr></table></figure>
<h6 id="要点-1-:串行队列数量和处理器数量相同"><a href="#要点-1-:串行队列数量和处理器数量相同" class="headerlink" title="要点 1 :串行队列数量和处理器数量相同"></a>要点 1 :串行队列数量和处理器数量相同</h6><p>一个 n 核设备同一时刻最多能 并行 执行 n 个任务,也就是最多有 n 个线程是相互不竞争 CPU 资源的。</p>
<p>开辟的线程过多,超过了处理数量,实际上某些并行的线程之间就可能竞争同一个处理器的资源,频繁的切换上下文也会消耗处理器资源。</p>
<p>而串行队列中只有一个线程,该框架中,作者使用和处理器相同数量的串行队列来轮询处理异步任务,有效的减少了线程调度操作。</p>
<h6 id="要点-2-:创建串行队列,设置优先级"><a href="#要点-2-:创建串行队列,设置优先级" class="headerlink" title="要点 2 :创建串行队列,设置优先级"></a>要点 2 :创建串行队列,设置优先级</h6><p>在 8.0 以上的系统,队列的优先级为 QOS_CLASS_USER_INITIATED,低于用户交互相关的 QOS_CLASS_USER_INTERACTIVE。</p>
<p>在 8.0 以下的系统,通过 dispatch_set_target_queue() 函数设置优先级为 DISPATCH_QUEUE_PRIORITY_DEFAULT (第二个参数如果使用串行队列会强行将我们创建的所有线程串行执行任务)。</p>
<p>可以猜测主队列的优先级是大于或等于 QOS_CLASS_USER_INTERACTIVE的,让这些串行队列的优先级低于主队列,避免框架创建的线程和主线程竞争资源。</p>
<h6 id="要点-3-:轮询返回队列"><a href="#要点-3-:轮询返回队列" class="headerlink" title="要点 3 :轮询返回队列"></a>要点 3 :轮询返回队列</h6><p>使用原子自增函数 OSAtomicIncrement32() 对局部静态变量 counter进行自增,然后通过取模运算轮询返回队列。</p>
<p>注意这里使用了一个判断:if (cur < 0) cur = -cur;,当 cur 自增越界时就会变为负数最大值(在二进制层面,是用正整数的反码加一来表示其负数的)。</p>
<h6 id="为什么要使用-n-个串行队列实现并发"><a href="#为什么要使用-n-个串行队列实现并发" class="headerlink" title="为什么要使用 n 个串行队列实现并发"></a>为什么要使用 n 个串行队列实现并发</h6><p>为什么这里需要使用 n 个串行队列来调度,而不用一个并行队列。</p>
<p>主要是因为并行队列无法精确的控制线程数量,很有可能创建过多的线程,导致 CPU 线程调度过于频繁,影响交互性能。</p>
<p>可能会想到用信号量 (dispatch_semaphore_t) 来控制并发,然而这样只能控制并发的任务数量,而不能控制线程数量,并且使用起来不是很优雅。而使用串行队列就很简单了,我们可以很明确的知道自己创建的线程数量,一切皆在掌控之中。</p>
<h3 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h3><p><a class="link" href="https://www.jianshu.com/p/154451e4bd42" >YYAsyncLayer 源码剖析:异步绘制<i class="fas fa-external-link-alt"></i></a></p>
]]></content>
<tags>
<tag>源码学习</tag>
</tags>
</entry>
<entry>
<title>GCD研究</title>
<url>/2018/07/08/GCD%E7%A0%94%E7%A9%B6/</url>
<content><![CDATA[<p>[TOC]</p>
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务<br>多线程技术可以提高程序的执行效率</p>
<h5 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h5><p>显示\刷新UI界面、处理UI事件(比如点击事件、滚动事件、拖拽事件等)在主线程执行。</p>
<p>耗时操作,开启子线程执行。</p>
<h5 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h5><p>同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。</p>
<p>如果线程非常多,CPU会在N多线程之间调度,会消耗大量CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。<br>因此我们一般只开3-5条线程。</p>
<h5 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h5><p>能适当提高程序的执行效率<br>能适当提高资源利用率(CPU、内存利用率)</p>
<h5 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h5><p>创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间<br>如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。<br>程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题。</p>
<h3 id="多线程方案"><a href="#多线程方案" class="headerlink" title="多线程方案"></a>多线程方案</h3><p>在iOS中多线程方案有多中,但最常用的属GCD和NSOperation,然后NSThread、pthread很少使用。本文主要研究GCD这块内容。</p>
<h3 id="GCD"><a href="#GCD" class="headerlink" title="GCD"></a>GCD</h3><figure class="highlight routeros"><table><tr><td class="code"><pre><code class="hljs routeros">Grand Central Dispatch (GCD), contains language features, runtime libraries, <span class="hljs-keyword">and</span><span class="hljs-built_in"> system </span>enhancements that provide systemic, comprehensive improvements <span class="hljs-keyword">to</span> the support <span class="hljs-keyword">for</span> concurrent code execution on multicore<span class="hljs-built_in"> hardware </span><span class="hljs-keyword">in</span> macOS, iOS, watchOS, <span class="hljs-keyword">and</span> tvOS.<br><br>The BSD subsystem, Core Foundation, <span class="hljs-keyword">and</span> Cocoa APIs have all been extended <span class="hljs-keyword">to</span> use these enhancements <span class="hljs-keyword">to</span> help both the<span class="hljs-built_in"> system </span><span class="hljs-keyword">and</span> your application <span class="hljs-keyword">to</span> <span class="hljs-builtin-name">run</span> faster, more efficiently, <span class="hljs-keyword">and</span> with improved responsiveness. Consider how difficult it is <span class="hljs-keyword">for</span> a single application <span class="hljs-keyword">to</span> use multiple cores effectively, let alone <span class="hljs-keyword">to</span> <span class="hljs-keyword">do</span> it on different computers with different numbers of computing cores <span class="hljs-keyword">or</span> <span class="hljs-keyword">in</span> an environment with multiple applications competing <span class="hljs-keyword">for</span> those cores. GCD, operating at the<span class="hljs-built_in"> system </span>level, can better accommodate the needs of all running applications, matching them <span class="hljs-keyword">to</span> the available<span class="hljs-built_in"> system </span>resources <span class="hljs-keyword">in</span> a balanced fashion.<br></code></pre></td></tr></table></figure>
<p>GCD全称为Grand Central Dispatch,包含语言功能,运行时库和系统增强功能,这些功能提供了系统的,全面的改进,以支持在macOS,iOS,watchOS和tvOS中的多核硬件上并发代码执行的支持。</p>
<p>BSD子系统,Core Foundation和Cocoa API均已扩展为使用这些增强功能,以帮助系统和您的应用程序更快,更高效地运行,并提高响应速度。单个应用程序有效地使用多个内核很困难,在具有不同数量计算内核的不同计算机上或在多个应用程序竞争那些内核的环境中进行操作就更困难了。在系统级别运行的GCD可以更好地满足所有正在运行的应用程序的需求,并以平衡的方式将它们与可用的系统资源进行匹配。</p>
<h5 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h5><h6 id="串行"><a href="#串行" class="headerlink" title="串行"></a>串行</h6><p>让任务串行执行(一个任务执行完毕后,再执行下一个任务)</p>
<h6 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h6><p>让多个任务并发执行(自动开启多个线程同时执行任务)<br>并发功能只有在异步(dispatch_async)函数下才有效</p>
<h6 id="dispatch-queue-set-specific"><a href="#dispatch-queue-set-specific" class="headerlink" title="dispatch_queue_set_specific"></a>dispatch_queue_set_specific</h6><p>向指定队列里面设置一个标识</p>
<p>向queue1对了中设置一个queueKey1标识:</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml">dispatch<span class="hljs-constructor">_queue_set_specific(<span class="hljs-params">queue1</span>, <span class="hljs-params">queueKey1</span>, &<span class="hljs-params">queueKey1</span>,NULL)</span>;<br></code></pre></td></tr></table></figure>
<h6 id="dispatch-queue-get-specific"><a href="#dispatch-queue-get-specific" class="headerlink" title="dispatch_queue_get_specific"></a>dispatch_queue_get_specific</h6><p>是获取指定调度队列的上下文键/值数据。</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"> <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> * queueKey = <span class="hljs-string">"queueKey"</span>;<br> <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> * queueKey2 = <span class="hljs-string">"queueKey2"</span>;<br> <span class="hljs-built_in">dispatch_queue_t</span> queue = dispatch_queue_create(queueKey, <span class="hljs-literal">NULL</span>);<br> <span class="hljs-built_in">dispatch_queue_t</span> queue2 = dispatch_queue_create(queueKey2, <span class="hljs-literal">NULL</span>);<br> <br> <span class="hljs-comment">//调用此方法会触发queueFunction函数,留个疑问queueFunction是在什么时候触发?</span><br> dispatch_queue_set_specific(queue, queueKey, &queueKey, queueFunction);<br> dispatch_queue_set_specific(queue2, queueKey2, &queueKey2, <span class="hljs-literal">NULL</span>);<br> <br> <span class="hljs-built_in">dispatch_sync</span>(queue, ^{<br> go();<br> });<br> <span class="hljs-built_in">dispatch_sync</span>(queue2, ^{<br> go();<br> });<br> <br> <span class="hljs-keyword">if</span> (dispatch_queue_get_specific(queue, queueKey)) {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"__run in queue"</span>);<br> }<br><br> <span class="hljs-comment">//main queue中找不到queueKey,所以这段Log不会触发,使用dispatch_get_specific(queueKey)的原理也一样</span><br> <span class="hljs-keyword">if</span> (dispatch_queue_get_specific(dispatch_get_main_queue(), queueKey)) {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"__run in main queue"</span>);<br> }<br> <span class="hljs-keyword">if</span> (dispatch_get_specific(queueKey)) {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"__run in main queue"</span>);<br> }<br> <br> <br><span class="hljs-keyword">void</span> go() {<br> <span class="hljs-comment">//使用dispatch_sync改变了当前的执行队列,所以这里可以检索到queueKey</span><br> <span class="hljs-keyword">if</span>(dispatch_get_specific(<span class="hljs-string">"queueKey"</span>)) {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"queue"</span>);<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(dispatch_get_specific(<span class="hljs-string">"queueKey2"</span>)) {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"queue2"</span>);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"main queue"</span>);<br> }<br>}<br><br><span class="hljs-keyword">void</span> queueFunction() {<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"__queueFunction"</span>);<br>} <br></code></pre></td></tr></table></figure>
<h6 id="dispatch-get-specific"><a href="#dispatch-get-specific" class="headerlink" title="dispatch_get_specific"></a>dispatch_get_specific</h6><p>在当前队列中取出标识</p>
<p>注意iOS中线程和队列的关系,所有的动作都是在队列中执行的!</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-meta">#import <span class="hljs-meta-string"><Foundation/Foundation.h></span></span><br> <br><span class="hljs-keyword">int</span> main(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> * argv[]) {<br> <span class="hljs-keyword">@autoreleasepool</span> {<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> *queueKey1 = <span class="hljs-string">"queueKey1"</span>;<br> <br> <span class="hljs-built_in">dispatch_queue_t</span> queue1 = dispatch_queue_create(queueKey1, DISPATCH_QUEUE_SERIAL);<br> dispatch_queue_set_specific(queue1, queueKey1, &queueKey1, <span class="hljs-literal">NULL</span>);<br> <br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"1. 当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> <br> <span class="hljs-keyword">if</span> (dispatch_get_specific(queueKey1)) {<br> <span class="hljs-comment">//当前队列是主队列,不是queue1队列,所以取不到queueKey1对应的值,故而不执行</span><br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"2. 当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">1</span>];<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"3. 当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">1</span>];<br> }<br> <br> <span class="hljs-built_in">dispatch_sync</span>(queue1, ^{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"4. 当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">1</span>];<br> <br> <span class="hljs-keyword">if</span> (dispatch_get_specific(queueKey1)) {<br> <span class="hljs-comment">//当前队列是queue1队列,所以能取到queueKey1对应的值,故而执行</span><br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"5. 当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">1</span>];<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"6. 当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">1</span>];<br> }<br> });<br> <span class="hljs-built_in">dispatch_async</span>(queue1, ^{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"7. t当前线程是: %@, 当前队列是: %@ 。"</span>,[<span class="hljs-built_in">NSThread</span> currentThread],dispatch_get_current_queue());<br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">1</span>];<br> });<br> <br> [<span class="hljs-built_in">NSThread</span> sleepForTimeInterval:<span class="hljs-number">5</span>];<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br></code></pre></td></tr></table></figure>
<p>输出结果:</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">2016</span>-<span class="hljs-number">02</span>-<span class="hljs-number">19</span> <span class="hljs-number">14</span>:<span class="hljs-number">31</span>:<span class="hljs-number">23</span>.<span class="hljs-number">390</span> gcd[<span class="hljs-number">96865</span>:<span class="hljs-number">820267</span>] <span class="hljs-number">1</span>.当前线程是: <NSThread: <span class="hljs-number">0</span>x<span class="hljs-number">1001053</span>e<span class="hljs-number">0</span>>{number = <span class="hljs-number">1</span>, name = main},当前队列是: <OS_dispatch_queue: com.apple.main-thread[<span class="hljs-number">0</span>x<span class="hljs-number">100059</span>ac<span class="hljs-number">0</span>]>。<br><br><span class="hljs-attribute">2016</span>-<span class="hljs-number">02</span>-<span class="hljs-number">19</span> <span class="hljs-number">14</span>:<span class="hljs-number">31</span>:<span class="hljs-number">23</span>.<span class="hljs-number">391</span> gcd[<span class="hljs-number">96865</span>:<span class="hljs-number">820267</span>] <span class="hljs-number">3</span>.当前线程是: <NSThread: <span class="hljs-number">0</span>x<span class="hljs-number">1001053</span>e<span class="hljs-number">0</span>>{number = <span class="hljs-number">1</span>, name = main},当前队列是: <OS_dispatch_queue: com.apple.main-thread[<span class="hljs-number">0</span>x<span class="hljs-number">100059</span>ac<span class="hljs-number">0</span>]>。<br><br><span class="hljs-attribute">2016</span>-<span class="hljs-number">02</span>-<span class="hljs-number">19</span> <span class="hljs-number">14</span>:<span class="hljs-number">31</span>:<span class="hljs-number">24</span>.<span class="hljs-number">396</span> gcd[<span class="hljs-number">96865</span>:<span class="hljs-number">820267</span>] <span class="hljs-number">4</span>.当前线程是: <NSThread: <span class="hljs-number">0</span>x<span class="hljs-number">1001053</span>e<span class="hljs-number">0</span>>{number = <span class="hljs-number">1</span>, name = main},当前队列是: <OS_dispatch_queue: queueKey<span class="hljs-number">1</span>[<span class="hljs-number">0</span>x<span class="hljs-number">103000000</span>]>。<br><br><span class="hljs-attribute">2016</span>-<span class="hljs-number">02</span>-<span class="hljs-number">19</span> <span class="hljs-number">14</span>:<span class="hljs-number">31</span>:<span class="hljs-number">25</span>.<span class="hljs-number">400</span> gcd[<span class="hljs-number">96865</span>:<span class="hljs-number">820267</span>] <span class="hljs-number">5</span>.当前线程是: <NSThread: <span class="hljs-number">0</span>x<span class="hljs-number">1001053</span>e<span class="hljs-number">0</span>>{number = <span class="hljs-number">1</span>, name = main},当前队列是: <OS_dispatch_queue: queueKey<span class="hljs-number">1</span>[<span class="hljs-number">0</span>x<span class="hljs-number">103000000</span>]>。<br><br><span class="hljs-attribute">2016</span>-<span class="hljs-number">02</span>-<span class="hljs-number">19</span> <span class="hljs-number">14</span>:<span class="hljs-number">31</span>:<span class="hljs-number">26</span>.<span class="hljs-number">402</span> gcd[<span class="hljs-number">96865</span>:<span class="hljs-number">820367</span>] <span class="hljs-number">7</span>. t当前线程是: <NSThread: <span class="hljs-number">0</span>x<span class="hljs-number">100105</span>e<span class="hljs-number">10</span>>{number = <span class="hljs-number">2</span>, name = (null)},当前队列是: <OS_dispatch_queue: queueKey<span class="hljs-number">1</span>[<span class="hljs-number">0</span>x<span class="hljs-number">103000000</span>]>。<br><br><span class="hljs-attribute">Program</span> ended with exit code: <span class="hljs-number">0</span> <br></code></pre></td></tr></table></figure>
<h5 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h5><h6 id="同步"><a href="#同步" class="headerlink" title="同步"></a>同步</h6><p>在当前线程中执行任务,不具备开启新线程的能力。</p>
<p>注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)</p>
<p>demo:</p>
<p>无论怎样都不会开启新线程</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-comment">/*</span><br><span class="hljs-comment"> 同步+并发 不会开启新线程,依旧在当前线程执行任务</span><br><span class="hljs-comment"> */</span><br>- (<span class="hljs-keyword">void</span>)test3 {<br> <span class="hljs-built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="hljs-string">"syncConcrrent"</span>, DISPATCH_QUEUE_CONCURRENT);<br> <span class="hljs-built_in">dispatch_sync</span>(queue, ^{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"1 %@"</span>, [<span class="hljs-built_in">NSThread</span> currentThread]);<br> });<br> <br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> 2019-12-20 16:22:30.890416+0800 threadDemo[30526:6608103] 1 <NSThread: 0x600001b9c680>{number = 1, name = main}</span><br><span class="hljs-comment"> */</span><br>}<br><br></code></pre></td></tr></table></figure>
<p>线程死锁</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec">- (<span class="hljs-keyword">void</span>)test1 {<br> <span class="hljs-comment">// dispatch_async 开启的新线程,在队列 queue 中 ,往queue队列添加同步任务执行,线程卡死</span><br> <span class="hljs-built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="hljs-string">"asyncSerial"</span>, DISPATCH_QUEUE_SERIAL);<br> <br> <span class="hljs-built_in">dispatch_async</span>(queue, ^{ <br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"test1 start%@"</span>, [<span class="hljs-built_in">NSThread</span> currentThread]);<br> <span class="hljs-comment">//Thread 3: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)</span><br> <span class="hljs-built_in">dispatch_sync</span>(queue, ^{<br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"test1 %@"</span>, [<span class="hljs-built_in">NSThread</span> currentThread]);<br> });<br> <br> <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"test1 end%@"</span>, [<span class="hljs-built_in">NSThread</span> currentThread]);<br> });<br> <br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> 2019-12-20 09:56:54.932893+0800 threadDemo[11409:6405262] test1 start<NSThread: 0x600002235e40>{number = 5, name = (null)}</span><br><span class="hljs-comment"> </span><br><span class="hljs-comment"> 只打印了第一条。后面出现线程死锁了。</span><br><span class="hljs-comment"> </span><br><span class="hljs-comment"> 卡住原因:</span><br><span class="hljs-comment"> 1、串行队列,任务需要一个任务执行完毕接着下一个才执行</span><br><span class="hljs-comment"> 2、现在队列queue要执行 dispatch_sync 函数添加一个同步任务block</span><br><span class="hljs-comment"> 3、dispatch_sync 是同步的,需要将添加的任务block立即执行</span><br><span class="hljs-comment"> 4、此时调用 dispatch_sync 函数所在线程处于等待状态,需要block任务执行才继续往后执行,而执行 dispatch_sync 函数的线程与block执行所在线程是同一个线程,所以这个线程一直处于等待状态。不会往后执行,也不会执行block。</span><br><span class="hljs-comment"> */</span><br>}<br><br>``` <br><br>YYKit库中应用<br></code></pre></td></tr></table></figure>
<p>/**<br> YYKit库中,在SDWebImage中也有类似的应用</p>
<p> 用于确保任务在主线程下执行<br> <em>/<br>static inline void _yy_dispatch_sync_on_main_queue(void (^block)(void)) {<br> if (pthread_main_np()) {<br> block();<br> } else {<br> dispatch_sync(dispatch_get_main_queue(), block);<br> }<br>}<br><figure class="highlight plain"><table><tr><td class="code"><pre><code class="hljs plain"><br>AFNetworking库中应用<br></code></pre></td></tr></table></figure><br>/*</em><br> AFNetworking库中</p>
<p> AFURLSessionManager 中session创建时用到:<br> 由于session创建在iOS8之前是线程不安全的,所以使用同步+串行队列实现锁的功能</p>
<p> 注意:当前队列与同步函数中任务的队列不是一个队列<br> */<br>// static void url_session_manager_create_task_safely(dispatch_block_t block) {<br>// if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {<br>// // Fix of bug<br>// // Open Radar:<a class="link" href="http://openradar.appspot.com/radar?id=5871104061079552" >http://openradar.appspot.com/radar?id=5871104061079552<i class="fas fa-external-link-alt"></i></a> (status: Fixed in iOS8)<br>// // Issue about:<a class="link" href="https://github.com/AFNetworking/AFNetworking/issues/2093" >https://github.com/AFNetworking/AFNetworking/issues/2093<i class="fas fa-external-link-alt"></i></a><br>// dispatch_sync(url_session_manager_creation_queue(), block);<br>// } else {<br>// block();<br>// }<br>// }</p>
<figure class="highlight clean"><table><tr><td class="code"><pre><code class="hljs clean"><br>###### 异步<br><br>在新的线程中执行任务,具备开启新线程的能力<br><br>demo:<br><br>在主队列,异步执行任务,不会开启新线程<br></code></pre></td></tr></table></figure>
<ul>
<li><p>(void)test1 {<br> dispatch_queue_t queue = dispatch_get_main_queue();</p>
<p> dispatch_async(queue, ^{ NSLog(@”1 %@”, [NSThread currentThread]); });<br> dispatch_async(queue, ^{ NSLog(@”2 %@”, [NSThread currentThread]); });<br> dispatch_async(queue, ^{ NSLog(@”3 %@”, [NSThread currentThread]); });</p>
<p> /**</p>
<pre><code> 2019-12-18 11:50:33.358149+0800 threadDemo[4613:5504490] 1 <NSThread: 0x600001cde1c0>{number = 1, name = main}
2019-12-18 11:50:33.358549+0800 threadDemo[4613:5504490] 2 <NSThread: 0x600001cde1c0>{number = 1, name = main}
2019-12-18 11:50:33.358696+0800 threadDemo[4613:5504490] 3 <NSThread: 0x600001cde1c0>{number = 1, name = main}
</code></pre><p> */<br>}</p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><code class="hljs plain"><br>除主队列外,异步执行任务,都会开启新线程<br><br></code></pre></td></tr></table></figure>
<ul>
<li><p>(void)test2 {<br> dispatch_queue_t queue1= dispatch_queue_create(“asyncSerial”, DISPATCH_QUEUE_SERIAL);<br> dispatch_async(queue1, ^{ NSLog(@”1-1 %@”, [NSThread currentThread]); });<br> dispatch_async(queue1, ^{ NSLog(@”1-2 %@”, [NSThread currentThread]); });<br> dispatch_async(queue1, ^{ NSLog(@”1-3 %@”, [NSThread currentThread]); });</p>
<p> /**</p>
<pre><code> 2019-12-18 11:53:59.972777+0800 threadDemo[4656:5507191] 1 <NSThread: 0x6000012fd380>{number = 3, name = (null)}
2019-12-18 11:53:59.973284+0800 threadDemo[4656:5507191] 2 <NSThread: 0x6000012fd380>{number = 3, name = (null)}
2019-12-18 11:53:59.973437+0800 threadDemo[4656:5507191] 3 <NSThread: 0x6000012fd380>{number = 3, name = (null)}
</code></pre><p> */</p>
</li>
</ul>
<p>// dispatch_queue_t queue2 = dispatch_queue_create(“asyncConcurrent”, DISPATCH_QUEUE_CONCURRENT);<br>// dispatch_async(queue2, ^{ NSLog(@”2-1 %@”, [NSThread currentThread]); });<br>// dispatch_async(queue2, ^{ NSLog(@”2-2 %@”, [NSThread currentThread]); });<br>// dispatch_async(queue2, ^{ NSLog(@”2-3 %@”, [NSThread currentThread]); });</p>
<pre><code>/**
2019-12-18 11:57:09.136050+0800 threadDemo[4686:5509014] 2-2 <NSThread: 0x600002350000>{number = 6, name = (null)}
2019-12-18 11:57:09.136154+0800 threadDemo[4686:5509013] 2-1 <NSThread: 0x60000237bd00>{number = 5, name = (null)}
2019-12-18 11:57:09.136174+0800 threadDemo[4686:5509015] 2-3 <NSThread: 0x60000237b480>{number = 4, name = (null)}
*/
</code></pre><p>}<br><figure class="highlight clean"><table><tr><td class="code"><pre><code class="hljs clean"><br>##### Dispatch Group<br><br>[参考](https:<span class="hljs-comment">//xiaozhuanlan.com/topic/0863519247)</span><br> <br>dispatch_group可以将GCD的任务合并到一个组里来管理,也可以同时监听组里所有任务的执行情况。<br> <br>###### dispatch_group_create<br><br>本质是一个初始value为LONG_MAX的semaphore,通过信号量来实现一组任务的管理,代码如下:<br><br></code></pre></td></tr></table></figure><br>dispatch_group_t dispatch_group_create(void) {<br> //申请内存空间<br> dispatch_group_t dg = (dispatch_group_t)_dispatch_alloc(<br> DISPATCH_VTABLE(group), sizeof(struct dispatch_semaphore_s));<br> //使用LONG_MAX初始化信号量结构体<br> _dispatch_semaphore_init(LONG_MAX, dg);<br> return dg;<br>}<br><figure class="highlight clean"><table><tr><td class="code"><pre><code class="hljs clean"><br>###### dispatch_group_async<br> <br>加入组的任务异步执行 <br><br>###### dispatch_group_enter / leave <br><br>dispatch_group_enter的逻辑是将dispatch_group_t转换成dispatch_semaphore_t后将dsema_value的值减一。<br> <br>应用注意:<br><span class="hljs-number">1</span>、dispatch_group_enter必须在dispatch_group_leave之前出现<br><span class="hljs-number">2</span>、dispatch_group_enter和dispatch_group_leave必须成对出现<br><br>demo:<br><br></code></pre></td></tr></table></figure></p>
<ul>
<li>(void)test1 {<br> NSLog(@”1:%@”,[NSThread currentThread]);<br> dispatch_group_t group = dispatch_group_create();<br> dispatch_queue_t queue = dispatch_queue_create(“concurrentQueue”, DISPATCH_QUEUE_CONCURRENT);</li>
</ul>
<p>// //注意不要放在这,而应该放在dispatch_group_enter / leave 最后面。<br>// //否则可额能计数不对,导致提前回调<br>// dispatch_group_notify(group, queue, ^{<br>// NSLog(@”2”);<br>// });</p>
<pre><code>dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"3:%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"4:%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"2:%@",[NSThread currentThread]);
});
NSLog(@"5:%@",[NSThread currentThread]);
/**
2019-12-18 16:03:42.575564+0800 threadDemo[5518:5610267] 1:<NSThread: 0x600001c085c0>{number = 1, name = main}
2019-12-18 16:03:42.576226+0800 threadDemo[5518:5610267] 5:<NSThread: 0x600001c085c0>{number = 1, name = main}
2019-12-18 16:03:43.578856+0800 threadDemo[5518:5610368] 3:<NSThread: 0x600001c4ffc0>{number = 4, name = (null)}
2019-12-18 16:03:45.579819+0800 threadDemo[5518:5610366] 4:<NSThread: 0x600001c4b580>{number = 6, name = (null)}
2019-12-18 16:03:45.580037+0800 threadDemo[5518:5610366] 2:<NSThread: 0x600001c4b580>{number = 6, name = (null)}
*/
</code></pre><p>}<br><figure class="highlight clean"><table><tr><td class="code"><pre><code class="hljs clean"><br>###### dispatch_group_wait<br><br>同步等待,直到group里面的block全部执行完毕,才会继续往后执行。<br><br>需要注意下 dispatch_group_wait 的位置,不能放在任务添加之前。<br><br>demo:<br><br></code></pre></td></tr></table></figure></p>
<ul>
<li><p>(void)test3 {<br> dispatch_queue_t aDQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);<br> dispatch_group_t group = dispatch_group_create();<br> // Add a task to the group<br> dispatch_group_async(group, aDQueue, ^{</p>
<pre><code> sleep(2);
printf("task 1 \n");
</code></pre><p> });<br> dispatch_group_async(group, aDQueue, ^{</p>
<pre><code> printf("task 2 \n");
</code></pre><p> });</p>
<p> printf(“wait 1 2 \n”);<br> //同步等待,直到group里面的block全部执行完毕,才会继续往后执行。<br> dispatch_group_wait(group, DISPATCH_TIME_FOREVER);<br> printf(“task 1 2 finished \n”);</p>
<p> /**</p>
<pre><code> wait 1 2
task 2
task 1
task 1 2 finished
</code></pre><p> */<br>}</p>
<figure class="highlight clean"><table><tr><td class="code"><pre><code class="hljs clean"><br>###### dispatch_group_notify<br><br>队列组拦截通知模块(内部本身是异步执行的,不会阻塞线程) <br><br>需要注意下 dispatch_group_notify 的位置,不能放在任务添加之前。<br><br>demo:<br><br></code></pre></td></tr></table></figure>
</li>
<li><p>(void)test2 {<br> NSLog(@”1:%@”,[NSThread currentThread]);<br> // 创建队列<br> dispatch_queue_t queue = dispatch_get_global_queue(0, 0);<br> // 创建队列组<br> dispatch_group_t group = dispatch_group_create();</p>
<p> /*<br> // 不要写在这,没意义,我们都知道他在组内所有的任务执行完毕会调用 dispatch_group_notify 中的回调块。<br> // 但是后面还有一句,当组内没有任务时,dispatch_group_notify 中的回调块也会立即执行。<br> // 队列组拦截通知模块(内部本身是异步执行的,不会阻塞线程)<br> dispatch_group_notify(group, queue, ^{</p>
<pre><code> NSLog(@"4:%@",[NSThread currentThread]);
</code></pre><p> });</p>
<p> 2019-12-18 16:07:54.677923+0800 threadDemo[5581:5613688] 1:<NSThread: 0x6000035c57c0>{number = 1, name = main}<br> 2019-12-18 16:07:54.678160+0800 threadDemo[5581:5613816] 4:<NSThread: 0x600003595cc0>{number = 6, name = (null)}<br> 2019-12-18 16:07:55.678663+0800 threadDemo[5581:5613688] 5:<NSThread: 0x6000035c57c0>{number = 1, name = main}<br> 2019-12-18 16:07:56.681079+0800 threadDemo[5581:5613812] 2:<NSThread: 0x6000035995c0>{number = 5, name = (null)}<br> 2019-12-18 16:07:57.680645+0800 threadDemo[5581:5613815] 3:<NSThread: 0x60000359f300>{number = 3, name = (null)}<br> */</p>
<p> //队列组异步执行任务<br> dispatch_group_async(group, queue, ^{</p>
<pre><code> sleep(2);
NSLog(@"2:%@",[NSThread currentThread]);
</code></pre><p> });<br> dispatch_group_async(group, queue, ^{</p>
<pre><code> sleep(3);
NSLog(@"3:%@",[NSThread currentThread]);
</code></pre><p> });<br> // 队列组拦截通知模块(内部本身是异步执行的,不会阻塞线程)<br> dispatch_group_notify(group, queue, ^{</p>
<pre><code> NSLog(@"4:%@",[NSThread currentThread]);
</code></pre><p> });</p>
<p> sleep(1);<br> NSLog(@”5:%@”,[NSThread currentThread]);</p>
<p> /<em><br> 2019-12-18 16:06:55.380553+0800 threadDemo[5558:5612492] 1:<NSThread: 0x600003d60800>{number = 1, name = main}<br> 2019-12-18 16:06:56.381177+0800 threadDemo[5558:5612492] 5:<NSThread: 0x600003d60800>{number = 1, name = main}<br> 2019-12-18 16:06:57.384415+0800 threadDemo[5558:5612611] 2:<NSThread: 0x600003d30380>{number = 5, name = (null)}<br> 2019-12-18 16:06:58.385496+0800 threadDemo[5558:5612614] 3:<NSThread: 0x600003d351c0>{number = 3, name = (null)}<br> 2019-12-18 16:06:58.385737+0800 threadDemo[5558:5612614] 4:<NSThread: 0x600003d351c0>{number = 3, name = (null)}
</em>/<br>}</p>
<figure class="highlight clean"><table><tr><td class="code"><pre><code class="hljs clean"><br>##### dispatch_block<br><br>[dispatch_block](https:<span class="hljs-comment">//www.cnblogs.com/KobeLuo/p/6464233.html)</span><br><br>[dispatch_block](https:<span class="hljs-comment">//www.jianshu.com/p/5a16dfd36fad)</span><br><br><br>##### dispatch_semaphore<br> 信号量,有三个方法:<br> <br>###### dispatch_semaphore_create<br><br>dispatch_semaphore_t dispatch_semaphore_create(long value);<br> 创建一个信号量,信号量的值为入参 value<br><br>###### dispatch_semaphore_wait<br><br>long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);<br> 接收一个信号和时间值,若信号的信号量为<span class="hljs-number">0</span>,则会阻塞当前线程,直到信号量大于<span class="hljs-number">0</span>或者经过输入的时间值;<br> 若信号量大于<span class="hljs-number">0</span>,则会使信号量减<span class="hljs-number">1</span>并返回,程序继续住下执行<br><br>###### dispatch_semaphore_signal<br><br>long dispatch_semaphore_signal(dispatch_semaphore_t dsema);<br> 接收一个信号量,发送信号使信号量的值 +<span class="hljs-number">1</span>并返回<br> <br>###### 使用<br><br>应用场景:<br><br><span class="hljs-number">1.</span> 充当锁的功能<br><span class="hljs-number">2.</span> 异步任务,同步返回(同步获取指定APP在AppStore中的当前版本)<br><span class="hljs-number">3.</span> 并发控制(实现与NSOperationQueue中max-ConcurrentOperationCount 类似功能。)<br><br>demo:<br><br>并发控制<br><br></code></pre></td></tr></table></figure>
<p>@implementation SemaphoreMaxConcurrentCount {<br> dispatch_semaphore_t _semaphore;<br> dispatch_queue_t _queue;<br>}</p>
</li>
<li><p>(instancetype)init {<br> return [self initWithMaxConcurrentCount:3];;<br>}</p>
</li>
<li><p>(instancetype)initWithMaxConcurrentCount:(NSInteger)count {<br> if (self = [super init]) {</p>
<pre><code> if (count < 1) {
count = 3;
}
_semaphore = dispatch_semaphore_create(count);
_queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
</code></pre><p> }<br> return self;<br>}</p>
</li>
</ul>
<ul>
<li>(void)addTask:(TaskBlock)block {<br> dispatch_async(_queue, ^{<pre><code> dispatch_semaphore_wait(self->_semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
block();
dispatch_semaphore_signal(self->_semaphore);
});
</code></pre> });<br>}</li>
</ul>
<p>@end<br><figure class="highlight plain"><table><tr><td class="code"><pre><code class="hljs plain"><br>AFNetworking库中<br><br></code></pre></td></tr></table></figure><br>/**<br> AFNetworking库中</p>
<p> 获取session中完成的tasks。这个 与 <code>appVersionInAppStore:</code> 方法中的作用差不多。</p>
<p> 都是通过block获取数据,然后将数据直接返回出去,避免了其他地方获取task也要用回调的方式获取数据。</p>
<p> <em>/<br>//- (NSArray </em>)tasksForKeyPath:(NSString <em>)keyPath {<br>// __block NSArray </em>tasks = nil;<br>// dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);<br>// [self.session getTasksWithCompletionHandler:^(NSArray <em>dataTasks, NSArray </em>uploadTasks, NSArray *downloadTasks) {<br>// if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {<br>// tasks = dataTasks;<br>// } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {<br>// tasks = uploadTasks;<br>// } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {<br>// tasks = downloadTasks;<br>// } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {<br>// tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@”@unionOfArrays.self”];<br>// }<br>//<br>// dispatch_semaphore_signal(semaphore);<br>// }];<br>//<br>// dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);<br>//<br>// return tasks;<br>//}</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><code class="hljs plain"><br>充当锁的功能<br><br></code></pre></td></tr></table></figure>
<p>/**<br> 充当锁的功能</p>
<p> 当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;<br> 当在线程1NSLog这一行代码还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完NSLog并执行完dispatch_semaphore_signal使信号量为1后,线程2才能解除阻塞继续住下执行。<br> 这就可以保证同时只有一个线程执行NSLog这一行代码。</p>
<p> */</p>
<ul>
<li><p>(void)test1 {<br> dispatch_queue_t queue = dispatch_get_global_queue(0, 0);<br> dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);<br> for (int i = 0; i < 100; i++) {</p>
<pre><code> dispatch_async(queue, ^{
// 相当于加锁
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"i = %d semaphore = %@", i, semaphore);
// 相当于解锁
dispatch_semaphore_signal(semaphore);
});
</code></pre><p> }<br>}</p>
<figure class="highlight gcode"><table><tr><td class="code"><pre><code class="hljs gcode"><br>异步任务,同步返回<span class="hljs-comment">(同步获取指定APP在AppStore中的当前版本)</span><br><br></code></pre></td></tr></table></figure>
<p>/**<br>异步任务,同步返回</p>
<p>同步获取指定APP在AppStore中的当前版本<br>*/</p>
</li>
<li><p>(NSString <em>)appVersionInAppStore:(NSString </em>)appId {<br> __block NSString <em>appVersion = @””;<br> NSString </em>url = [NSString stringWithFormat:@”<a class="link" href="https://itunes.apple.com/lookup?id=%@",appId" >https://itunes.apple.com/lookup?id=%@",appId<i class="fas fa-external-link-alt"></i></a>];<br> NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];</p>
<p> dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);</p>
<p> NSURLSessionDataTask <em>dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData </em> _Nullable data, NSURLResponse <em> _Nullable response, NSError </em> _Nullable error) {</p>
<pre><code> NSError *err;
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingMutableContainers) error:&err];
if (!err) {
NSArray *results = jsonData[@"results"];
if ([results isKindOfClass:[NSArray class]] && results != nil && results.count > 0) {
appVersion = results.firstObject[@"version"];
}
}
dispatch_semaphore_signal(semaphore);
</code></pre><p> }];</p>
<p> [dataTask resume];</p>
<p> dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);</p>
<p> return appVersion;</p>
<figure class="highlight mipsasm"><table><tr><td class="code"><pre><code class="hljs mipsasm"><br><span class="hljs-comment">##### dispatch_barrier</span><br> <br>在一个并行队列中,有多个线程在执行多个任务,在这个并行队列中,有一个<span class="hljs-keyword">dispatch_barrier任务。这样会使所有在这个dispatch_barrier之后的任务总会等待barrier之前的所有任务结束之后,才会执行。</span><br><span class="hljs-keyword"></span><br><span class="hljs-keyword">dispatch_barrier </span>又分为 <span class="hljs-keyword">dispatch_barrier_sync </span>和 <span class="hljs-keyword">dispatch_barrier_async</span><br><span class="hljs-keyword"></span><br><span class="hljs-keyword">注意:</span><br><span class="hljs-keyword">1.barrier和串行队列配合是完全没有意义的。</span><br><span class="hljs-keyword">barrier的目的是为了在某种情况下,同一个队列中一些并发任务必须在另一些并发任务之后执行,所以需要一个类似于拦截的功能,迫使后执”</span><br><span class="hljs-keyword">“行的任务必须等待。那么,串行队列中的所有任务本身就是按照顺序执行的。</span><br><span class="hljs-keyword"></span><br><span class="hljs-keyword">2.在global </span>queue中使用<span class="hljs-keyword">barrier没有意义。</span><br><span class="hljs-keyword">barrier实现的基本条件是,要写在同一队列中。举个例子,你现在创建了两个并行队列,你在其中一个队列中插入了一个barrier任务,那么你不可能期待他可以在第二个队列中生效,对吧。同样的,每一次使用global </span>queue,系统分配给你的可能是不同的并行队列,你在其中插入一个<span class="hljs-keyword">barrier任务,没有意义。</span><br><span class="hljs-keyword"></span><br><span class="hljs-keyword">###### </span><span class="hljs-keyword">dispatch_barrier_sync</span><br><span class="hljs-keyword"></span><br><span class="hljs-keyword">dispatch_barrier_sync </span>中的任务同步执行,会阻塞当前线程<br><br><span class="hljs-symbol">demo:</span><br><br></code></pre></td></tr></table></figure></li>
<li><p>(void)test1 {<br> dispatch_queue_t queue = dispatch_queue_create(“asyncConcurrent”, DISPATCH_QUEUE_CONCURRENT);</p>
<p> for (int i = 0; i < 10; i++) {</p>
<pre><code> if (i % 2 == 0) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"dispatch_barrier_sync 之前的任务:%d",i);
});
}
else {
dispatch_async(queue, ^{
NSLog(@"dispatch_barrier_sync 之前的任务:%d",i);
});
}
</code></pre><p> }</p>
<p> dispatch_barrier_sync(queue, ^{</p>
<pre><code> NSLog(@"dispatch_barrier_sync 任务执行 %@", [NSThread currentThread]);
</code></pre><p> });</p>
<p> NSLog(@”dispatch_barrier_sync 所在线程 %@”, [NSThread currentThread]);</p>
<p> for (int i = 0; i < 10; i++) {</p>
<pre><code> if (i % 2 == 0) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"dispatch_barrier_sync 之后的任务:%d",i);
});
}
else {
dispatch_async(queue, ^{
NSLog(@"dispatch_barrier_sync 之后的任务:%d",i);
});
}
</code></pre><p> }</p>
<p> /<em>*<br> 2019-12-19 13:41:20.335598+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之前的任务:1<br> 2019-12-19 13:41:20.335702+0800 threadDemo[8655:6044001] dispatch_barrier_sync 之前的任务:3<br> 2019-12-19 13:41:20.336030+0800 threadDemo[8655:6044003] dispatch_barrier_sync 之前的任务:5<br> 2019-12-19 13:41:20.336205+0800 threadDemo[8655:6044004] dispatch_barrier_sync 之前的任务:7<br> 2019-12-19 13:41:20.336373+0800 threadDemo[8655:6044005] dispatch_barrier_sync 之前的任务:9<br> 2019-12-19 13:41:21.340593+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之前的任务:6<br> 2019-12-19 13:41:21.340593+0800 threadDemo[8655:6043998] dispatch_barrier_sync 之前的任务:0<br> 2019-12-19 13:41:21.340593+0800 threadDemo[8655:6044001] dispatch_barrier_sync 之前的任务:8<br> 2019-12-19 13:41:21.340593+0800 threadDemo[8655:6044002] dispatch_barrier_sync 之前的任务:4<br> 2019-12-19 13:41:21.340636+0800 threadDemo[8655:6044000] dispatch_barrier_sync 之前的任务:2<br> 2019-12-19 13:41:21.340931+0800 threadDemo[8655:6043503] dispatch_barrier_sync 任务执行 <NSThread: 0x6000014820c0>{number = 1, name = main}<br> 2019-12-19 13:41:21.341159+0800 threadDemo[8655:6043503] dispatch_barrier_sync 所在线程 <NSThread: 0x6000014820c0>{number = 1, name = main}<br> 2019-12-19 13:41:21.341339+0800 threadDemo[8655:6044002] dispatch_barrier_sync 之后的任务:1<br> 2019-12-19 13:41:21.341552+0800 threadDemo[8655:6043998] dispatch_barrier_sync 之后的任务:3<br> 2019-12-19 13:41:21.342288+0800 threadDemo[8655:6044002] dispatch_barrier_sync 之后的任务:5<br> 2019-12-19 13:41:21.342807+0800 threadDemo[8655:6044004] dispatch_barrier_sync 之后的任务:7<br> 2019-12-19 13:41:21.342890+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之后的任务:9<br> 2019-12-19 13:41:22.345189+0800 threadDemo[8655:6044001] dispatch_barrier_sync 之后的任务:2<br> 2019-12-19 13:41:22.345189+0800 threadDemo[8655:6044005] dispatch_barrier_sync 之后的任务:4<br> 2019-12-19 13:41:22.345227+0800 threadDemo[8655:6044003] dispatch_barrier_sync 之后的任务:6<br> 2019-12-19 13:41:22.345227+0800 threadDemo[8655:6044000] dispatch_barrier_sync 之后的任务:0<br> 2019-12-19 13:41:22.345237+0800 threadDemo[8655:6043998] dispatch_barrier_sync 之后的任务:8
</em>/<br>}</p>
<figure class="highlight dns"><table><tr><td class="code"><pre><code class="hljs dns"><br>###### dispatch_barrier_async<br> <br>dispatch_barrier_async 中的任务异步执行,不会阻塞当前线程<br><br>demo:<br><br>``` <br>- (void)test2 {<br> dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT)<span class="hljs-comment">;</span><br> <br> for (int i = <span class="hljs-number">0</span><span class="hljs-comment">; i < 10; i++) {</span><br> if (i % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) {<br> dispatch_async(queue, ^{<br> sleep(<span class="hljs-number">1</span>)<span class="hljs-comment">;</span><br> NSLog(@"dispatch_barrier_sync 之前的任务:%d",i)<span class="hljs-comment">;</span><br> })<span class="hljs-comment">;</span><br> }<br> else {<br> dispatch_async(queue, ^{<br> NSLog(@"dispatch_barrier_sync 之前的任务:%d",i)<span class="hljs-comment">;</span><br> })<span class="hljs-comment">;</span><br> }<br> }<br> <br> dispatch_barrier_async(queue, ^{<br> NSLog(@"dispatch_barrier_sync 任务执行 %@", [NSThread currentThread])<span class="hljs-comment">;</span><br> })<span class="hljs-comment">;</span><br> <br> NSLog(@"dispatch_barrier_async 所在线程 %@", [NSThread currentThread])<span class="hljs-comment">;</span><br> <br> for (int i = <span class="hljs-number">0</span><span class="hljs-comment">; i < 10; i++) {</span><br> if (i % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) {<br> dispatch_async(queue, ^{<br> sleep(<span class="hljs-number">1</span>)<span class="hljs-comment">;</span><br> NSLog(@"dispatch_barrier_sync 之后的任务:%d",i)<span class="hljs-comment">;</span><br> })<span class="hljs-comment">;</span><br> }<br> else {<br> dispatch_async(queue, ^{<br> NSLog(@"dispatch_barrier_sync 之后的任务:%d",i)<span class="hljs-comment">;</span><br> })<span class="hljs-comment">;</span><br> }<br> }<br> <br> /**<br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:48.135837</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043503</span>] dispatch_barrier_async 所在线程 <NSThread: <span class="hljs-number">0</span>x60<span class="hljs-number">00014820c0</span>>{number = <span class="hljs-number">1</span>, name = main}<br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:48.137458</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043615</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">1</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:48.152424</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043615</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">3</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:48.152613</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043615</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">5</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:48.152732</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043711</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">7</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:48.152829</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043615</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">9</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.140811</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043611</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">0</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.153305</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043710</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">6</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.153305</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043609</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">2</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.153305</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043709</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">4</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.153359</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043712</span>] dispatch_barrier_sync 之前的任务:<span class="hljs-number">8</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.153667</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043712</span>] dispatch_barrier_sync 任务执行 <NSThread: <span class="hljs-number">0</span>x60000140dd00>{number = <span class="hljs-number">7</span>, name = (null)}<br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.153994</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043712</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">1</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.154061</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043710</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">3</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.154194</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043710</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">5</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.154419</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043714</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">7</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:49.154460</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043713</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">9</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:50.154831</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043709</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">0</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:50.154831</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043611</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">4</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:50.154869</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043712</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">6</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:50.154869</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043615</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">8</span><br> <span class="hljs-number">2019-12-19</span> <span class="hljs-number">13</span>:<span class="hljs-number">40:50.154867</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">8655</span>:<span class="hljs-number">6043609</span>] dispatch_barrier_sync 之后的任务:<span class="hljs-number">2</span><br> <br> */<br>}<br><br></code></pre></td></tr></table></figure>
</li>
</ul>
<h5 id="dispatch-once"><a href="#dispatch-once" class="headerlink" title="dispatch_once"></a>dispatch_once</h5><p>dispatch_once能保证任务只会被执行一次,同时多线程调用也是线程安全的。</p>
<p>原理:dispatch_once用原子性操作block执行完成标记位,同时用信号量确保只有一个线程执行block,等block执行完再唤醒所有等待中的线程。</p>
<p>应用场景:</p>
<ol>
<li>dispatch_once 常被用于创建单例</li>
<li>只需执行一次的函数都可以使用</li>
</ol>
<p>demo:</p>
<p>AFNetworking 库中<br><figure class="highlight awk"><table><tr><td class="code"><pre><code class="hljs awk">/*<br> AFNetworking 库中创建队列<br> <span class="hljs-number">1</span>、用到时才创建<br> <span class="hljs-number">2</span>、确保只要创建一次<br> <span class="hljs-number">3</span>、需要线程安全<br> 综上原因,使用 dispatch_once 是最优选择<br> <br> */<br><span class="hljs-regexp">//</span>static dispatch_queue_t url_session_manager_processing_queue() {<br><span class="hljs-regexp">//</span> static dispatch_queue_t af_url_session_manager_processing_queue;<br><span class="hljs-regexp">//</span> static dispatch_once_t onceToken;<br><span class="hljs-regexp">//</span> dispatch_once(&onceToken, ^{<br><span class="hljs-regexp">//</span> af_url_session_manager_processing_queue = dispatch_queue_create(<span class="hljs-string">"com.alamofire.networking.session.manager.processing"</span>, DISPATCH_QUEUE_CONCURRENT);<br><span class="hljs-regexp">//</span> });<br><span class="hljs-regexp">//</span><br><span class="hljs-regexp">//</span> return af_url_session_manager_processing_queue;<br><span class="hljs-regexp">//</span>}<br><br></code></pre></td></tr></table></figure></p>
<p>单利<br><figure class="highlight dns"><table><tr><td class="code"><pre><code class="hljs dns">- (void)test1 {<br> TDManager *manager1 = [TDManager shareManager]<span class="hljs-comment">;</span><br> TDManager *manager2 = [TDManager new]<span class="hljs-comment">;</span><br> TDManager *manager3 = [[TDManager alloc] init]<span class="hljs-comment">;</span><br> TDManager *manager4 = [manager1 copy]<span class="hljs-comment">;</span><br> TDManager *manager5 = [manager1 mutableCopy]<span class="hljs-comment">;</span><br> <br> NSLog(@"manager1:%p",manager1)<span class="hljs-comment">;</span><br> NSLog(@"manager2:%p",manager2)<span class="hljs-comment">;</span><br> NSLog(@"manager3:%p",manager3)<span class="hljs-comment">;</span><br> NSLog(@"manager4:%p",manager4)<span class="hljs-comment">;</span><br> NSLog(@"manager5:%p",manager5)<span class="hljs-comment">;</span><br> <br> /*<br> 打印内存地址都一致,起到了单利效果<br> <br> <span class="hljs-number">2019-12-20</span> <span class="hljs-number">17</span>:<span class="hljs-number">00</span>:<span class="hljs-number">15.974273</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">30701</span>:<span class="hljs-number">6628246</span>] manager1:<span class="hljs-number">0</span>x60<span class="hljs-number">00021d68b0</span><br> <span class="hljs-number">2019-12-20</span> <span class="hljs-number">17</span>:<span class="hljs-number">00</span>:<span class="hljs-number">15.974428</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">30701</span>:<span class="hljs-number">6628246</span>] manager2:<span class="hljs-number">0</span>x60<span class="hljs-number">00021d68b0</span><br> <span class="hljs-number">2019-12-20</span> <span class="hljs-number">17</span>:<span class="hljs-number">00</span>:<span class="hljs-number">15.974544</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">30701</span>:<span class="hljs-number">6628246</span>] manager3:<span class="hljs-number">0</span>x60<span class="hljs-number">00021d68b0</span><br> <span class="hljs-number">2019-12-20</span> <span class="hljs-number">17</span>:<span class="hljs-number">00</span>:<span class="hljs-number">15.974634</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">30701</span>:<span class="hljs-number">6628246</span>] manager4:<span class="hljs-number">0</span>x60<span class="hljs-number">00021d68b0</span><br> <span class="hljs-number">2019-12-20</span> <span class="hljs-number">17</span>:<span class="hljs-number">00</span>:<span class="hljs-number">15.974714</span>+<span class="hljs-number">0800</span> threadDemo[<span class="hljs-number">30701</span>:<span class="hljs-number">6628246</span>] manager5:<span class="hljs-number">0</span>x60<span class="hljs-number">00021d68b0</span><br> */<br>}<br></code></pre></td></tr></table></figure></p>
<h5 id="dispatch-apply"><a href="#dispatch-apply" class="headerlink" title="dispatch_apply"></a>dispatch_apply</h5><p>dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次.<br> 如果队列是并发队列,则会并发执行block任务;<br> 如果队列是串行队列,则会串行在当前队列执行block任务;<br> dispatch_apply是一个同步调用,block任务执行n次后才返回。</p>
<p>demo:</p>
<p>串行队列中执行</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><code class="hljs yaml"><span class="hljs-string">///</span> <span class="hljs-string">串行队列中执行,效率还是略高于普通的</span> <span class="hljs-string">for</span> <span class="hljs-string">循环</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">(void)test1</span> {<br> <span class="hljs-string">NSLog(@"start");</span><br> <br> <span class="hljs-string">dispatch_queue_t</span> <span class="hljs-string">queue=</span> <span class="hljs-string">dispatch_queue_create("asyncSerial"</span>, <span class="hljs-string">DISPATCH_QUEUE_SERIAL);</span><br><span class="hljs-string">//</span> <span class="hljs-string">dispatch_queue_t</span> <span class="hljs-string">queue</span> <span class="hljs-string">=</span> <span class="hljs-string">dispatch_queue_create("asyncConcurrent"</span>, <span class="hljs-string">DISPATCH_QUEUE_CONCURRENT);</span><br> <br> <span class="hljs-string">CFTimeInterval</span> <span class="hljs-string">startTimeInterval</span> <span class="hljs-string">=</span> <span class="hljs-string">CACurrentMediaTime();</span><br> <span class="hljs-string">//dispatch_apply是一个同步调用,block任务执行都执行完才返回,会卡住当前线程(无论串行还是并发队列)</span><br> <span class="hljs-string">dispatch_apply(10000</span>, <span class="hljs-string">queue</span>, <span class="hljs-string">^(size_t</span> <span class="hljs-string">i)</span> {<br> <br><span class="hljs-string">//</span> [<span class="hljs-string">NSThread</span> <span class="hljs-string">sleepForTimeInterval:arc4random()%1</span>]<span class="hljs-string">;</span><br> <br> <span class="hljs-string">NSLog(@"%zu</span> <span class="hljs-string">%@"</span>,<span class="hljs-string">i</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> }<span class="hljs-string">);</span><br> <span class="hljs-string">CFTimeInterval</span> <span class="hljs-string">endTimeInterval</span> <span class="hljs-string">=</span> <span class="hljs-string">CACurrentMediaTime();</span><br> <span class="hljs-string">NSLog(@"end");</span><br> <br> <span class="hljs-string">NSLog(@"endTimeInterval</span> <span class="hljs-bullet">-</span> <span class="hljs-string">startTimeInterval:%f"</span>,<span class="hljs-string">endTimeInterval</span> <span class="hljs-bullet">-</span> <span class="hljs-string">startTimeInterval);</span><br> <br> <span class="hljs-string">/*</span><br> <span class="hljs-number">2019-12-19 15:46:19.281002</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">0</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:46:19.281440</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">1</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:46:19.281440</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">2</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:46:19.281440</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">3</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-string">....</span><br> <span class="hljs-number">2019-12-19 15:46:19.281002</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">9997</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:46:19.281440</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">9998</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:46:19.282302</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-number">9999</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000008aa0c0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:46:19.282819</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-string">end</span><br> <span class="hljs-number">2019-12-19 15:46:19.283203</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9336</span><span class="hljs-string">:6115616</span>] <span class="hljs-string">endTimeInterval</span> <span class="hljs-bullet">-</span> <span class="hljs-string">startTimeInterval:4.206515</span><br> <br> <span class="hljs-string">*/</span><br>}<br> <br></code></pre></td></tr></table></figure>
<p>并发队列执行</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><code class="hljs yaml"><span class="hljs-string">///</span> <span class="hljs-string">并发队列中执行,比串行队列效率高</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">(void)test2</span> {<br> <span class="hljs-string">NSLog(@"start");</span><br> <br> <span class="hljs-string">dispatch_queue_t</span> <span class="hljs-string">queue</span> <span class="hljs-string">=</span> <span class="hljs-string">dispatch_queue_create("asyncConcurrent"</span>, <span class="hljs-string">DISPATCH_QUEUE_CONCURRENT);</span><br> <br> <span class="hljs-string">CFTimeInterval</span> <span class="hljs-string">startTimeInterval</span> <span class="hljs-string">=</span> <span class="hljs-string">CACurrentMediaTime();</span><br> <span class="hljs-string">//dispatch_apply是一个同步调用,block任务执行都执行完才返回,会卡住当前线程(无论串行还是并发队列)</span><br> <span class="hljs-string">dispatch_apply(10000</span>, <span class="hljs-string">queue</span>, <span class="hljs-string">^(size_t</span> <span class="hljs-string">i)</span> {<br> <br><span class="hljs-string">//</span> [<span class="hljs-string">NSThread</span> <span class="hljs-string">sleepForTimeInterval:arc4random()%1</span>]<span class="hljs-string">;</span><br> <br> <span class="hljs-string">NSLog(@"%zu</span> <span class="hljs-string">%@"</span>,<span class="hljs-string">i</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> }<span class="hljs-string">);</span><br> <span class="hljs-string">CFTimeInterval</span> <span class="hljs-string">endTimeInterval</span> <span class="hljs-string">=</span> <span class="hljs-string">CACurrentMediaTime();</span><br> <span class="hljs-string">NSLog(@"end");</span><br> <br> <span class="hljs-string">NSLog(@"endTimeInterval</span> <span class="hljs-bullet">-</span> <span class="hljs-string">startTimeInterval:%f"</span>,<span class="hljs-string">endTimeInterval</span> <span class="hljs-bullet">-</span> <span class="hljs-string">startTimeInterval);</span><br> <br> <span class="hljs-string">/*</span><br><br> <span class="hljs-number">2019-12-19 16:23:13.700098</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-number">0</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002f5a140</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 16:23:13.700240</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-number">1</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002f5a140</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 16:23:13.700767</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-number">2</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002f5a140</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-string">...</span><br> <span class="hljs-number">2019-12-19 16:23:17.318679</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-number">9995</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002f5a140</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 16:23:17.318908</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141715</span>] <span class="hljs-number">9996</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002fd6900</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">8</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">(null)</span>}<br> <span class="hljs-number">2019-12-19 16:23:17.319090</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-number">9997</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002f5a140</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 16:23:17.319294</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141714</span>] <span class="hljs-number">9998</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002fd6780</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">7</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">(null)</span>}<br> <span class="hljs-number">2019-12-19 16:23:17.319498</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141662</span>] <span class="hljs-number">9999</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600002fd6740</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">6</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">(null)</span>}<br> <span class="hljs-number">2019-12-19 16:23:17.321036</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-string">end</span><br> <span class="hljs-number">2019-12-19 16:23:17.321507</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">9717</span><span class="hljs-string">:6141471</span>] <span class="hljs-string">endTimeInterval</span> <span class="hljs-bullet">-</span> <span class="hljs-string">startTimeInterval:3.620898</span><br> <span class="hljs-string">*/</span><br>}<br></code></pre></td></tr></table></figure>
<h5 id="dispatch-after"><a href="#dispatch-after" class="headerlink" title="dispatch_after"></a>dispatch_after</h5><p>延时函数,不会卡住所在线程</p>
<p>demo:</p>
<p>任务执行所在队列为主队列,任务在主线程中执行的<br><figure class="highlight yaml"><table><tr><td class="code"><pre><code class="hljs yaml"><span class="hljs-bullet">-</span> <span class="hljs-string">(void)test1</span> {<br> <span class="hljs-string">NSLog(@"1</span> <span class="hljs-string">%@"</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> <span class="hljs-string">dispatch_after(dispatch_time(DISPATCH_TIME_NOW</span>, <span class="hljs-string">(int64_t)(2</span> <span class="hljs-string">*</span> <span class="hljs-string">NSEC_PER_SEC))</span>, <span class="hljs-string">dispatch_get_main_queue()</span>, <span class="hljs-string">^</span>{<br> <span class="hljs-string">NSLog(@"2</span> <span class="hljs-string">%@"</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> }<span class="hljs-string">);</span><br> <span class="hljs-string">NSLog(@"3</span> <span class="hljs-string">%@"</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> <br> <span class="hljs-string">/*</span><br> <span class="hljs-number">2019-12-19 14:58:46.529442</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">8900</span><span class="hljs-string">:6083577</span>] <span class="hljs-number">1</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000036b8b80</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 14:58:46.529659</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">8900</span><span class="hljs-string">:6083577</span>] <span class="hljs-number">3</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000036b8b80</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 14:58:48.529967</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">8900</span><span class="hljs-string">:6083577</span>] <span class="hljs-number">2</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x6000036b8b80</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br><br> <span class="hljs-string">*/</span><br>}<br></code></pre></td></tr></table></figure></p>
<p>修改任务执行所在队列为并发队列. 任务是在子线程中执行的.</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><code class="hljs yaml"><span class="hljs-bullet">-</span> <span class="hljs-string">(void)test2</span> {<br> <span class="hljs-string">NSLog(@"1</span> <span class="hljs-string">%@"</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> <span class="hljs-string">dispatch_after(dispatch_time(DISPATCH_TIME_NOW</span>, <span class="hljs-string">(int64_t)(2</span> <span class="hljs-string">*</span> <span class="hljs-string">NSEC_PER_SEC))</span>, <span class="hljs-string">dispatch_get_global_queue(0</span>, <span class="hljs-number">0</span><span class="hljs-string">)</span>, <span class="hljs-string">^</span>{<br> <span class="hljs-string">NSLog(@"2</span> <span class="hljs-string">%@"</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> }<span class="hljs-string">);</span><br> <span class="hljs-string">NSLog(@"3</span> <span class="hljs-string">%@"</span>,[<span class="hljs-string">NSThread</span> <span class="hljs-string">currentThread</span>]<span class="hljs-string">);</span><br> <br> <span class="hljs-string">/*</span><br> <span class="hljs-number">2019-12-19 15:00:48.693679</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">8943</span><span class="hljs-string">:6085531</span>] <span class="hljs-number">1</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600000d72ac0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:00:48.694252</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">8943</span><span class="hljs-string">:6085531</span>] <span class="hljs-number">3</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600000d72ac0</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">main</span>}<br> <span class="hljs-number">2019-12-19 15:00:50.855673</span><span class="hljs-string">+0800</span> <span class="hljs-string">threadDemo</span>[<span class="hljs-number">8943</span><span class="hljs-string">:6085641</span>] <span class="hljs-number">2</span> <span class="hljs-string"><NSThread:</span> <span class="hljs-number">0x600000d21080</span><span class="hljs-string">></span>{<span class="hljs-string">number</span> <span class="hljs-string">=</span> <span class="hljs-number">3</span>, <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">(null)</span>}<br><br> <span class="hljs-string">*/</span><br>}<br></code></pre></td></tr></table></figure>
<p>修改任务执行所在队列为自定义的串行队列. 任务是在子线程中执行的.<br>通过查看打印结果,很明显延时函数底层肯定是异步执行任务。只有在主队列时任务才在主线程执行</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><code class="hljs reasonml">- (void)test3 {<br> <span class="hljs-constructor">NSLog(@<span class="hljs-string">"1 %@"</span>,[NSThread <span class="hljs-params">currentThread</span>])</span>;<br> dispatch_queue_t queue = dispatch<span class="hljs-constructor">_queue_create(<span class="hljs-string">"syncConcrrent"</span>, DISPATCH_QUEUE_CONCURRENT)</span>;<br> dispatch<span class="hljs-constructor">_after(<span class="hljs-params">dispatch_time</span>(DISPATCH_TIME_NOW, (<span class="hljs-params">int64_t</span>)</span>(<span class="hljs-number">2</span><span class="hljs-operator"> * </span>NSEC_PER_SEC)), queue, ^{<br> <span class="hljs-constructor">NSLog(@<span class="hljs-string">"2 %@"</span>,[NSThread <span class="hljs-params">currentThread</span>])</span>;<br> });<br> <span class="hljs-constructor">NSLog(@<span class="hljs-string">"3 %@"</span>,[NSThread <span class="hljs-params">currentThread</span>])</span>;<br> <br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> 2019-12-19 15:09:06.207532+0800 threadDemo[9037:6090879] 1 <NSThread: 0x600001960f40>{number = 1, name = main}</span><br><span class="hljs-comment"> 2019-12-19 15:09:06.207745+0800 threadDemo[9037:6090879] 3 <NSThread: 0x600001960f40>{number = 1, name = main}</span><br><span class="hljs-comment"> 2019-12-19 15:09:08.207879+0800 threadDemo[9037:6090962] 2 <NSThread: 0x6000019ee4c0>{number = 8, name = (null)}</span><br><span class="hljs-comment"> */</span><br>}<br></code></pre></td></tr></table></figure>
<h5 id="Dispatch-Source"><a href="#Dispatch-Source" class="headerlink" title="Dispatch Source"></a>Dispatch Source</h5><p>Dispatch Source是BSD系统内核惯有功能kqueue的包装,kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。<br> 它的CPU负荷非常小,尽量不占用资源。当事件发生时,Dispatch Source会在指定的Dispatch Queue中执行事件的处理。</p>
<p> dispatch_source_t<br> 一共有一下几种类型(dispatch_source_type_t):</p>
<p> 监控进程:DISPATCH_SOURCE_TYPE_PROC,<br> 定时器:DISPATCH_SOURCE_TYPE_TIMER,<br> 从描述符中读取数据:DISPATCH_SOURCE_TYPE_READ,<br> 向描述符中写入字符:DISPATCH_SOURCE_TYPE_WRITE,<br> 监控文件系统对象:DISPATCH_SOURCE_TYPE_VNODE,…..</p>
<p> demo演示定时器使用</p>
<p> Dispatch Source使用最多的就是用来实现定时器,source创建后默认是暂停状态,需要手动调用dispatch_resume启动定时器。dispatch_after只是封装调用了dispatch source定时器,然后在回调函数中执行定义的block。</p>
<p> Dispatch Source定时器使用时也有一些需要注意的地方,不然很可能会引起crash:</p>
<p> 1、循环引用:因为dispatch_source_set_event_handler回调是个block,在添加到source的链表上时会执行copy并被source强引用,如果block里持有了self,self又持有了source的话,就会引起循环引用。正确的方法是使用weak+strong或者提前调用dispatch_source_cancel取消timer。</p>
<p> 2、dispatch_resume和dispatch_suspend调用次数需要平衡,如果重复调用dispatch_resume则会崩溃,因为重复调用会让dispatch_resume代码里if分支不成立,从而执行了DISPATCH_CLIENT_CRASH(“Over-resume of an object”)导致崩溃。</p>
<p> 3、source在suspend状态下,如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在resume状态下调用dispatch_source_cancel(source)后再重新创建。</p>
<h6 id="GCD定时器"><a href="#GCD定时器" class="headerlink" title="GCD定时器"></a>GCD定时器</h6><p>定时器不受runloop影响,效率高。</p>
<p>demo:</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><code class="hljs angelscript">- (<span class="hljs-built_in">void</span>)test1 {<br> __block <span class="hljs-built_in">int</span> timeout=<span class="hljs-number">30</span>; <span class="hljs-comment">//倒计时时间</span><br> dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, dispatch_get_main_queue());<br> dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, <span class="hljs-number">0</span>), <span class="hljs-number">1</span> * NSEC_PER_SEC, <span class="hljs-number">0</span>);<br> dispatch_source_set_event_handler(_timer, ^{<br> <span class="hljs-keyword">if</span>(timeout<=<span class="hljs-number">0</span>){ <span class="hljs-comment">//倒计时结束,关闭</span><br> dispatch_source_cancel(_timer);<br> dispatch_async(dispatch_get_main_queue(), ^{<br> <span class="hljs-comment">//设置界面的按钮显示 根据自己需求设置</span><br> NSLog(@<span class="hljs-string">"time 倒计时结束"</span>);<br> });<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-built_in">int</span> minutes = timeout / <span class="hljs-number">60</span>;<br> <span class="hljs-built_in">int</span> seconds = timeout % <span class="hljs-number">60</span>;<br> NSString *strTime = [NSString <span class="hljs-built_in">string</span>WithFormat:@<span class="hljs-string">"%d分%.2d秒后重新获取验证码"</span>,minutes, seconds];<br> dispatch_async(dispatch_get_main_queue(), ^{<br> <span class="hljs-comment">//设置界面的按钮显示 根据自己需求设置</span><br> NSLog(@<span class="hljs-string">"strTime %@"</span>,strTime);<br> });<br> timeout--;<br> }<br> });<br> <span class="hljs-comment">//启动timer</span><br> dispatch_resume(_timer);<br>}<br><br></code></pre></td></tr></table></figure>
<h3 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h3><p>只有当多个线程同时去访问并且修改一块资源的内容时可能会有线程安全问题。</p>
<p>当多线程只是访问并不做修改,不会出现线程安全问题。</p>
<p>举例:</p>
<p>假设银行卡有余额1000元,有2个线程A、B;<br>现在A、B同时查询余额,得到的都是1000元;<br>然后A线程存入1000元,余额为1000 + 1000 = 2000;<br>然后B线程取出500元,余额为1000 - 500 = 500;<br>这样就出现线程安全问题了,下次去查询余额就只有500了😹</p>
<p>为了保证线程安全,需要给线程加锁。</p>
<h5 id="同步方案"><a href="#同步方案" class="headerlink" title="同步方案"></a>同步方案</h5><h6 id="OSSpinLock"><a href="#OSSpinLock" class="headerlink" title="OSSpinLock"></a>OSSpinLock</h6><p>OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源</p>
<p>目前已经不再安全,可能会出现优先级反转问题</p>
<p>如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁</p>
<p>需要导入头文件#import <libkern/OSAtomic.h></p>
<h6 id="os-unfair-lock"><a href="#os-unfair-lock" class="headerlink" title="os_unfair_lock"></a>os_unfair_lock</h6><p>os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持</p>
<p>从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等</p>
<p>需要导入头文件#import <os/lock.h></p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2361" alt="6a32356cd62bffb395c55eff0729375d.png"> </p>
<h6 id="pthread-mutex"><a href="#pthread-mutex" class="headerlink" title="pthread_mutex"></a>pthread_mutex</h6><p>mutex叫做”互斥锁”,等待锁的线程会处于休眠状态</p>
<p>需要导入头文件#import <pthread.h></p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2362" alt="9848ab391ea130b47de60ac90949c5d7.png"><br><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2363" alt="1e9bf9133cafc7fe1e68e53239f70754.png"><br><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2364" alt="8d423ed41c44ae90fe1f4e55b26964f9.png"><br><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2365" alt="eeeb5c6a5d872001eb1f1c7917d810e0.png"></p>
<h6 id="dispatch-semaphore"><a href="#dispatch-semaphore" class="headerlink" title="dispatch_semaphore"></a>dispatch_semaphore</h6><p>semaphore叫做”信号量”</p>
<p>信号量的初始值,可以用来控制线程并发访问的最大数量</p>
<p>信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2366" alt="844e0aa7a21c56f9568325ebf1df04b2.png"></p>
<h6 id="dispatch-queue-DISPATCH-QUEUE-SERIAL"><a href="#dispatch-queue-DISPATCH-QUEUE-SERIAL" class="headerlink" title="dispatch_queue(DISPATCH_QUEUE_SERIAL)"></a>dispatch_queue(DISPATCH_QUEUE_SERIAL)</h6><p>直接使用GCD的串行队列,也是可以实现线程同步的</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2367" alt="4da7c092e947d453289322166866701b.png"></p>
<h6 id="NSLock、NSRecursiveLock"><a href="#NSLock、NSRecursiveLock" class="headerlink" title="NSLock、NSRecursiveLock"></a>NSLock、NSRecursiveLock</h6><p>NSLock是对mutex普通锁的封装</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2371" alt="1cb32ab8ac4967086fbbee75dbb15433.png"><br><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2372" alt="f288c06d4f718baddb110b329023936d.png"><br><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2373" alt="c02fbd60aa09ed858fec23c3b8d4a6a3.png"></p>
<p>NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致</p>
<h6 id="NSCondition"><a href="#NSCondition" class="headerlink" title="NSCondition"></a>NSCondition</h6><p>NSCondition是对mutex和cond的封装</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2370" alt="5f93ec4282fecd19e347dd383be722ec.png"></p>
<h6 id="NSConditionLock"><a href="#NSConditionLock" class="headerlink" title="NSConditionLock"></a>NSConditionLock</h6><p>NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2369" alt="65483fd9373eed4ce11ef7ecb2b60fda.png"></p>
<h6 id="synchronized"><a href="#synchronized" class="headerlink" title="@synchronized"></a>@synchronized</h6><p>@synchronized是对mutex递归锁的封装</p>
<p>源码查看:objc4中的objc-sync.mm文件</p>
<p>@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作</p>
<p><img src="evernotecid://3252F98A-C8F0-41C3-834D-D7F7BDDE3269/appyinxiangcom/11846024/ENResource/p2368" alt="27d559c23065755f60c6afe842dd3096.png"></p>
<h5 id="同步方案性能比较"><a href="#同步方案性能比较" class="headerlink" title="同步方案性能比较"></a>同步方案性能比较</h5><p>性能从高到低排序<br>os_unfair_lock<br>OSSpinLock<br>dispatch_semaphore<br>pthread_mutex<br>dispatch_queue(DISPATCH_QUEUE_SERIAL)<br>NSLock<br>NSCondition<br>pthread_mutex(recursive)<br>NSRecursiveLock<br>NSConditionLock<br>@synchronized</p>
<h5 id="自旋-互斥锁"><a href="#自旋-互斥锁" class="headerlink" title="自旋 / 互斥锁"></a>自旋 / 互斥锁</h5><h6 id="什么是自旋锁"><a href="#什么是自旋锁" class="headerlink" title="什么是自旋锁"></a>什么是自旋锁</h6><p>等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源</p>
<h6 id="什么是互斥锁"><a href="#什么是互斥锁" class="headerlink" title="什么是互斥锁"></a>什么是互斥锁</h6><p>等待锁的线程会处于休眠状态</p>
<h6 id="什么情况使用自旋锁更划算?"><a href="#什么情况使用自旋锁更划算?" class="headerlink" title="什么情况使用自旋锁更划算?"></a>什么情况使用自旋锁更划算?</h6><p>预计线程等待锁的时间很短<br>加锁的代码(临界区)经常被调用,但竞争情况很少发生<br>CPU资源不紧张<br>多核处理器</p>
<h6 id="什么情况使用互斥锁更划算?"><a href="#什么情况使用互斥锁更划算?" class="headerlink" title="什么情况使用互斥锁更划算?"></a>什么情况使用互斥锁更划算?</h6><p>预计线程等待锁的时间较长<br>单核处理器<br>临界区有IO操作<br>临界区代码复杂或者循环量大<br>临界区竞争非常激烈 </p>
<h5 id="高性能读写安全方案"><a href="#高性能读写安全方案" class="headerlink" title="高性能读写安全方案"></a>高性能读写安全方案</h5><h6 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h6><p>同一时间,只能有1个线程进行写的操作<br>同一时间,允许有多个线程进行读的操作<br>同一时间,不允许既有写的操作,又有读的操作</p>
<h6 id="实现方案"><a href="#实现方案" class="headerlink" title="实现方案"></a>实现方案</h6><p>“多读单写”,经常用于文件等数据的读写操作,实现方案有<br>pthread_rwlock:读写锁<br>dispatch_barrier_async:异步栅栏调用</p>
<p>demo:</p>
<p>由于 NSMutableDictionary 线程是不安全的。</p>
<p>现在实现一个线程安全的可变字典</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">void</span> (^ThreadSafeBlock)(ThreadSafeMutableDictionary *dict, <span class="hljs-built_in">NSString</span> *key, <span class="hljs-keyword">id</span> object);<br><br><span class="hljs-class"><span class="hljs-keyword">@implementation</span> <span class="hljs-title">ThreadSafeMutableDictionary</span> </span>{<br> <span class="hljs-built_in">dispatch_queue_t</span> _concurrentQueue;<br>}<br><br>- (<span class="hljs-keyword">instancetype</span>)init {<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">self</span> = [<span class="hljs-keyword">super</span> init]) {<br> _concurrentQueue = dispatch_queue_create(<span class="hljs-string">@"com.thread.ThreadSafeMutableDictionary"</span>, DISPATCH_QUEUE_CONCURRENT);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>;<br>}<br><br>- (<span class="hljs-keyword">void</span>)objectForKey:(<span class="hljs-built_in">NSString</span> *)key block:(ThreadSafeBlock)block {<br> <span class="hljs-keyword">id</span> cKey = [key <span class="hljs-keyword">copy</span>];<br> __<span class="hljs-keyword">weak</span> __typeof__(<span class="hljs-keyword">self</span>) weakSelf = <span class="hljs-keyword">self</span>;<br><br> <span class="hljs-built_in">dispatch_sync</span>(_concurrentQueue, ^{<br> ThreadSafeMutableDictionary *strongSelf = weakSelf;<br> <span class="hljs-keyword">if</span> (!strongSelf) {<br> block(<span class="hljs-literal">nil</span>,cKey,<span class="hljs-literal">nil</span>);<br> <span class="hljs-keyword">return</span> ;<br> }<br> <br> <span class="hljs-keyword">id</span> object = [strongSelf objectForKey:cKey];<br> block(strongSelf,cKey,object);<br> });<br>}<br><br>- (<span class="hljs-keyword">void</span>)setObject:(<span class="hljs-keyword">id</span>)object forKey:(<span class="hljs-built_in">NSString</span> *)key block:(ThreadSafeBlock)block {<br> <span class="hljs-keyword">if</span> (!key || !object) {<br> <span class="hljs-keyword">return</span>;<br> }<br> <br> <span class="hljs-built_in">NSString</span> *aKey = [key <span class="hljs-keyword">copy</span>];<br> __<span class="hljs-keyword">weak</span> __typeof__(<span class="hljs-keyword">self</span>) weakSelf = <span class="hljs-keyword">self</span>;<br> dispatch_barrier_async(_concurrentQueue, ^{<br> ThreadSafeMutableDictionary *strongSelf = weakSelf;<br> <span class="hljs-keyword">if</span> (!strongSelf) {<br> block(<span class="hljs-literal">nil</span>,aKey,<span class="hljs-literal">nil</span>);<br> <span class="hljs-keyword">return</span> ;<br> }<br> <br> [<span class="hljs-keyword">self</span> setObject:object forKey:aKey];<br> <span class="hljs-keyword">if</span> (block) {<br> block(strongSelf,aKey,object);<br> }<br> });<br>}<br><br></code></pre></td></tr></table></figure>
<h3 id="多线程优化"><a href="#多线程优化" class="headerlink" title="多线程优化"></a>多线程优化</h3><h5 id="优化思路"><a href="#优化思路" class="headerlink" title="优化思路"></a>优化思路</h5><h6 id="尽量减少队列切换"><a href="#尽量减少队列切换" class="headerlink" title="尽量减少队列切换"></a>尽量减少队列切换</h6><p>当线程数量超过 CPU 核心数量,CPU 核心通过线程调度切换用户态线程,意味着有上下文的转换,过多的上下文切换会带来资源开销。 </p>
<p>如下代码:<br><figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="hljs-string">"x.x.x"</span>, DISPATCH_QUEUE_CONCURRENT);<br>- (<span class="hljs-keyword">void</span>)tast1 {<br> <span class="hljs-built_in">dispatch_async</span>(queue, ^{<br> <span class="hljs-comment">//执行任务1</span><br> <span class="hljs-built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{<br> <span class="hljs-comment">//任务1完成</span><br> [<span class="hljs-keyword">self</span> tast2];<br> });<br> });<br>}<br>- (<span class="hljs-keyword">void</span>)tast2 {<br> <span class="hljs-built_in">dispatch_async</span>(queue, ^{<br> <span class="hljs-comment">//执行任务2</span><br> <span class="hljs-built_in">dispatch_async</span>(dispatch_get_main_queue(), ^{<br> <span class="hljs-comment">//任务2完成</span><br> });<br> });<br>}<br></code></pre></td></tr></table></figure><br>这里创建了一个并行队列,调用 tast1 会执行两个任务,任务2要等待任务1执行完成,这里一共有四次队列的切换。其实是没必要的。</p>
<p>优化后:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">dispatch_queue_t</span> <span class="hljs-built_in">queue</span> = dispatch_queue_create(<span class="hljs-string">"x.x.x"</span>, DISPATCH_QUEUE_SERIAL);<br>dispatch_async(<span class="hljs-built_in">queue</span>, ^{<br> <span class="hljs-comment">//执行任务1</span><br> <span class="hljs-comment">//执行任务2</span><br> dispatch_async(dispatch_get_main_queue(), ^{<br> <span class="hljs-comment">//任务1、2完成</span><br> });<br>});<br></code></pre></td></tr></table></figure>
<h6 id="控制线程数量"><a href="#控制线程数量" class="headerlink" title="控制线程数量"></a>控制线程数量</h6><p>使用 GCD 并行队列,当任务过多且耗时较长时,队列会开辟大量的线程,而部分线程里面的耗时任务已经耗尽了 CPU 资源,所以其他的线程也只能等待 CPU 时间片,过多的线程也会让线程调度过于频繁。</p>
<p>GCD 中并行队列并不能限制线程数量,可以创建多个串行队列来模拟并行的效果,业界知名框架 YYKit 就做了这个逻辑,通过和 CPU 核心数量相同的串行队列轮询返回来达到并行队列的效果</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">dispatch_queue_t</span> <span class="hljs-title">YYAsyncLayerGetDisplayQueue</span><span class="hljs-params">()</span> </span>{<br><span class="hljs-comment">//最大队列数量</span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MAX_QUEUE_COUNT 16</span><br><span class="hljs-comment">//队列数量</span><br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> queueCount;<br><span class="hljs-comment">//使用栈区的数组存储队列</span><br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">dispatch_queue_t</span> queues[MAX_QUEUE_COUNT];<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">dispatch_once_t</span> onceToken;<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int32_t</span> counter = <span class="hljs-number">0</span>;<br> dispatch_once(&onceToken, ^{<br><span class="hljs-comment">//串行队列数量和处理器数量相同</span><br> queueCount = (<span class="hljs-keyword">int</span>)[NSProcessInfo processInfo].activeProcessorCount;<br> queueCount = queueCount < <span class="hljs-number">1</span> ? <span class="hljs-number">1</span> : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;<br><span class="hljs-comment">//创建串行队列,设置优先级</span><br> <span class="hljs-keyword">if</span> ([UIDevice currentDevice].systemVersion.floatValue >= <span class="hljs-number">8.0</span>) {<br> <span class="hljs-keyword">for</span> (NSUInteger i = <span class="hljs-number">0</span>; i < queueCount; i++) {<br> <span class="hljs-keyword">dispatch_queue_attr_t</span> attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, <span class="hljs-number">0</span>);<br> queues[i] = dispatch_queue_create(<span class="hljs-string">"com.ibireme.yykit.render"</span>, attr);<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">for</span> (NSUInteger i = <span class="hljs-number">0</span>; i < queueCount; i++) {<br> queues[i] = dispatch_queue_create(<span class="hljs-string">"com.ibireme.yykit.render"</span>, DISPATCH_QUEUE_SERIAL);<br> dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="hljs-number">0</span>));<br> }<br> }<br> });<br><span class="hljs-comment">//轮询返回队列</span><br> <span class="hljs-keyword">uint32_t</span> cur = (<span class="hljs-keyword">uint32_t</span>)OSAtomicIncrement32(&counter);<br> <span class="hljs-keyword">return</span> queues[cur % queueCount];<br><span class="hljs-meta">#<span class="hljs-meta-keyword">undef</span> MAX_QUEUE_COUNT</span><br>}<br></code></pre></td></tr></table></figure>
<h6 id="线程优先级权衡"><a href="#线程优先级权衡" class="headerlink" title="线程优先级权衡"></a>线程优先级权衡</h6><p>线程调度除了轮转法以外,还有优先级调度的方案,在线程调度时,高优先级的线程会更早的执行。有两个概念需要明确:<br> a. IO 密集型线程:频繁等待的线程,等待的时候会让出时间片。<br> b. CPU 密集型线程:很少等待的线程,意味着长时间占用着 CPU。 </p>
<p>这样就会存在一个问题:<br> 当CPU密集型线程优先级较高,而且长期霸占CPU大部分资源,这样IO密集型线程由于优先级较低,就持续的等待,产生线程饿死的现象。这时系统会根据情况提高IO密集型线程的优先级,但即使这样,等待也是需要时间的。</p>
<p>这样可以考虑优化的方向:<br> a. 让 IO 密集型线程优先级高于 CPU 密集型线程。<br> b. 让紧急的任务拥有更高的优先级。</p>
<h6 id="主线程任务的优化"><a href="#主线程任务的优化" class="headerlink" title="主线程任务的优化"></a>主线程任务的优化</h6><p>这摘抄了yykit作者的提出的部分内容<a class="link" href="https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/" >iOS 保持界面流畅的技巧<i class="fas fa-external-link-alt"></i></a></p>
<p>一些耗时的操作尽量移动到子线程执行。</p>
<p>但是有些操作必须在主线程,比如 UI 类组件的初始化及其布局。 </p>
<p>那么主线程的优化有哪些方案可以参考呢?</p>
<p>1、尽量使用轻量级对象</p>
<p>对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗 CPU 资源。比如 CALayer 比 UIView 要轻量许多,那么不需要响应触摸事件的控件,用 CALayer 显示会更加合适。</p>
<p>2、尽量减少对象的调整</p>
<p>当视图层次调整时,UIView、CALayer 之间会出现很多方法调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。</p>
<p>3、对象销毁其实是可以在后台线程执行的</p>
<p>这时yykit作者提倡的方式:</p>
<p>需要注意的是下面的例子中,如果self.array在别的地方还有依赖,那tmp不是唯一的销毁helper。这一套就没用了。</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><code class="hljs objectivec"><span class="hljs-built_in">NSArray</span> *tmp = <span class="hljs-keyword">self</span>.array;<br><span class="hljs-keyword">self</span>.array = <span class="hljs-literal">nil</span>;<br><span class="hljs-built_in">dispatch_async</span>(queue, ^{<br> [tmp <span class="hljs-keyword">class</span>];<br>});<br></code></pre></td></tr></table></figure>
<p>4、内存复用</p>
<p>最常见的就是cell复用,避免了大量cell对象的创建,节省内存的同时也节省了开辟内存所消耗的时间。</p>
<p>5、懒加载任务</p>
<p>懒加载对象,用到时再创建,可以减少没必要的内存开销。开辟内存是要时间的。勉强算是变相的吧任务进行了拆分,不必初始化时就创建一系列对象。</p>
<p>6、任务拆分排队执行</p>
<p>将大量的任务拆分开来,监听Runloop运行状态,当runloop将要休息的时候,让 Runloop 循环周期执行少量任务。</p>
<p>。。。</p>
<h3 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h3><p><a class="link" href="https://github.com/apple/swift-corelibs-libdispatch" >GCD源码<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="http://www.gnustep.org/resources/downloads.php" >GNUstep<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/" >不再安全的 OSSpinLock<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/" >iOS 保持界面流畅的技巧<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://www.jianshu.com/p/594d15d6c6a7" >iOS 如何高效的使用多线程<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link" href="https://xiaozhuanlan.com/u/3785694919" >深入浅出 GCD<i class="fas fa-external-link-alt"></i></a></p>
<h3 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h3><p><a class="link" href="https://github.com/Baichenghui/Study/tree/master/threadDemo" >threadDemo<i class="fas fa-external-link-alt"></i></a></p>
]]></content>
<tags>
<tag>GCD</tag>
</tags>
</entry>
</search>