-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1749 lines (902 loc) · 131 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">
<meta name="keywords" content="Hexo, NexT" />
<meta property="og:type" content="website">
<meta property="og:title" content="Alex's Field">
<meta property="og:url" content="http://yoursite.com/index.html">
<meta property="og:site_name" content="Alex's Field">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Alex's Field">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Pisces',
version: '5.1.4',
sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: '博主'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="http://yoursite.com/"/>
<title>Alex's Field</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">Alex's Field</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle"></p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2018/03/15/Runloop浅析/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jasperay">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Alex's Field">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/03/15/Runloop浅析/" itemprop="url">Runloop浅析</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-03-15T22:02:08+08:00">
2018-03-15
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h2 id="什么是Runloop"><a href="#什么是Runloop" class="headerlink" title="什么是Runloop"></a>什么是Runloop</h2><p><img src="http://upload-images.jianshu.io/upload_images/1141183-1e969725ecf1b334.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="RunLoop01.png"></p>
<ul>
<li>运行循环</li>
<li>跑圈</li>
<li>内部类似一个 do-while 循环, 在循环内部不断处理各种任务 (Source, Observe, Timer)</li>
<li>一个线程对应一个 RunLoop</li>
</ul>
<h2 id="用途"><a href="#用途" class="headerlink" title="用途"></a>用途</h2><ul>
<li>保持程序持续运行</li>
<li>处理 APP 各种事件 (触摸事件, 定时器事件, Selector事件)</li>
<li>节省 CPU 资源, 提高程序性能: 该做事情的时候做事情, 该休息时休息</li>
</ul>
<h2 id="没有RunLoop"><a href="#没有RunLoop" class="headerlink" title="没有RunLoop"></a>没有RunLoop</h2><p>程序一启动就结束了</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> NSLog(@"execute main function");</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="如果有了-RunLoop"><a href="#如果有了-RunLoop" class="headerlink" title="如果有了 RunLoop"></a>如果有了 RunLoop</h2><p>程序大致是这样子,但是要更加复杂</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> BOOL running = YES;</span><br><span class="line"> do {</span><br><span class="line"> // 执行各种任务,处理各种事件</span><br><span class="line"> // ......</span><br><span class="line"> } while (running);</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由于 main 函数里面启动了一个 RunLoop, 因此程序不会马上退出, 会保持程序的运行状态</p>
<h2 id="main-函数中的-RunLoop"><a href="#main-函数中的-RunLoop" class="headerlink" title="main 函数中的 RunLoop"></a>main 函数中的 RunLoop</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> @autoreleasepool {</span><br><span class="line"> return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>UIApplicationMain 函数内部启动了一个 RunLoop 对象</li>
<li>UIApplicationMain 函数一直没有返回, 保持了程序的运行</li>
<li>这个默认启动的 RunLoop 是与主线程相关</li>
</ul>
<h2 id="程序一旦启动"><a href="#程序一旦启动" class="headerlink" title="程序一旦启动"></a>程序一旦启动</h2><ul>
<li>执行UIApplicationMain 函数</li>
<li>默认启动一个 RunLoop</li>
<li>这个 RunLoop 会一直处理主线程相关的事情</li>
<li>这个 RunLoop 会一直遍历, 监听用户事件</li>
<li>这就是主线程的事件响应的这么快的原因</li>
</ul>
<h2 id="RunLoop-要想跑圈"><a href="#RunLoop-要想跑圈" class="headerlink" title="RunLoop 要想跑圈"></a>RunLoop 要想跑圈</h2><ul>
<li>模式(Mode)里面要有东西 (事件源 / Observer / 定时器)</li>
<li>RunLoop 要启动 (主线程默认创建并启动, 子线程需要手动启动)</li>
<li>没有事件源, 没有定时器, RunLoop 就会进入睡眠状态</li>
</ul>
<h2 id="RunLoop-对象"><a href="#RunLoop-对象" class="headerlink" title="RunLoop 对象"></a>RunLoop 对象</h2><p>iOS 中提供了两套 API 来访问和使用 RunLoop</p>
<ul>
<li>Foundation : NSRunLoop</li>
<li>Core Foundation : CFRunLoopRef</li>
</ul>
<p>NSRunLoop 是基于 CFRunLoopRef 的OC 包装, 如果研究 RunLoop 内部结构, 需要研究 CFRunLoopRef</p>
<h2 id="RunLoop-与线程"><a href="#RunLoop-与线程" class="headerlink" title="RunLoop 与线程"></a>RunLoop 与线程</h2><ul>
<li>每条线程都有唯一一个与之对应的 RunLoop 对象</li>
<li>主线程的 RunLoop 已经创建好, 子线程的 RunLoop 需要手动创建</li>
<li>RunLoop 在第一次获取时创建, 在线程结束时销毁</li>
<li>RunLoop 对象是使用字典存储, 以线程作为 key</li>
</ul>
<h2 id="RunLoop-相关类"><a href="#RunLoop-相关类" class="headerlink" title="RunLoop 相关类"></a>RunLoop 相关类</h2><p><img src="http://upload-images.jianshu.io/upload_images/1141183-1c20a8b3169b5cb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="RunLoop02.png"></p>
<h3 id="01-CFRunLoopModeRef"><a href="#01-CFRunLoopModeRef" class="headerlink" title="01 - CFRunLoopModeRef"></a>01 - CFRunLoopModeRef</h3><ul>
<li>CFRunLoopModeRef 代表着RunLoop的运行模式</li>
<li>一个RunLoop包含若干个Mode,每个Mode又包含若干个 Source/Timer/Observer</li>
<li>每次RunLoop启动时, 都会指定其中一个Mode, 这个Mode被称作CurrentMode</li>
<li>如果需要切换 Mode, 只能退出 RunLoop, 再重新指定一个 Mode 进入</li>
</ul>
<p> 系统默认注册了 5 个 Mode :</p>
<ul>
<li>kCFRunLoopDefultMode : APP 的默认 Mode, 通常主线程是在这个 Mode下</li>
<li>UITrackingRunLoopMode :<br>界面跟踪 Mode, 用于 scrollView 跟踪触摸滑动, 保证界面不受其他 Mode 影响 (添加定时器不好使)</li>
<li>UIInitializationRunLoopMode :<br>在刚启动 APP 时进入的第一个 Mode, 启动完就不再使用</li>
<li>GSEventReceiveRunLoopMode :<br>接收系统事件的内部 Mode, 通常用不到</li>
<li>kCFRunLoopCommonModes :<br>这是一个占位用的 Mode, 不是一个真正的 Mode (也就说 RunLoop 无法启动此模式)</li>
</ul>
<h3 id="02-CFRunLoopTimerRef"><a href="#02-CFRunLoopTimerRef" class="headerlink" title="02 - CFRunLoopTimerRef"></a>02 - CFRunLoopTimerRef</h3><ul>
<li>CFRunLoopTimerRef 是基于时间的触发器</li>
<li>基本上相当于 NSTimer</li>
<li>定时器会跑在 common modes 模式下</li>
<li>标记为 common modes 的模式有:<ul>
<li>kCFRunLoopDefultMode</li>
<li>UITrackingRunLoopMode</li>
</ul>
</li>
</ul>
<h4 id="定时器添加到-kCFRunLoopDefultMode"><a href="#定时器添加到-kCFRunLoopDefultMode" class="headerlink" title="定时器添加到 kCFRunLoopDefultMode"></a>定时器添加到 kCFRunLoopDefultMode</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];</span><br><span class="line">[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];</span><br></pre></td></tr></table></figure>
<p>将定时器添加到 NSDefaultRunLoopMode , 滑动 scollView 的时候, 定时器就会停止执行, RunLoop 此时会自动切换到 UITrackingRunLoopMode 模式, 定时器就会停止执行</p>
<h4 id="定时器添加到-NSRunLoopCommonModes"><a href="#定时器添加到-NSRunLoopCommonModes" class="headerlink" title="定时器添加到 NSRunLoopCommonModes"></a>定时器添加到 NSRunLoopCommonModes</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];</span><br><span class="line">[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];</span><br></pre></td></tr></table></figure>
<p>将定时器添加到 NSRunLoopCommonModes, 此时就不会停止执行</p>
<h3 id="03-CFRunLoopSourceRef"><a href="#03-CFRunLoopSourceRef" class="headerlink" title="03 - CFRunLoopSourceRef"></a>03 - CFRunLoopSourceRef</h3><ul>
<li>CFRunLoopSourceRef是事件源(输入源)</li>
</ul>
<h4 id="以前的分法"><a href="#以前的分法" class="headerlink" title="以前的分法"></a>以前的分法</h4><ul>
<li>Port-Based Sources</li>
<li>Custom Input Sources</li>
<li>Cocoa Perform Selector Sources</li>
</ul>
<p>####现在的分法</p>
<ul>
<li>Source0:非基于Port的</li>
<li>Source1:基于Port的</li>
</ul>
<h3 id="04-CFRunLoopObserverRef"><a href="#04-CFRunLoopObserverRef" class="headerlink" title="04 - CFRunLoopObserverRef"></a>04 - CFRunLoopObserverRef</h3><ul>
<li>CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">- (void)observer</span><br><span class="line">{</span><br><span class="line"> // 创建observer</span><br><span class="line"> CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {</span><br><span class="line"> NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> // 添加观察者:监听RunLoop的状态</span><br><span class="line"> CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);</span><br><span class="line"></span><br><span class="line"> // 释放Observer</span><br><span class="line"> CFRelease(observer);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>可以监听的时间点有以下几个</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">/* Run Loop Observer Activities */</span><br><span class="line">typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {</span><br><span class="line"> kCFRunLoopEntry = (1UL << 0), // 即将进入 Loop</span><br><span class="line"> kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer</span><br><span class="line"> kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source</span><br><span class="line"> kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠</span><br><span class="line"> kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒</span><br><span class="line"> kCFRunLoopExit = (1UL << 7), // 即将推出 Loop</span><br><span class="line"> kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有事件</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h2 id="RunLoop-处理逻辑"><a href="#RunLoop-处理逻辑" class="headerlink" title="RunLoop 处理逻辑"></a>RunLoop 处理逻辑</h2><p>下图简介了 RunLoop 处理过程, 一个线程的 RunLoop 在存在事件源 / 定时器的条件下, 会不断的处理事件, 处理的事件包括 </p>
<ul>
<li>处理基于 port 的 CFRunLoopSourceRef</li>
<li>处理 customer 自定义事件源</li>
<li>处理 selector 事件</li>
<li>处理定时器执行</li>
</ul>
<p><img src="http://upload-images.jianshu.io/upload_images/1141183-a32fbe3bd934f252.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="RunLoop处理逻辑(官方示意图).png"></p>
<p>官方的图解很清楚, RunLoop 在不停的跑圈, 跑圈的前提是满足以下条件之一:</p>
<ul>
<li>输入源 (事件源), 即 CFRunLoopSourceRef, 基于端口的输入源 (port) 和 自定义输入源 (custom), 当然还包含 performSelector:onThread…</li>
<li>拥有添加在 RunLoop 内的定时器</li>
</ul>
<p><img src="http://upload-images.jianshu.io/upload_images/1141183-5b6860cd100c6a1c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="RunLoop处理逻辑(网友整理).png"></p>
<h2 id="RunLoop-实际应用"><a href="#RunLoop-实际应用" class="headerlink" title="RunLoop 实际应用"></a>RunLoop 实际应用</h2><h3 id="1-常驻线程"><a href="#1-常驻线程" class="headerlink" title="(1) 常驻线程"></a>(1) 常驻线程</h3><p>即让子线程处于 “不消亡” 的状态, 一直在后台处理某些频发事件 / 等待其他线程发来消息</p>
<ul>
<li>在子线程监控网络状态</li>
<li>在子线程开启一个定时器</li>
<li>在子线程长期监控其他行为</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">+ (void)networkRequestThreadEntryPoint:(id)__unused object { </span><br><span class="line"> @autoreleasepool { </span><br><span class="line"> [[NSThread currentThread] setName:@"AFNetworking"]; </span><br><span class="line"> NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; </span><br><span class="line"> [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>摘自 AFNetworking 源代码, AFN这样做的原理在于子线程下默认不开启 RunLoop, 需要手动开启, 而 RunLoop 不断跑圈需要满足以下条件之一 :</p>
<ul>
<li>RunLoop有事件源(输入源), 包含基于端口 (port) 的事件源 / custom 事件源等</li>
<li>RunLoop存在定时器<br>因此, AFN为RunLoop的default模式增加了一个NSMachPort端口(实际上也可以是其他端口),也就相当于为RunLoop添加了事件源, 因此RunLoop可以不断的跑圈, 保证线程的不死状态<br>顺便提一下, AFN保持一个常驻线程的原因, 第一是因为子线程默认不会开启RunLoop, 它会像一个C语言程序一样运行完所有代码后退出线程, 而网络请求是异步的, 这就可能会出现通过网络请求获取到数据之后, 线程已经退出, 无法执行请求成功/失败的代理方法, 因此AFN开启了一个RunLoop, 保活了线程</li>
</ul>
<h3 id="2-控制定时器在特定模式下运行"><a href="#2-控制定时器在特定模式下运行" class="headerlink" title="(2) 控制定时器在特定模式下运行"></a>(2) 控制定时器在特定模式下运行</h3><p>即可以将计时器 timer 添加到 kCFRunLoopDefultMode 下, 如果 RunLoop 切换到 UITrackingRunLoopMode (UIScrollView 滚动过程中), 那么定时器就会暂停执行, 等到滚动结束, 定时器就会继续执行<br>也可以将定时器 timer 添加到 NSRunLoopCommonModes 下, 此时不管有无 scrollView 滑动, 都不会影响 timer 的执行</p>
<h3 id="3-控制某些事件在特定模式下执行"><a href="#3-控制某些事件在特定模式下执行" class="headerlink" title="(3) 控制某些事件在特定模式下执行"></a>(3) 控制某些事件在特定模式下执行</h3><p>即可以让某个 selector 在某个线程 (key) 的 RunLoop 下的特定模式下执行 (数组中包含 Mode)</p>
<p>通过以下的 API :</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);</span><br></pre></td></tr></table></figure>
<h3 id="4-添加-Observer-监听-RunLoop-状态-可以监听点击事件的处理-在所有点击事件之前做一些事情"><a href="#4-添加-Observer-监听-RunLoop-状态-可以监听点击事件的处理-在所有点击事件之前做一些事情" class="headerlink" title="(4) 添加 Observer 监听 RunLoop 状态, 可以监听点击事件的处理 (在所有点击事件之前做一些事情)"></a>(4) 添加 Observer 监听 RunLoop 状态, 可以监听点击事件的处理 (在所有点击事件之前做一些事情)</h3><p>调用 C 语言函数 CFRunLoopObserverCreateWithHandler () 创建 Observer, 监听某个 RunLoop 状态, 注意要手动释放</p>
<h2 id="关于自动释放池与-RunLoop"><a href="#关于自动释放池与-RunLoop" class="headerlink" title="关于自动释放池与 RunLoop"></a>关于自动释放池与 RunLoop</h2><h3 id="Autorelease-pool"><a href="#Autorelease-pool" class="headerlink" title="Autorelease pool"></a>Autorelease pool</h3><p>在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop</p>
<p>也就意味着, 在@autorelasepool 中的代码, 默认都是加在了一个自动释放池当中, 这个自动释放池是与主线程的 RunLoop 相关, 内部所有对象会在自动释放池释放的时候对内部所有对象进行一次 release 操作</p>
<p>至于主线程 RunLoop 下的自动释放池什么时候释放, 是在主线程 RunLoop 迭代 (睡眠)之前释放, 这个 RunLoop 什么时候睡眠呢? 是在没有接收任何输入源(事件源)/定时器的条件下</p>
<h3 id="自动释放池什么时候释放"><a href="#自动释放池什么时候释放" class="headerlink" title="自动释放池什么时候释放?"></a>自动释放池什么时候释放?</h3><p>在 RunLoop 睡眠之前释放 (KCFRunLoopBeforeWaiting), 也有人说 Autorelease对象是在当前的runloop迭代结束时释放的, 实际是一个意思</p>
<h3 id="什么时候用-autoreleasepool"><a href="#什么时候用-autoreleasepool" class="headerlink" title="什么时候用@autoreleasepool"></a>什么时候用@autoreleasepool</h3><p>根据Apple的文档,使用场景如下:</p>
<ul>
<li>写基于命令行的的程序时,就是没有UI框架,如AppKit等Cocoa框架时。</li>
<li>写循环,循环里面包含了大量临时创建的对象。(本文的例子)</li>
<li>创建了新的线程。(非Cocoa程序创建线程时才需要)</li>
<li>长时间在后台运行的任务。</li>
</ul>
<h2 id="RunLoop-研究资料"><a href="#RunLoop-研究资料" class="headerlink" title="RunLoop 研究资料"></a>RunLoop 研究资料</h2><ul>
<li>苹果官方文档<br><a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html" target="_blank" rel="noopener">https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html</a></li>
<li>CFRunLoopRef 是开源的<br><a href="http://opensource.apple.com/source/CF/CF-1151.16/" target="_blank" rel="noopener">http://opensource.apple.com/source/CF/CF-1151.16/</a></li>
</ul>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul>
<li>李明杰关于 RunLoop 的研究</li>
<li>sunnyxx-<a href="http://blog.sunnyxx.com/2014/10/15/behind-autorelease/" target="_blank" rel="noopener">http://blog.sunnyxx.com/2014/10/15/behind-autorelease/</a></li>
</ul>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2018/03/15/使用UICollectionView自定义样式01-水平线性布局/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jasperay">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Alex's Field">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/03/15/使用UICollectionView自定义样式01-水平线性布局/" itemprop="url">使用UICollectionView自定义样式01-水平线性布局</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-03-15T22:00:08+08:00">
2018-03-15
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>UICollectionView 是 iOS6 推出的 API, 可以实现复杂的定制效果, 使用和 UITableView 类似, 这篇主要通过自定义 UICollectionViewFlowLayout 实现一个左右滑动, 支持放大的相册效果</p>
<h2 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h2><p><img src="http://upload-images.jianshu.io/upload_images/1141183-0d7c8baf4e22958f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="003.png"></p>
<h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><ul>
<li>可以滚动, 必定继承自 UIScrollView</li>
<li>数据量未知, 为了避免内存过大导致 crash, 因此需要使用具有循环利用机制的控件, 否则还需要自写一套循环利机制</li>
<li>UITableView 也可实现, 但是比较奇葩 (需要将整个 tableView 90 °, cell 内容旋转 90 °)</li>
<li>自定义 UICollectionViewLayout 完全可以满足需求, 并实现一套完全可以复用的布局方案</li>
<li>可继承自 UICollectionView 提供 的 UICollectionViewFlowLayout, 并在此基础上做改动</li>
</ul>
<h2 id="API-简介"><a href="#API-简介" class="headerlink" title="API 简介"></a>API 简介</h2><p>UICollectionViewLayout 提供了以下 API 控制自定义布局的样式, 实现定制效果</p>
<h4 id="01-layoutAttributesForElementsInRect"><a href="#01-layoutAttributesForElementsInRect" class="headerlink" title="01 - layoutAttributesForElementsInRect:"></a>01 - layoutAttributesForElementsInRect:</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect</span><br></pre></td></tr></table></figure>
<ul>
<li>这个方法的返回值是一个数组(数组里面存放着rect范围内所有元素的布局属性)</li>
<li>这个方法的返回值决定了rect范围内所有元素的排布(frame)</li>
<li>返回数组内存放的是 UICollectionViewLayoutAttributes 对象, 一个 cell 对应一个 UICollectionViewLayoutAttributes 对象, 该对象决定了 cell 的 frame</li>
</ul>
<h4 id="02-prepareLayout"><a href="#02-prepareLayout" class="headerlink" title="02 - prepareLayout"></a>02 - prepareLayout</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (void)prepareLayout;</span><br></pre></td></tr></table></figure>
<ul>
<li>用来做布局的初始化操作</li>
<li>不建议在 init 方法中进行布局的初始化操作</li>
<li>一定要调用[super prepareLayout], 官方文档中有明确注释, Subclasses should always call super if they override.</li>
</ul>
<h4 id="03-shouldInvalidateLayoutForBoundsChange"><a href="#03-shouldInvalidateLayoutForBoundsChange" class="headerlink" title="03 - shouldInvalidateLayoutForBoundsChange:"></a>03 - shouldInvalidateLayoutForBoundsChange:</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds; // return YES to cause the collection view to requery the layout for geometry information</span><br></pre></td></tr></table></figure>
<p>如果返回YES,那么collectionView显示的范围发生改变时,就会重新刷新布局<br>一旦重新刷新布局,就会按顺序调用下面的方法:</p>
<ul>
<li>prepareLayout</li>
<li>layoutAttributesForElementsInRect:</li>
</ul>
<h4 id="04-targetContentOffsetForProposedContentOffset"><a href="#04-targetContentOffsetForProposedContentOffset" class="headerlink" title="04 - targetContentOffsetForProposedContentOffset:"></a>04 - targetContentOffsetForProposedContentOffset:</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity; // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior</span><br></pre></td></tr></table></figure>
<p>这个方法的返回值,就决定了collectionView停止滚动时的偏移量<br>参数:</p>
<ul>
<li>proposedContentOffset:collectionView停止滚动时最终的偏移</li>
<li>velocity:滚动速率,通过这个参数可以了解滚动的方向</li>
</ul>
<h2 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h2><h4 id="01-重写-layoutAttributesForElementsInRect-方法"><a href="#01-重写-layoutAttributesForElementsInRect-方法" class="headerlink" title="01 - 重写 layoutAttributesForElementsInRect: 方法]"></a>01 - 重写 layoutAttributesForElementsInRect: 方法]</h4><ul>
<li>调用 [super layoutAttributesForElementsInRect], 拿到父类算好的 cell 布局属性(位置和尺寸)数组 </li>
<li>遍历布局属性数组, 修改每个 cell 布局属性的缩放比例 scale (原则应该是在一定范围内, cell 距离 collectionView 的 contentView 中心线越近, cell 显示比例越大)</li>
<li><p>在父类算好的基础上进行修改 (transform), 修改完后, 返回这个数组即可</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect</span><br><span class="line">{</span><br><span class="line"> // 获得super已经计算好的布局属性</span><br><span class="line"> NSArray *array = [super layoutAttributesForElementsInRect:rect];</span><br><span class="line"> </span><br><span class="line"> // 计算collectionView最中心点的x值</span><br><span class="line"> CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;</span><br><span class="line"> </span><br><span class="line"> // 在原有布局属性的基础上,进行微调</span><br><span class="line"> for (UICollectionViewLayoutAttributes *attrs in array) {</span><br><span class="line"> // cell的中心点x 和 collectionView最中心点的x值 的间距</span><br><span class="line"> CGFloat delta = ABS(attrs.center.x - centerX);</span><br><span class="line"> </span><br><span class="line"> // 根据间距值 计算 cell的缩放比例</span><br><span class="line"> CGFloat scale = 1 - delta / self.collectionView.frame.size.width;</span><br><span class="line"> </span><br><span class="line"> // 设置缩放比例</span><br><span class="line"> attrs.transform = CGAffineTransformMakeScale(scale, scale);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> return array;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h4 id="02-重写-shouldInvalidateLayoutForBoundsChange-方法-返回-YES"><a href="#02-重写-shouldInvalidateLayoutForBoundsChange-方法-返回-YES" class="headerlink" title="02 - 重写 shouldInvalidateLayoutForBoundsChange 方法, 返回 YES"></a>02 - 重写 shouldInvalidateLayoutForBoundsChange 方法, 返回 YES</h4><p>意味着当collectionView显示的范围发生改变时,就会重新刷新布局, 一旦刷新布局, 就会按顺序调用:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (void)prepareLayout;</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect</span><br></pre></td></tr></table></figure>
<h4 id="03-重写-targetContentOffsetForProposedContentOffset-方法决定-collectionView-停止滚动时的偏移量"><a href="#03-重写-targetContentOffsetForProposedContentOffset-方法决定-collectionView-停止滚动时的偏移量" class="headerlink" title="03 - 重写 targetContentOffsetForProposedContentOffset: 方法决定 collectionView 停止滚动时的偏移量"></a>03 - 重写 targetContentOffsetForProposedContentOffset: 方法决定 collectionView 停止滚动时的偏移量</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity</span><br><span class="line">{</span><br><span class="line"> // 计算出最终显示的矩形框</span><br><span class="line"> CGRect rect;</span><br><span class="line"> rect.origin.y = 0;</span><br><span class="line"> rect.origin.x = proposedContentOffset.x;</span><br><span class="line"> rect.size = self.collectionView.frame.size;</span><br><span class="line"> </span><br><span class="line"> // 获得super已经计算好的布局属性</span><br><span class="line"> NSArray *array = [super layoutAttributesForElementsInRect:rect];</span><br><span class="line"> </span><br><span class="line"> // 计算collectionView最中心点的x值</span><br><span class="line"> CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;</span><br><span class="line"> </span><br><span class="line"> // 存放最小的间距值</span><br><span class="line"> CGFloat minDelta = MAXFLOAT;</span><br><span class="line"> for (UICollectionViewLayoutAttributes *attrs in array) {</span><br><span class="line"> if (ABS(minDelta) > ABS(attrs.center.x - centerX)) {</span><br><span class="line"> minDelta = attrs.center.x - centerX;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> // 修改原有的偏移量</span><br><span class="line"> proposedContentOffset.x += minDelta;</span><br><span class="line"> return proposedContentOffset;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="04-补充-关于-scale-缩放比例"><a href="#04-补充-关于-scale-缩放比例" class="headerlink" title="04 - (补充) 关于 scale - 缩放比例"></a>04 - (补充) 关于 scale - 缩放比例</h4><p> 计算出 cell 的中心线和 collectionView 的 contentView 的中心线的绝对距离 delta</p>
<ul>
<li><p>计算collectionView最中心点的x值</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;</span><br></pre></td></tr></table></figure>
</li>
<li><p>计算cell的中心点 x 和 collectionView 最中心点的 x 值的间距</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CGFloat delta = ABS(attrs.center.x - centerX);</span><br></pre></td></tr></table></figure>
</li>
<li><p>根据 delta, 加一定系数, 算出 scale</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CGFloat scale = 1 - delta / self.collectionView.frame.size.width;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="UICollectionView-的优点"><a href="#UICollectionView-的优点" class="headerlink" title="UICollectionView 的优点"></a>UICollectionView 的优点</h2><ul>
<li>相对与功能简单的 TableView, 能实现复杂的定制效果</li>
<li>内部缓存机制, cell 复用</li>
<li>TableView 类似的 dataSource, delegate 方法, 简单易用的 API</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul>
<li>UICollectionView 提供的自定义 layout 功能可以方便定制很多效果, 比如电商应用常见的瀑布流, tagView 等等</li>
<li>UITableView 是以行为单位, 功能有限, 而 UICollectionView 可以方便的通过自定 layout 实现各种不同效果</li>
<li>UICollectionView 也可以通过自定 layout 实现 UITableView</li>
<li>UICollectionView 内部的循环利用机制可以有效的节约内存, 防止程序 crash</li>
</ul>
<h2 id="程序代码地址"><a href="#程序代码地址" class="headerlink" title="程序代码地址"></a>程序代码地址</h2><p><a href="https://git.oschina.net/aLonelyRoot3/AYCustomLayout.git" target="_blank" rel="noopener">https://git.oschina.net/aLonelyRoot3/AYCustomLayout.git</a><br>UICollectionView 实现的很多其他流行效果会在以后贴出</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2018/03/15/const修饰全局变量取代宏定义/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jasperay">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Alex's Field">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/03/15/const修饰全局变量取代宏定义/" itemprop="url">const修饰全局变量取代宏定义</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-03-15T21:56:58+08:00">
2018-03-15
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h2 id="宏定义"><a href="#宏定义" class="headerlink" title="宏定义"></a>宏定义</h2><p>C系程序中广泛使用宏定义,宏只是一种简单的字符串替换,根据是否带参数分为无参和带参<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 宽高</span><br><span class="line">#define kWBCellTopMargin 8 // cell 顶部灰色留白</span><br><span class="line">#define kWBCellTitleHeight 36 // cell 标题高度 (例如"仅自己可见")</span><br><span class="line">#define kWBCellPadding 12 // cell 内边距</span><br><span class="line">#define kWBCellPaddingText 10 // cell 文本与其他元素间留白</span><br><span class="line">#define kWBCellPaddingPic 4 // cell 多张图片中间留白</span><br><span class="line">#define kWBCellProfileHeight 56 // cell 名片高度</span><br><span class="line">#define kWBCellCardHeight 70 // cell card 视图高度</span><br><span class="line">#define kWBCellNamePaddingLeft 14 // cell 名字和 avatar 之间留白</span><br><span class="line">#define kWBCellContentWidth (kScreenWidth - 2 * kWBCellPadding) // cell 内容宽度</span><br><span class="line">#define kWBCellNameWidth (kScreenWidth - 110) // cell 名字最宽限制</span><br></pre></td></tr></table></figure></p>
<p>开发中可能用这么一大堆的宏定义来记录某段间距, 某个视图的高度, 或者某段字符串等, 宏定义可以简单的理解为一个常量</p>
<p>###宏的优点<br>这样做的目的在于用一个比较清楚的名称记录某个常量, 集中在某个区域管理这些常量, 在以后方便修改,修改一处便可以修改全局</p>
<p>###宏的缺点<br>但是如果一份代码中有多处需要访问某个宏的值, 这种方法就变得不可行</p>
<ul>
<li><p>你可能会想到将这些宏定义放入.pch文件内,这样不就可以办到全局访问了, 但是这样会使.pch文件内部变得十分臃肿</p>
</li>
<li><p>你可能又会想到将这些宏定义在一个头文件内, 然后再将这个头文件导入到.pch文件内, 这样就可以办到全局访问了, 但是这样仍然存在问题</p>
</li>
<li><p>比如我的一份代码内有10处用到kWBCellTopMargin这个宏, 每次用到这个宏, 系统相当于需要分配一块临时内存给这个宏, 这样累计多了, 会比较浪费</p>
</li>
</ul>
<h2 id="const修饰的全局变量"><a href="#const修饰的全局变量" class="headerlink" title="const修饰的全局变量"></a>const修饰的全局变量</h2><p>比较好的做法应该是使用”全局常量”, 类似于这种形式</p>
<ul>
<li>const CGFloat AYAlpha = 0.5;</li>
<li>const NSString *AYName = @”陈洋”;</li>
</ul>
<p>为了保证这个变量不被其他地方修改,做到只读, 使用const修饰,变量也就变为了常量</p>
<h3 id="const全局变量的优势"><a href="#const全局变量的优势" class="headerlink" title="const全局变量的优势"></a>const全局变量的优势</h3><ul>
<li>这样也保证了常量在创建的时候, 开辟一块内存, 而全局的其他地方, 只是访问它, 不会开辟临时内存</li>
<li>完全可以代替宏的作用</li>
</ul>
<h3 id="建议做法"><a href="#建议做法" class="headerlink" title="建议做法"></a>建议做法</h3><p>为了集中管理, 分清业务逻辑, 可以在工程内单独建立一个文件管理这些”全局常量”</p>
<p>一般情况, 我会在工程内搞一个名为AYConst的.h和.m文件, 注意这两个文件并不是构成一个类, 并不具备类的入口, 这样命名只是为了层次清楚</p>
<p>.m文件 (保存全局常量)<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#import <UIKit/UIKit.h></span><br><span class="line"></span><br><span class="line">const CGFloat AYCellTopMargin = 8.0;</span><br><span class="line">const CGFloat AYCellLeftMargin = 0.6;</span><br><span class="line">const CGFloat AYCellRightMargin = 0.7;</span><br><span class="line">const CGFloat AYAlpha = 1.0;</span><br><span class="line">NSString * const AYName = @"jack";</span><br></pre></td></tr></table></figure></p>
<p>.h文件(此文件导入到.pch文件, 让每个文件都可以引用)<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#import <UIKit/UIKit.h></span><br><span class="line"></span><br><span class="line">UIKIT_EXTERN const CGFloat AYCellTopMargin;</span><br><span class="line">UIKIT_EXTERN const CGFloat AYCellLeftMargin;</span><br><span class="line">UIKIT_EXTERN const CGFloat AYCellRightMargin;</span><br><span class="line">UIKIT_EXTERN const CGFloat AYAlpha;</span><br><span class="line">UIKIT_EXTERN NSString * const AYName;</span><br></pre></td></tr></table></figure></p>
<p>我会根据需求,将.h导入合适的位置,对应使用的类也会引用到存在于.m文件中的这些全局常量</p>
<p>###关于UIKIT_EXTERN关键字<br>UIKIT_EXTERN关键字是OC中苹果推荐的引用外部变量的关键字, 实际就是extern关键字的OC优化,作用和extern一致,引用外部变量需要的关键字</p>
<h2 id="补充知识"><a href="#补充知识" class="headerlink" title="补充知识"></a>补充知识</h2><p>###关于const关键字的位置问题</p>
<ul>
<li>const NSString *p;</li>
<li>NSString const *p;</li>
<li>NSString *const p;</li>
</ul>
<p>判断准则: 看const右侧修饰的变量是谁</p>
<ul>
<li>前两个都是修饰的*p,也就是指针指向的变量是”readOnly”的,指针p是可以指向其他内存空间的,前两种写法一致</li>
<li>最后一个修饰的是p,也就是指针p不能再指向其他内存空间, 但是p指向的变量却是可以改变的</li>
</ul>
<hr>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2018/03/15/理解iOS中的GCD/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jasperay">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Alex's Field">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/03/15/理解iOS中的GCD/" itemprop="url">理解iOS中的GCD</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-03-15T21:55:32+08:00">
2018-03-15
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>关于GCD,大体上要说的有这些:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1141183-eeb384c0c10f6e66.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="本文就这么点事.png"></p>
<h2 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h2><p>一个千篇一律的应届生面试题:什么是进程和线程,以及二者的区别和联系</p>
<h4 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h4><p>像Xcode,GitBook一样,系统中正在运行的应用程序,可以说系统中有一个运行的程序,系统就会启动一个进程</p>
<h4 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h4><p>可以说,线程是进程的子单元,是进程执行的基本单位,进程中所有的任务都是在线程中执行。</p>
<h4 id="多线程-1"><a href="#多线程-1" class="headerlink" title="多线程"></a>多线程</h4><p>一个进程可能在同时执行多个任务,比如说,chrome浏览器可以一边执行文件的下载任务,一边进行网页的浏览,设置还可以一边执行音乐的播放任务。<br>这就需要系统为chrome开辟多条线程协同操作,让每条线程分别执行不同的任务,并且是同时</p>
<h4 id="线程间只是看上去的“同时执行”"><a href="#线程间只是看上去的“同时执行”" class="headerlink" title="线程间只是看上去的“同时执行”"></a>线程间只是看上去的“同时执行”</h4><ul>
<li>实际上在同一时间,CPU仅仅能处理一条线程,只有一条线程在执行</li>
<li>N多条线程同时执行,实际上是CPU快速的在多条线程之间频繁的调度(众所周知CPU的执行频率是极其快速的,调度足够快便可以认为是“同时执行”)</li>
</ul>
<p>多线程的优点在于可以充分的利用CPU的“多核心”,提高系统资源的利用率,提高程序执行效率。<br>但是存在一定弊端:</p>
<ul>
<li>创建线程具有一定开销(iOS主要包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间)</li>
<li>开启大量线程,会降低程序性能</li>
<li>线程越多,CPU在调度线程上的开销就越大</li>
<li>程序设计更加复杂:比如线程之间的通信、多线程的数据共享,势必带来更多逻辑问题</li>
</ul>
<h4 id="iOS开发与多线程"><a href="#iOS开发与多线程" class="headerlink" title="iOS开发与多线程"></a>iOS开发与多线程</h4><ul>
<li>一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”</li>
<li>主线程的主要作用是显示\刷新UI界面,处理UI事件(比如点击事件、滚动事件、拖拽事件等)</li>
<li>开发中需注意不要将比较耗时的操作放到主线程中,考虑到用户体验,实际上这里就需要理解线程的串行执行。</li>
</ul>
<h4 id="线程的串行执行"><a href="#线程的串行执行" class="headerlink" title="线程的串行执行"></a>线程的串行执行</h4><ul>
<li>一个线程中的任务是串行执行的</li>
<li>一个线程中执行多个任务,只能一个一个的按顺序执行、</li>
<li>因此比较耗时的操作应该放在“非主线程”</li>
</ul>
<h4 id="iOS多线程的实现方案"><a href="#iOS多线程的实现方案" class="headerlink" title="iOS多线程的实现方案"></a>iOS多线程的实现方案</h4><p><img src="http://upload-images.jianshu.io/upload_images/1141183-28cbcfa48cfa6753.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="iOS多线程的实现方案.png"></p>
<h2 id="为什么研究GCD"><a href="#为什么研究GCD" class="headerlink" title="为什么研究GCD"></a>为什么研究GCD</h2><p>研究GCD的目的在于:</p>
<ul>
<li>相对于pThread,NSThread这两种功能简单的实现多线程的方案,GCD可以提供了更为强大的API实现更复杂的需求。</li>
<li>之后的NSOperation也是基于GCD的OC包装,并在此基础上实现的更高级的功能。</li>
</ul>
<p>因此,研究GCD对于研究多线程技术是不可或缺的。</p>
<h2 id="什么是GCD"><a href="#什么是GCD" class="headerlink" title="什么是GCD ?"></a>什么是GCD ?</h2><p>Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法,iOS4.0推出,是替代NSThread的技术,基于C语言的API,大部分是通过配合block进行使用</p>
<h2 id="任务和队列"><a href="#任务和队列" class="headerlink" title="任务和队列"></a>任务和队列</h2><p>研究GCD,需要弄清楚GCD的几个名词,包括任务、队列。GCD进行线程操作,需要配合使用任务和队列。</p>
<h3 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h3><ul>
<li>想要做的事情/执行什么操。</li>
<li>GCD 中的任务可以定义在block中。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">void (^myBlock)() = ^{</span><br><span class="line"> // 想要做的事情/任务</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h3><ul>
<li>用来’存放’任务</li>
<li>队列 != 线程</li>
<li>队列中存放的任务最后都要由线程来执行</li>
<li>队列的原则:先进先出,后进后出(FIFO/ First In First Out)</li>
</ul>
<p>队列的类型包括:</p>
<h4 id="串行队列-Serial-Dispatch-Queue"><a href="#串行队列-Serial-Dispatch-Queue" class="headerlink" title="串行队列 : (Serial Dispatch Queue)"></a>串行队列 : (Serial Dispatch Queue)</h4><ul>
<li>存放按顺序执行的任务!(一个任务执行完毕,再执行下一个任务)</li>
<li>创建方法 - 指定为 DISPATCH_QUEUE_SERIAL 类型<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// 创建一个串行队列</span><br><span class="line"> dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h4 id="并发队列-Concurrent-Dispatch-Queue"><a href="#并发队列-Concurrent-Dispatch-Queue" class="headerlink" title="并发队列 : (Concurrent Dispatch Queue)"></a>并发队列 : (Concurrent Dispatch Queue)</h4><ul>
<li>存放想要同时(并发)执行的任务,这里可能会有疑惑,但是注意队列不等于线程就OK了</li>
<li>创建方法 - 指定为 DISPATCH_QUEUE_CONCURRENT 类型</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// 创建一个并发队列</span><br><span class="line">dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent",DISPATCH_QUEUE_CONCURRENT);</span><br></pre></td></tr></table></figure>
<p>###注意两个非常常用的特殊队列:</p>
<h4 id="主队列"><a href="#主队列" class="headerlink" title="主队列"></a>主队列</h4><ul>
<li>UI 操作放在主队列中执行</li>
<li>跟主线程相关联的队列!</li>
<li>主队列是 GCD 自带的一种特殊的串行队列,注意是串行</li>
<li>主队列中的任务都会在主线程中执行</li>
<li>获取主队列</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dispatch_queue_t mainQueue = dispatch_get_main_queue();</span><br></pre></td></tr></table></figure>
<h4 id="全局并发队列"><a href="#全局并发队列" class="headerlink" title="全局并发队列"></a>全局并发队列</h4><ul>
<li>一般情况下,并发任务都可以放在全局并发队列中</li>
<li>获取全局并发队列</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="执行函数"><a href="#执行函数" class="headerlink" title="执行函数"></a>执行函数</h2><p>有了队列是远远不够的,队列仅仅用来存放任务。队列的类型只是告诉GCD,以后我的任务是想要按顺序执行or同时执行,注意是“想要”。但是GCD想要执行队列中的任务,开线程也好,不开线程也罢,总之需要配合执行函数。</p>
<p>从功能上讲,大体可以分为两种(在不细分的前提下)</p>
<h3 id="dispatch-sync"><a href="#dispatch-sync" class="headerlink" title="dispatch_sync"></a>dispatch_sync</h3><ul>
<li>同步执行函数</li>
<li>只能在“当前”线程执行任务</li>
<li>不能开启新线程</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)</span><br></pre></td></tr></table></figure>
<ul>
<li><#dispatch _queue _t queue#> :队列</li>
<li><#^ ( void)block#>:任务</li>
</ul>
<h3 id="dispatch-async"><a href="#dispatch-async" class="headerlink" title="dispatch_async"></a>dispatch_async</h3><ul>