-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
1028 lines (971 loc) · 134 KB
/
Copy pathsearch.xml
File metadata and controls
1028 lines (971 loc) · 134 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>classes-and-metaclasses</title>
<url>/2020/02/14/classes-and-metaclasses/</url>
<content><![CDATA[<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2020/02/ClassAndMetaclass.png" alt="ClassesAndMetaclasses"></p>
<span id="more"></span>
<p>Objective-C是一个基于类(calss)的对象系统,每一个对象都是某个类(calss)的实例。对象的isa指针指向他的class,这个class描述的对象的数据:allocation size,ivar类型,以及内存布局。类(calss)还描述了对象的行为:选择子是否被响应,以及method的实现地址。</p>
<p>类(calss)的方法列表是实例方法的集合,即对象响应的选择子。当你给实例对象发送消息时,objc_msgSend()会查找对象的类(calss)(和超类(如果有))的方法列表,来决定哪一个方法会被调用。</p>
<p>每一个Objective-c的class也是一个对象,它有isa指针和其他数据,并且可以响应选择子。当你调用类方法(+号方法)时,比如[NSObject alloc],实际上是向该类的类对象发送消息.</p>
<p>由于类是一个对象,因此它必须是其他类的实例:元类(metaclass)。元类是类对象的描述,就像类是普通对象的描述一样。元类的方法列表是类方法(+号方法):类对象响应的选择子。当你想向类(元类的实例)发送消息时,objc_msgSend()会浏览元类的方法列表(和超类(如果有)),以及确定要调用的方法。类方法由元类代表类对象描述,就像实例方法由类代表实例对象描述是一样的。</p>
<p>那元类呢?它会一直向下延伸吗?答案是否定的。metaclass是root metaclass的实例。root metaclass本身就是root metaclass的实例(自指)。isa指向链在此处以一个循环结束。因为现实世界中,没有人会给元类对象发送消息。</p>
<p>更重要的是元类的super类(超类)。元类的super链和类的super链是平行的。因此,类方法和实例方法是并行继承的。root metaclass的super类是root class,因此每个类对象都会响应根类的实例方法。比如:[NSObject copy]。最后,与任何其他对象一样,类对象及其子类都是root class的实例。</p>
<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2020/02/ClassAndMetaclass.png" alt="ClassesAndMetaclasses"></p>
<p>你现在感到困惑吗?上图可能会对你有所帮助。请记住,当消息发送给任何对象的时候,方法查找从该对象的isa指针开始,然后查找继续super链。实例方法由类定义,类方法由metaclass和root class定义。</p>
<p>在计算机科学语言理论中,类和元类的层次结构可以更自由,具有更深的元类链和从任何单个元类实例化的多个类。Objective-C使用元类来实现诸如类方法之类的实际目标。但另一方面却趋向于隐藏元类。例如:[NSObject class]和[NSObject self]是相同的,从形式上讲,它应该返回NSObject->isa指向的元类。Objective-C在这里采取了折中的方案。在Objective-C变得太meta之前,限制了类的模式。</p>
]]></content>
<tags>
<tag>Objectice-C</tag>
</tags>
</entry>
<entry>
<title>Objective-C 编码规范</title>
<url>/2017/03/12/coding-template-for-objective-C/</url>
<content><![CDATA[<p><strong><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2019/12/codingguide.png" alt="title"></strong></p>
<p>Objective-C 编码规范,内容来自苹果的文档翻译,自己的编码经验和对其它资料的总结。</p>
<span id="more"></span>
<h2 id="一-命名规范"><a href="#一-命名规范" class="headerlink" title="一.命名规范"></a>一.命名规范</h2><h3 id="基本原则"><a href="#基本原则" class="headerlink" title="基本原则"></a>基本原则</h3><p><strong>1.清晰</strong><br>命名应该尽可能的清晰和简洁,但在 Objective-C 中,清晰比简洁更重要。由于 Xcode 强大的自动补全功能,我们不必担心名称过长的问题。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 清晰</span><br><span class="line">insertObject:atIndex:</span><br><span class="line">// 不清晰, insert 的对象类型和 at 的位置属性没有说明</span><br><span class="line">insert:at:</span><br><span class="line">// 清晰</span><br><span class="line">removeObjectAtIndex:</span><br><span class="line">// 不清晰, remove 的对象类型没有说明,参数的作用没有说明</span><br><span class="line">remove:</span><br></pre></td></tr></table></figure>
<p>不要使用单词的简写,拼写出完整的单词:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 清晰</span><br><span class="line">setBackgroundColor:</span><br><span class="line">// 不清晰,不要使用简写</span><br><span class="line">setBkgdColor:</span><br></pre></td></tr></table></figure>
<p>然而,有部分单词简写在 Objective-C 编码过程中是非常常用的,以至于成为了一种规范,这些简写可以在代码中直接使用,下面列举了部分</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">alloc == Allocate </span><br><span class="line">max == Maximum</span><br><span class="line">alt == Alternate </span><br><span class="line">min == Minimum</span><br><span class="line">app == Application </span><br><span class="line">msg == Message</span><br><span class="line">calc == Calculate </span><br><span class="line">nib == Interface Builder archive</span><br><span class="line">dealloc == Deallocate </span><br><span class="line">pboard == Pasteboard</span><br><span class="line">func == Function </span><br><span class="line">rect == Rectangle</span><br><span class="line">horiz == Horizontal </span><br><span class="line">Rep == Representation (used in class name such as NSBitmapImageRep).</span><br><span class="line">info == Information </span><br><span class="line">temp == Temporary</span><br><span class="line">init == Initialize </span><br><span class="line">vert == Vertical</span><br><span class="line">int == Integer</span><br></pre></td></tr></table></figure>
<p>命名方法或者函数时要避免歧义<br>功能的英文词义命名, 以它**[做什么]**来命名,而不是[怎么做]来命名</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 有歧义,是返回 sendPort 还是 send 一个 Port ?</span><br><span class="line">sendPort</span><br><span class="line">// 有歧义,是返回一个名字属性的值还是 display 一个 name 的动作?</span><br><span class="line">displayName</span><br><span class="line">// 正确 很明显这个函数是返回一个消息的时间戳</span><br><span class="line">getAmsgTimeStamp;</span><br></pre></td></tr></table></figure>
<p><strong>2.一致性</strong><br>整个工程的命名风格要保持一致性,最好和苹果 SDK 的代码保持统一。不同类中完成相似功能的方法应该叫一样的名字,比如我们总是用 count 来返回集合的个数,不能在 A 类中使用 count 而在 B 类中使用 getNumber 。</p>
<ul>
<li>如果代码需要打包成 Framework 给别的工程使用,或者工程项目非常庞大,需要拆分成不同的模块,使用命名前缀是非常有用的。</li>
<li>可以在为类、协议、函数、常量以及 typedef 宏命名的时候使用前缀,但注意不要为成员变量或者方法使用前缀,因为他们本身就包含在类的命名空间内。</li>
<li>命名前缀的时候不要和苹果 SDK 框架冲突</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">前缀由大写的字母缩写组成,比如 Cocoa 中 NS 前缀代表 Founation 框架中的类, IB 则代表 Interface Builder 框架。</span><br></pre></td></tr></table></figure>
<p><strong>所以要选择符合自己项目的前缀,</strong></p>
<h3 id="执行准则"><a href="#执行准则" class="headerlink" title="执行准则"></a>执行准则</h3><p><strong>1.命名类和协议( Class&Protocol )</strong></p>
<ul>
<li>类名以大写字母开头,应该包含一个名词来表示它代表的对象类型,同时可以加上必要的前缀,比如 NSString,NSDate,NSScanner,NSApplication 等等。</li>
<li>协议名称应该清晰地表示它所执行的行为,而且要和类名区别开来,所以通常使用 ing 词尾来命名一个协议,比如 NSCopying,NSLocking 。</li>
<li>有些协议本身包含了很多不相关的功能,主要用来为某一特定类服务,这时候可以直接用类名来命名这个协议,比如 NSObject 协议,它包含了 id 对象在生存周期内的一系列方法。</li>
</ul>
<p><strong>2.命名头文件( Headers )</strong><br>_源码的头文件名应该清晰地暗示它的功能和包含的内容_:</p>
<ul>
<li>如果头文件内只定义了单个类或者协议,直接用类名或者协议名来命名头文件,比如 NSLocale.h 定义了 NSLocale 类。</li>
<li>如果头文件内定义了一系列的类、协议、类别,使用其中最主要的类名来命名头文件,比如 NSString.h 定义了 NSString 和 NSMutableString 。</li>
<li>每一个 Framework 都应该有一个和框架同名的头文件,包含了框架中所有公共类头文件的引用,比如 Foundation.h</li>
<li>Framework 中有时候会实现在别的框架中类的类别扩展,这样的文件通常使用被扩展的框架名 +Additions 的方式来命名,比如 NSBundleAdditions.h 。</li>
</ul>
<p><strong>3.命名方法( Methods )</strong><br>Objective-C 的方法名通常都比较长,这是为了让程序有更好地可读性,按苹果的说法 “_ 好的方法名应当可以以一个句子的形式朗读出来_ ” 。</p>
<p>方法一般以小写字母打头,每一个后续的单词首字母大写,方法名中不应该有标点符号(包括下划线),有两个例外:</p>
<ul>
<li>可以用一些通用的大写字母缩写打头方法,比如 PDF,TIFF 等。</li>
<li>可以用带下划线的前缀来命名私有方法或者类别中的方法。</li>
</ul>
<p>如果方法表示让对象执行一个动作,使用动词打头来命名,_注意不要使用 do , does 这种多余的关键字_,动词本身的暗示就足够了:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 动词打头的方法表示让对象执行一个动作</span><br><span class="line">- (void)invokeWithTarget:(id)target;</span><br><span class="line">- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;</span><br></pre></td></tr></table></figure>
<p>如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加 get 或者其他的动词前缀:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 正确,使用属性名来命名方法</span><br><span class="line">- (NSSize)cellSize;</span><br><span class="line">// 错误,添加了多余的动词前缀</span><br><span class="line">- (NSSize)calcCellSize;</span><br><span class="line">- (NSSize)getCellSize;</span><br></pre></td></tr></table></figure>
<p>对于有<strong>多个参数的方法</strong>,务必<strong>在每一个参数前都添加关键词</strong>,关键词应当清晰说明参数的作用<strong>不要用 and 来连接两个参数</strong>,通常 and 用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法).</p>
<p>方法的参数命名也有一些需要注意的地方 :</p>
<ul>
<li>和方法名类似,参数的第一个字母小写,后面的每一个单词首字母大写</li>
<li>不要再方法名中使用类似 pointer,ptr 这样的字眼去表示指针,参数本身的类型足以说明</li>
<li>不要使用只有一两个字母的参数名</li>
<li>不要使用简写,拼出完整的单词</li>
</ul>
<p>下面列举了一些常用参数名:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">...action:(SEL)aSelector</span><br><span class="line">...alignment:(int)mode</span><br><span class="line">...atIndex:(int)index</span><br><span class="line">...content:(NSRect)aRect</span><br><span class="line">...doubleValue:(double)aDouble</span><br><span class="line">...floatValue:(float)aFloat</span><br><span class="line">...font:(NSFont *)fontObj</span><br><span class="line">...frame:(NSRect)frameRect</span><br><span class="line">...intValue:(int)anInt</span><br><span class="line">...keyEquivalent:(NSString *)charCode</span><br><span class="line">...length:(int)numBytes</span><br><span class="line">...point:(NSPoint)aPoint</span><br><span class="line">...stringValue:(NSString *)aString</span><br><span class="line">...tag:(int)anInt</span><br><span class="line">...target:(id)anObject</span><br><span class="line">...title:(NSString *)aString</span><br></pre></td></tr></table></figure>
<p><strong>4.命名常量( Constants )</strong></p>
<p>如果要定义一组相关的常量,尽量使用枚举类型( enumerations ),枚举类型的命名规则和函数的命名规则相同:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//下拉刷新</span><br><span class="line">typedef enum{</span><br><span class="line"> HWRefreshStatuPulling = 0,//下拉</span><br><span class="line"> HWRefreshStatuRefrshing = 1,//刷新</span><br><span class="line"> HWRefreshStatuFinish = 2//刷新完成</span><br><span class="line">}HWRefreshStatus;</span><br><span class="line">//这是SDWebImage的枚举</span><br><span class="line">typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {</span><br><span class="line"> SDWebImageDownloaderLowPriority = 1 << 0,</span><br><span class="line"> SDWebImageDownloaderProgressiveDownload = 1 << 1,</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * By default, request prevent the of NSURLCache. With this flag, NSURLCache</span><br><span class="line"> * is used with default policies.</span><br><span class="line"> */</span><br><span class="line"> SDWebImageDownloaderUseNSURLCache = 1 << 2,</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * Call completion block with nil image/imageData if the image was read from NSURLCache</span><br><span class="line"> * (to be combined with `SDWebImageDownloaderUseNSURLCache`).</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line"> SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,</span><br><span class="line"> /**</span><br><span class="line"> * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for</span><br><span class="line"> * extra time in background to let the request finish. If the background task expires the operation will be cancelled.</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line"> SDWebImageDownloaderContinueInBackground = 1 << 4,</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * Handles cookies stored in NSHTTPCookieStore by setting </span><br><span class="line"> * NSMutableURLRequest.HTTPShouldHandleCookies = YES;</span><br><span class="line"> */</span><br><span class="line"> SDWebImageDownloaderHandleCookies = 1 << 5,</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * Enable to allow untrusted SSL ceriticates.</span><br><span class="line"> * Useful for testing purposes. Use with caution in production.</span><br><span class="line"> */</span><br><span class="line"> SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * Put the image in the high priority queue.</span><br><span class="line"> */</span><br><span class="line"> SDWebImageDownloaderHighPriority = 1 << 7,</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p><strong>枚举也要选取适合自己项目的前缀</strong></p>
<p><strong>5.命名通知( Notifications)</strong><br>通知常用于在模块间传递消息,所以通知要尽可能地表示出发生的事件,通知的命名范式是:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[ k ] + Notification + [ 动作 ] + [Did | Will] + [ 状态 ]</span><br><span class="line">kNotificationMessageDidSendFail</span><br></pre></td></tr></table></figure>
<p><strong>6.文件注释</strong><br>每一个文件都必须写文件注释,文件注释通常包含:</p>
<ul>
<li>作者信息</li>
<li>文件包含的内容,作用</li>
<li>历史版本</li>
<li>文件所在模块</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/*******************************************************************************</span><br><span class="line"> Author: Sean Xue (Xue yunqiang)</span><br><span class="line"> E-mail: x402399587@163.com</span><br><span class="line"> Description:</span><br><span class="line"> This file provide some covenient tool in calling library tools. One can easily include</span><br><span class="line"> library headers he wants by declaring the corresponding macros.</span><br><span class="line"> I hope this file is not only a header, but also a useful Linux library note.</span><br><span class="line"> History:</span><br><span class="line"> 2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"</span><br><span class="line"> 2013-01-07: Add basic data type such as "sint8_t"</span><br><span class="line"> 2013-01-18: Add CFG_LIB_STR_NUM.</span><br><span class="line"> 2013-01-22: Add CFG_LIB_TIMER.</span><br><span class="line"> 2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h</span><br><span class="line"> ********************************************************************************/</span><br><span class="line"></span><br><span class="line">//</span><br><span class="line">// ViewController.m</span><br><span class="line">// LRMacroDefinition</span><br><span class="line">//</span><br><span class="line">// Created by lu on 16/7/4.</span><br><span class="line">// Copyright © 2016年 scorpio. All rights reserved.</span><br><span class="line">//</span><br></pre></td></tr></table></figure>
<p><strong>7.代码注释</strong></p>
<p>按住Command+Option+/即可得到</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> <#Description#></span><br><span class="line"></span><br><span class="line"> @param imageName <#imageName description#></span><br><span class="line"> @return <#return value description#></span><br><span class="line"> */</span><br></pre></td></tr></table></figure>
<p>依次注释即可</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//代码样式举例</span><br><span class="line">/**</span><br><span class="line"> 输入图片名获取图片</span><br><span class="line"></span><br><span class="line"> @param imageName 图片名成</span><br><span class="line"> @return UIImage对象</span><br><span class="line"> */</span><br></pre></td></tr></table></figure>
<p><strong>8.import 和 include</strong></p>
<p>import 是 Cocoa 中常用的引用头文件的方式,它能自动防止重复引用文件,什么时候使用 import ,什么时候使用 include 呢?</p>
<ul>
<li>当引用的是一个 Objective-C 或者 Objective-C++ 的头文件时,使用 import</li>
<li>当引用的是一个 C 或者 C++ 的头文件时,使用 include ,这时必须要保证被引用的文件提供了保护域( define guard )。</li>
</ul>
<h2 id="二-代码格式"><a href="#二-代码格式" class="headerlink" title="二.代码格式"></a>二.代码格式</h2><p><strong>1.将空格设置为四个空格</strong></p>
<p>在 Xcode > Preferences > Text Editing 将 Tab 和自动缩进都设置为 4 个空格。</p>
<p><strong>2.每一行的最大长度</strong></p>
<p>同样的,在 Xcode > Preferences > Text Editing > Page guide at column: 中将最大行长设置为 80 ,过长的一行代码将会导致可读性问题。</p>
<p><strong>3.函数的书写</strong></p>
<p>一个典型的 Objective-C 函数应该是这样的:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在 - 和 (void) 之间应该有一个空格,第一个大括号 { 的位置在函数所在行的末尾,同样应该有一个空格。</p>
<p>如果一个函数有特别多的参数或者名称很长,应该将其按照 : 来对齐分行显示:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">-(id)initWithModel:(IPCModle)model</span><br><span class="line"> ConnectType:(IPCConnectType)connectType</span><br><span class="line"> Resolution:(IPCResolution)resolution</span><br><span class="line"> AuthName:(NSString *)authName</span><br><span class="line"> Password:(NSString *)password</span><br><span class="line"> MAC:(NSString *)mac</span><br><span class="line"> AzIp:(NSString *)az_ip</span><br><span class="line"> AzDns:(NSString *)az_dns</span><br><span class="line"> Token:(NSString *)token</span><br><span class="line"> Email:(NSString *)email</span><br><span class="line"> Delegate:(id)delegate;</span><br></pre></td></tr></table></figure>
<p>在分行时,如果第一段名称过短,后续名称可以以 Tab 的长度( 4 个空格)为单位进行缩进:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"> - (void)short:(int)theFoo</span><br><span class="line"> longKeyword:(NSRect)theRect</span><br><span class="line">evenLongerKeyword:(float)theInterval</span><br><span class="line"> error:(NSError **)theError;</span><br></pre></td></tr></table></figure>
<p><strong>4.函数调用</strong><br>函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 写在一行</span><br><span class="line">[myObject doFooWith:arg1 name:arg2 error:arg3];</span><br><span class="line">// 分行写,按照 ':' 对齐</span><br><span class="line">[myObject doFooWith:arg1</span><br><span class="line"> name:arg2</span><br><span class="line"> error:arg3];</span><br><span class="line">// 第一段名称过短的话后续可以进行缩进</span><br><span class="line">[myObj short:arg1</span><br><span class="line"> longKeyword:arg2</span><br><span class="line">evenLongerKeyword:arg3</span><br><span class="line"> error:arg4];</span><br></pre></td></tr></table></figure>
<p><strong>5.闭包( Blocks )</strong></p>
<ul>
<li>较短的 block 可以写在一行内。</li>
<li>如果分行显示的话, block 的右括号 } 应该和调用 block 那行代码的第* 一个非空字符对齐。</li>
<li>block 内的代码采用 4 个空格 的缩进。</li>
<li>如果 block 过于庞大,应该单独声明成一个变量来使用。</li>
<li>^ 和 ( 之间, ^ 和 { 之间都没有空格,参数列表的右括号 ) 和 { 之间有一个空格。</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 较短的 block 写在一行内</span><br><span class="line">[operation setCompletionBlock:^{ [self onOperationDone]; }];</span><br><span class="line">// 分行书写的 block ,内部使用 4 空格缩进</span><br><span class="line">[operation setCompletionBlock:^{</span><br><span class="line"> [self.delegate newDataAvailable];</span><br><span class="line">}];</span><br><span class="line">// 使用 C 语言 API 调用的 block 遵循同样的书写规则</span><br><span class="line">dispatch_async(_fileIOQueue, ^{</span><br><span class="line"> NSString* path = [self sessionFilePath];</span><br><span class="line"> if (path) {</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line">// 较长的 block 关键字可以缩进后在新行书写,注意 block 的右括号 '}' 和调用 block 那行代码的第一个非空字符对齐</span><br><span class="line">[[SessionService sharedService]</span><br><span class="line">loadWindowWithCompletionBlock:^(SessionWindow *window) {</span><br><span class="line"> if (window) {</span><br><span class="line"> [self windowDidLoad:window];</span><br><span class="line"> } else {</span><br><span class="line"> [self errorLoadingWindow];</span><br><span class="line"> }</span><br><span class="line">}];</span><br><span class="line">// 较长的 block 参数列表同样可以缩进后在新行书写</span><br><span class="line">[[SessionService sharedService]</span><br><span class="line">loadWindowWithCompletionBlock:</span><br><span class="line">^(SessionWindow *window) {</span><br><span class="line"> if (window) {</span><br><span class="line"> [self windowDidLoad:window];</span><br><span class="line"> } else {</span><br><span class="line"> [self errorLoadingWindow];</span><br><span class="line"> }</span><br><span class="line">}];</span><br><span class="line">// 庞大的 block 应该单独定义成变量使用</span><br><span class="line">void (^largeBlock)(void) = ^{</span><br><span class="line"> // ...</span><br><span class="line">};</span><br><span class="line">[_operationQueue addOperationWithBlock:largeBlock];</span><br><span class="line">// 在一个调用中使用多个 block ,注意到他们不是像函数那样通过 ':' 对齐的,而是同时进行了 4 个空格的缩进</span><br><span class="line">[myObject doSomethingWith:arg1</span><br><span class="line">firstBlock:^(Foo *a) {</span><br><span class="line"> // ...</span><br><span class="line">}</span><br><span class="line">secondBlock:^(Bar *b) {</span><br><span class="line"> // ...</span><br><span class="line">}];</span><br></pre></td></tr></table></figure>
<h2 id="三-代码组织"><a href="#三-代码组织" class="headerlink" title="三.代码组织"></a>三.代码组织</h2><p>在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#pragma mark - Lifecycle</span><br><span class="line"></span><br><span class="line">- (instancetype)init {}</span><br><span class="line">- (void)dealloc {}</span><br><span class="line">- (void)viewDidLoad {}</span><br><span class="line">- (void)viewWillAppear:(BOOL)animated {}</span><br><span class="line">- (void)didReceiveMemoryWarning {}</span><br><span class="line"></span><br><span class="line">#pragma mark - Public</span><br><span class="line"></span><br><span class="line">- (void)publicMethod {}</span><br><span class="line"></span><br><span class="line">#pragma mark - Private</span><br><span class="line"></span><br><span class="line">- (void)privateMethod {}</span><br><span class="line"></span><br><span class="line">#pragma mark - NSCopying</span><br><span class="line"></span><br><span class="line">- (id)copyWithZone:(NSZone *)zone {}</span><br><span class="line"></span><br><span class="line">#pragma mark - NSObject</span><br><span class="line"></span><br><span class="line">- (NSString *)description {}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>Objectice-C</tag>
</tags>
</entry>
<entry>
<title>客户端,web 端,微信端设计差别</title>
<url>/2017/03/26/design-guide-for-different-device/</url>
<content><![CDATA[<p>同一款产品为了更好的用户体验,通常要针对不同平台的特点对产品进行设计的,下面我们来聊一聊同一款产品在客户端(iOS,andriod,winPhone),web及微信端该如何设计。</p>
<span id="more"></span>
<h3 id="三端差异性对比"><a href="#三端差异性对比" class="headerlink" title="三端差异性对比"></a>三端差异性对比</h3><p><strong>本文中提到的客户端为 ipad 端</strong></p>
<p><strong>产品体验差异</strong></p>
<table>
<thead>
<tr>
<th>产品体验</th>
<th>web 端</th>
<th>客户端(ipad)</th>
<th>微信端(iphone)</th>
</tr>
</thead>
<tbody><tr>
<td>屏幕大小</td>
<td>最大</td>
<td>较大</td>
<td>适中</td>
</tr>
<tr>
<td>交互方式</td>
<td>光标点触</td>
<td>手指交互</td>
<td>手指交互</td>
</tr>
<tr>
<td>动作影响范围</td>
<td>很小</td>
<td>较大</td>
<td>较大</td>
</tr>
<tr>
<td>是否可以横屏</td>
<td>否</td>
<td>是</td>
<td>是</td>
</tr>
<tr>
<td>便携性</td>
<td>极差</td>
<td>适中</td>
<td>很好</td>
</tr>
<tr>
<td>开发人员配置</td>
<td>产品,服务器,前端, UI, 测试,运维</td>
<td>产品,服务器,UI,测试,运维</td>
<td>产品 服务器,测试,运维</td>
</tr>
<tr>
<td>持久化存储</td>
<td>极差</td>
<td>较好</td>
<td>较好</td>
</tr>
<tr>
<td>单一页面功能点</td>
<td>很多</td>
<td>较多</td>
<td>很少</td>
</tr>
<tr>
<td>交互流畅性</td>
<td>较好</td>
<td>非常好</td>
<td>适中</td>
</tr>
<tr>
<td>用户输入文字意愿</td>
<td>较高</td>
<td>较低</td>
<td>适中</td>
</tr>
<tr>
<td>用户使用出发点</td>
<td>不怕麻烦</td>
<td>方便使用</td>
<td>方便使用</td>
</tr>
</tbody></table>
<p><strong>硬件设施差异</strong></p>
<table>
<thead>
<tr>
<th>硬件设施</th>
<th>web 端</th>
<th>客户端(ipad)</th>
<th>微信端(iphone)</th>
</tr>
</thead>
<tbody><tr>
<td>陀螺仪</td>
<td>不支持</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>加速传感器</td>
<td>不支持</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>光线传感器</td>
<td>不支持</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>支持 flash</td>
<td>支持</td>
<td>不支持</td>
<td>不支持</td>
</tr>
<tr>
<td>支持 蓝牙</td>
<td>支持(隐藏较深)</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>支持 GPS</td>
<td>不支持</td>
<td>支持</td>
<td>支持</td>
</tr>
</tbody></table>
<p><strong>数据流向差异</strong></p>
<p><strong>web 端</strong></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">PC上的浏览器 ---data---> 我们的服务器 ---data---> PC上的浏览器</span><br></pre></td></tr></table></figure>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-563d0fa217a1b400.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="img"></p>
<p><strong>客户端</strong></p>
<p><em>http 通讯的情况下</em></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端 ---data---> 我们的服务器 ---data---> 客户端</span><br></pre></td></tr></table></figure>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-e703a7e29ff72540.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="img"></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">我们的服务器 ---data---> 苹果的服务器 ---data---> 客户端</span><br></pre></td></tr></table></figure>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-4e299b6e984e674c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="img"></p>
<p><strong>微信端</strong></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">手机上的微信端 ---data---> 微信服务器 ---data---> 我们的服务器 ---data---> 手机上的微信端</span><br></pre></td></tr></table></figure>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-2f8214cdade9a85f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="img"></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">注:我们的服务器向主动和微信企业号通信,可以访问微信的接口,由微信服务器和微信客户端通信,原理都相似,就不画图了.</span><br></pre></td></tr></table></figure>
<h3 id="主要指导原则"><a href="#主要指导原则" class="headerlink" title="主要指导原则"></a>主要指导原则</h3><h4 id="客户端"><a href="#客户端" class="headerlink" title="客户端"></a>客户端</h4><p><strong>宗旨</strong>:移动端更偏重体验!</p>
<p>关键词:<a href="%E5%BC%95%E7%94%A8%E8%87%AA%E3%80%8AiOS8%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83%E3%80%8B">^脚注一</a></p>
<blockquote>
<ul>
<li>遵从:UI能够更好地帮助用户理解内容并与之互动,但却不会分散用户对内容本身的注意力。</li>
<li>清晰:各种大小的文字应该易读,图标应该醒目,去除多余的修饰,突出重点,很好地突显了设计理念。</li>
<li>深度:视觉的层次和生动的交互动作会赋予UI新的活力,不但帮助用户更好理解新UI的操作并让用户在使用过程中感到惊喜。<br>引用自<iOS8设计规范></li>
</ul>
</blockquote>
<p>原则:<a href="%E5%BC%95%E7%94%A8%E8%87%AA%E3%80%8AiOS8%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83%E3%80%8B">^脚注二</a></p>
<blockquote>
<ul>
<li>首先,去除了UI元素让应用的核心功能呈现得更加直接并强调其相关性。</li>
<li>其次,直接使用iOS的系统主题让其成为应用的UI,这样能给用户统一的视觉感受。</li>
<li>最后,保证你设计的UI可以适应各种设备和不同操作模式,这样用户可以在不同场景下舒适地享用你的应用。</li>
</ul>
</blockquote>
<p><strong>特点</strong>:</p>
<ol>
<li>客户端只要通过用户的触摸,拖拽来完成交互.</li>
<li>屏幕比较大.</li>
<li>便携性适中</li>
</ol>
<p><strong>原理</strong>:客户端是一个基于苹果公司的iOS操作系统上面的一个软件(进程),相对于 PC 来说,性能略差,不处理过于复杂的逻辑.所以需要和服务器来进行交互,如果把服务器比喻为数据的加工者,那么客户端就是数据采集以及数据展示者.当然客户端也可以有浏览器,只不过性能比 PC 上的浏览器略差,但是客户端的浏览器的独特之处在于他可以和客户端之间进行一些交互(比如点击客户端网页上的某个按钮,弹出客户端的提示框).</p>
<p><strong>局限性</strong></p>
<ol>
<li>客户端的局限性是他的性能没有 pc 好</li>
<li>屏幕在功能设计上的局限性也是有的,这也是屏幕,体积的双刃剑</li>
<li>点触没有鼠标精准,这也是客户端本身的局限性,也是一把双刃剑</li>
</ol>
<p><strong>优点</strong></p>
<ol>
<li>交互性较强,手指操作灵活</li>
<li>便携,走到哪拿到哪</li>
<li>持久化存储方便,可以针对用户这个点,提升很强的定制化</li>
</ol>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-fdb61635aa84d1ab.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="14900839448012.jpg"></p>
<p>####web 端</p>
<p><strong>宗旨</strong>:web 端相对比较偏功能和内容!</p>
<p><strong>特点</strong>:</p>
<ol>
<li>web 端只要通过鼠标点击来完成交互</li>
<li>屏幕很大,鼠标较小,所以点击可以很精准.</li>
<li>便携性基本没有</li>
</ol>
<p><strong>原理</strong>: web 端是通过浏览器来对服务器进行交互的,其实 web 端的浏览器也是一个软件(进程),其功能强大,相比服务器,网页就是信息的采集者和展示者, web 端想和服务器交互有两种方式.</p>
<ol>
<li>html 通过http(短连接)来进行表单提交,服务端返回数据的时候,利用浏览器进行渲染.</li>
<li>html 通过 js 来进行一些事件的驱动来让浏览器做一些事情,并发送 http(短连接)来和服务器进行交互,服务端返回数据的时候,利用js 和浏览器进行一系列事件的触发.</li>
</ol>
<p><strong>局限性</strong></p>
<ol>
<li>pc 携带不便是他致命的弱点,谁没事拿着电脑到处跑啊?</li>
<li>通过网址来登录,没有点击就出现的效果好</li>
<li>复杂持久化存储机制不完善,开发者不能利用 web 端去给做一些针对用户个性化的持久化存储</li>
</ol>
<p><strong>优势</strong></p>
<ol>
<li>可以进行复杂的操作</li>
<li>屏幕大,看着很爽</li>
</ol>
<p>web端的优势就是源于性能较好的 PC 端和强大的浏览器来和用户进行一系列较复杂的操作.</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-20b665ad294e8e5b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="14900816447614.jpg"></p>
<p>功能强大,一个界面布满所有功能.</p>
<p>####微信端</p>
<p><strong>宗旨</strong>:微信端设计更加轻量级,简单功能的完成,一些信息的传递.</p>
<p><strong>特点</strong>:</p>
<ol>
<li>微信端主要靠下面的选项栏和发消息(发送指定消息,返回给用户一些信息)来完成交互</li>
<li>屏幕较小</li>
<li>便携性较强</li>
</ol>
<p><strong>原理</strong>:前面我们提到,客户端是一个基于苹果公司的iOS操作系统上面的一个软件(进程),相对于 PC 来说,性能略差,那么微信端就是微信这个软件给一些公司提供的接口,微信的用户量较大,所以可以通过向一些有意向在微信上展示自己信息的公司收费,然后他们给我们提供接口,让我们可以再微信上展示一些我们自己的信息(按照他们的要求),所以微信端的开发是我们的后台开发人员通过他们的接口给腾讯的服务器提供数据,腾讯的服务器再去通知他们的客户端进行一些我们公司的信息展示</p>
<p>微信端的功能可以划分为两种</p>
<ol>
<li>我们提供按照要求给他们服务器数据,他们服务器传递数据给微信,微信在他们的软件上进行一些展示(按照一种特定的形式)</li>
<li>我们给他们一个链接地址(web 端的页面),他们用软件内的浏览器进行一些展示.</li>
</ol>
<p><strong>局限性</strong></p>
<ol>
<li>既然使用别人的软件去展示你的信息,那么就要按照人家的规矩来.</li>
<li>目前微信提供的服务还不是很丰富,所以对业务展示来说,不能很好地支持,只能展示一些比较简单的信息.</li>
</ol>
<p><strong>优点</strong></p>
<ol>
<li>微信的用户基数大,让用户了解我们产品的代价较低,所以进行一些基本信息的展示效果很不错.可以通过新本信息去吸引用户去下载我们的 app</li>
<li>用户在接受我们信息的时候代价较低,人人都玩微信,获取我们信息很方便.</li>
</ol>
<p>附:<br><a href="https://www.iamue.com/14576">ios8设计规范 中文版 完整版</a></p>
]]></content>
<tags>
<tag>Design</tag>
</tags>
</entry>
<entry>
<title>Effective Objective-C 2.0 笔记(一)</title>
<url>/2017/06/04/effective-Objective-C-one/</url>
<content><![CDATA[<p>编写高质量的 iOS 和 OS X 代码的52个有效办法-读书笔记</p>
<p>最近在读<code>Effective Objective-C 2.0</code>,想做一下记录,我是挑选自己感兴趣的,和书中的顺序不一样,看到那写到哪</p>
<span id="more"></span>
<h2 id="第4条-多用类型常量-少用-define-预处理命令"><a href="#第4条-多用类型常量-少用-define-预处理命令" class="headerlink" title="第4条:多用类型常量,少用 #define 预处理命令"></a>第4条:多用类型常量,少用 #define 预处理命令</h2><p>在定义常量的时候,我们通常需要确定一下几件事:</p>
<ol>
<li><code>常量代表的含义</code></li>
<li><code>常量的作用域</code></li>
<li><code>什么样的常量的命名方式简单易懂?</code></li>
</ol>
<p>如果我们定义个动画时间的时候,我们通常会这么定义</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#define ANIMATION_DURATION 0.3</span><br></pre></td></tr></table></figure>
<p>这么定义的缺点是</p>
<ul>
<li><code>常量代表的含义</code>不清晰,未明确指出0.3与时间有关.</li>
<li><code>常量的作用域</code>过大,预处理过程会把碰到所有的<code>ANIMATION_DURATION</code>一律替换成0.3,这样的话,假设此指令声明在某个头文件中,那么引入这个头文件的代码中的<code>ANIMATION_DURATION</code>都会被替换掉,所以说即使你使用#define定义常量,也要放在 <code>.m</code>文件里</li>
<li><code>什么样的常量的命名方式简单易懂?</code>,这种命名方式,没有规范,下面会介绍一下规范的定义常量</li>
</ul>
<p>正确的定义方式应该按照下面两种方式:</p>
<p>第一,常量的作用域仅在当前类,即<code>局部常量</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//in the implementation file - EOCAnimatedView.m</span><br><span class="line">static const NSTimeInterval kAnimationDuration = 0.3;</span><br></pre></td></tr></table></figure>
<p>第二,常量的作用域在所有引用了当前类的代码中,即<code>全局常量</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//in the header file - EOCAnimatedView.h</span><br><span class="line">extern const NSTimeInterval EOCAnimatedViewAnimationDuration;</span><br><span class="line"></span><br><span class="line">//in the implementation file - EOCAnimatedView.m</span><br><span class="line">const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;</span><br></pre></td></tr></table></figure>
<p>为什么要这样定义呢?</p>
<ol>
<li><code>常量代表的含义</code>明确,0.3与时间有关,所以我们用<code>NSTimeInterval</code>表示,这样容易让人理解</li>
<li><code>常量的作用域</code>明确,常量的作用域仅在当前类时,我们用第一种方法定义,常量的作用域在所有引用了当前类的代码中,即<code>全局变量</code>我们用第二种方式定义</li>
<li><code>什么样的常量的命名方式简单易懂?</code>若某常量局限于<code>编译单元(translation unit)</code> 也就是”实现文件”即<code>.m</code>文件,则在前面加字母<code>k</code>,因为<code>局部变量</code>仅在当前类使用,所以不用加以区分,若常量在类外可见,则通常以类名为前缀,因为<code>全局常量</code>加入到<code>全局常量表</code>中,必须用类名加以区分</li>
</ol>
<p>为什么我们不能在.h文件里面定义<code>局部常量</code>?</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">局部常量`的定义不应该出现在`.h`文件里,因为 OC 没有 namespace 这个概念,所有引入这份头文件的其他文件都会出现这个名字,如果在`局部常量`的定义出现在`.h`文件里,就等于声明了一个`kAnimationDuration`的`全局常量</span><br></pre></td></tr></table></figure>
<p>为什么常量的作用域仅在当前类时,我们用第一种方法定义?</p>
<p>因为<code>static</code> 修饰符意味着该常量仅定义在定义该<code>常量的编译单元中(.m)</code>可见, 编译器每收到一个编译单元,就会输出一份<code>目标文件(object file)</code>,如声明此常量不加<code>static</code>,编译器会为它创建一个<code>外部符号(external symbol)</code>, 此时,另一个编译也声明了同名变量,那么编译器就会报错.</p>
<p>为什么常量的作用域在所有引用了当前类的代码中,即<code>全局变量</code>我们用第二种方式定义?</p>
<p><code>全局常量</code>需要放在<code>全局符号表(global symbol table)</code> 中,这种常量的定义是在头文件<code>声明</code>,在实现文件里<code>定义</code>,这样,编译器在使用此常量的时候无需查看其定义,即允许代码使用此常量,因为他知道,当链接成二进制文件之后,肯定能找到这个常量.</p>
<p>Note:</p>
<ul>
<li>常量的定义应该从右到左解读</li>
<li>使用<code>第二种定义</code>优于使用<code>#define</code>,因为<code>#define</code>定义出来的常量不含类型信息,编译器只会在编译前据此执行查找与替换操作,即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致</li>
</ul>
]]></content>
<tags>
<tag>Objectice-C</tag>
</tags>
</entry>
<entry>
<title>配置一个炫酷的终端</title>
<url>/2017/06/11/how-to-make-a-cool-terminal/</url>
<content><![CDATA[<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2019/09/terminal.png" alt="title"></p>
<p>我们每天都在使用终端工具,配置的好看些,可以让我们在工作的时候有一个愉悦的心情 :)</p>
<span id="more"></span>
<p>这个终端是<a href="http://iterm2.com/">iTerm2</a>,<code>MAC OS</code>下一个非常牛逼的终端。顺利通过官网下载安装,然后开始配置过程。</p>
<h2 id="基本配置"><a href="#基本配置" class="headerlink" title="基本配置"></a>基本配置</h2><p>按<code>Command + ,</code>打开偏好设定窗口:<code>Profiles / Window</code>,把<code>Settings for New Windows</code>中的Style改成<code>Left of Screen</code>让它在屏幕左侧显示(在宽屏显示器上效果不错) 勾选<code>Hotkey</code>中的<code>Show/hide iTerm2 with a system-wide hotkey</code>,然后设定一个热键。我个人偏爱 <code>Command + ;</code>。 现在按<code>Command + ;</code>.就可以随时调出或者隐藏<code>iTerm2</code>了。 你还可以在<code>Profile</code>中更改字体等,其他的希望大家自己研究。</p>
<h2 id="配色方案"><a href="#配色方案" class="headerlink" title="配色方案"></a>配色方案</h2><p><a href="http://ethanschoonover.com/solarized">Solarized</a>可以说是目前网络上最流行的配色解决方案,其优美的配色和众多软件的支持,成就了她现在的火爆。我们这里就使用它来进行<code>iTerm2</code>的配色 下载,解压,打开<code>iTerm2</code>的偏好设定,<code>Profiles / Colors</code>,最下面的<code>Load Presets ... / Import...</code> 直接加载<code>iterm2-colors-solarized/Solarized Dark.itermcolors</code>配色方案就可以了,这时候可以看到。看到效果了。</p>
<p>其实到这一步,<code>iTerm2</code>看上去已经好多了,但是为了能让它的样子更风骚,我们需要进一步配置。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">zsh`和`oh-my-zsh</span><br></pre></td></tr></table></figure>
<p>众所周知在<code>Unix/Linux</code>系统下是存在很多不同的<code>shell</code>,常见的就有<code>bash</code>, <code>csh</code>,<code>ksh</code>,<code>zsh</code>等等。在<code>Linux</code>和<code>MAC OS</code>系统中,默认使用的是<code>bash</code>。这里我们要是功能更加强大的 <code>zsh</code>。这里可以通过<code>Homebrew</code>下载安装<code>zsh</code>:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">brew install zsh</span><br></pre></td></tr></table></figure>
<p>安装完成后,将<code>zsh</code>设置成系统默认<code>shell</code>,以代替<code>bash</code>。 用编辑器打开<code>/etc/shells</code>,在末尾添加<code>/usr/local/bin/zsh</code>,保存关闭。 在终端中执行以下命令:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">chsh -s /usr/local/bin/zsh</span><br></pre></td></tr></table></figure>
<p>然后重新启动<code>iTerm2</code>,<code>zsh</code>就已经被配置成默认的<code>shell</code>了。</p>
<p>此时的<code>zsh</code>还是最初始的样子,我们要将其变成强大的终端,需要对其进行配置,为了让<code>zsh</code>产生想上面图片那样的效果,我们需要安装<code>oh-my-zsh</code>。</p>
<p><code>oh-my-zsh</code>是一个功能强大框架,发布于<code>Github</code>。它可以让你以纯傻瓜的方式对zsh进行配置已得到强大的功能,这里是其<a href="https://github.com/robbyrussell/oh-my-zsh">发布页面</a>,如果有什么疑问,都可以去这个页面进行查询,里面有最详细的说明。 <code>on-my-zsh</code>的安装支持自动和手动,为了省去麻烦,我建议优先使用自动,如果要使用手动安装,请查看安装说明。这里只说一下自动安装。 在终端里,如果你有<code>curl</code>,则在终端输入</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">curl -L http://install.ohmyz.sh | sh</span><br></pre></td></tr></table></figure>
<p>如果你使用<code>wget</code>,则在终端输入</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">wget --no-check-certificate http://install.ohmyz.sh -O - | sh</span><br></pre></td></tr></table></figure>
<p>这样<code>oh-my-zsh</code>就安装完成了。开始对其进行配置。</p>
<p><code>zsh</code>的配置几乎都在<code>~/.zshrc</code>下面,用编辑器打开文件,你会看到很多选项,建议你感觉自己的需求进行配置,不懂得可以查询文档。这里我只说一下主题的配置。在<code>~/.oh-my-zsh/themes</code>存在各式各样的主题文件,每个主题的样子你可以通过这个<a href="https://github.com/robbyrussell/oh-my-zsh/wiki/Themes">网址</a>进行查看,选好样式后,在<code>.zshrc</code>中的<code>ZSH_THEME</code>设置成你所选用的主题。重启<code>iTerm2</code>,便可以看到相应的效果。这里建议使用<code>agnoster</code>,比较好看。</p>
<p>做完上步后,<code>iTerm2</code>的大概样子应该跟我们的第一张图片差不多了,但是没有图片中的箭头,那是因为产生箭头效果是需要特殊字体支持的,这个字体最开始是一个叫<a href="https://powerline.readthedocs.org/">Powerline</a>的项目开始的,其目的是美化<code>Vim</code>中操作栏的字体状态使其产生箭头效果,当然这个被移植到了<code>iTerm2</code>上。</p>
<p>要使用这种字体,需要做如下的事情</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># clone</span><br><span class="line">git clone https://github.com/powerline/fonts.git</span><br><span class="line"># install</span><br><span class="line">cd fonts</span><br><span class="line">./install.sh</span><br><span class="line"># clean-up a bit</span><br><span class="line">cd ..</span><br><span class="line">rm -rf fonts</span><br></pre></td></tr></table></figure>
<p>安装完成之后,需要在<code>iTerm2</code>做如下配置</p>
<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2019/09/terminal.png" alt="Text 配置"></p>
<p>配置完成之后,最终的效果便跟第一张图片中样式一样了。</p>
<h2 id="更多的配色方案"><a href="#更多的配色方案" class="headerlink" title="更多的配色方案"></a>更多的配色方案</h2><p><code>Solarized</code>虽然很好,但是使用久了终究会变得平淡,这时候我们要换换主题,<code>Github</code>上有一个项目可以使用非常丰富的主题库,地址在<a href="https://github.com/mbadolato/iTerm2-Color-Schemes">这里</a>,上面有详细的安装步骤,你可以根据自己的喜好来选择主题了。</p>
<p>至此,<code>iTerm2</code>的配置告一段落了,其他的,就需要大家在使用中不断的熟悉和配置你的终端了。</p>
<h2 id="终端的更多配置"><a href="#终端的更多配置" class="headerlink" title="终端的更多配置"></a>终端的更多配置</h2><p>其实 <code>zsh</code>功能比<code>bash</code>好用且强大多了!特别是他的自动补全功能还有自定义功能,还有自动纠错能力,命令颜色高亮等!<a href="http://www.cnblogs.com/ma6174/archive/2012/05/08/2490921.html">这里</a>提供了一些<code>zsh</code>的配置方案,供大家参考一下</p>
<p>配置了这么炫酷的终端,在你<code>open</code>一些文件的时候,会使用默认的文本编辑器打开,这会让大家很不爽,比如如果使用默认的文本编辑器编辑<code>Podfile</code> 的时候,会出现一些问题,这时候我们想要把默认的文本编辑器替换成 <code>Sublime</code>,<a href="http://xiaoyu2er.github.io/2016/01/31/mac-default-app-for-spec-file-type/">这里</a>为大家提功了参考方案</p>
<h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><p><a href="https://laoshuterry.gitbooks.io/mac_os_setup_guide/content/4_ZshConfig.html">https://laoshuterry.gitbooks.io/mac_os_setup_guide/content/4_ZshConfig.html</a></p>
<p><a href="https://github.com/powerline/fonts/issues/44">https://github.com/powerline/fonts/issues/44</a></p>
<p><a href="http://www.cnblogs.com/ma6174/archive/2012/05/08/2490921.html">http://www.cnblogs.com/ma6174/archive/2012/05/08/2490921.html</a></p>
<p><a href="http://xiaoyu2er.github.io/2016/01/31/mac-default-app-for-spec-file-type/">http://xiaoyu2er.github.io/2016/01/31/mac-default-app-for-spec-file-type/</a></p>
]]></content>
<tags>
<tag>Tool</tag>
</tags>
</entry>
<entry>
<title>iOS字典的排序问题</title>
<url>/2017/02/19/map-sort-ios/</url>
<content><![CDATA[<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2019/12/sort.PNG" alt="title"></p>
<span id="more"></span>
<p>在日常开发中,经常会遇到根据字典的Key对Value进行排序的问题,所以对于此类问题抽出一个通用方法,废话不多说,下面是代码.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> 对字典(Key-Value)排序 区分大小写</span><br><span class="line"></span><br><span class="line"> @param dict 要排序的字典</span><br><span class="line"> */</span><br><span class="line">- (void)sortedDictionary:(NSDictionary *)dict{</span><br><span class="line"> </span><br><span class="line"> //将所有的key放进数组</span><br><span class="line"> NSArray *allKeyArray = [dict allKeys];</span><br><span class="line"> </span><br><span class="line"> //序列化器对数组进行排序的block 返回值为排序后的数组</span><br><span class="line"> NSArray *afterSortKeyArray = [allKeyArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id</span><br><span class="line"> Nonnull obj2) {</span><br><span class="line"> /**</span><br><span class="line"> In the compare: methods, the range argument specifies the</span><br><span class="line"> subrange, rather than the whole, of the receiver to use in the</span><br><span class="line"> comparison. The range is not applied to the search string. For</span><br><span class="line"> example, [@"AB" compare:@"ABC" options:0 range:NSMakeRange(0,1)]</span><br><span class="line"> compares "A" to "ABC", not "A" to "A", and will return</span><br><span class="line"> NSOrderedAscending. It is an error to specify a range that is</span><br><span class="line"> outside of the receiver's bounds, and an exception may be raised.</span><br><span class="line"> </span><br><span class="line"> - (NSComparisonResult)compare:(NSString *)string;</span><br><span class="line"> </span><br><span class="line"> compare方法的比较原理为,依次比较当前字符串的第一个字母:</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"> 排序结果</span><br><span class="line"> NSComparisonResult resuest = [obj1 compare:obj2];为从小到大,即升序;</span><br><span class="line"> NSComparisonResult resuest = [obj2 compare:obj1];为从大到小,即降序;</span><br><span class="line"> </span><br><span class="line"> 注意:compare方法是区分大小写的,即按照ASCII排序</span><br><span class="line"> */</span><br><span class="line"> //排序操作</span><br><span class="line"> NSComparisonResult resuest = [obj1 compare:obj2];</span><br><span class="line"> return resuest;</span><br><span class="line"> }];</span><br><span class="line"> NSLog(@"afterSortKeyArray:%@",afterSortKeyArray);</span><br><span class="line"> </span><br><span class="line"> //通过排列的key值获取value</span><br><span class="line"> NSMutableArray *valueArray = [NSMutableArray array];</span><br><span class="line"> for (NSString *sortsing in afterSortKeyArray) {</span><br><span class="line"> NSString *valueString = [dict objectForKey:sortsing];</span><br><span class="line"> [valueArray addObject:valueString];</span><br><span class="line"> }</span><br><span class="line"> NSLog(@"valueArray:%@",valueArray);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>代码中提到了排序是按照ASCII码排序,因为ASCII码表示区分大小写的,所以我们的排序结果也区分大小写.</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-616c5fd38d77672a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="ASCII"></p>
<p>如果不想区分大小写,可以在比较前,可以用lowercaseString方法将字符串转换为小写.代码如下</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> 对字典(Key-Value)排序 不区分大小写</span><br><span class="line"> </span><br><span class="line"> @param dict 要排序的字典</span><br><span class="line"> */</span><br><span class="line">- (void)sortedDictionarybyLowercaseString:(NSDictionary *)dict{</span><br><span class="line"> </span><br><span class="line"> //将所有的key放进数组</span><br><span class="line"> NSArray *allKeyArray = [dict allKeys];</span><br><span class="line"> </span><br><span class="line"> //序列化器对数组进行排序的block 返回值为排序后的数组</span><br><span class="line"> NSArray *afterSortKeyArray = [allKeyArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id</span><br><span class="line"> Nonnull obj2) {</span><br><span class="line">/**</span><br><span class="line"> In the compare: methods, the range argument specifies the</span><br><span class="line"> subrange, rather than the whole, of the receiver to use in the</span><br><span class="line"> comparison. The range is not applied to the search string. For</span><br><span class="line"> example, [@"AB" compare:@"ABC" options:0 range:NSMakeRange(0,1)]</span><br><span class="line"> compares "A" to "ABC", not "A" to "A", and will return</span><br><span class="line"> NSOrderedAscending. It is an error to specify a range that is</span><br><span class="line"> outside of the receiver's bounds, and an exception may be raised.</span><br><span class="line"> </span><br><span class="line"> - (NSComparisonResult)compare:(NSString *)string;</span><br><span class="line"> </span><br><span class="line"> compare方法的比较原理为,依次比较当前字符串的第一个字母:</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"> 排序结果</span><br><span class="line"> NSComparisonResult resuest = [obj1 compare:obj2];为从小到大,即升序;</span><br><span class="line"> NSComparisonResult resuest = [obj2 compare:obj1];为从大到小,即降序;</span><br><span class="line"> </span><br><span class="line"> 注意:compare方法是区分大小写的,即按照ASCII排序</span><br><span class="line"> */</span><br><span class="line"> //小写转化</span><br><span class="line"> obj1 = [obj1 lowercaseString];</span><br><span class="line"> obj2 = [obj2 lowercaseString];</span><br><span class="line"> //排序操作</span><br><span class="line"> NSComparisonResult resuest = [obj1 compare:obj2];</span><br><span class="line"> return resuest;</span><br><span class="line"> }];</span><br><span class="line"> NSLog(@"afterSortKeyArray:%@",afterSortKeyArray);</span><br><span class="line"> </span><br><span class="line"> //通过排列的key值获取value</span><br><span class="line"> NSMutableArray *valueArray = [NSMutableArray array];</span><br><span class="line"> for (NSString *sortsing in afterSortKeyArray) {</span><br><span class="line"> NSString *valueString = [dict objectForKey:sortsing];</span><br><span class="line"> [valueArray addObject:valueString];</span><br><span class="line"> }</span><br><span class="line"> NSLog(@"valueArray:%@",valueArray);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="简单测试代码"><a href="#简单测试代码" class="headerlink" title="简单测试代码"></a>简单测试代码</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"Danny",@"Basd",@"12",@"Cerff",@"man",@"ABCDEFG",@"Sean",@"Cerf",nil];</span><br><span class="line"> NSLog(@"dictionary:%@",dict);</span><br><span class="line"> [self sortedDictionary:dict];</span><br></pre></td></tr></table></figure>
<h6 id="输出为"><a href="#输出为" class="headerlink" title="输出为:"></a>输出为:</h6><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">2017-02-12 11:23:03.484 根据key对dictionary排序[6122:164377] dictionary:{</span><br><span class="line"> ABCDEFG = 12345;</span><br><span class="line"> Basd = Rddd;</span><br><span class="line"> Cerf = Sean;</span><br><span class="line"> Cerff = iiiii;</span><br><span class="line">}</span><br><span class="line">2017-02-12 11:23:03.484 根据key对dictionary排序[6122:164377] afterSortKeyArray:(</span><br><span class="line"> ABCDEFG,</span><br><span class="line"> Basd,</span><br><span class="line"> Cerf,</span><br><span class="line"> Cerff</span><br><span class="line">)</span><br><span class="line">2017-02-12 11:23:03.485 根据key对dictionary排序[6122:164377] valueArray:(</span><br><span class="line"> 12345,</span><br><span class="line"> Rddd,</span><br><span class="line"> Sean,</span><br><span class="line"> iiiii</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<h4 id="代码此时例子较多-就不都放上来了-大家可放心食用"><a href="#代码此时例子较多-就不都放上来了-大家可放心食用" class="headerlink" title="代码此时例子较多,就不都放上来了,大家可放心食用."></a>代码此时例子较多,就不都放上来了,大家可放心食用.</h4>]]></content>
<tags>
<tag>Objectice-C</tag>
</tags>
</entry>
<entry>
<title>关于MD5安全性的讨论</title>
<url>/2017/01/05/md5/</url>
<content><![CDATA[<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2019/12/md5.PNG" alt="title"></p>
<p>MD5本身是不可逆和无冲突的,但是用一些巧妙地方法会被破解出来.一个密码系统的是没有绝对安全的,密码系统只是增加了被破解的代价.</p>
<span id="more"></span>
<h2 id="MD5的常识性知识"><a href="#MD5的常识性知识" class="headerlink" title="MD5的常识性知识"></a>MD5的常识性知识</h2><ul>
<li>一个密码系统的安全性只在于密钥的保密性,而不在于算法的保密性.</li>
<li>MD5就是把不论什么长度的文字内容,给精简成128位散列数.不论文字内容只有一个字母a,还是1w多字的长篇论文,都精简(或填充)成128位散列数.</li>
<li>MD5主要用在文档校验上,用来生成密钥检测文档是否被篡改.</li>
<li>MD5算法本身是不可被破解的.</li>
<li>目前没有软件能有效地破解 MD5.大多数时候只是把常见字符串的 MD5 存了起来为彩虹表,通过一定的算法,进行匹配.</li>
<li>厉害的软件还会在彩虹桥的基础上加入哈希碰撞.</li>
<li>MD5 目前来说还是可以用的,尤其是考虑到合适的加盐以后可以解决大多数彩虹表带来的危险.<br>所有的不被破解的基础,一定要做到秘钥保密!秘钥保密!秘钥保密!</li>
</ul>
<h2 id="一些知识点的补充"><a href="#一些知识点的补充" class="headerlink" title="一些知识点的补充"></a>一些知识点的补充</h2><h3 id="哈希"><a href="#哈希" class="headerlink" title="哈希"></a>哈希</h3><p>Hash,一般翻译做”散列”,也有直接音译为”哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值.<br>简单的说就是一种将任意长度的消息压缩到某一固定长度的信息摘要的函数.<br>HASH主要用于信息安全领域中加密算法,他把一些不同长度的信息转化成杂乱的128位的编码里,叫做HASH值. 也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系.<br>Hash算法在信息安全方面的应用主要体现在以下的3个方面:</p>
<ol>
<li>文件校验<br>我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏.<br>MD5 Hash算法的”数字指纹”特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令.</li>
<li>数字签名<br>Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。 对 Hash 值,又称”数字摘要”进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的.而且这样的协议还有其他的优点.</li>
<li>鉴权协议<br>如下的鉴权协议又被称作”挑战–认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法.</li>
</ol>
<p><strong>-以上解释摘自百度</strong></p>
<h3 id="彩虹表"><a href="#彩虹表" class="headerlink" title="彩虹表"></a>彩虹表</h3><p>如果将哈希后的密文比作一把锁,暴力破解的方法就是现场制作各种各样不同齿形的钥匙,再来尝试能否开锁,这样耗时无疑很长;我以前错误理解的“彩虹表”,是事先制作好所有齿形的钥匙,全部拿过来尝试开锁,这样虽然省去了制作钥匙的时间,但是后来发现这些钥匙实在是太多了,没法全部带在身上。而真正的彩虹表,是将钥匙按照某种规律进行分组,每组钥匙中只需要带最有特点的一个,当发现某个“特征钥匙”差一点就能开锁了,则当场对该钥匙进行简单的打磨,直到能开锁为止。这种方法是既省力又省时的.</p>
<h3 id="哈希碰撞"><a href="#哈希碰撞" class="headerlink" title="哈希碰撞"></a>哈希碰撞</h3><p>哈希碰撞就是一种优化过算法,其基本原理就是把密码明文对应的MD5与你的MD5进行对比,因为经过一些优化,所以无论是时间上,还是空间都很很快.其优化方法我也没研究过,不过感兴趣的可以查一下王小云教授关于哈希碰撞的论文.</p>
<h2 id="常用破解MD5方法"><a href="#常用破解MD5方法" class="headerlink" title="常用破解MD5方法"></a>常用破解MD5方法</h2><p>目前来说,破解MD5加密的最有效的方法就是 <strong>哈希碰撞+彩虹表+对应秘钥</strong>,一些网络黑客会在一些明文存储用户密码的网站上窃取信息,假如黑客有一亿条数据,因为都是真实用户所以经过哈希碰撞之后,你的密码被破译出来的几率就真的非常大了,那破译不出来的可能就是因为大小写和一些特殊符号,这就用到了彩虹表,最后就是你的秘钥,比如你是之前对用户的密码进行加盐,还是之后对MD5之后的字符串进行的特殊处理,只要对方知道你的秘钥,那么你密码被破译出来的几率就非常非常高了,所以我们说: <strong>一个密码系统的安全性只在于密钥的保密性,而不在于算法的保密性.</strong></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MD5本身是不可逆和无冲突的,但是用一些巧妙地方法会被破解出来.一个密码系统的是没有绝对安全的,密码系统只是增加了被破解的代价.</p>
<p>PS:一切明文存储用户密码的网站都是耍流氓!</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1401726-32ca4ee535719034.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="lol"></p>
]]></content>
<tags>
<tag>Objectice-C</tag>
</tags>
</entry>
<entry>
<title>iOS项目开发中使用protobuf</title>
<url>/2016/12/11/protobuf-in-iOS/</url>
<content><![CDATA[<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2019/12/protobuf.PNG" alt="title"></p>
<span id="more"></span>
<h2 id="背景信息"><a href="#背景信息" class="headerlink" title="背景信息"></a>背景信息</h2><p>最近公司中的项目使用到了protobuf,因为开始没有接触过,所以对protobuf进行了一些粗浅的了解,大体来说,protobuf就是一种把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输,你可以把protobuf理解成一种代替XML的东西,protobuf的优势在于:</p>
<ul>
<li>相同的数据protobuf相比XML更加的轻量级,更加节省每次传输的数据量.</li>
<li>protobuf是google公司的开源项目,这个项目是有稳定支撑的,停止更新和优化的几率非常低,而且专业性更强.</li>
<li>因为是开源项目所以只要你能力足够,你可以随意更改,随意个性化到你们自己的需求.</li>
</ul>
<p>如果你想更加深入的了解protobuf你可以再看一下<a href="http://blog.csdn.net/caisini_vc/article/details/5599468">扫盲文章</a>,或者看一下<a href="https://github.com/google/protobuf/tree/master/objectivec">项目源码</a>.好了,废话不多说,下面我们就为将protobuf集成到项目中做一些必要的准备吧.</p>
<h3 id="安装Protobuf编译器"><a href="#安装Protobuf编译器" class="headerlink" title="安装Protobuf编译器"></a>安装Protobuf编译器</h3><p>在terminal输入brew -v检查MAC是否装有brew,如果没有安装,请自行百度安装 : )<br>如果已经安装brew,就开始部署环境吧</p>
<ol>
<li>brew install automake</li>
<li>git clone <a href="https://github.com/google/protobuf">https://github.com/google/protobuf</a></li>
<li>按照terminal的提示继续输入命令./configure CXXFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib</li>
</ol>
<p>如果提示你<br>libtool: install: /usr/bin/install -c protoc-gen-objc /usr/local/bin/protoc-gen-objc<br>就说明已经安装成功了.<br>按照以上命令进行环境部署后,尽可以进入下一步了.</p>
<h3 id="创建proto文件"><a href="#创建proto文件" class="headerlink" title="创建proto文件"></a>创建proto文件</h3><p>proto文件是需要你和要进行通讯的一方制定的,这个一般是服务器,这里只是举一个例子,可以参考一下:</p>
<ol>
<li><p>cd Desktop/ 切换到桌面</p>
</li>
<li><p>mkdir ProtoBuf 在桌面创建一个文件夹,文件夹名为ProtoBuf</p>
</li>
<li><p>创建一个proto文件,内容举例:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package web.platform;</span><br><span class="line">message login_account_request</span><br><span class="line">{</span><br><span class="line">required bytes account = 1;</span><br><span class="line">optional bytes passwd = 2;</span><br><span class="line">}</span><br><span class="line">message login_account_response</span><br><span class="line">{</span><br><span class="line">required int32 ec = 1;</span><br><span class="line">optional bytes token = 2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
</ol>
<ul>
<li><p>文件一定要以.proto格式保存</p>
</li>
<li><p>proto的内容格式一定要正确,可以将自己创建出来的文本格式和示例的进行对比,package有没有拼写错误,需要分号的地方是否有分号,包括大括号的格式。如果内容格式有问题,可能会导致后面无法生成正确的OC文件</p>
<h3 id="生成OC文件"><a href="#生成OC文件" class="headerlink" title="生成OC文件"></a>生成OC文件</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">cd ~/Desktop/ProtoBuf</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">protoc --plugin=/usr/local/bin/protoc-gen-objc person.proto --objc_out="./"</span><br></pre></td></tr></table></figure>
<p>执行完成后就在ProtoBuf的文件夹下面生成了OC文件</p>
<h3 id="将protobuf使用到工程中"><a href="#将protobuf使用到工程中" class="headerlink" title="将protobuf使用到工程中"></a>将protobuf使用到工程中</h3><h4 id="使用pod导入"><a href="#使用pod导入" class="headerlink" title="使用pod导入"></a>使用pod导入</h4><p>在pod文件中写入</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pod 'Protobuf', '~> 3.3'</span><br></pre></td></tr></table></figure>
<p>然后执行</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pod install</span><br></pre></td></tr></table></figure>
<p>最后把你生成的protobuf的添加到工程中就可以啦 : )</p>
</li>
</ul>
]]></content>
<tags>
<tag>protobuf</tag>
</tags>
</entry>
<entry>
<title>Detect and diagnose memory issues 检测和诊断 iOS 内存问题</title>
<url>/2021/07/03/Detect-and-diagnose-memory-issues/</url>
<content><![CDATA[<p><img src="https://sean-1300081233.cos.ap-beijing.myqcloud.com/2022/05/Detect-and-diagnose-memory-issues_banner.png" alt="title"></p>
<p>这篇文章主要讲解了如何使用 Xcode 检测和诊断 iOS 内存问题。首先需要了解内存构成,内存占用对 app 的影响、以及一些常见的内存问题,最后学习使用一些工具来分析并解决内存问题。</p>
<blockquote>
<p>本文基于 WWDC21 Session 10180 - <a href="https://developer.apple.com/videos/play/wwdc2021/10180/">Detect and diagnose memory issues</a> </p>
</blockquote>
<span id="more"></span>
<h2 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h2><p>本 session 讲解了如何使用 Xcode 检测和诊断内存问题。首先需要了解内存构成,内存占用对 app 的影响、以及一些常见的内存问题,最后学习使用一些工具来分析并解决内存问题。</p>
<p>阅读指导:为了保证文章的完整性,我们会对一些概念进行详细的解释,你可以速读已了解的部分,或者直接跳至下一小节继续阅读。</p>
<h2 id="内存占用的组成"><a href="#内存占用的组成" class="headerlink" title="内存占用的组成"></a>内存占用的组成</h2><p>在了解内存问题之前,首先让我们先来复习一些内存的基础知识。让我们看看是什么组成了内存占用 (memory profile)。我们用三个类别来对 app 的内存占用进行分类。脏内存 (Dirty memory),压缩内存 (Compressed memory), 干净的内存 (Clean memory)。让我们快速的看一下每一项都包含什么。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/b3295c97868028ec49392aace1720f56.jpg" alt="image-20210615001122429"></p>
<h3 id="Dirty-memory"><a href="#Dirty-memory" class="headerlink" title="Dirty memory"></a>Dirty memory</h3><p>Dirty memory 是已经被 app 写入的内存,包含如下:</p>
<ol>
<li>它包括所有的 heap allocations:当你用 malloc 时,申请的就是堆上的存储空间。</li>
<li>图像解码的 buffer。</li>
<li>以及 frameworks 中的 <code>__DATA</code> 和 <code>__DATA_DIRTY</code> 部分也同样存储在 Dirty memory。</li>
</ol>
<blockquote>
<p>Tis: Frameworks you link actually use clean memory and dirty memory</p>
</blockquote>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/44aa0f9809f8b1a33de18d6f9a9d827b.jpg" alt="image-20210615001213304"></p>
<h3 id="Compressed-memory"><a href="#Compressed-memory" class="headerlink" title="Compressed memory"></a>Compressed memory</h3><p>苹果最初只是公开了从 OS X Mavericks 开始使用 Compressed memory 技术,但 iOS 系统也从 iOS 7 开始悄悄地使用。从 <a href="https://images.apple.com/media/us/osx/2013/docs/OSX_Mavericks_Core_Technology_Overview.pdf">OSX_Mavericks_Core_Technology_Overview</a> 文档中可以了解到该技术在内存紧张时能够将最近未使用过的内存占用压缩至原有大小的一半以下,并且能够在需要时解压复用。它在节省内存的同时提高了系统的响应速度,其特点可以归结为:</p>
<ul>
<li>Shrinks memory usage 减少了不活跃内存占用</li>
<li>Improves power efficiency 改善电源效率,通过压缩减少磁盘IO带来的损耗</li>
<li>Minimizes CPU usage 压缩/解压十分迅速,能够尽可能减少 CPU 的时间开销</li>
<li>Is multicore aware 支持多核操作</li>
</ul>
<p>Compressed memory 是将 Dirty memory 中最近没有访问过得内存,使用内存压缩器对 Dirty page 进行压缩。这些 page 会在被访问时解压缩。注意 iOS 是没有交换内存(Disk swap)技术的,交换内存是 MacOS 特有的。</p>
<blockquote>
<p>Disk swap 是指在 macOS 以及一些其他桌面操作系统中,当内存可用资源紧张时,系统将内存中的内容写入磁盘中的backing store (Swapping out),并且在需要访问时从磁盘中再读入 RAM (Swapping in)。与大多数 UNIX 系统不同的是,macOS 没有预先分配磁盘中的一部分作为 backing store,而是利用引导分区所有可用的磁盘空间。</p>
</blockquote>
<blockquote>
<p>iOS 在内存紧张的时候会使用到内存压缩技术,而MacOS在内存紧张的时候会使用到内存压缩技术及磁盘交换技术</p>
</blockquote>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/42289f80b09d3d2744018a792e5cfcb6.jpg" alt="image-20210615001236922"></p>
<h3 id="Clean-Memory"><a href="#Clean-Memory" class="headerlink" title="Clean Memory"></a>Clean Memory</h3><p>Clean Memory 是还没有被写入的内存或可以被 page out 的内存。指的是还没有被加载到内存或者能够被系统清理出内存且在需要时能重新加载的数据。包括:</p>
<ul>
<li>Memory mapped files (内存映射文件)</li>
<li>加载到内存中的磁盘上的图像</li>
<li>Frameworks 中的 __DATA_CONST 部分</li>
<li>应用的二进制可执行文件</li>
</ul>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/d612928530518e2ca65f2205bcd2052f.jpg" alt="image-20210615001328356"></p>
<p><strong>因此, memory footprint = dirty size + compressed size ,也就是我们需要尝试去减少的内存占用。</strong></p>
<p>如果想对内存技术有更深刻的了解,建议观看 <a href="https://developer.apple.com/videos/play/wwdc2018/416">WWDC18 iOS Memory Deep Dive</a> 。</p>
<h2 id="内存占用的影响"><a href="#内存占用的影响" class="headerlink" title="内存占用的影响"></a>内存占用的影响</h2><p>那么,为什么我们需要关注应用的内存占用 (Memory footprint)?</p>
<p><strong>为了更好的用户体验。</strong></p>
<p>[^Memory footprint]: 这里的 Memory Footprint 指的是:Dirty + Compressed。</p>
<blockquote>
<p>Your app’s memory footprint consists of the data that you allocated in RAM, and that must stay in RAM (or the equivalent) at all times.</p>
</blockquote>
<blockquote>
<p><a href="https://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA%E5%AD%98%E5%8F%96%E5%AD%98%E5%82%A8%E5%99%A8">RAM</a>:随机存取存储器(英语:Random Access Memory)是与 <a href="https://zh.wikipedia.org/wiki/CPU">CPU</a> 直接交换数据的内部存储器</p>
</blockquote>
<p><strong>合理的利用内存,可以从四个方面来提升用户体验</strong></p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/e0434ad026028fc78815313366909efc.jpg" alt="image-20210613150748853"></p>
<h3 id="Faster-application-activations-更快的应用程序激活"><a href="#Faster-application-activations-更快的应用程序激活" class="headerlink" title="Faster application activations(更快的应用程序激活)"></a>Faster application activations(更快的应用程序激活)</h3><p>系统的内存是有限的,如果你的 app 切换到后台却占用大量的内存空间时,很有可能被系统终止你的 app 的运行来回收内存空间。所以<strong>我们应该在 App 进入后台时释放内存占用较大的资源,进入前台时重新加载</strong>。因为资源加载到内存是需要时间的,所以保持内存占用的紧凑,可以有效提高你的 app 保留在内存的几率,这样你就能够获得更快的应用程序激活。</p>
<blockquote>
<p>Tips:如何在 app 进入后台时释放内存占用较大的资源,进入前台时重新加载。请参考 <a href="https://developer.apple.com/videos/play/wwdc2018/416">WWDC18 iOS Memory Deep Dive</a> Optimizing when in backgroud 部分。</p>
</blockquote>
<h3 id="Responsive-experience-快速响应的经验"><a href="#Responsive-experience-快速响应的经验" class="headerlink" title="Responsive experience(快速响应的经验)"></a>Responsive experience(快速响应的经验)</h3><p>当用户浏览你的新功能时,他们想要更快速的响应。而减少内存占用就可以让你的 app 获得更快的响应。慎重考虑一下 app 加载到内存的内容,可以有效地减少用户在和你的 app 交互时,系统对内存的回收。</p>
<h3 id="Complex-features-复杂的功能"><a href="#Complex-features-复杂的功能" class="headerlink" title="Complex features(复杂的功能)"></a>Complex features(复杂的功能)</h3><p>对内存使用采取有效策略,这样节省下来的内存可以让你为 app 增加更多复杂的功能,比如加载视频,做动画等等。</p>
<h3 id="Wider-device-compatibility-广泛的设备的兼容性"><a href="#Wider-device-compatibility-广泛的设备的兼容性" class="headerlink" title="Wider device compatibility(广泛的设备的兼容性)"></a>Wider device compatibility(广泛的设备的兼容性)</h3><p>最后,Apple 的设备会随着时间不断发展,新设备拥有比以前更多的物理内存。通过减少内存占用,你的应用在旧设备上的表现依旧会很好,从而增加欣赏你的应用的用户。</p>
<p><strong>总结一下:</strong></p>
<p>通过监控你 app 的内存占用,你的 app 将获得<strong>更快的应用程序激活</strong>,<strong>更快速的响应</strong>,<strong>处理更复杂的功能</strong>,<strong>能在更多的设备上运行</strong>。</p>
<h2 id="常见的内存问题"><a href="#常见的内存问题" class="headerlink" title="常见的内存问题"></a>常见的内存问题</h2><p>既然内存对 App 的体验如此重要,那么常见的内存问题有哪些呢?</p>
<ul>
<li>Leaks</li>
<li>Heap size issues</li>
</ul>
<h3 id="Leaks"><a href="#Leaks" class="headerlink" title="Leaks"></a>Leaks</h3><p>内存泄露是常见的内存问题,它还可以被细分成:</p>
<ul>
<li><p>Allocated objects to which there are no active references 对象失去了引用,却还存活着</p>
</li>
<li><p>Retain cycles 循环引用</p>
</li>
</ul>
<p>为了方便读者理解,我们会对这两种情况进行更加详细的解释,如果你确认你已经十分了解这两种情况,请跳到 3.2 小节继续阅读。</p>
<h4 id="Allocated-objects-to-which-there-are-no-active-references"><a href="#Allocated-objects-to-which-there-are-no-active-references" class="headerlink" title="Allocated objects to which there are no active references"></a>Allocated objects to which there are no active references</h4><p><img src="https://images.xiaozhuanlan.com/photo/2021/7bac88c0f21104306d2646d526f47f68.jpg" alt="image-20210615105635144"></p>
<p>当进程创建了对象,在失去了所有指向该对象的指针的时候,并没有回收该对象。我们称这种情况为泄露 (Leak)。我们用灰色的箭头表示对象之间的引用,每个对象都有至少有一个引用。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/e650cf317123b1dd4b289c2acde6b284.jpg" alt="image-20210615105830292"></p>
<p>注意 A 和 B 之间的虚线,这表示此时我们把 A 对 B 的引用置为 nil,并且移除 A 上的 B 对象。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/c473b9fba40cb079de61cc09b6581410.jpg" alt="image-20210615110643754"></p>
<p>当指针被移除时,B 对象就泄露了。已经没有任何对 B 的引用,但是 B 对象依旧被存储在 Dirty Memory 中。但是进程中已经没有引用指向它了,也就没有办法去释放它。当泄露的对象越多时,他们所占用的 Dirty Memory 就越多,所以我们需要修复泄露。</p>
<blockquote>
<p>这种情况的泄露,只会发生在 MRC 下,在 ARC 下当移除指针时,我们一般就会认为此对象已经被释放。</p>
</blockquote>
<h4 id="Retain-cycles"><a href="#Retain-cycles" class="headerlink" title="Retain cycles"></a>Retain cycles</h4><p><img src="https://images.xiaozhuanlan.com/photo/2021/d0dff195c775926c64c62bda133874ed.jpg" alt="image-20210615112430981"></p>
<p>循环引用也会引起泄露。Swift 中的最常见对象泄露就是循环引用引起的。在上图中,对象 A 和 B 就是循环引用。它们相互引用,但没有外部引用。这意味着进程不能访问或者释放他们。所以它们被认定为泄露。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/ddfe68b70b7dff168f9d1d00d771f2b2.jpg" alt="image-20210615151156529"></p>
<p>幸运的是,大多数的 swift 对象都被 Swift 的自动引用计数系统 ( Swift’s automatic reference counting system)或者 ARC 管理,这样可以阻止大部分的泄露。如果你用 ARC 管理对象,需要注意 unsafe 类型,确保你会在失去所有引用之前去释放它们。即使是 ARC 管理的对象,也容易变成循环引用。所以,避免创建循环强引用,如果这个循环引用是绝对必要的,考虑使用 <code>weak</code> 引用代替强引用,因为 <code>weak</code> 引用不会阻止对象被回收。</p>
<h3 id="Heap-size-issues"><a href="#Heap-size-issues" class="headerlink" title="Heap size issues"></a>Heap size issues</h3><p>堆是进程地址空间的一部分,用来存储动态生成的对象。所以 堆的大小也对内存占用起到了至关重要的影响。为了保证程序的运行,我们无法避免的要在堆上生成对象,那么这些对象该如何有效的治理呢?</p>
<p>那么首先我们需要确定堆上容易出现哪些问题?</p>
<ul>
<li><p>Heap allocation regressions 堆分配回归</p>
</li>
<li><p>Fragmentation 碎片化</p>
</li>
</ul>
<p>下面我们会分析这些问题的成因,以及对应的治理策略。</p>
<h4 id="Heap-allocation-regressions"><a href="#Heap-allocation-regressions" class="headerlink" title="Heap allocation regressions"></a>Heap allocation regressions</h4><p><img src="https://images.xiaozhuanlan.com/photo/2021/08e65f8bc88da87686d009318cd78cd6.jpg" alt="image-20210615204512092"></p>
<p>堆只是进程地址空间的一部分,用来存储动态生成的对象。堆分配回归会增加内存占用,因为进程在堆上比以前生成了更多对象。 为了减少堆的回归,可以删除无用分配并缩小不必要的大内存分配。你也应该关注一下你一次持有多少内存。释放掉你不在使用的内存,并在你需要的时候才去分配内存。这将减少 app 的内存峰值。让它被终止的几率变得更小。</p>
<p>总结一下 Heap allocation regressions 对应的治理策略:</p>
<ul>
<li><p>移除无用内存分配。</p>
</li>
<li><p>减少过大内存的分配。</p>
</li>
<li><p>不再使用的内存需要释放。</p>
</li>
<li><p>在你需要的时候,才去分配内存。</p>
</li>
</ul>
<h4 id="Fragmentation"><a href="#Fragmentation" class="headerlink" title="Fragmentation"></a>Fragmentation</h4><p>碎片带来了碎片化的问题,那么碎片是如何产生的? 首先让我们快速回顾一下 page 在 iOS 中是怎样工作的。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/22fa0f5381e9f31f97ce704857e8176a.jpg" alt="image-20210616215204050"></p>
<p>page 是系统授予进程的固定大小、不可分割的最小内存块。因为 page 是不可分割的,当进程写入 page 的任意部分,整个 page 都会被认为是 dirty 的并且进程将会管理它,即使 page 的大部分没有被使用到。</p>
<p>当进程的 dirty page 没有被 100% 占用时,就会产生碎片化。为了理解为什么出现碎片,我们来看一个例子: <img src="https://images.xiaozhuanlan.com/photo/2021/388d4bcc24d9c3adbba2a60683f692f4.jpg" alt="image-20210616215810552"></p>
<p>首先有三页 clean page。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/4d56c6d102f5f722b108c0fdedff49d7.jpg" alt="image-20210616215948349"></p>
<p>当进程运行的时候,创建的对象会填满这些 page。此时 clean page 就变成了 dirty page。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/2c185907f175ecc32ccba89d2bac1b20.jpg" alt="image-20210616220217236"></p>
<p>当部分对象被释放,它们填充过得地方就会变成空槽,在上图中被标记为 free memory。因为依旧填充着对象,这两个 page 依旧被标记为 dirty。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/0000be48f76d8bb8b93b9601651a277d.jpg" alt="image-20210616220624885"></p>
<p>系统想用将要创建的对象填充空槽,右侧蓝色方块是即将要创建的对象,不幸的是,即将创建的对象太大而不能插到空槽里,即使空槽的大小加起来足够大,但是空槽不是连续的。它们不能给一整个对象使用。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/fdc7025b0e6384549691fa80a46fa699.jpg" alt="image-20210616222503351"></p>
<p>因为现有的空槽不能使用,系统就会启用一个新的 page 给即将要创建的对象。如上图最右侧的方格就是新的 page,现有的内存空槽依旧没有没填满, 这种情况我们就称之为<strong>碎片化内存</strong>。</p>
<p><strong>最好解决内存碎片化的方法就是创建内存相邻,生命周期相似的对象</strong>。这能帮助确保所有这些对象会被一起释放,这样进程就会得到一大块连续的空闲内存来为即将要被创建的对象服务。</p>
<p>总结一下解决内存碎片化的方法:</p>
<ul>
<li>创建内存相邻,生命周期相似的对象,这样在这些对象释放之后,我们就会得到一大块连续的空闲内存</li>
</ul>
<h2 id="内存治理的工具"><a href="#内存治理的工具" class="headerlink" title="内存治理的工具"></a>内存治理的工具</h2><p>既然内存中会有这么多的问题,我们又不可能在开发代码的阶段就<strong>完全避免</strong>这些问题,苹果为了让我们可以有效的检测和诊断这些内存问题,开发了一系列的工具来帮助开发者,下面让我们来谈谈这些工具。</p>
<h3 id="已有的内存治理工具"><a href="#已有的内存治理工具" class="headerlink" title="已有的内存治理工具"></a>已有的内存治理工具</h3><p>内存问题由来已久,苹果在今年之前就有很多工具可以帮我们来检测和诊断内存问题,我们简单的把已有工具在使用维度上分为:</p>
<ul>
<li><p>可视化工具</p>
</li>
<li><p>命令行工具</p>
</li>
</ul>
<p>下面我们会详细的列举这些工具,并且简单的阐述一下这些工具的优缺点,以及组合使用方案,因为一些工具存在的时间比较长,笔者并不能一定能找到对应工具组合的最优解,如果你知道,请在评论区留言,如果你的方案更好,我们会更新到文章中。</p>
<h4 id="可视化工具"><a href="#可视化工具" class="headerlink" title="可视化工具"></a>可视化工具</h4><p>可视化工具又分为:</p>
<ul>
<li>Xcode 集成的工具</li>
<li>instruments 相关工具</li>
</ul>
<h4 id="Xcode-集成的工具:"><a href="#Xcode-集成的工具:" class="headerlink" title="Xcode 集成的工具:"></a>Xcode 集成的工具:</h4><ul>
<li><p>Memory Report</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/fefe10491726e15168d49afcd28fc7a1.jpg" alt="Memory Report"></p>
<p>Memory Report 存在 Debug navigator 中,当<strong>程序运行起来</strong>,切换到 Debug navigator 点击 memory 就可以查看 Memory Report , 这个报告只能粗略的查看内存状况,比如:<strong>通过 push 出一个 controller 查看对应的内存增长,pop 掉这个 controller 之后一般会有对应的内存减少</strong>。当然如果这个 controller 存在大量的网络图片展示,就比较特殊了,一般的网络图片下载和缓存框架为了减少磁盘 IO 以及提高多次访问图片的命中率,会对<strong>进行图片缓存</strong>,这时 push 的内存增长和 pop 内存减少就是不对称的状态。比如 <code>SDWebImage</code> 会在程序切换到后台的时候,会释放掉一部分缓存。你可以通过切换到后来来验证,当前的不对称是都由网络图片的缓存造成的。所以说 Memory Report 是一个更加整体的内存概况,比较适合查看内存概况,以及没有网络图片缓存的 controller 的释放情况。</p>
<p><strong>优势</strong>:快速查看整体内存预览。</p>
<p><strong>短板</strong>:内存概况不够详细,即使查看对应 controller 的创建以及释放都有一定的局限性。</p>
</li>
<li><p>Product->Analyze</p>
</li>
</ul>
<p> Product 中的静态分析主要分析以下四种问题:</p>
<p> a.) 逻辑错误:访问空指针或未初始化的变量等</p>
<p> b.) 内存管理错误:如内存泄漏等</p>
<p> c.) 声明错误:从未使用过的变量</p>
<p> d.) Api调用错误:未包含使用的库和框架</p>
<p> 注意使用静态分析是基于编译器的静态检查,而 <code>Objective-C</code> 是具有相当强大的动态性,所以静态分析能够检查出一些内存泄露问题,一些动态执行引起的内存泄露需要其他工具来检查。</p>
<p> <strong>优势</strong>:静态分析是基于编译器的静态检查,且检查会涵盖多种问题的检查。</p>
<p> <strong>短板</strong>:静态检查本事是基于静态的检查,对应 <code>Objective-C</code> 这种动态性语言的检查具有一定的局限性。</p>
<ul>
<li><p>Schemes 的诊断工具中的 Memory Management</p>
<ul>
<li><p>Malloc Scribble</p>
<blockquote>
<p>申请内存后在申请的内存上填 <code>0xAA</code>,内存释放后在释放的内存上填 <code>0x55</code>;再就是说如果内存未被初始化就被访问,或者释放后被访问,就会引发异常,这样就可以使问题尽快暴漏出来。</p>
<p><code>Scribble</code> 其实是 <code>malloc</code> 库 <code>libsystem_malloc.dylib</code> 自身提供的调试方案</p>
</blockquote>
</li>
<li><p>Malloc Guard Edges</p>
<blockquote>
<p>申请大片内存的之前或者之后都会在 page 上加保护</p>
</blockquote>
</li>
<li><p>Guard Malloc</p>
<blockquote>
<p>使用 <code>libgmalloc</code> 捕获常见的内存问题,比如越界、释放之后继续使用。</p>
<p>由于 <code>libgmalloc</code> 在真机上不存在,因此这个功能只能在模拟器上使用.</p>
</blockquote>
<p>Guard edge 和 Guard Malloc 可以帮助你发现内存溢出,并在通过对申请的大块内存保护和延迟释放来使你的程序在误用内存时产生更明确地崩溃。</p>
</li>
<li><p>Zombie Objects</p>
<blockquote>
<p><code>Zombie</code> 的原理是用生成僵尸对象来替换 <code>dealloc</code> 的实现,当对象引用计数为 <code>0</code> 的时候,将需要<code>dealloc</code> 的对象转化为僵尸对象。如果之后再给这个僵尸对象发消息,则抛出异常,并打印出相应的信息,调试者可以很轻松的找到异常发生位置。</p>
</blockquote>
</li>
<li><p>Malloc Stack logging</p>
<p>Malloc Stack logging 可以结合 Debug Memory Graph 进行使用,我们会在 Debug Memory Graph 处更加详细的说明 Malloc Stack logging 的作用。</p>
<p><strong>诊断工具 Memory Management 总结</strong>:</p>
<p><strong>优势</strong>:诊断工具 Memory Management 更加聚焦于最基础的内存使用,包括涂鸦,page 边界保护,越界以及对已经释放的地址进行访问等。</p>
<p><strong>短板</strong>:部分会存在模拟器的限制,因为这块比较聚焦基础的内存,部分功能对开发者的要求也比较高。</p>
<p><strong>注意</strong>:Memory Management 的这五个工具是在对应的 scheme 上生效的,如果你不想 dirty 公共的工程配置,一般可以 选择 <code>Duplicate Scheme</code> 并且取消 <code>share</code> 选项的勾选。而且 Malloc Stack logging 会在你使用 Debug Memory Graph 之后,记录很多日志,增大 app 的沙盒占用,会耗掉手机很多的磁盘空间。建议使用完成之后及时关闭 Malloc Stack logging 。</p>
</li>
</ul>
</li>
<li><p>Debug Memory Graph</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/0735a3329f5a7814b1f5326905dbe804.jpg" alt="image-20210614231737001"></p>
<p><strong>基础使用</strong>:</p>
<p>Xcode 运行起 app 之后,在调试栏点击 Debug Memory Graph ,这是 Xcode 会捕获当前 app 的内存快照,此时你可以很方便的查看内存中的存活对象,以及从 app 启动到此刻产生的内存泄露(紫色的叹号代表内存泄露),你可以灵活的选择展示当前内存内所有的存活对象,内存泄露的对象,也可以屏蔽系统的存活对象只关注当前工程调用产生的对象,或者是基于上述的选择,筛选指定类型对象。筛选之后,你可以看到当前类型对象有多少个,点击某个对象可以查看它的引用关系,右侧的 inspectors 还会展示当前对象的详细信息,比如占用大小,调用堆栈等。</p>
<p><strong>进阶使用</strong>:</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/486cbcfd1cbc6c46282bb746d6c07acd.jpg" alt="malloc stack logging"></p>
<p>如果你开启 Malloc Stack logging,选择 All Allocation and Free History 选项,你则可以通过调用堆栈直接锚定到具体的代码了。</p>
<p>如果你需要记录当前内存以备后续分析,你可以在 Xcode 的 File 选项下,导出 <strong>memgraph</strong> 。Xcode 使用 memgraph 的文件格式来储存应用程序的占用信息,导出 memgraph 文件可以结合命令行工具进行分析。</p>
</li>
</ul>
<h4 id="instruments-相关工具:"><a href="#instruments-相关工具:" class="headerlink" title="instruments 相关工具:"></a>instruments 相关工具:</h4><ul>
<li>leaks</li>
</ul>
<p> 用于检测程序运行过程中的内存泄露,并记录对象的历史信息。</p>
<ul>
<li><p>Allocations</p>
<p>追踪程序的虚拟内存占用和堆信息,提供对象的类名、大小以及调用栈等信息。</p>
</li>
<li><p>Zombies</p>
<p>用于检测程序运行过程中的僵尸对象,并记录对象的产生过程,调用堆栈及位置。</p>
</li>
<li><p>VM Tracker</p>
<p>能够区分程序运行时前文所述的各种内存类型占用情况,Instruments User Guide 中给出了各个参数的具体定义。</p>
<p><strong>Tips</strong>: 在使用上述工具时,如果看不到类和方法名称,绝大部分原因是你的打包模式没有开启dSYM或者debug symbols。</p>
<p>因为 instruments 相关工具的使用解释起来需要很长的篇幅,这里我推荐几篇文章方便大家了解这几个工具的使用:<a href="https://www.wangquanwei.com/63.html">leaks</a> <a href="https://blog.csdn.net/Hello_Hwc/article/details/83241475?spm=1001.2014.3001.5501">Allocations</a> <a href="https://blog.csdn.net/weixin_41963895/article/details/107231347">Zombies</a> <a href="https://www.jianshu.com/p/f82e2b378455">VM Tracker</a> 想要了解更加详细的信息,请参阅 <a href="https://developer.apple.com/videos/play/wwdc2019/411">WWDC19 Getting Started with Instruments</a> 。</p>
</li>
</ul>
<h4 id="命令行工具"><a href="#命令行工具" class="headerlink" title="命令行工具"></a>命令行工具</h4><p>在上面我们已经了解了 Xcode 内置的可视化工具,<strong>虽然可视化工具已经能够直观的表现我们想要了解的内存占用信息,但是在终端中不仅可以灵活地利用各种命令和 flag 突出我们想要的内容,更可以快速的实现信息查找和文本化交互。</strong>在了解内存问题分类之前我们先简单的了解下<strong>四种常用的命令行工具</strong>。</p>
<ul>
<li><p>vmmp</p>
<blockquote>
<p>vmmap 能够打印出进程信息,所有分配给该进程的 VMRegions 以及 VMRegion 的种类、内存占用信息等内容。利用 –summary 则能够根据不同的 region type 打印出详细的内存占用类型和信息。这里需要注意的是 SWAPPED SIZE 在 iOS 上指的是 Compressed memory size 且其值表示压缩前的占用大小。</p>
</blockquote>
</li>
<li><p>leaks</p>
<blockquote>
<p>leaks 追踪堆中的对象,打印出进程中内存泄露情况、调用堆栈以及循环引用信息。利用 –traceTree 和指定对象的地址,leaks 还能以树形结构打印出对象的相关引用。</p>
</blockquote>
</li>
<li><p>heap</p>
<blockquote>
<p>heap 会打印出所有在堆上的对象信息,默认按类数量排序,也可以通过 -sortBySize 按大小排序,对于追踪堆中较大的对象十分有帮助。找到目标对象后,通过 -address 获得所有/指定类的地址,继而可以利用 malloc_history 寻找其调用堆栈信息。</p>
</blockquote>
</li>
<li><p>malloc_history</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">malloc_history App.memgraph --fuStacks [address]</span><br></pre></td></tr></table></figure>
<blockquote>
<p>使用上述命令能够获得我们知道地址的对象的调用堆栈信息,它能够得到的比 memory inspector 中 Backtrace 更加详细。但是需要开启 Dignostics 中的 Malloc Stack 选项,才能通过 malloc_history 获得 memgraph 记录的调用堆栈信息。</p>
</blockquote>
</li>
</ul>
<p>更多拓展信息请参考 <a href="https://www.toutiao.com/i6569037697183121934">深入解析iOS内存 iOS Memory Deep Dive</a> 。</p>
<h3 id="新增的内存治理工具"><a href="#新增的内存治理工具" class="headerlink" title="新增的内存治理工具"></a>新增的内存治理工具</h3><p>今年苹果为开发者提供了<strong>使用 XCTest 框架进行测试,然后通过生成的 Ktrace file 和 Memory graphs 文件来检测和诊断内存问题</strong>,并且拓展已有的命令行工具的参数来帮助开发者更快的定位到问题。学习使用新工具之前,简单了解一下现在你可以使用到的分析内存占用的一些工具。Xcode 提供了一套工具来协助我们监控开发阶段和线上的 app 内存性能。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/5f98569574282ed75ac8f9d8858cc821.jpg" alt="image-20210614163556168"></p>
<p>XCTest 框架帮助我们直接在项目的单元测试和 UI 测试中监控 app 的内存占用, MetricKit 和 Xcode Organize 帮助我们自定义的监控生产环境上的内存指标。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/a13bb66577d5e585ee49da7d5f892590.jpg" alt="image-20210614165019022"></p>
<p>我们将使用 XCTests 做性能测试,但是注意这些技术还可以应用在一般内存问题分类和调查中。 使用 XCTests 做性能测试, 你能测量系统资源,比如:内存利用率, CPU 使用率,磁盘写入等等。 </p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/ac2552ffcee3e35a54f7352dfa7f901b.jpg" alt="image-20210614223611948"></p>
<p>苹果在Xcode 13 <strong>新增</strong>了使用 <strong>XCTest</strong> 收集诊断数据的新功能,来帮忙分类测试回归。通过执行 <strong>XCTest</strong> 用例来生成 <code>Ktrace files</code> 和 <code>Memory graphs</code>。</p>
<h4 id="Ktrace-file"><a href="#Ktrace-file" class="headerlink" title="Ktrace file"></a>Ktrace file</h4><p><img src="https://images.xiaozhuanlan.com/photo/2021/bf90eb6642008670b123d0c915ed7db5.jpg" alt="image-20210614231308710"></p>
<p><code>Ktrace files</code> 即强大又灵活。它可以用于一般的问题诊断也能聚焦于一些特殊的问题,比如可以深入渲染管线调查 <code>hitches</code> 问题,或者查找阻塞主线程并导致挂起的原因。 在日常工作中,这些 <code>Ktrace files</code> 可以用 instruments 打开并分析。 </p>
<h4 id="Memory-graphs"><a href="#Memory-graphs" class="headerlink" title="Memory graphs"></a>Memory graphs</h4><p><img src="https://images.xiaozhuanlan.com/photo/2021/60ec1bef534976e8020a11b5e381af4d.jpg" alt="image-20210614231737001"><br><code>Memory graphs</code> 对于特定的问题查询很有用,<code>Memory graphs</code> 即可以在 Xcode 的可视化调试工具中使用,也可以作为多个命令行工具使用。其中一些我们将会在后面讨论。 <code>Memory graphs</code> 本质上是一份进程地址空间的快照,<code>Memory graphs</code> 记录了每一个虚拟内存 region 的地址和大小和每一个分配地址的 block ,以及这些 region 和 blocks 的指向。这些足以支撑你去检查每一个堆上对象,查看与链接框架(Link Framworks)关联的数据区域等等。</p>
<p>XCTest 默认打开 malloc stack 的日志,并捕获新创建对象的堆栈。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/a66c3a351db8b882be26a40eaae265d2.jpg" alt="image-20210614232529918"></p>
<p>为了收集诊断可以使用命令行工具把 enablePerformanceTestDianostics 设置为 YES。这个参数可以让 <code>Ktrace</code> 收集非内存指标和内存指标的内存图。</p>
<h2 id="如何使用新工具"><a href="#如何使用新工具" class="headerlink" title="如何使用新工具"></a>如何使用新工具</h2><p><img src="https://images.xiaozhuanlan.com/photo/2021/99ff0011c79c19fbca4bf3892e9ab640.jpg" alt="image-20210614170804690"></p>
<p>苹果提供了一个名为 Meal Planner 的 app 来测量内存的使用情况。当点击保存按钮时,会下载对应的食谱到用户的设备上。case 如下:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// Monitor memory performance with XCTests</span><br><span class="line"></span><br><span class="line">func testSaveMeal() {</span><br><span class="line"> let app = XCUIApplication()</span><br><span class="line"> </span><br><span class="line"> let options = XCTMeasureOptions()</span><br><span class="line"> options.invocationOptions = [.manuallyStart]</span><br><span class="line"> </span><br><span class="line"> measure(metrics: [XCTMemoryMetric(application: app)],</span><br><span class="line"> options: options) {</span><br><span class="line"></span><br><span class="line"> app.launch()</span><br><span class="line"></span><br><span class="line"> startMeasuring()</span><br><span class="line"></span><br><span class="line"> app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()</span><br><span class="line"> </span><br><span class="line"> let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch</span><br><span class="line"> XCTAssertTrue(savedButton.waitForExistence(timeout: 30))</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p> <code>measure(metrics:options:block:) </code> 需要指定对应的 app,在 <code>block</code> 中 启动 app, 调用 <code>startMeasuring</code> 开始测量,点击 <code>Save meal</code> 按钮, 使用 <code>waitForExistence</code> 等待下载食谱完成,并检查 UI 是否更新。执行测试代码之后,点击测试 case 旁边的菱形,弹出测量面板, 选择物理内存选项。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/e951b870f0ec8c75018e3c03701edb11.jpg" alt="image-20210614220821604"></p>
<p>我们可以看到 case 执行了五次,内存均值在 116000 KB, Set Baseline 文字下方可以看到 case 每次运行的的详细数据,一般情况下我们会参考平均值设置我们的 baseline,设置完 baseline 后再次运行,可以在 Result 看到回归。</p>
<blockquote>
<p>case 每次执行的结果与 baseline 的偏差称为回归(<a href="https://zh.wikipedia.org/wiki/%E8%BF%B4%E6%AD%B8%E5%88%86%E6%9E%90">regression</a>)</p>
</blockquote>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/cb36fdeed99b0c20e033ca398d2bea86.jpg" alt="image-20210614222606040"></p>
<p>如果回归大于 baseline, case 就会执行失败。这时候我们就需要停下来查找问题,并且修复,直到 case 执行成功。</p>
<p>当执行完成之前写的性能测试,我们将看到上面的控制台上的打印。打印的内容非常多,但是可以直接查找几个关键词, 第一个要找到的是我们的执行结果是否通过,这个 case 的执行就没有通过。输出也会指出测试失败是因为回归 (<a href="https://zh.wikipedia.org/wiki/%E8%BF%B4%E6%AD%B8%E5%88%86%E6%9E%90">regression</a>),新的平均值要比 baseline 糟糕 12%,最后我们能够找到 xcresult bundle 的路径。当我们在 Xcode 打开 xcresult bundle, 我们将看到内存测试在顶部,与测试名称相邻。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/eda893578902a4cbfd3bb17eeb2d5170.jpg" alt="image-20210614235156514"><br>展开测试日志,可以找到可以获取的内存图。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/1f3d84aaabb4fd8540c8df6d847e4059.jpg" alt="image-20210614235423403"></p>
<p>下载并且解压后,我们会发现如下两个内存图,苹果收集了最初的内存图并在名称前面添加了 pre,收集了最后一次迭代的内存图,并在名称前添加了 post。我们可以通过前后两个内存快照分析期间的内存增长。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/46a61883aa25fbff2d609bf357e69db2.jpg" alt="image-20210615000432894"></p>
<p>有了<code>Ktrace files</code> 和 <code>Memory graphs</code> 并且设置了 malloc stack 为 YES,你不仅可以知道回归(<a href="https://zh.wikipedia.org/wiki/%E8%BF%B4%E6%AD%B8%E5%88%86%E6%9E%90">regression</a>)出现了问题,还可以知道为什么回归 (<a href="https://zh.wikipedia.org/wiki/%E8%BF%B4%E6%AD%B8%E5%88%86%E6%9E%90">regression</a>)会出现问题。</p>
<h3 id="检测和诊断内存泄露"><a href="#检测和诊断内存泄露" class="headerlink" title="检测和诊断内存泄露"></a>检测和诊断内存泄露</h3><p><img src="https://images.xiaozhuanlan.com/photo/2021/c5d8d14aa0274557e873e1abb27f35b1.jpg" alt="image-20210615151419176"></p>
<p>大家还记得我们之前使用 XCTest 执行 case 失败时生成的两份文件吧,我们将使用这两份文件来检查泄露。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/b6c46d10cfdb4ee3c40cfc18d95db075.jpg" alt="image-20210615151716402"></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">leaks App.memgraph</span><br></pre></td></tr></table></figure>
<p>leaks 命令可以帮查找已经产生的泄露。</p>
<p><img src="/./image/34.jpg" alt="4leaksfor240bytes"></p>
<p>输出展示了我们有 4 个泄露,一共 240 byte。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/8115ee170b6e406c83bcdd5eec4f5140.jpg" alt="image-20210615152124699"></p>
<p>接下来,输出包含了每一个泄露的详细的情况,这些信息可以给我们一些线索,帮助我们找到是什么引起了泄露。最上面的对象图指出 ROOT CYCLE,这表示我们面对的是循环引用。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/d323fdc05a1152cfea2d8506089fa09f.jpg" alt="image-20210615152802392"></p>
<p>这里有一些有用的符号,让我们看一下,这个循环引用可能包含 MealPlan 和 MeunItem 对象。因为 malloc stack logging 是对 XCTest 打开的,输出就会包含每一个泄露的创建堆栈。这个对于定位是哪个对象产生了泄露真的很有用。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/7daeed6aa432e140571a9173b53a43f9.jpg" alt="image-20210615181249641"></p>
<p>通常,你会希望从代码中找到具有符号的调用堆栈,这个是我代码中的一部分调用。正在泄露的 MealPlan 对象是在 populateMealData 中创建的。我们打开 Xcode 来看一下我们是否能够修复这个问题。</p>
<p><img src="/./image/38jpg" alt="image-20210615181611352"></p>
<p>这个函数就是我们在 leaks 的输出中看到的,这里我分别创建了 MealPlan 对象和 MealItem 对象,日志里面说这两个对象有循环引用。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/88ecb228cb958dba9e3ef555a341d32c.jpg" alt="image-20210615183622686"></p>
<p> <code>addMealToMealPlan</code> 这个函数看起来就有点可疑,让我们看一下。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/544fd25ec373e8772ae83394d2cbc287.jpg" alt="image-20210615183738662"></p>
<p>这里我们调用了 addItem 到 mealPlan,也调用了 addPlan 到 menuItem。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/5a2f224e79459f28a951a1e4944b99a9.jpg" alt="image-20210615184000286"></p>
<p>果然,这里存在循环引用。MenuItem 持有 MealPlan, MealPlan 也会持有 MenuItem。这两个对象进行了互相的强引用。</p>
<p>当执行完 <code>addMealToMealPlan</code>,就没有任何引用指向 MenuItem 和 MealPlan 的对象了,但是他们依旧互相引用着对方,这就导致了泄露。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/1848fa73d89469edd0f8f3429c018e06.jpg" alt="image-20210615184444778"></p>
<p>我们应该寻找一种方案来解决这个问题,这里使用了最快的解决方式,通过改变 MenuItem 对 MealPlan 的引用关系为弱引用来打破了引用循环。因为这里已经不存在强引用回环了。</p>
<h3 id="检测和诊断堆分配回归"><a href="#检测和诊断堆分配回归" class="headerlink" title="检测和诊断堆分配回归"></a>检测和诊断堆分配回归</h3><p>我们使用 <code>Meal Planner</code> 执行测试失败生成的 memgraph,检查一下堆内存增长的问题。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/5f47fd5199f6f5837266cb4779a87e3a.jpg" alt="image-20210615211510398"></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vmmap -summary app.memgraph</span><br></pre></td></tr></table></figure>
<p>这里我们会对 pre 和 post memegraph 文件使用 vmmap 来获取内存使用的概览。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/ea7ba5715528fae7efbdeb5a7abb49d5.jpg" alt="image-20210615212406816"></p>
<p>在 pre memegraph 中物理占用在 112 MB 左右。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/bb5b38c5ba42cff25b8af6506a38d8d5.jpg" alt="image-20210615212632183"></p>
<p>而 post memegraph 中物理占用在 125 MB 左右。两次结果差值大概有 13 MB 左右。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/7a3b9cabcd630b0c48223144d6b352f6.jpg" alt="image-20210615213215716"></p>
<p>向下滚动输出,可以看见进程的内存占用被按 region 进行了划分。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/b916bc2618c428185a174cc1c6074d86.jpg" alt="image-20210615213419470"></p>
<p>因为我怀疑这块是一个堆分配的问题,所以我想从 MALLOC 范围的看看这些 regions。因为这些 regions 包含我所有的堆对象。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/1b939c872bd835397b74b6c76e6f9946.jpg" alt="image-20210615214206310"></p>
<p>还记得上面说过的 <strong>memory footprint = dirty size + compressed size</strong> 在这个工具中 swapped 代表 compressed, 所以这些列我们只需要关心 <strong>dirty size</strong> 和 <strong>swapped size</strong> 。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/5cafb7a0647fedd08bf2b44e117dcb1d.jpg" alt="image-20210615214254471"></p>
<p>图上显示 MALLOC_LARGE 块大概持有 13 MB 的 dirty memory。这大概就等于我们回归的大小。所以我们需要查一下到底是谁贡献了这 13 MB。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/38a09377212a7407b7244dfb047183a4.jpg" alt="image-20210615214621447"></p>
<blockquote>
<p>这里可以使用 <code>heap --diffFrom</code> 来看一下 pre 和 post 的差别。这个命令可以得到 post 中存在但 pre 中没有的对象。</p>
</blockquote>
<p>最下面显示了这些 diff 大概有 13MB (13680384 bytes)。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/7dfbc2391dc9a8786ab89f4f75f6c725.jpg" alt="image-20210615215202648"></p>
<p>高亮部分是按照类名来进行划分的。每一种对象都会有个数和大小的总结。这里我们可以看到我们大概有 13 MB 的 <code>non-object</code> 类型,在 Swift 中,这种类型通常是 raw 分配的bytes,这种对象可以用一点小技巧去追查。首要要拿到这些 <code>non-object</code> 的地址。</p>
<blockquote>
<p>注意上图中 AVG 这一列代表的是每个 CLASS_NAME 对应 CLASS 对象的平均大小, <code>non-object</code> 类型对象的平均大小在 26777.3 byte,其他对象是没有超过 500 byte 的。</p>
</blockquote>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/184e72216e8574f8ba75088b127c4cbb.jpg" alt="image-20210615215855078"></p>
<p>可以使用如上 <code>heap -addresses</code> 命令来分析,特别注明只要 <code>non-object</code> 类型,并且大小至少 500 kb。如输出所示,<code>0x11380000</code> 地址的 <code>non-object</code> 大概有13 MB。所以它就是问题的根源。记录下来这个地址,我们需要通过这个地址来查询这些 <code>non-object</code> 的创建堆栈。</p>
<p>拿到对象地址后,我们有几个选择。可以根据具体情况来选择使用哪种方式继续追查,每种方法都有其好处,我将简要地逐一介绍。</p>
<p><strong>选择一</strong></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">leaks --trace=address app.memgraph</span><br></pre></td></tr></table></figure>
<p>这个命令可以得到这个地址的对象引用树,这个在查找特殊对象更多信息的时候很有用,特别是在 memgraph 没有打开 malloc stack logging 或者没有启用 MSL的时候 。注意 XCTest memgraph 会自动启用 MSL。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/f6b1b1d91e4ca01eba7af76b2a31074f.jpg" alt="image-20210616110444685"></p>
<p>这个高亮的部分可能与我们的要查询的部分有关, 内存中的 MALLOC_LARGE 可能在 MKTCustomMealPlannerCollectionViewCell 中对 mealData 对象做了什么。 </p>
<p><strong>选择二</strong></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">leaks --referenceTree app.memgraph</span><br></pre></td></tr></table></figure>
<p>这个命令会得到一个进程中所有内存自上而下的引用树,他可以帮我们很好的推断出根节点。根据输出可以看出在 app 中内存聚集的场景,如果存在 large regression, 但是不知道是哪个对象引起的。 用这个工具有奇效。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/180d8a7ddca400c1d26c534b4873cc14.jpg" alt="image-20210616114028220"></p>
<p>我们可以传递 <code>--groupByType</code> 参数来把相同类型做一个聚类,来让输出更简洁,更容易看懂。large trunk regression 通常会在树中分组到一个节点下, 这让我们可以更容易发现那块内存是什么。注意上图高亮部分,同样是 mealData 大概有 13 MB。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/b0e19f1f0e4f7052d6ea84899d14aeec.jpg" alt="image-20210616120714875"></p>
<p>因为 memgraph 启用了 MSL,所以可以使用 <code>malloc_hisatory -fullStacks</code> 来弄清楚这个对象怎么被创建的。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">malloc_hisatory -fullStacks app.memgraph address</span><br></pre></td></tr></table></figure>
<p>address 可以使用我们之前收集的地址,这样我们就可以得到这个地址的对象创建的堆栈了。请看上图第三行, saveMeal 函数创建了这个对象。</p>
<p><strong>验证</strong></p>
<p>所有线索都指向 mealData, 现在让我们打开 Xcode 一探究竟。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/1273d834846ba7ed8dcf63016316e773.jpg" alt="image-20210616164455729"></p>
<p>找到罪魁祸首了,函数在一个自定义的 cell 的 view 里,这里创建了 raw buffer 并包装成了 mealData 对象。为了填充数据和保存数据到磁盘,这里创建了这个 buffer。一旦保存到磁盘,我们就不再需要这个 buffer 了。所以不应该一直用 mealData 属性保存着这个数据,因为只要这个 view 实例存在,这个 buffer 就会一直存在。这意味着,当我点击任意 cell 上的 saveMeal 按钮,这个 cell 就会创建并持有一个很大的 buffer,直到这个 cell 被销毁。当我点击多个 cell 上的保存按钮时,内存加起来就会很大。所以我们该如何解决?有两种常用的解决方式,可以根据具体情况进行选择。</p>
<p><strong>方法一</strong>:我们只把 mealData 定义在函数中,但是我知道这个类的其他地方在使用 mealData,所以我不想这么做。 </p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/afa51c858dde1ce1c0bc7d7a7c6cf83b.jpg" alt="image-20210616170232527"></p>
<p><strong>方法二</strong>:另一个方法就是,当 mealData 保存到磁盘之后,手动置 nil。swift中的数据对象管理很聪明,一旦这个对象没有任何引用的时候,它就会被自动释放。</p>
<h3 id="检测和诊断碎片化"><a href="#检测和诊断碎片化" class="headerlink" title="检测和诊断碎片化"></a>检测和诊断碎片化</h3><p><img src="https://images.xiaozhuanlan.com/photo/2021/aa952c9dc749e221a5a640c95c8d5636.jpg" alt="image-20210616223523732"></p>
<p>我手动创建了一些对象,被标记为 my object,由于我没有太关注我的代码,系统最终交错安插了我的对象和其他对象,</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/8306720d5c10eb6e80ac6de96b268dc6.jpg" alt="image-20210616223633731"></p>
<p>现在我释放掉了我的对象,出现了四个空闲的空槽,因为 allocated object 的存在,它们都没有连续,这将导致50%的碎片化和四个 dirty page。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/29022872ba94fba3247a23fe77e8a3f9.jpg" alt="image-20210616224228526"></p>
<p>假如我写的代码<strong>一起创建了所有 my object</strong>, 它们最终就会只会占用两个 page。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/fd2ff6d52b21f46be8b1a5d683390efb.jpg" alt="image-20210616224248384"></p>
<p>当我释放掉所有的 my object,进程就会空出两个 clean page 给系统。结果就会得到两个 clean page 两个 dirty page 以及0%的碎片化。</p>
<p>注意碎片化是怎样成为占用空间的倍增器的。50%的碎片化就会让我们的内存占用翻倍。从两个 dirty page 变成 4 个 dirty page。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/57f5081700cda90d28c10e09f9b3dc2b.jpg" alt="image-20210617103651724"></p>
<p>在大多数真实的场景中,一些碎片是不可避免的, 所以作为经验法则,把我们需要把<strong>碎片化降低到25%或者更少</strong>。</p>
<p><strong>使用 autorelease pool 是一种减少碎片的方式</strong>,自动释放池会在执行超出释放池范围时告诉系统释放在它内部分配的所有对象,这有助于确保创建所有在释放池内的对象具有<strong>相似的生命周期。</strong></p>
<p>尽管碎片化可能是所有进程的问题,<strong>长时间运行的进程尤其容易产生碎片化</strong>。因为他们有许多创建和销毁的对象,分割地址空间的可能性会更大。比如:如果你的 app 使用长时间运行 extensions,一定要看一看这些进程的碎片化。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/74fe75393d7c015ef540aa3b156c5464.jpg" alt="image-20210617103956837"></p>
<p>下面来快速的看一下我的进程碎片,我使用 <code>vmmap -sunmmary</code>,并且滚动到输出的最下面。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/b706059096b44deb797b4f18e5db87d2.jpg" alt="image-20210617104153630"></p>
<p>高亮的部分按照 malloc zone 进行划分,每个 zone 包含不同类型的创建,通常我只需要关心 DefaultMallocZone,因为那是我的堆分配默认结束的地方。然而因为这个 memgraph 启用了 MSL,我真正关心的是 MallocStackLoggingLiteZone,只要启用了 MSL,这个区域就是所有堆分配结束的地方。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/48b32c375511337836e156d96e6ec29a.jpg" alt="image-20210617105552030"></p>
<p><code>% FRAG</code> 这一列展示了我的内存在分配的所有 zone 上因为碎片产生浪费的百分比。他们中的一些数值真的比较大。但是我只需关心 <code>MallocStackLoggingLiteZone</code>,这个因为 <code>MallocStackLoggingLiteZone</code> 有最多的脏内存份额。脏内存总共5 MB,<code>MallocStackLoggingLiteZone</code> 占用4.3 MB。所以这种情况下,我可以忽略其他 zone。 <code>dirty + swap frag size</code> 这一列精确的展示了因为碎片每一个 <code>malloc zone</code> 有多少内存被浪费了 。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/a9caa13938defe91ea30505391b1c6ce.jpg" alt="image-20210617170856971"></p>
<p> 在这个 <code>case</code> 中,我因为碎片浪费了大约 800 KB。这个看起来很多,但是我们之前提过,一些碎片化问题是无法避免的,所以只要我还在 <strong>25%</strong> 碎片化以下,我就认为这个浪费是可以接受的。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/411a2566d1770e72cca6b64c71d691cb.jpg" alt="image-20210617171420011"></p>
<p>目前<code>MallocStackLoggingLiteZone</code> 的碎片化还在 19% 左右,这显然低于 25% 的经验法则,所以我还不用担心。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/25aef6666143aecad144f7cd62b674f1.jpg" alt="image-20210617195647203"></p>
<p>如果我真的有碎片化问题,我可以使用 <code>instruments</code> 的工具 <code>Allocations</code> 去追踪这个问题,具体来说,我希望查看分配列表视图,看看在我感兴趣的区域中哪些对象被持久化和销毁了。</p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/c6842211337697a01b6a0441ab017019.jpg" alt="image-20210617195714870"></p>
<p>在碎片化的背景下,被销毁的对象创建了内存空槽,而持久化对象是剩余的对象负责保持 <code>dirty page</code>, 当你研究碎片化的时候,它们都值得研究。想知道怎样使用 <code>instruments</code> 工具的更多信息请参阅: <a href="https://developer.apple.com/videos/play/wwdc2019/411">WWDC19 Getting Started with Instruments</a></p>
<p><img src="https://images.xiaozhuanlan.com/photo/2021/cc0d7a6cbf2749140eab2087868107f5.jpg" alt="image-20210617200756322"></p>
<p>总结一下如何解决碎片化问题:</p>
<ul>
<li>尽量保证连续创建生命周期相似的对象</li>