-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
1350 lines (714 loc) · 737 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
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>适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr屏幕尺寸及安全区域</title>
<link href="/2020/12/30/%E9%80%82%E9%85%8DiPhoneXiPhoneXsiPhoneXsMaxiPhoneXr%E5%B1%8F%E5%B9%95%E5%B0%BA%E5%AF%B8%E5%8F%8A%E5%AE%89%E5%85%A8%E5%8C%BA%E5%9F%9F/"/>
<url>/2020/12/30/%E9%80%82%E9%85%8DiPhoneXiPhoneXsiPhoneXsMaxiPhoneXr%E5%B1%8F%E5%B9%95%E5%B0%BA%E5%AF%B8%E5%8F%8A%E5%AE%89%E5%85%A8%E5%8C%BA%E5%9F%9F/</url>
<content type="html"><![CDATA[<ol><li><p>先来认识<code>px</code>与<code>pt</code>区别<br><code>px</code>就是表示<code>pixel</code>像素,是屏幕上显示数据的最基本的点,它不是自然界的长度单位,点的大小是会变的,也称为<code>相对长度</code>;<br><code>pt</code>就是<code>point</code>,是印刷行业常用单位<code>磅</code>,等于1/72英寸,所以它是一个自然界标准的长度单位,也称为<code>绝对长度</code>。</p></li><li><p>我们再来了解缩放因子<code>(scale factor between logic point and device pixel)</code><br>早期的<code>iPhone3GS</code>的屏幕分辨率是<code>320*480(PPI=163)</code>,<code>iOS</code>绘制图形<code>(CGPoint/CGSize/CGRect)</code>均以<code>point</code>为单位:<code>1 point = 1 pixel</code><br>后来在<code>iPhone4</code>中,同样大小<code>3.5 inch</code>的屏幕采用了<code>Retina显示技术</code>,横、纵向方向像素密度都被<code>放大到2倍</code>,像素分辨率提高到<code>(320x2)x(480x2)= 960x640(PPI=326)</code>显像分辨率提升至<code>iPhone3GS</code>的<code>4倍</code>(<code>1个Point</code>被渲染成<code>1个2x2的像素矩阵</code>)<br>但是对于开发者来说,<code>iOS</code>绘制图形的<code>API</code>依然沿袭<code>point</code>为单位。在同样的逻辑坐标系下:<code>1 point = scale*pixel</code>(在<code>iPhone4~6</code>中,缩放因子<code>scale=2</code>;在<code>iPhone6+</code>中,缩放因子<code>scale=3</code>)</p></li><li><p>iPhone 各种机型尺寸信息列表入下表:</p></li></ol><table><thead><tr><th>机型</th><th>尺寸</th><th>逻辑缩放因子(UIKit Scale factor)</th><th>实际缩放因子(Native Scale factor)</th><th>屏幕宽高(开发尺寸)</th><th>屏幕分辨率</th><th>是否全面屏</th><th>有无刘海</th></tr></thead><tbody><tr><td>3GS</td><td>3.5寸</td><td>1.0</td><td>1.0</td><td>320x480</td><td>320x480</td><td>非全面屏</td><td>无</td></tr><tr><td>4(S)</td><td>3.5寸</td><td>2.0</td><td>2.0</td><td>320x480</td><td>640x960</td><td>非全面屏</td><td>无</td></tr><tr><td>5(C)/5(S)/SE</td><td>4寸</td><td>2.0</td><td>2.0</td><td>320x568</td><td>640x1136</td><td>非全面屏</td><td>无</td></tr><tr><td>6(S)/7/8</td><td>4.7寸</td><td>2.0</td><td>2.0</td><td>375x667</td><td>750x1334</td><td>非全面屏</td><td>无</td></tr><tr><td>6(S)+/7+/8+</td><td>5.5寸</td><td>3.0</td><td>2.608</td><td>414x736</td><td>1080x1920</td><td>非全面屏</td><td>无</td></tr><tr><td>X/XS</td><td>5.8寸</td><td>3.0</td><td>3.0</td><td>375x812</td><td>1125x2436</td><td>全面屏</td><td>有</td></tr><tr><td>XR</td><td>6.1寸</td><td>2.0</td><td>2.0</td><td>414×896</td><td>828 x1792</td><td>全面屏</td><td>有</td></tr><tr><td>XS Max</td><td>6.5寸</td><td>3.0</td><td>3.0</td><td>414×896</td><td>1242x2688</td><td>全面屏</td><td>有</td></tr></tbody></table><p>需要注意的地方是<code>6(S)+/7+/8+</code>的时候,实际的缩放因子并<code>不等于</code>逻辑上的缩放因子。所以,他的屏幕分辨率是<code>1080x1920</code>而不是<code>1242x2208</code><br>上述数据,可以通过代码获取逻辑缩放因子、逻辑屏幕宽度;实际缩放/物理因子、实际/物理屏幕宽度:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">//逻辑缩放因子</span><br><span class="line">[UIScreen mainScreen].scale</span><br><span class="line">//逻辑屏幕宽度</span><br><span class="line">[UIScreen mainScreen].bounds</span><br><span class="line"> </span><br><span class="line">//实际/物理缩放因子</span><br><span class="line">[UIScreen mainScreen].nativeScale</span><br><span class="line">//实际/物理屏幕宽度</span><br><span class="line">[UIScreen mainScreen].nativeBounds</span><br></pre></td></tr></table></figure></p><ol start="4"><li>适配常用宏<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">//获得屏幕的宽高</span><br><span class="line">#define kScreenWidth [UIScreen mainScreen].bounds.size.width</span><br><span class="line">#define kScreenHeight [UIScreen mainScreen].bounds.size.height</span><br><span class="line">//iPhoneX / iPhoneXS</span><br><span class="line">#define isIphoneX_XS kScreenWidth == 375.f && kScreenHeight == 812.f ? YES : NO</span><br><span class="line">//iPhoneXR / iPhoneXSMax</span><br><span class="line">#define isIphoneXR_XSMax kScreenWidth == 414.f && kScreenHeight == 896.f ? YES : NO</span><br><span class="line">//异性全面屏</span><br><span class="line">#define isFullScreen isIphoneX_XS || isIphoneXR_XSMax</span><br><span class="line"></span><br><span class="line">// Status bar height.</span><br><span class="line">#define StatusBarHeight isFullScreen ? 44.f : 20.f</span><br><span class="line"></span><br><span class="line">// Navigation bar height.</span><br><span class="line">#define NavigationBarHeight 44.f</span><br><span class="line"></span><br><span class="line">// Tabbar height.</span><br><span class="line">#define TabbarHeight isFullScreen ? (49.f+34.f) : 49.f</span><br><span class="line"></span><br><span class="line">// Tabbar safe bottom margin.</span><br><span class="line">#define TabbarSafeBottomMargin isFullScreen ? 34.f : 0.f</span><br><span class="line"></span><br><span class="line">// Status bar & navigation bar height.</span><br><span class="line">#define StatusBarAndNavigationBarHeight isFullScreen ? 88.f : 64.f</span><br></pre></td></tr></table></figure></li><li>获取安全区域<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">+ (CGRect)GetSafeAreaImpl{</span><br><span class="line"> UIView *view = UIApplication.sharedApplication.windows.lastObject;</span><br><span class="line">// UIView *view = (UIView *)GetAppController().unityView;</span><br><span class="line"> CGRect area = [self CustomComputeSafeArea:view];</span><br><span class="line"> // x = area.origin.x;</span><br><span class="line"> // y = area.origin.y;</span><br><span class="line"> // w = area.size.width;</span><br><span class="line"> // h = area.size.height;</span><br><span class="line"> return area;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (CGRect)CustomComputeSafeArea:(UIView *)view{</span><br><span class="line"> CGSize screenSize = view.bounds.size;</span><br><span class="line"> CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);</span><br><span class="line"> </span><br><span class="line"> UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);</span><br><span class="line"> if (@available(iOS 11.0, *)) {</span><br><span class="line"> insets = [view safeAreaInsets];</span><br><span class="line"> }</span><br><span class="line"> screenRect.origin.x = screenRect.origin.x + insets.left;</span><br><span class="line"> screenRect.origin.y = screenRect.origin.y + insets.top;</span><br><span class="line">// screenRect.size.width -= insets.left + insets.right;</span><br><span class="line"> screenRect.size.width = screenRect.size.width - (insets.left + insets.right);</span><br><span class="line"> screenRect.size.height = screenRect.size.height - (insets.top + insets.bottom);</span><br><span class="line"> // 乘以缩放因子,根据需求可以省略</span><br><span class="line"> float scale = view.contentScaleFactor;</span><br><span class="line"> screenRect.origin.x = screenRect.origin.x * scale;</span><br><span class="line"> screenRect.origin.y = screenRect.origin.y * scale;</span><br><span class="line"> screenRect.size.width = screenRect.size.width * scale;</span><br><span class="line"> screenRect.size.height = screenRect.size.height * scale;</span><br><span class="line"> return screenRect;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>启动图数据<br><img src="https://upload-images.jianshu.io/upload_images/1948913-aae05ade339fa12f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p></li><li><p>有刘海机型安全区域,有无刘海机型开发尺寸对比图<br><img src="https://upload-images.jianshu.io/upload_images/1948913-9ecc84eb5651f604.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="刘海机型安全区域"><br><img src="https://upload-images.jianshu.io/upload_images/1948913-c49f6bcfc715714e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="刘海机型开发尺寸"><br><img src="https://upload-images.jianshu.io/upload_images/1948913-78a1a153f3be10c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="非刘海机型开发尺寸"></p></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 初探 AFNetworking</title>
<link href="/2020/07/08/iOS%E5%88%9D%E6%8E%A2AFNetworking/"/>
<url>/2020/07/08/iOS%E5%88%9D%E6%8E%A2AFNetworking/</url>
<content type="html"><![CDATA[<p>本文不对<code>AFNetworking</code>作全面的解析,仅对比解析一下<code>2.x</code>和<code>3.x</code>的差异。</p><ol><li><code>AFNetworking</code>分为如下<code>5个功能模块</code>:</li></ol><ul><li>网络通信模块(<code>AFURLSessionManager、AFHTTPSessionManger</code>)</li><li>网络状态监听模块(<code>Reachability</code>)</li><li>网络通信安全策略模块(<code>Security</code>)</li><li>网络通信信息序列化/反序列化模块(<code>Serialization</code>)</li><li>对于<code>iOS UIKit</code>库的扩展(<code>UIKit</code>)</li></ul><ol start="2"><li><p><code>AFNetworking 2.x</code>需要常驻线程而<code>3.x</code>不需要常驻线程<br><code>2.x</code>常驻线程用来<code>并发请求和处理数据回调</code>,避免多个网络请求的线程开销(<code>不用开辟一个线程,就保活一条线程</code>);而<code>3.x</code>不需要常驻线程是因为<code>NSURLSession</code>可以指定回调<code>delegateQueue</code>,<code>NSURLConnection</code>不行;<br><code>NSURLConnection</code>的一大痛点就是:发起请求后,需要一直处于<code>等待回调的状态</code>。而<code>3.x</code>后<code>NSURLSession</code>解决了这个问题;<code>NSURLSession</code>发起的请求,不再需要在当前线程进行回调,可以指定回调的<code>delegateQueue</code>,这样就不用为了等待代理回调方法而保活线程了</p></li><li><p><code>3.x</code>需要设置最大并发数为<code>1</code>(<code>self.operationQueue.maxConcurrentOperationCount = 1</code>),<code>2.x</code>为什么不需要<br>功能不一样:<code>3.x</code>的<code>operationQueue</code>是用来接收<code>NSURLSessionDelegate</code>回调的,鉴于一些多线程数据访问的安全性考虑,设置了<code>maxConcurrentOperationCount = 1</code>来达到<code>并发的请求串行的进行回调</code>的效果。而<code>2.x</code>的<code>operationQueue</code>是用来添加<code>operation</code>进行<code>并发请求</code>的,所以不要设置为<code>1</code><br><strong>注意:并发数并不等于所开辟的线程数,具体开辟几条线程由系统决定</strong></p></li><li><p><code>3.x</code>为什么要串行回调</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {</span><br><span class="line"> NSParameterAssert(task);</span><br><span class="line"> AFURLSessionManagerTaskDelegate *delegate = nil;</span><br><span class="line"> [self.lock lock];</span><br><span class="line"> //给所要访问的资源加锁,防止造成数据混乱</span><br><span class="line"> delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];</span><br><span class="line"> [self.lock unlock];</span><br><span class="line"> return delegate;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从代码可以看出,这边对<code>self.mutableTaskDelegatesKeyedByTaskIdentifier</code>的访问进行了加锁,目的是<code>保证多线程环境下的数据安全</code>。既然加了锁,就算<code>maxConcurrentOperationCount</code>不设为<code>1</code>,当某个请求正在回调时,下一个请求还是得等待一直到上个请求获取完所要的资源后解锁,所以这边并发回调也是没有意义的。相反多<code>task</code>回调导致的多线程并发,还会导致性能的浪费</p></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 正则匹配常用方法</title>
<link href="/2020/07/08/iOS%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95/"/>
<url>/2020/07/08/iOS%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<ol><li>验证手机号<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 验证手机号</span><br><span class="line">+ (BOOL)isValidatePhone:(NSString *)phone{</span><br><span class="line"> NSString *phoneRegex = @"^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$";</span><br><span class="line"> NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", phoneRegex];</span><br><span class="line"> return [phoneTest evaluateWithObject:phone];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>邮箱账号有效性判断<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 邮箱账号的有效性判断</span><br><span class="line">+ (BOOL)isValidateEmail:(NSString *)email{</span><br><span class="line"> NSString * emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";</span><br><span class="line"> NSPredicate * emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];</span><br><span class="line"> return [emailTest evaluateWithObject:email];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>匹配密码格式(长度6~20位,只能是数字、大小写字母)<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 匹配密码格式</span><br><span class="line">+ (BOOL)isValidatePassword:(NSString *)password{</span><br><span class="line"> NSString * passwordRegex = @"[a-zA-Z0-9]{6,20}";</span><br><span class="line"> NSPredicate * passwordTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", passwordRegex];</span><br><span class="line"> return [passwordTest evaluateWithObject:password];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>车牌号码判断<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">// 车牌号码正则表达式</span><br><span class="line">+ (BOOL)isValidateCarID:(NSString *)carID{</span><br><span class="line"> if (carID.length==7) {</span><br><span class="line"> //普通汽车,7位字符,不包含I和O,避免与数字1和0混淆</span><br><span class="line"> NSString *carRegex = @"^[\u4e00-\u9fa5]{1}[a-hj-np-zA-HJ-NP-Z]{1}[a-hj-np-zA-HJ-NP-Z0-9]{4}[a-hj-np-zA-HJ-NP-Z0-9\u4e00-\u9fa5]$";</span><br><span class="line"> NSPredicate *carTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", carRegex];</span><br><span class="line"> return [carTest evaluateWithObject:carID];</span><br><span class="line"> }else if(carID.length==8){</span><br><span class="line"> //新能源车,8位字符,第一位:省份简称(1位汉字),第二位:发牌机关代号(1位字母);</span><br><span class="line"> //小型车,第三位:只能用字母D或字母F,第四位:字母或者数字,后四位:必须使用数字;([DF][A-HJ-NP-Z0-9][0-9]{4})</span><br><span class="line"> //大型车3-7位:必须使用数字,后一位:只能用字母D或字母F。([0-9]{5}[DF])</span><br><span class="line"> NSString *carRegex = @"^[\u4e00-\u9fa5]{1}[a-hj-np-zA-HJ-NP-Z]{1}([0-9]{5}[d|f|D|F]|[d|f|D|F][a-hj-np-zA-HJ-NP-Z0-9][0-9]{4})$";</span><br><span class="line"> NSPredicate *carTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", carRegex];</span><br><span class="line"> return [carTest evaluateWithObject:carID];</span><br><span class="line"> }</span><br><span class="line"> return NO;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>身份证号判断<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">// 身份证号段正则表达式</span><br><span class="line">+ (BOOL)isValidateIDCard:(NSString *)identityString{</span><br><span class="line"> if (identityString.length != 18) return NO;</span><br><span class="line"> // 正则表达式判断基本 身份证号是否满足格式</span><br><span class="line"> NSString *regex2 = @"^(^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$)|(^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])((\\d{4})|\\d{3}[Xx])$)$";</span><br><span class="line"> NSPredicate *identityStringPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex2];</span><br><span class="line"> //如果通过该验证,说明身份证格式正确,但准确性还需计算</span><br><span class="line"> if(![identityStringPredicate evaluateWithObject:identityString]) return NO;</span><br><span class="line"> //** 开始进行校验 *//</span><br><span class="line"> //将前17位加权因子保存在数组里</span><br><span class="line"> NSArray *idCardWiArray = @[@"7", @"9", @"10", @"5", @"8", @"4", @"2", @"1", @"6", @"3", @"7", @"9", @"10", @"5", @"8", @"4", @"2"];</span><br><span class="line"> //这是除以11后,可能产生的11位余数、验证码,也保存成数组</span><br><span class="line"> NSArray *idCardYArray = @[@"1", @"0", @"10", @"9", @"8", @"7", @"6", @"5", @"4", @"3", @"2"];</span><br><span class="line"> //用来保存前17位各自乖以加权因子后的总和</span><br><span class="line"> NSInteger idCardWiSum = 0;</span><br><span class="line"> for(int i = 0;i < 17;i++) {</span><br><span class="line"> NSInteger subStrIndex = [[identityString substringWithRange:NSMakeRange(i, 1)] integerValue];</span><br><span class="line"> NSInteger idCardWiIndex = [[idCardWiArray objectAtIndex:i] integerValue];</span><br><span class="line"> idCardWiSum += subStrIndex * idCardWiIndex;</span><br><span class="line"> }</span><br><span class="line"> //计算出校验码所在数组的位置</span><br><span class="line"> NSInteger idCardMod=idCardWiSum%11;</span><br><span class="line"> //得到最后一位身份证号码</span><br><span class="line"> NSString *idCardLast= [identityString substringWithRange:NSMakeRange(17, 1)];</span><br><span class="line"> //如果等于2,则说明校验码是10,身份证号码最后一位应该是X</span><br><span class="line"> if(idCardMod==2) {</span><br><span class="line"> if(![idCardLast isEqualToString:@"X"]||[idCardLast isEqualToString:@"x"]) {</span><br><span class="line"> return NO;</span><br><span class="line"> }</span><br><span class="line"> }else{</span><br><span class="line"> //用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码</span><br><span class="line"> if(![idCardLast isEqualToString: [idCardYArray objectAtIndex:idCardMod]]) {</span><br><span class="line"> return NO;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return YES;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>随机获取八位字符<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">- (NSString *)obtain8RandomCode {</span><br><span class="line"> NSArray *changeArray = [[NSArray alloc] initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",@"a",@"b",@"c",@"d",@"e",@"f",@"g",@"h",@"i",@"j",@"k",@"l",@"m",@"n",@"o",@"p",@"q",@"r",@"s",@"t",@"u",@"v",@"w",@"x",@"y",@"z",@"!",@"@",@"#",@"$",@"^",@"&",@"*",@"-",@"+",nil];</span><br><span class="line"> NSArray *specailArray = [[NSArray alloc] initWithObjects:@"!",@"@",@"#",@"$",@"^",@"&",@"*",@"-",@"+", nil];</span><br><span class="line"> NSMutableString *changeString = [[NSMutableString alloc] initWithCapacity:8];</span><br><span class="line"> </span><br><span class="line"> NSInteger specialIndex = arc4random()%7;</span><br><span class="line"> NSInteger specialArrayIndex = arc4random()%([specailArray count] - 1);</span><br><span class="line"> for(int i = 0; i < 8; i++){</span><br><span class="line"> if (i==specialIndex) {</span><br><span class="line"> changeString = (NSMutableString *)[changeString stringByAppendingString:[specailArray objectAtIndex:specialArrayIndex]];</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> NSInteger index = arc4random()%([changeArray count] - 1);</span><br><span class="line"> changeString = (NSMutableString *)[changeString stringByAppendingString:[changeArray objectAtIndex:index]];</span><br><span class="line"> }</span><br><span class="line"> return changeString;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 内购项目的App Store推广</title>
<link href="/2020/05/27/iOS%E5%86%85%E8%B4%AD%E9%A1%B9%E7%9B%AE%E7%9A%84AppStore%E6%8E%A8%E5%B9%BF/"/>
<url>/2020/05/27/iOS%E5%86%85%E8%B4%AD%E9%A1%B9%E7%9B%AE%E7%9A%84AppStore%E6%8E%A8%E5%B9%BF/</url>
<content type="html"><![CDATA[<p><code>iOS 11</code>以后的用户可以在<code>App Store</code>内的下载页面内直接购买应用的内购商品,这项功能苹果称作做<code>Promoting In-App Purchases</code>,如果你的<code>App</code>需要在<code>App Store</code>推广自己的内购商品,则需要在<code>App Store</code>推广里上传推广用的图像,另外苹果也在<code>iOS11 SDK</code>里面新增了从<code>App Store</code>购买内购项目跳转到<code>App</code>的新方法</p><ol><li>选择<code>推广App内购买项目</code>的好处</li></ol><ul><li>提高展示促销机会,在产品页面上,开发者可一次性推广多达<code>20个App</code>内购买项目</li><li>提高下载量,<code>App</code>内购买项目的推广还能促进<code>App</code>的下载量。如果用户尚未安装<code>App</code>,在点击购买<code>App内购项目</code>时,会引导其先下载</li></ul><ol start="2"><li>如何推广<code>App内购买项目</code></li></ol><ul><li><p>在<code>App Store Connect</code>中为准备推广的<code>App</code>内购买项目上传宣传图像。该图像不但会显示在<code>App Store</code>产品页面,也可能显示在搜索结果中。如果入选精品推荐,它更可能显示在<code>Today、游戏今日亮点、App今日亮点</code>中。当<code>App</code>内购买项目显示在<code>App Store</code>产品页面以外的地方(如搜索结果中),<code>App</code>图标会显示在外框的左下方,所以要确保设计的宣传图像不会被外框遮盖,注意重要细节不要放在左下角,不建议在图像上叠加文字</p></li><li><p><code>App Store</code>后台内购项目的配置,默认情况下,推广的<code>App</code>内购买项目将面向所有设备显示,即使它们没有安装<code>App</code>。①在工具栏中,点按<code>功能</code>,然后在左列中点按<code>App 内购买项目</code>。②点需要修改的<code>App内购买项目</code>,然后前往<code>App Store 推广</code>部分。③配置<code>面向所有 App Store 用户显示,即使是没有安装该 App 的用户</code>复选框设置。④点按<code>存储</code>。⑤在左列中点按<code>App Store 推广</code>,勾选需要推广的项目<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.05.27.01.png" alt=""></p></li></ul><ol start="3"><li>开发者需注意</li></ol><ul><li><code>iOS11以上</code>用户可见,所以产品需要针对<code>iOS11以上</code>系统兼容</li><li>开发者显示<code>App</code>内购买项目推广后,不一定被显示在苹果搜索结果中,只是可能</li><li>苹果明确规定只有<code>除消耗型 App 内购买项目</code>会显示在搜索结果中</li><li>产品提供订阅获取收益十分可观,从而苹果针对<code>自动续期订阅</code>也十分看重,并且为其提供了相关设置方法及运营手段的介绍,所以开发者们可重点尝试<code>App</code>内购买项目中<code>自动续期订阅</code>形式展示,能够得到苹果更多认可及展示</li><li>游戏开发者可考虑利用关卡设置<code>App 内购买项目</code>的形式吸引苹果关注,不同关卡有不同的<code>App 内购买项目(非消耗型)</code>或完整体验需付费的形式,类似这类<code>App</code>苹果是给予鼓励的</li><li>用户直接在<code>App</code>下载页面购买内购商品,这就涉及到从<code>App Store</code>跳转到自己<code>App</code>,所以苹果在<code>SKPaymentTransactionObserver</code>新增了一个代理方法:<br><code>- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product</code><br>这个代理函数是在<code>App Store</code>发起购买的时候会有回调,用户如果在<code>App</code>下载页面点击购买你推广的内购商品,如果用户已经安装过你的<code>App</code>则会直接跳转你的<code>App</code>并调用上述代理方法;如果用户还没有安装你的<code>App</code>那么就会去下载你的<code>App</code>,下载完成之后系统会推送一个通知,如果用户点击该通知就会跳转到你的<code>App</code>并且调用上面的代理方法<br>上面的代理方法返回<code>YES</code>则表示跳转到你的<code>App</code>,<code>IAP</code>继续完成交易,如果返回<code>NO</code>则表示推迟或者取消购买,实际开发中因为可能还需要用户登录自己的账号、生成订单等,一般都是返回<code>NO</code>,之后自己手动把代理方法里面返回的<code>SKPayment</code>加入支付队列,然后在按照自己的支付、验证逻辑完成支付</li></ul><ol start="4"><li>测试<br>苹果提了测试方法,就是修改下面的链接地址,然后在<code>safari</code>浏览器打开,就可以测试从<code>App Store</code>发起购买了。其中链接中的<code>bundleId</code>修改为你自己应用的<code>bundleId</code>,<code>productId</code>修改为你创建的商品的<code>id</code>,如:<code>itms-services://?action=purchaseIntent&bundleId=bundleId&productIdentifier=productId</code></li></ol><p><a href="https://xiaovv.me/2018/05/03/My-iOS-In-App-Purchase-Summarize">附:iOS 内购总结</a><br><a href="https://developer.apple.com/videos/play/wwdc2017/303">附:[官方文档] What’s New in StoreKit</a><br><a href="https://help.apple.com/app-store-connect">附:[官方文档] App Store Connect 帮助</a><br><a href="https://developer.apple.com/cn/app-store/promoting-in-app-purchases">附:[官方文档] 推广您的 App 内购买项目</a><br><a href="https://developer.apple.com/documentation/storekit/in-app_purchase/testing_promoted_in-app_purchases">附:[官方文档] Testing Promoted In-App Purchases</a><br><a href="https://github.com/Gsl201600/Promoting-In-App-PurchasesDemo.git">附:Promoting-In-App-PurchasesDemo</a></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS block原理详解</title>
<link href="/2020/05/13/iOSblock%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3/"/>
<url>/2020/05/13/iOSblock%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3/</url>
<content type="html"><![CDATA[<ol><li>block本质</li></ol><ul><li><code>block</code>底层就是一个<code>struct __main_block_impl_0</code>类型的<code>结构体</code>,这个结构体中包含一个<code>isa</code>指针,本质上是一个<code>OC</code>对象</li><li><code>block</code>是封装了<code>函数调用</code>以及<code>函数调用环境</code>的<code>OC</code>对象</li></ul><ol start="2"><li><p>block底层结构<br><code>block</code>底层结构就是<code>__main_block_impl_0</code>结构体,内部包含了<code>impl结构体</code>和<code>Desc结构体</code>以及外部需要访问的<code>变量</code>,<code>block</code>将需要执行的代码放到一个函数里,<code>impl</code>内部的<code>FuncPtr</code>指向这个函数的地址,通过地址调用这个函数,就可以执行<code>block</code>里面的代码了。<code>Desc</code>用来描述<code>block</code>,内部的<code>reserved</code>作保留,<code>Block_size</code>描述<code>block</code>占用内存<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.05.13.01.png" alt=""></p></li><li><p>block的变量捕获<br><code>局部变量block</code>访问方式是<code>值传递</code>,<code>auto自动变量</code>可能会销毁,内存可能会消失,不采用指针访问;<br><code>局部静态变量block</code>访问方式是<code>指针传递</code>,<code>static变量</code>一直保存在内存中,指针访问即可;<br><code>全局变量、静态全局变量block</code>不需要对变量捕获,直接取值<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.05.13.02.png" alt=""></p></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">// block的变量捕获代码解析如下</span><br><span class="line">auto int age = 10;</span><br><span class="line">static int height = 10; </span><br><span class="line">void (^block)(void) = ^{</span><br><span class="line"> NSLog(@"age is %d,height is %d",age,height);</span><br><span class="line">}; </span><br><span class="line">age = 20;</span><br><span class="line">height = 20; </span><br><span class="line">block();</span><br><span class="line">-------------------------------------------------</span><br><span class="line">output: age is 10,height is 20</span><br><span class="line"></span><br><span class="line">struct __main_block_impl_0 {</span><br><span class="line"> struct __block_impl impl;</span><br><span class="line"> struct __main_block_desc_0* Desc;</span><br><span class="line"> int age; // 值传递</span><br><span class="line"> int *height; // 指针传递</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="4"><li>block的类型</li></ol><table><thead><tr><th>block类型</th><th>环境</th><th>存储域</th><th>copy操作后</th></tr></thead><tbody><tr><td><code>__NSGlobalBlock__</code></td><td>没有访问<code>auto变量</code></td><td>数据区</td><td>什么也不做,类型不改变</td></tr><tr><td><code>__NSStackBlock__</code></td><td>访问了<code>auto变量</code></td><td>栈区</td><td>从栈复制到堆,类型改变为<code>__NSMallocBlock__</code></td></tr><tr><td><code>__NSMallocBlock__</code></td><td><code>__NSStackBlock__</code>调用了<code>copy</code></td><td>堆区</td><td>引用计数<code>+1</code>,类型不改变</td></tr></tbody></table><p>在<code>ARC</code>下<code>Block</code>访问<code>auto变量</code>时系统默认帮我们进行了<code>copy</code>操作,<code>NSGlobalBlock</code>访问了<code>auto变量</code>时会变成<code>NSStackBlock</code>,当<code>NSStackBlock</code>进行<code>copy</code>操作后会变成<code>NSMallocBlock</code></p><ul><li>在<code>ARC</code>环境下,编译器会根据以下几种情况自动将<code>栈上的block</code>复制到<code>堆上</code>:<br>1、<code>block</code>作为函数返回值时,比如使用<code>=</code><br>2、将<code>block</code>赋值给<code>__strong</code>指针时<br>3、<code>block</code>作为<code>Cocoa API</code>中方法名含有<code>usingBlock</code>的方法参数时<br>4、<code>block</code>作为<code>GCD API</code>的方法参数时</li></ul><ol start="5"><li>对象类型的auto变量</li></ol><ul><li>当<code>block</code>内部访问了对象类型的<code>auto变量</code>时:<br>如果<code>block在栈空间</code>,不论是<code>ARC还是MRC</code>环境,不管<code>外部变量</code>是<code>强引用还是弱引用</code>,<code>block</code>都会<code>弱引用</code>访问对象<br>如果<code>block在堆空间</code>,如果外部<code>强引用</code>,<code>block</code>内部也是<code>强引用</code>;如果外部<code>弱引用</code>,<code>block</code>内部也是<code>弱引用</code></li><li>栈block:<br>a) 如果<code>block</code>是在<code>栈上</code>,将不会对<code>auto变量</code>产生<code>强引用</code><br>b) <code>栈上的block</code>随时会被销毁,也没必要去强引用其他对象</li><li>堆block:<br><strong>1、如果block被拷贝到堆上</strong><br>a) 会调用<code>block</code>内部的<code>copy</code>函数<br>b) <code>copy</code>函数内部会调用<code>_Block_object_assign</code>函数<br>c) <code>_Block_object_assign</code>函数会根据<code>auto变量</code>的修饰符<code>__strong</code>、<code>__weak</code>、<code>__unsafe_unretained</code>做出相应的操作,形成<code>强引用</code>或者<code>弱引用</code><br><strong>2、如果block从堆上移除</strong><br>a) 会调用<code>block</code>内部的<code>dispose</code>函数<br>b) <code>dispose</code>函数内部会调用<code>_Block_object_dispose</code>函数<br>c) <code>_Block_object_dispose</code>函数会自动释放引用的<code>auto变量</code>(<code>release</code>,引用计数<code>-1</code>,若为<code>0</code>,则销毁)</li></ul><ol start="6"><li>__block</li></ol><ul><li><code>__block</code>修饰符作用:<br><code>__block</code>可以用于解决<code>block</code>内部无法修改<code>auto变量值</code>的问题<br><code>__block</code>不能修饰全局变量、静态变量<code>static</code></li><li><code>__block</code>修饰符原理:<br>编译器会将<code>__block</code>变量包装成一个结构体<code>__Block_byref_age_0</code>,结构体内部<code>*__forwarding</code>是指向自身的指针,内部还存储着外部<code>auto变量</code>的值<br><code>__block</code>的<code>forwarding</code>指针如下图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.05.13.03.png" alt=""></li></ul><p><code>栈上</code>,<code>__block</code>结构体中的<code>__forwarding</code>指针<code>指向自己</code>,一旦复制到<code>堆上</code>,<code>栈上的__block</code>结构体中的<code>__forwarding</code>指针会指向<code>堆上的__block</code>结构体,<code>堆上__block</code>结构体中的<code>__forwarding</code>还是<code>指向自己</code>。假设<code>age</code>是<code>栈上</code>的变量,<code>age->__forwarding</code>会拿到<code>堆上的__block</code>结构体,<code>age->__forwarding->age</code>会把<code>20</code>赋值到<code>堆上</code>,不论是<code>栈上还是堆上的__block</code>结构体,都能保证<code>20</code>赋值到<code>堆的结构体</code>里</p><ol start="7"><li>思考题:block修改NSMutableString、NSMutableArray、NSMutableDictionary,需不需要添加__block<br>题目如下:以下代码是否可以正确执行<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, const char * argv[]) {</span><br><span class="line"> @autoreleasepool {</span><br><span class="line"> NSMutableArray *array = [NSMutableArray array];</span><br><span class="line"> void (^block)(void) = ^{</span><br><span class="line"> [array addObject: @"5"];</span><br><span class="line"> [array addObject: @"5"];</span><br><span class="line"> NSLog(@"%@",array);</span><br><span class="line"> };</span><br><span class="line"> block();</span><br><span class="line"> }</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>分析:可以正确执行,因为在<code>block</code>块中仅仅是使用了<code>array的内存地址</code>,往<code>内存地址</code>中<code>添加内容</code>,并没有修改<code>arry的内存地址</code>,因此<code>array</code>不需要使用<code>__block修饰</code>也可以正确编译。当仅仅是使用<code>局部变量的内存地址</code>,而不是<code>修改</code>的时候,尽量不要添加<code>__block</code>,通过上述分析我们知道一旦添加了<code>__block</code>修饰符,系统会自动创建相应的结构体,占用不必要的内存空间</li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 数据结构</title>
<link href="/2020/05/06/iOS%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<url>/2020/05/06/iOS%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</url>
<content type="html"><![CDATA[<ol><li>数组和链表的区别</li></ol><ul><li>数组<br>地址连续,查找速度快,操作效率低<br>存储单元在定义时分配,元素个数固定,内存空间要求高</li><li>链表<br>地址不连续,查找速度慢,操作效率高<br>存储单元在程序执行时动态申请,可按需动态增减</li></ul><ol start="2"><li>iOS内存分区的情况,五大区域</li></ol><ul><li>栈区<code>Stack</code><br>先进后出<code>FILO</code><br>由编译器自动分配和释放<br>栈空间多线程不共享<br>连续的内存地址,由高向低分配,不会产生碎片<br>空间较小,运行速度较快,效率高<br>栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高</li><li>堆区<code>Heap</code><br>分配方式类似链表,先进先出<code>FIFO</code><br>一般需要手动分配和释放<br>堆内存多线程共享<br>不连续的内存地址,由低向高分配,容易产生碎片<br>空间较大,运行速度较慢,效率不如栈<br>计算机底层并没有对堆的支持,堆是有<code>C/C++</code>函数库提供的,加上碎片问题,导致堆的效率比栈低</li><li>全局区<br>全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域<code>.data段</code>,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域<code>.bss段</code><br>程序结束后由系统释放</li><li>常量区<br>常量字符串就是放在这里的<br>程序结束后由系统释放</li><li>代码区<br>存放函数体的二进制代码</li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.05.06.01.png" alt="内存分区"></p><ul><li>当一个app启动后,代码区、常量区、全局区大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)</li></ul><ol start="3"><li><p><code>Hash</code>表<br>哈希表(<code>Hash table</code>,也叫<code>散列表</code>)是根据<code>键Key</code>直接访问在内存中存储位置的数据结构。也就是说,它通过计算一个关于<code>键值的函数</code>,将所需<code>查询的数据</code>映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数叫做<code>散列函数</code>,存放记录的数组叫做<code>散列表</code>。通俗讲就是把<code>Key</code>通过一个固定的<code>算法函数(hash函数)</code>转换成一个<code>整型数字</code>,然后就对该<code>数字用数组的长度进行取余</code>,取余结果就当作<code>数组的下标</code>,将<code>value</code>存储在以该数字为<code>下标的数组</code>空间里。当使用<code>hash</code>表查询时,就是使用<code>hash</code>函数将<code>key</code>转换成对应的<code>数组下标</code>,并定位到该下标的数组空间里获取<code>value</code>,这样就充分利用到数组的定位性能进行数据定位</p></li><li><p><code>iOS</code>里有哪些地方用到了<code>Hash</code>表<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.05.06.02.png" alt=""></p></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS Crash文件获取及符号化</title>
<link href="/2020/04/29/iOSCrash%E6%96%87%E4%BB%B6%E8%8E%B7%E5%8F%96%E5%8F%8A%E7%AC%A6%E5%8F%B7%E5%8C%96/"/>
<url>/2020/04/29/iOSCrash%E6%96%87%E4%BB%B6%E8%8E%B7%E5%8F%96%E5%8F%8A%E7%AC%A6%E5%8F%B7%E5%8C%96/</url>
<content type="html"><![CDATA[<ol><li><code>Crash</code>文件获取</li></ol><ul><li>大致可以分为两种方式:远程获取和本地获取;具体可以分为如下四种途径</li></ul><p>1.1. 远程获取;已经上传到<code>iTunes Connect</code>的应用,可以通过<code>iTunes Connect</code>的<code>App分析</code>查看<code>App</code>崩溃情况<code>不会有崩溃日志</code>,如果是<code>TestFlight</code>测试,则可以在<code>iTunes Connect</code>获取到崩溃日志</p><p>1.2. 远程获取;通过<code>Xcode</code>菜单<code>Window -> Organizer -> Crashes</code>获取用户的崩溃日志<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.29.01.png" alt=""></p><ul><li>注意:以上两种途径都需要登录开发者账号,并且需要用户共享<code>iPhone</code>分析,才能够获取到用户的崩溃日志</li><li>注意:官方提供的崩溃信息并不是实时的,只能查看两天之前的崩溃信息,需要实时的可以使用第三方工具</li></ul><p>1.3. 本地获取;在手机上<code>设置 -> 隐私 -> 分析与改进 -> 分析数据</code>中,根据应用名称和日期时间找到你需要的日志,点击进去后,右上角会有个分享按钮,分享给<code>Mac</code></p><p>1.4. 把手机连接到<code>Mac</code>,通过<code>Xcode</code>菜单<code>Window -> Devices and Simulators -> Devices -> View Device Logs</code>获取用户的崩溃日志</p><ul><li>注意某些<code>iOS</code>系统会没有上面提到的分享按钮,这时候可以全选复制,再发送给<code>Mac</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.29.02.png" alt="1.1该图为早期iTunes Connect官网,具体以当前官网为主"></li></ul><ol start="2"><li><code>Crash</code>文件符号化</li></ol><ul><li>大致也是分为两种方式:使用<code>Xcode</code>自动符号化和通过手动命令行工具<code>symbolicatecrash</code>符号化;这两种方式原理一样,都需要<code>dSYM</code>文件,只不过前者是<code>Xcode</code>自动帮我们完成的</li><li>注意:如果你们的应用是通过<code>Xcode</code>上传<code>iTunes Connect</code>的,并同时上传了<code>.xcarchive</code>文件<code>(实际上是一个文件夹,包含.ipa和.dSYM文件)</code>,<code>Xcode</code>会默认帮你勾选该选项,那么从<code>iTunes Connect</code>获取到的日志就已经是符号化过的了</li></ul><p>2.1. 使用<code>Xcode</code>自动符号化<code>Crash</code>文件,<code>Xcode</code>自带的工具非常好用</p><ul><li>如果你用的<code>Mac</code>就是打包的机子,并且得到了发生崩溃的手机,那么手机连接电脑,通过<code>Xcode</code>菜单<code>Window -> Devices and Simulators -> Devices -> View Device Logs</code>找到自己的日志,就是符号化过后的,如果没有符号化,就稍微等待一会儿,或者右击点出菜单选择<code>Re-Symbolicate Log</code></li><li>如果只有<code>Mac</code>出包机,没有手机只有崩溃日志,那么同样可以通过<code>Xcode</code>菜单<code>Window -> Devices and Simulators -> Devices -> View Device Logs</code>把崩溃日志直接拖进去,就是符号化过后的,如果没有符号化,就稍微等待一会儿,或者右击点出菜单选择<code>Re-Symbolicate Log</code></li><li>注意:在有些版本的<code>Xcode</code>是拖不进去的,遇到这种情况可以用下面的手动符号化方式</li><li>注意:上面的方法不一定要是出包机,本质是只要你的电脑上有<code>dSYM</code>文件,<code>Xcode</code>就能自动找到他并为你符号化<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.29.03.png" alt=""></li></ul><p>2.2. 通过终端命令行工具<code>symbolicatecrash</code>符号化<br>大概需要如下三个文件,下面是获取这些文件的方法<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.29.04.png" alt=""></p><ul><li>通过菜单<code>Xcode -> Window -> Organizer -> Archiver</code>找到打包的项目,右键<code>Show In Finder</code>,找到<code>AppName.xcarchive</code>,右键显示包内容,找到<code>AppName.app.dSYM</code></li><li>在桌面创建一个文件夹<code>tmp</code>,将以上两个文件拷贝到<code>tmp</code>文件夹中</li><li>打开终端,用<code>find /Applications/Xcode.app -name symbolicatecrash -type f</code>查找<code>symbolicatecrash</code>,其中<code>/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash</code>路径是需要的<code>symbolicatecrash</code>文件,将<code>symbolicatecrash</code>文件也拷贝到<code>tmp</code>文件夹中</li><li>将需要分析的<code>crash</code>文件也拷贝到<code>tmp</code>文件夹中,<code>crash</code>文件的格式可能是<code>.beta</code>、<code>.crash</code>或<code>.ips</code></li><li>在终端中使用以下命令行,<code>crash</code>文件格式以<code>.crash</code>为例<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 进入到 tmp 文件夹中</span><br><span class="line">cd ~/Desktop/tmp </span><br><span class="line"></span><br><span class="line"># 分析 crash 文件,会在 `tmp` 文件夹中生成 crash.log 文件</span><br><span class="line">./symbolicatecrash ./xxx.crash ./AppName.app.dSYM > crash.log</span><br><span class="line">或./symbolicatecrash ./xxx.crash ./.app.dSYM > crash.log</span><br></pre></td></tr></table></figure></li><li>如果终端报类似这样的错<code>zsh: permission denied: ./symbolicatecrash</code><br>说明是<code>symbolicatecrash</code>文件有问题,可能该文件不是本机获取的,或者是之前获取的、<code>Xcode</code>升级等问题造成的,重新在本机上按上面方法获取即可</li><li>如果终端报类似这样的错<code>Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69.</code><br>尝试以下命令后,再重复上面命令,正常情况就可以分析bug了<code>export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer</code></li></ul><ol start="3"><li>符号化之前,首先得确保<code>Crash</code>文件和<code>dSYM</code>这两个文件里面的<code>UUID</code>是一致的,如果不一致,就说明不是本次<code>Crash</code>对应的文件,就不能进行符号化;查看<code>dSYM</code>文件里面的<code>UUID</code>命令:<code>dwarfdump --uuid AppName.app.dSYM</code>;查看<code>Crash</code>文件文件的<code>UUID</code>就比较简单了,直接打开,<code>Crash</code>最上面的就是各种信息,不同系统版本给的格式可能会有不同,下图内容为参考<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.29.05.png" alt=""></li></ol><ul><li><code>Xcode</code>在<code>Debug</code>模式下默认关闭生成<code>dSYM</code>文件,<code>Release</code>模式下默认生成<code>dSYM</code>文件的, 要生成<code>dSYM</code>文件需要查看一下项目的<code>Build Settigns -> Build Options -> Debug information Format</code>属性;只有该属性设置为<code>DWARF with dSYM File</code>时,编译才会生成<code>dSYM</code>文件</li><li>该文是在<code>Xcode 11.2</code>和<code>iOS 13.2</code>上写的教程,不同的系统版本的<code>Xcode</code>和手机系统获取路径和符号化方式会有变化</li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 时间日期总结</title>
<link href="/2020/04/22/iOS%E6%97%B6%E9%97%B4%E6%97%A5%E6%9C%9F%E6%80%BB%E7%BB%93/"/>
<url>/2020/04/22/iOS%E6%97%B6%E9%97%B4%E6%97%A5%E6%9C%9F%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<ol><li>获取时间戳 </li></ol><ul><li>单位秒,保留六位有效数字,格式如:<code>1574068247.545103</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSDate *datenow = [NSDate date];</span><br><span class="line">NSString *timeSp = [NSString stringWithFormat:@"%f", (double)[datenow timeIntervalSince1970]];</span><br></pre></td></tr></table></figure></li><li>单位秒,整数,格式如:<code>1574068265</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSDate *datenow = [NSDate date];</span><br><span class="line">NSString *timeSp = [NSString stringWithFormat:@"%ld", (long)[datenow timeIntervalSince1970]];</span><br></pre></td></tr></table></figure></li><li>单位毫秒,整数,不精确,后面直接补三个<code>0</code>,格式如:<code>1574068602000</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSDate *datenow = [NSDate date];</span><br><span class="line">NSString *timeSp = [NSString stringWithFormat:@"%ld", (long)[datenow timeIntervalSince1970]*1000];</span><br></pre></td></tr></table></figure></li><li>单位毫秒,整数,精确,格式如:<code>1574070082387</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 获取当前时间0秒后的时间</span><br><span class="line">NSDate *date = [NSDate dateWithTimeIntervalSinceNow:0];</span><br><span class="line">// *1000 是精确到毫秒,不乘就是精确到秒</span><br><span class="line">NSTimeInterval time = [date timeIntervalSince1970]*1000;</span><br><span class="line">NSString *timeStr = [NSString stringWithFormat:@"%.0f", time];</span><br></pre></td></tr></table></figure></li></ul><ol start="2"><li>时间戳转日期<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 传入的时间戳timeStr如果是精确到毫秒的记得要/1000</span><br><span class="line">NSTimeInterval timeInterval = [timeStr doubleValue]/1000;</span><br><span class="line">NSDate *detailDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];</span><br><span class="line">NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];</span><br><span class="line">// 实例化一个NSDateFormatter对象,设定时间格式,这里可以设置成自己需要的格式</span><br><span class="line">[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss SS"];</span><br><span class="line">NSString *dateStr = [dateFormatter stringFromDate:detailDate];</span><br></pre></td></tr></table></figure></li><li>两个日期比较<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">//1.将这两个时间戳转换成日期</span><br><span class="line">NSDate *date1 = [NSDate dateWithTimeIntervalSince1970:1451047216];</span><br><span class="line">NSDate *date2 = [NSDate dateWithTimeIntervalSince1970:1451847216];</span><br><span class="line">//2.开始比较</span><br><span class="line">// 比较date1是不是比date2早——>会返回一个比较早的日期</span><br><span class="line">NSDate *date3 = [date1 earlierDate:date2];</span><br><span class="line">NSLog(@"比较早的日期:%@",date3);</span><br><span class="line">//比较两个日期谁比谁晚</span><br><span class="line">NSDate *date4 = [date1 laterDate:date2];</span><br><span class="line">NSLog(@"比较晚的日期:%@",date4);</span><br><span class="line">// 比较两个日期 是不是相同 ——>返回值BOOL类型</span><br><span class="line">BOOL result = [date1 isEqualToDate:date2];</span><br><span class="line">NSLog(@"%d",result);</span><br></pre></td></tr></table></figure></li></ol><ul><li>可以解决跨年、跨月、平闰年时间处理问题<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 100天后</span><br><span class="line">NSDate *date = [NSDate dateWithTimeIntervalSinceNow:60 * 60 * 24 * 100];</span><br><span class="line">NSDate *nowDate = [NSDate date];</span><br><span class="line">// 日期升序</span><br><span class="line">if ([nowDate compare:date] == NSOrderedAscending) {</span><br><span class="line"> NSLog(@"如果打印,nowDate比ndate时间早,如nowDate=2019-11-18, ndate=2020-02-26");</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><ol start="4"><li>日历组件<code>NSCalendar</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">NSDate *nowDate = [NSDate date];</span><br><span class="line">NSCalendar *calendar = [NSCalendar currentCalendar];</span><br><span class="line">// 初始化日历组件,可以选择需要的组件</span><br><span class="line">NSDateComponents *comps = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitWeekday|NSCalendarUnitWeekdayOrdinal|NSCalendarUnitWeekOfMonth|NSCalendarUnitWeekOfYear|NSCalendarUnitYearForWeekOfYear fromDate:nowDate];</span><br><span class="line">// 得到:今天是星期几,返回日期的工作日索引(1 =星期日,2 =星期一,…,7 =星期六)</span><br><span class="line">NSInteger weekDay = [comps weekday];</span><br><span class="line">// 得到:今天是几号</span><br><span class="line">NSInteger day = [comps day];</span><br><span class="line">// 得到:一年中的第几周</span><br><span class="line">NSInteger weekOfYear = [comps weekOfYear];</span><br></pre></td></tr></table></figure></li><li>常见<code>NSDateFormatter</code>格式<br>可以使用以下<code>dateFormatter</code>符号单独格式化,拿到需要的数据进行处理</li></ol><table><thead><tr><th>符号</th><th>说明</th></tr></thead><tbody><tr><td><code>y/yyy/yyyy/Y/YYY/YYYY/u/uu/uuu/uuuu/U/UUU/UUUU</code></td><td>完整的年份</td></tr><tr><td><code>yy/YY/UU</code></td><td>2个数字的年份</td></tr><tr><td><code>M/MM/L/LL</code></td><td>1~12 第几月</td></tr><tr><td><code>MMM/LLL</code></td><td>Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec 月份简写</td></tr><tr><td><code>MMMM/LLLL</code></td><td>January/February/March/April/May/June/July/August/September/October/November/December 月份全称</td></tr><tr><td><code>d</code></td><td>1~31 (月份的第几天,带0)</td></tr><tr><td><code>D</code></td><td>1~366 (年份的第几天,带0)</td></tr><tr><td><code>e/c/cc</code></td><td>1~7 (一周的第几天,周日为1,带0)</td></tr><tr><td><code>E~EEE/eee/ccc</code></td><td>Sun/Mon/Tue/Wed/Thu/Fri/Sat (星期简写)</td></tr><tr><td><code>EEEE/eeee/cccc</code></td><td>Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday (星期全拼)</td></tr><tr><td><code>H</code></td><td>0~23 带0的时,24小时制</td></tr><tr><td><code>h</code></td><td>1~12 带0的时,12小时制</td></tr><tr><td><code>k</code></td><td>1~24 一天中的小时数,带0的时,24小时制</td></tr><tr><td><code>K</code></td><td>0~11 带0的时,12小时制</td></tr><tr><td><code>m</code></td><td>0~59 分钟</td></tr><tr><td><code>s</code></td><td>0~59 秒数</td></tr><tr><td><code>SSS</code></td><td>毫秒</td></tr><tr><td><code>a</code></td><td>AM/PM (上午/下午)</td></tr><tr><td><code>A</code></td><td>0~86399999 (一天的第几微秒)</td></tr><tr><td><code>F</code></td><td>1~5 每月的第几周</td></tr><tr><td><code>w</code></td><td>1~53 一年的第几周,一周的开始为周日,第一周从去年的最后一个周日起算</td></tr><tr><td><code>W</code></td><td>1~5 一个月的第几周,一周的开始为周日</td></tr><tr><td><code>q/qq/Q/QQ</code></td><td>1~4 第几季度</td></tr><tr><td><code>qqq/QQQ</code></td><td>Q1/Q2/Q3/Q4 季度简写</td></tr><tr><td><code>qqqq/QQQQ</code></td><td>1st quarter/2nd quarter/3rd quarter/4th quarter 季度全拼</td></tr><tr><td><code>z~zzz</code></td><td>指定GMT时区的缩写,GMT+8</td></tr><tr><td><code>zzzz/vvvv</code></td><td>指定GMT时区的名称,China Standard Time</td></tr><tr><td><code>Z~ZZZ</code></td><td>指定GMT时区的缩写,+0800</td></tr><tr><td><code>ZZZZ</code></td><td>指定GMT时区的缩写,GMT+08:00</td></tr><tr><td><code>v/VVVV</code></td><td>指定GMT时区的名称,China mainland Time</td></tr><tr><td><code>VV</code></td><td>指定GMT时区的名称,Asia/Shanghai</td></tr><tr><td><code>VVV</code></td><td>指定GMT时区的名称,Shanghai</td></tr></tbody></table>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>Mac OS + Mac PE + Win PE 三合一 U盘制作教程</title>
<link href="/2020/04/08/MacOS+MacPE+WinPE%E4%B8%89%E5%90%88%E4%B8%80U%E7%9B%98%E5%88%B6%E4%BD%9C%E6%95%99%E7%A8%8B/"/>
<url>/2020/04/08/MacOS+MacPE+WinPE%E4%B8%89%E5%90%88%E4%B8%80U%E7%9B%98%E5%88%B6%E4%BD%9C%E6%95%99%E7%A8%8B/</url>
<content type="html"><![CDATA[<p>开始之前需要准备一下工具:</p><ul><li><code>移动硬盘</code>或者<code>U盘</code>一个</li><li><code>Mac OS</code>原版安装文件</li><li><code>Mac PE</code></li><li><code>Win PE</code></li><li><code>DiskGenius</code>分区工具</li></ul><ol><li><p><code>Win PE</code>制作<br>下载好<code>U盘魔术师V5全能版</code>或者<code>通用PE工具箱</code>等<code>Win PE</code>制作软件,安装到电脑打开,然后插入<code>U盘</code>;一般保持默认设置就行,<code>Win PE</code>制作完成。</p></li><li><p><code>Mac OS</code>分区制作<br>打开<code>DiskGenius</code>分区工具,找到刚刚制作好的<code>U盘</code>,然后选中这个<code>U盘分区</code>,右击菜单选中<code>调整分区大小</code>,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.01.png" alt=""></p></li></ol><p>打开调整窗口,把鼠标放在分区的右边,出现拖拉箭头,然后往左拉,或者在下方直接填写你要分的空间大小,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.02.png" alt=""></p><p><code>Mac OS</code>分区的空间一般<code>8.5G</code>,也可以设置更大,看个人。然后点击<code>开始</code>,弹出对话框,选中<code>是</code>;制作完成之后点击<code>完成</code>,就会出现空闲<code>8.5G</code>,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.03.png" alt=""></p><p>右击空闲的分区,选择<code>建立新分区</code>,选中<code>NTFS</code>格式,<code>4K对齐</code>,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.04.png" alt=""></p><p>选择<code>确定</code>,然后点击左上角的<code>保存更改</code>,提示你格式化分区,选择<code>是</code>,格式化完成之后,<code>8.5G</code>的<code>Mac OS</code>分区就制作好了。</p><ol start="3"><li>制作<code>Mac PE</code>分区,分<code>9G</code>以上;方法跟制作<code>Mac OS</code>分区是一样的,这里不再重复,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.05.png" alt=""></li></ol><p>分区基本做好了,现在转到苹果系统去写入系统文件。</p><ol start="4"><li>格式化两个<code>Mac</code>分区</li></ol><p>打开苹果电脑的<code>磁盘工具</code>,找到刚刚分出来的<code>9G</code>容量的分区,然后选择<code>抹掉</code>,名称为<code>Mac PE</code>,只为做区分用,可以随意命名;格式为<code>Mac OS扩展 日志式</code>;然后选择<code>8.5G</code>的<code>Mac OS</code>安装分区,步骤和上面一样,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.06.png" alt=""></p><ol start="5"><li><code>Mac PE</code>系统文件写入,打开下载好的<code>iFen.OS X PE</code>,解压的过程选择<code>跳过</code>,不跳过也行,目的是为了加载进磁盘工具里,回到<code>磁盘工具</code>的界面,会出现解压出来的镜像,然后选中<code>Mac PE</code>分区,选择菜单的<code>恢复</code>按钮,恢复来源选择刚刚解压出来的<code>PE镜像</code>,点击<code>恢复</code>,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.07.png" alt=""></li></ol><p><strong>注意:</strong><code>iFen.OS X PE</code>是基于<code>Mac OS 10.14</code>制作的,所以要用<code>Mac OS 10.14</code>的电脑才能恢复,否则会恢复失败,恢复过程的快慢就要看<code>U盘</code>的速度了,大概<code>5分钟</code>的时间。</p><ol start="6"><li><code>Mac OS</code>系统文件写入,以<code>Mac OS 10.14</code>系统为例,不同的系统制作代码不同,代码中的<code>MyVolume</code>为上面命名的<code>U盘名称</code>,下载好<code>Mac OS 10.14.1</code>系统,并把它放在<code>Mac的应用程序</code>里备用,打开<code>终端</code>,在终端输入代码:<code>sudo /Applications/Install\ macOS\ Mojave.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume</code><br>等待制作完成,大概<code>5分钟</code>,完成之后;如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.08.png" alt=""></li></ol><p>至此,整个三合一的启动盘制作完成了,三合一U盘电脑启动界面,如图:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img2020/2020.04.08.09.png" alt=""></p><p><a href="https://www.applex.net/threads/mac-os-x-mac-pe-win-pe-u.92345">附:Mac OS X + Mac PE + Win PE 三合一 U盘制作教程</a><br><a href="https://support.apple.com/zh-cn/HT201372">附:[官方文档] 如何创建可引导的 macOS 安装器</a><br><a href="https://pan.baidu.com/s/1mGgc50GOZAaVsYbbHAjyHw">附:Mac PE下载地址 密码:6jkp</a><br><a href="https://www.applex.net/threads/mac-pe19.93480">附:最新版Mac PE</a></p>]]></content>
<tags>
<tag> 随笔 </tag>
</tags>
</entry>
<entry>
<title>iOS 如何优化 App 的启动耗时</title>
<link href="/2020/04/01/iOS%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96App%E7%9A%84%E5%90%AF%E5%8A%A8%E8%80%97%E6%97%B6/"/>
<url>/2020/04/01/iOS%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96App%E7%9A%84%E5%90%AF%E5%8A%A8%E8%80%97%E6%97%B6/</url>
<content type="html"><![CDATA[<h3 id="iOS-的-App-启动时长大概可以这样计算:"><a href="#iOS-的-App-启动时长大概可以这样计算:" class="headerlink" title="iOS 的 App 启动时长大概可以这样计算:"></a>iOS 的 App 启动时长大概可以这样计算:</h3><ul><li>t(<code>App 总启动时间</code>) = t1(<code>main 调用之前的加载时间</code>) + t2(<code>main 调用之后的加载时间</code>)</li><li>t1 = <code>系统 dylib(动态链接库)</code>和<code>自身 App 可执行文件的加载</code></li><li>t2 = <code>main</code>方法执行之后到<code>AppDelegate</code>类中的<code>application:didFinishLaunchingWithOptions:</code>方法执行结束前这段时间,主要是构建第一个界面,并完成渲染展示</li></ul><ol><li>在<code>t1</code>阶段加快<code>App</code>启动的建议:</li></ol><ul><li>尽量使用静态库,减少动态库的使用,动态链接比较耗时,如果要用动态库,尽量将多个<code>dylib</code>动态库合并成一个</li><li>尽量避免对系统库使用<code>optional linking</code>,如果<code>App</code>用到的系统库在你所有支持的系统版本上都有,就设置为<code>required</code>,因为<code>optional</code>会有些额外的检查</li><li>减少<code>Objective-C Class、Selector、Category</code>的数量,可以合并或者删减一些<code>OC</code>类</li><li>删减一些无用的静态变量,删减没有被调用到或者已经废弃的方法</li><li>将不必须在<code>+load</code>中做的事情尽量挪到<code>+initialize</code>中,<code>+initialize</code>是在第一次初始化这个类之前被调用,<code>+load</code>在加载类的时候就被调用。尽量将<code>+load</code>里的代码延后调用</li><li>尽量不要用<code>C++</code>虚函数,创建虚函数表有开销</li><li>不要使用<code>__attribute__((constructor))</code>将方法显式标记为初始化器,而是让初始化方法调用时才执行。比如使用<code>dispatch_once(),pthread_once()或 std::once()</code></li><li>在初始化方法中不调用<code>dlopen(),dlopen()</code>有性能和死锁的可能性</li><li>在初始化方法中不创建线程</li></ul><ol start="2"><li>在<code>t2</code>阶段加快<code>App</code>启动的建议:</li></ol><ul><li>尽量不要使用<code>xib/storyboard</code>,而是用纯代码作为首页<code>UI</code>,如果要用<code>xib/storyboard</code>,不要在<code>xib/storyboard</code>中存放太多的视图</li><li>对<code>application:didFinishLaunchingWithOptions:</code>里的任务尽量延迟加载或懒加载</li><li>不要在<code>NSUserDefaults</code>中存放太多的数据,<code>NSUserDefaults</code>是一个<code>plist</code>文件,<code>plist</code>文件会被反序列化一次</li><li>避免在启动时打印过多的<code>log</code>,少用<code>NSLog</code>,因为每一次<code>NSLog</code>的调用都会创建一个新的<code>NSCalendar</code>实例</li><li>为了防止使用<code>GCD</code>创建过多的线程,解决方法是创建串行队列,或者使用带有最大并发数限制的<code>NSOperationQueue</code></li><li>不要在主线程执行<code>磁盘、网络、Lock或者dispatch_sync、发送消息给其他线程</code>等操作</li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS RunLoop</title>
<link href="/2020/01/17/iOSRunLoop/"/>
<url>/2020/01/17/iOSRunLoop/</url>
<content type="html"><![CDATA[<h3 id="RunLoop概念"><a href="#RunLoop概念" class="headerlink" title="RunLoop概念"></a>RunLoop概念</h3><ul><li><code>RunLoop</code>是通过内部维护的<code>事件循环(Event Loop)</code>来对<code>事件/消息</code>进行管理的一个对象</li><li>没有消息处理时,休眠以避免资源占用;有消息需要处理时,立刻被唤醒</li></ul><ol><li>为什么<code>main</code>函数不会退出<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> @autoreleasepool {</span><br><span class="line"> return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><code>UIApplicationMain</code>内部默认开启了主线程的<code>RunLoop</code>,并执行了一段无限循环的代码(不是简单的<code>for循环</code>或<code>while循环</code>)<code>UIApplicationMain</code>函数一直没有返回,不断地接收处理消息以及等待休眠,所以运行程序之后,会保持持续运行状态</li></ol><h3 id="RunLoop结构体"><a href="#RunLoop结构体" class="headerlink" title="RunLoop结构体"></a>RunLoop结构体</h3><ul><li><code>Source1</code> : 基于<code>Port</code>的线程间通信</li><li><code>Source0</code> : 触摸事件、<code>PerformSelector</code></li><li><code>Timer</code> : 定时器</li><li><code>Observer</code> : 监听器,用于监听<code>RunLoop</code>的状态</li></ul><h3 id="RunLoop和线程"><a href="#RunLoop和线程" class="headerlink" title="RunLoop和线程"></a>RunLoop和线程</h3><ul><li>线程和<code>RunLoop</code>是一一对应的,其映射关系是保存在一个全局的<code>Dictionary</code>里,线程作为<code>key</code>,<code>RunLoop</code>作为<code>value</code></li><li>自己创建的线程默认是没有开启<code>RunLoop</code>的</li><li><code>runloop</code>在第一次获取时被创建,在线程结束时被销毁</li><li>对于主线程来说,<code>runloop</code>在程序一启动就默认创建好了</li><li>对于子线程来说,<code>runloop</code>是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的<code>runloop</code>被创建,不然定时器不会回调</li></ul><ol><li>怎么创建一个常驻线程</li></ol><ul><li>为当前线程开启一个<code>RunLoop</code>(第一次调用<code>[NSRunLoop currentRunLoop]</code>方法时,实际是会先去创建一个<code>RunLoop</code>)</li><li>向当前<code>RunLoop</code>中添加一个<code>Port/Source</code>等维持<code>RunLoop</code>的事件循环(如果<code>RunLoop</code>的<code>mode</code>中一个<code>item</code>都没有,<code>RunLoop</code>会退出)</li><li>启动该<code>RunLoop</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">@autoreleasepool {</span><br><span class="line"> NSRunLoop *runLoop = [NSRunLoop currentRunLoop];</span><br><span class="line"> [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];</span><br><span class="line"> [runLoop run];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><ol start="2"><li>输出下边代码的执行顺序<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">NSLog(@"1");</span><br><span class="line"></span><br><span class="line">dispatch_async(dispatch_get_global_queue(0, 0), ^{</span><br><span class="line"> NSLog(@"2");</span><br><span class="line"> [self performSelector:@selector(test) withObject:nil afterDelay:10];</span><br><span class="line"> NSLog(@"3");</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">NSLog(@"4");</span><br><span class="line"></span><br><span class="line">- (void)test{</span><br><span class="line"> NSLog(@"5");</span><br><span class="line">}</span><br></pre></td></tr></table></figure>答案是<code>1423</code>,<code>test</code>方法并不会执行<br>原因是:如果是带<code>afterDelay</code>的延时函数,会在内部创建一个<code>NSTimer</code>,然后添加到当前线程的<code>RunLoop</code>中,也就是如果当前线程没有开启<code>RunLoop</code>,该方法会失效<br>那么我们改成:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dispatch_async(dispatch_get_global_queue(0, 0), ^{</span><br><span class="line"> NSLog(@"2");</span><br><span class="line"> [[NSRunLoop currentRunLoop] run];</span><br><span class="line"> [self performSelector:@selector(test) withObject:nil afterDelay:10];</span><br><span class="line"> NSLog(@"3");</span><br><span class="line">});</span><br></pre></td></tr></table></figure><code>test</code>方法依然不执行<br>原因是:如果<code>RunLoop</code>的<code>mode</code>中一个<code>item</code>都没有,<code>RunLoop</code>会退出<br>即在调用<code>RunLoop</code>的<code>run</code>方法后,由于其<code>mode</code>中没有添加任何<code>item</code>去维持<code>RunLoop</code>的事件循环,<code>RunLoop</code>随即还是会退出,所以我们自己启动<code>RunLoop</code>,一定要在添加<code>item</code>后<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dispatch_async(dispatch_get_global_queue(0, 0), ^{</span><br><span class="line"> NSLog(@"2");</span><br><span class="line"> [self performSelector:@selector(test) withObject:nil afterDelay:10];</span><br><span class="line"> [[NSRunLoop currentRunLoop] run];</span><br><span class="line"> NSLog(@"3");</span><br><span class="line">});</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 多线程相关之performSelector、死锁</title>
<link href="/2020/01/08/iOS%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9B%B8%E5%85%B3%E4%B9%8BperformSelector/"/>
<url>/2020/01/08/iOS%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9B%B8%E5%85%B3%E4%B9%8BperformSelector/</url>
<content type="html"><![CDATA[<ol><li><code>performSelector</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">//在当前线程延迟1s执行,响应了OC语言的动态性:延迟到运行时才绑定方法</span><br><span class="line">[self performSelector:@selector(aaa) withObject:nil afterDelay:1];</span><br><span class="line">// 回到主线程,waitUntilDone:是否将该回调方法执行完再执行后面的代码</span><br><span class="line">// 如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程</span><br><span class="line">// 如果是NO:就是不等回调方法结束,不会阻塞当前线程</span><br><span class="line">[self performSelectorOnMainThread:@selector(aaa) withObject:nil waitUntilDone:YES];</span><br><span class="line">// 开辟子线程</span><br><span class="line">[self performSelectorInBackground:@selector(aaa) withObject:nil];</span><br><span class="line">//在指定线程执行</span><br><span class="line">[self performSelector:@selector(aaa) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];</span><br></pre></td></tr></table></figure></li></ol><ul><li><strong>需要注意的是:</strong>如果是带<code>afterDelay</code>的延时函数,会在内部创建一个<code>NSTimer</code>,然后添加到当前线程的<code>Runloop</code>中。也就是如果当前线程没有开启<code>runloop</code>,该方法会失效。在子线程中,需要启动<code>runloop</code>(注意调用顺序)<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[self performSelector:@selector(aaa) withObject:nil afterDelay:1];</span><br><span class="line">[[NSRunLoop currentRunLoop] run];</span><br></pre></td></tr></table></figure></li><li><code>performSelector:withObject:</code>只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的<code>Runloop</code>中也能执行</li><li>下面代码片段的<code>test</code>方法会去执行吗?<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dispatch_async(dispatch_get_global_queue(0, 0), ^{</span><br><span class="line"> [self performSelector:@selector(test:) withObject:nil afterDelay:0];</span><br><span class="line">});</span><br></pre></td></tr></table></figure>这里的<code>test</code>方法是不会去执行的,原因在于<code>- (void)performSelector: withObject: afterDelay:</code>这个方法要创建提交任务到<code>runloop</code>上的,而<code>gcd</code>底层创建的线程是默认没有开启对应<code>runloop</code>的,所有这个方法就会失效。<br>而如果将<code>dispatch_get_global_queue</code>改成主队列,由于主队列所在的主线程是默认开启了<code>runloop</code>的,就会去执行(将<code>dispatch_async</code>改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,<code>test</code>方法也是会去执行的)</li></ul><ol start="2"><li>死锁</li></ol><ul><li>死锁就是队列引起的循环等待,一个比较常见的死锁例子:主队列同步<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- (void)viewDidLoad {</span><br><span class="line"> [super viewDidLoad];</span><br><span class="line"> dispatch_sync(dispatch_get_main_queue(), ^{</span><br><span class="line"> NSLog(@"deallock");</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,<code>viewDidLoad</code>才会继续向下执行。而<code>viewDidLoad</code>和任务都是在主队列上的,由于队列的<code>先进先出</code>原则,任务又需等待<code>viewDidLoad</code>执行完毕后才能继续执行,<code>viewDidLoad</code>和这个任务就形成了相互循环等待,就造成了死锁。<br>想避免这种死锁,可以将同步改成异步<code>dispatch_async</code>或者将<code>dispatch_get_main_queue</code>换成其他串行或并行队列,都可以解决</li><li>同样,下边的代码也会造成死锁:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);</span><br><span class="line">dispatch_async(serialQueue, ^{</span><br><span class="line"> dispatch_sync(serialQueue, ^{</span><br><span class="line"> NSLog(@"deadlock");</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure></li><li>外面的函数无论是同步还是异步都会造成死锁。这是因为里面的任务和外面的任务都在同一个<code>serialQueue</code>队列内,又是同步,这就和上边主队列同步的例子一样造成了死锁。<br>解决方法也和上边一样,将里面的同步改成异步<code>dispatch_async</code>或者将<code>serialQueue</code>换成其他串行或并行队列,都可以解决</li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 中事件的响应链和传递链</title>
<link href="/2019/12/25/iOS%E4%B8%AD%E4%BA%8B%E4%BB%B6%E7%9A%84%E5%93%8D%E5%BA%94%E9%93%BE%E5%92%8C%E4%BC%A0%E9%80%92%E9%93%BE/"/>
<url>/2019/12/25/iOS%E4%B8%AD%E4%BA%8B%E4%BB%B6%E7%9A%84%E5%93%8D%E5%BA%94%E9%93%BE%E5%92%8C%E4%BC%A0%E9%80%92%E9%93%BE/</url>
<content type="html"><![CDATA[<p>iOS事件链有两条:事件的响应链;<code>Hit-Testing</code>事件的传递链</p><ul><li>响应链:由离用户最近的<code>view</code>向系统传递。<code>initial view</code> –> <code>super view</code> –> ….. –> <code>view controller</code> –> <code>window</code> –> <code>Application</code> –> <code>AppDelegate</code></li><li>传递链:由系统向离用户最近的view传递。<code>UIKit</code> –> <code>active app's event queue</code> –> <code>window</code> –> <code>root view</code> –> …… –> <code>lowest view</code> </li></ul><p>在iOS中只有继承<code>UIResponder</code>的对象才能够接收并处理事件,<code>UIResponder</code>是所有响应对象的基类,在<code>UIResponder</code>类中定义了处理上述各种事件的接口。我们熟悉的<code>UIApplication、UIViewController、UIWindow</code>和所有继承自<code>UIView</code>的<code>UIKit</code>类都直接或间接的继承自<code>UIResponder</code>,所以它们的实例都是可以构成响应者链的响应者对象,首先我们通过一张图来简单了解一下事件的传递以及响应<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.12.25.01.png" alt=""></p><ol><li>传递链</li></ol><ul><li>事件传递的两个核心方法<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system</span><br><span class="line">- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds</span><br></pre></td></tr></table></figure></li><li>第一个方法返回的是一个UIView,是用来寻找最终哪一个视图来响应这个事件</li><li>第二个方法是用来判断某一个点击的位置是否在视图范围内,如果在就返回YES</li><li>其中UIView不接受事件处理的情况有<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. alpha <0.01</span><br><span class="line">2. userInteractionEnabled = NO</span><br><span class="line">3. hidden = YES</span><br></pre></td></tr></table></figure></li><li><p>事件传递的流程图<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.12.25.02.png" alt=""></p></li><li><p>流程描述</p><blockquote><ol><li>我们点击屏幕产生触摸事件,系统将这个事件加入到一个由<code>UIApplication</code>管理的事件队列中,<code>UIApplication</code>会从消息队列里取事件分发下去,首先传给<code>UIWindow</code></li><li>在<code>UIWindow</code>中就会调用<code>hitTest:withEvent:</code>方法去返回一个最终响应的视图</li><li>在<code>hitTest:withEvent:</code>方法中就会去调用<code>pointInside: withEvent:</code>去判断当前点击的<code>point</code>是否在<code>UIWindow</code>范围内,如果是的话,就会去遍历它的子视图来查找最终响应的子视图</li><li>遍历的方式是使用倒序的方式来遍历子视图,也就是说最后添加的子视图会最先遍历,在每一个视图中都回去调用它的<code>hitTest:withEvent:</code>方法,可以理解为是一个递归调用</li><li>最终会返回一个响应视图,如果返回视图有值,那么这个视图就作为最终响应视图,结束整个事件传递;如果没有值,那么就会将<code>UIWindow</code>作为响应者</li></ol></blockquote></li></ul><ol start="2"><li>响应链</li></ol><ul><li><p>响应者链流程图<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.12.25.03.png" alt=""></p></li><li><p>响应者链的事件传递过程总结如下</p><blockquote><ol><li>如果<code>view</code>的控制器存在,就传递给控制器处理;如果控制器不存在,则传递给它的父视图</li><li>在视图层次结构的最顶层,如果也不能处理收到的事件,则将事件传递给<code>UIWindow</code>对象进行处理</li><li>如果<code>UIWindow</code>对象也不处理,则将事件传递给<code>UIApplication</code>对象</li><li>如果<code>UIApplication</code>也不能处理该事件,则将该事件丢弃</li></ol></blockquote></li></ul><ol start="3"><li>实例场景</li></ol><ul><li>在一个方形按钮中点击中间的圆形区域有效,而点击四角无效</li><li>核心思想是在<code>pointInside: withEvent:</code>方法中修改对应的区域<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.12.25.04.png" alt=""></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {</span><br><span class="line"> // 如果控件不允许与用用户交互,那么返回nil</span><br><span class="line"> if (!self.userInteractionEnabled || [self isHidden] || self.alpha <= 0.01) {</span><br><span class="line"> return nil;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //判断当前视图是否在点击范围内</span><br><span class="line"> if ([self pointInside:point withEvent:event]) {</span><br><span class="line"> //遍历当前对象的子视图(倒序)</span><br><span class="line"> __block UIView *hit = nil;</span><br><span class="line"> [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {</span><br><span class="line"> //坐标转换,把当前坐标系上的点转换成子控件坐标系上的点</span><br><span class="line"> CGPoint convertPoint = [self convertPoint:point toView:obj];</span><br><span class="line"> //调用子视图的hitTest方法,判断自己的子控件是不是最适合的View</span><br><span class="line"> hit = [obj hitTest:convertPoint withEvent:event];</span><br><span class="line"> //如果找到了就停止遍历</span><br><span class="line"> if (hit) *stop = YES;</span><br><span class="line"> }];</span><br><span class="line"></span><br><span class="line"> //返回当前的视图对象</span><br><span class="line"> return hit?hit:self;</span><br><span class="line"> }else {</span><br><span class="line"> return nil;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 该方法判断触摸点是否在控件身上,是则返回YES,否则返回NO,point参数必须是方法调用者的坐标系</span><br><span class="line">- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { </span><br><span class="line"> CGFloat x1 = point.x;</span><br><span class="line"> CGFloat y1 = point.y;</span><br><span class="line"> </span><br><span class="line"> CGFloat x2 = self.frame.size.width / 2;</span><br><span class="line"> CGFloat y2 = self.frame.size.height / 2;</span><br><span class="line"> </span><br><span class="line"> //判断是否在圆形区域内</span><br><span class="line"> double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));</span><br><span class="line"> if (dis <= self.frame.size.width / 2) {</span><br><span class="line"> return YES;</span><br><span class="line"> }</span><br><span class="line"> else{</span><br><span class="line"> return NO;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>一周年快乐</title>
<link href="/2019/12/25/hello-world1/"/>
<url>/2019/12/25/hello-world1/</url>
<content type="html"><![CDATA[<p>去年这个时候发布了我的第一篇博文 — Hello World<br>转眼已经写了一年了<br>物是人非,感慨良多<br>不管怎样我还是会继续坚持下去的,加油…<br>圣诞节快乐…<br>我的博客上线运行中…</p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 面试题-2019.上</title>
<link href="/2019/12/18/iOS%E9%9D%A2%E8%AF%95%E9%A2%982019%E4%B8%8A/"/>
<url>/2019/12/18/iOS%E9%9D%A2%E8%AF%95%E9%A2%982019%E4%B8%8A/</url>
<content type="html"><![CDATA[<ol><li><p><code>UIView</code>和<code>CALayer</code>是什么关系</p><blockquote><ul><li><code>UIView</code>继承自<code>UIResponder</code>类,可以响应事件</li><li><code>CALayer</code>直接继承自<code>NSObject</code>类,不可以响应事件</li><li><code>UIView</code>是<code>CALayer</code>的<code>delegate</code>(<code>CALayerDelegate</code>)</li><li><code>UIView</code>主要处理事件,<code>CALayer</code>负责绘制</li><li>每个<code>UIView</code>内部都有一个<code>CALayer</code>在背后提供内容的绘制和显示,并且<code>UIView</code>的尺寸样式都由内部的<code>Layer</code>所提供。两者都有树状层级结构,<code>Layer</code>内部有<code>SubLayers</code>,<code>View</code>内部有<code>SubViews</code>,但是<code>Layer</code>比<code>View</code>多了个<code>AnchorPoint</code></li></ul></blockquote></li><li><p><code>NSCache</code>和<code>NSMutableDictionary</code>的相同点与区别</p><blockquote><p>相同点:<br><code>NSCache</code>和<code>NSMutableDictionary</code>功能用法基本是相同的<br>区别:<br><code>NSCache</code>是线程安全的,<code>NSMutableDictionary</code>线程不安全,<code>Mutable开发的类</code>一般都是线程不安全的<br>当内存不足时<code>NSCache</code>会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)<br><code>NSCache</code>可以指定缓存的限额,当缓存超出限额自动释放内存<br><code>NSCache</code>的<code>Key</code>只是对对象进行了<code>Strong</code>引用,而非拷贝,所以不需要实现<code>NSCopying</code>协议</p></blockquote></li><li><p><code>atomic</code>的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)</p><blockquote><ul><li><code>atomic</code>会对属性的<code>setter/getter</code>方法进行加锁,这仅仅只能保证在操作<code>setter/getter</code>方法是安全的。不能保证其他线程的安全</li><li>例如:线程1调用了某一属性的<code>setter</code>方法并进行到了一半,线程2调用其<code>getter</code>方法,那么会执行完<code>setter</code>操作后,再执行<code>getter</code>操作,线程2会获取到线程1<code>setter</code>后的完整的值;当几个线程同时调用同一属性的<code>setter、getter</code>方法时,会获取到一个完整的值,但获取到的值不可控</li></ul></blockquote></li><li><p>iOS 中内省的几个方法</p><blockquote><p>对象在运行时获取其类型的能力称为内省。内省可以有多种方法实现<br>OC运行时内省的4个方法:</p></blockquote></li></ol><ul><li>判断对象类型:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-(BOOL) isKindOfClass: // 判断是否是这个类或者这个类的子类的实例</span><br><span class="line">-(BOOL) isMemberOfClass: // 判断是否是这个类的实例</span><br></pre></td></tr></table></figure></li><li>判断对象/类是否有这个方法<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-(BOOL) respondsToSelector: // 判断实例是否有这样方法</span><br><span class="line">+(BOOL) instancesRespondToSelector: // 判断类是否有这个方法</span><br></pre></td></tr></table></figure></li></ul><ol start="5"><li><p><code>objc</code>在向一个对象发送消息时,发生了什么</p><blockquote><p>根据对象的isa指针找到该对象所属的类,去objc的对应的类中找方法<br>1.首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行<br>2.如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行<br>3.如果没找到,去父类指针所指向的对象中执行1,2.<br>4.以此类推,如果一直到根类还没找到,转向拦截调用,走消息转发机制<br>5.如果没有重写拦截调用的方法,程序报错</p></blockquote></li><li><p>你是否接触过OC中的反射机制?简单聊一下概念和使用</p></li></ol><ul><li><code>class</code>反射</li><li>通过类名的字符串形式实例化对象<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Class class = NSClassFromString(@"student"); </span><br><span class="line">Student *stu = [[class alloc] init];</span><br></pre></td></tr></table></figure></li><li>将类名变为字符串<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Class class = [Student class];</span><br><span class="line">NSString *className = NSStringFromClass(class);</span><br></pre></td></tr></table></figure></li><li><code>SEL</code>的反射</li><li>通过方法的字符串形式实例化方法<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SEL selector = NSSelectorFromString(@"setName");</span><br><span class="line">[stu performSelector:selector withObject:@"Mike"];</span><br></pre></td></tr></table></figure></li><li>将方法变成字符串<br><code>NSStringFromSelector(@selector(setName:));</code></li></ul><ol start="7"><li><p>这个写法会出什么问题<code>@property (nonatomic, copy) NSMutableArray *arr;</code></p><blockquote><p>添加,删除,修改数组内元素的时候,程序会因为找不到对应的方法而崩溃。原因:是因为<code>copy</code>就是复制一个不可变<code>NSArray</code>的对象,不能对<code>NSArray</code>对象进行添加/修改</p></blockquote></li><li><p>如何让自己的类用<code>copy</code>修饰符</p><blockquote><p>若想令自己所写的对象具有拷贝功能,则需实现<code>NSCopying</code>协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现<code>NSCopying</code>与<code>NSMutableCopying</code>协议。<br>具体步骤:<br>1.需声明该类遵从<code>NSCopying</code>协议<br>2.实现<code>NSCopying</code>协议的方法,具体区别<a href="http://www.jianshu.com/p/f84803356cbb">戳这里</a></p></blockquote></li></ol><ul><li><code>NSCopying</code>协议方法为:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (id)copyWithZone:(NSZone *)zone {</span><br><span class="line"> MyObject *copy = [[[self class] allocWithZone: zone] init];</span><br><span class="line"> copy.username = self.username;</span><br><span class="line"> return copy;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><ol start="9"><li><p>为什么<code>assign</code>不能用于修饰对象</p><blockquote><p>首先我们需要明确,对象的内存一般被分配到堆上,基本数据类型和oc数据类型的内存一般被分配在栈上<br>如果用<code>assign</code>修饰对象,当对象被释放后,指针的地址还是存在的,也就是说指针并没有被置为<code>nil</code>,从而造成了野指针。因为对象是分配在堆上的,堆上的内存由程序员分配释放。而因为指针没有被置为<code>nil</code>,如果后续的内存分配中,刚好分配到了这块内存,就会造成崩溃<br>而<code>assign</code>修饰基本数据类型或oc数据类型,因为基本数据类型是分配在栈上的,由系统分配和释放,所以不会造成野指针</p></blockquote></li><li><p>请写出以下代码输出</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">int a[5] = {1, 2, 3, 4, 5};</span><br><span class="line">int *ptr = (int *)(&a + 1);</span><br><span class="line">printf("%d, %d", *(a + 1), *(ptr + 1));</span><br></pre></td></tr></table></figure><blockquote><p>参考答案:2,随机值<br>分析:<br><code>a</code>代表有5个元素的数组的首地址,<code>a[5]</code>的元素分别是1,2,3,4,5。接下来,<code>a + 1</code>表示数据首地址加1,那么就是<code>a[1]</code>,也就是对应于值为2,但是,这里是<code>&a + 1</code>,因为<code>a</code>代表的是整个数组,它的空间大小为<code>5 * sizeof(int)</code>,因此<code>&a + 1</code>就是<code>a + 5</code>。<code>a</code>是个常量指针,指向当前数组的首地址,指针+1就是移动<code>sizeof(int)</code>个字节<br>因此,<code>ptr</code>是指向<code>int *</code>类型的指针,而<code>ptr</code>指向的就是<code>a + 5</code>,那么<code>ptr + 1</code>也相当于<code>a + 6</code>,所以最后的<code>*(ptr + 1)</code>就是一个随机值了。而<code>*(ptr – 1)</code>就相当于<code>a + 4</code>,对应的值就是5</p></blockquote></li><li><p>一个<code>view</code>已经初始化完毕,<code>view</code>上面添加了n个<code>button</code>(可能使用循环创建),除用<code>view</code>的<code>tag</code>之外,还可以采用什么办法来找到自己想要的<code>button</code>来修改<code>Button</code>的值</p><blockquote><p>第一种:如果是点击某个按钮后,才会刷新它的值,其它不用修改,那么不用引用任何按钮,直接在回调时,就已经将接收响应的按钮给传过来了,直接通过它修改即可<br>第二种:点击某个按钮后,所有与之同类型的按钮都要修改值,那么可以通过在创建按钮时将按钮存入到数组中,在需要的时候遍历查找</p></blockquote></li><li><p><code>UIViewController</code>的<code>viewDidUnload、viewDidLoad</code>和<code>loadView</code>分别什么时候调用?<code>UIView</code>的<code>drawRect</code>和<code>layoutSubviews</code>分别起什么作用</p><blockquote><p>第一个问题:<br>在控制器被销毁前会调用<code>viewDidUnload</code>(<code>MRC</code>下才会调用)<br>在控制器没有任何<code>view</code>时,会调用<code>loadView</code><br>在<code>view</code>加载完成时,会调用<code>viewDidLoad</code><br>第二个问题:<br>在调用<code>setNeedsDisplay</code>后,会调用<code>drawRect</code>方法,我们通过在此方法中可以获取到<code>context</code>(设置上下文),就可以实现绘图<br>在调用<code>setNeedsLayout</code>后,会调用<code>layoutSubviews</code>方法,我们可以通过在此方法去调整UI。当然能引起<code>layoutSubviews</code>调用的方式有很多种的,比如添加子视图、滚动<code>scrollview</code>、修改视图的<code>frame</code>等</p></blockquote></li><li><p>自动释放池工作原理</p><blockquote><p>自动释放池是<code>NSAutorelease</code>类的一个实例,当向一个对象发送<code>autorelease</code>消息时,该对象会自动入池,待池销毁时,将会向池中所有对象发送一条<code>release</code>消息,释放对象<br><code>[pool release]、[pool drain]</code>表示的是池本身不会销毁,而是池子中的临时对象都被发送<code>release</code>,从而将对象销毁</p></blockquote></li><li><p>苹果是如何实现<code>autoreleasepool</code>的</p><blockquote><p><code>autoreleasepool</code>是由<code>AutoreleasePoolPage</code>以双向链表的方式实现的,主要通过下列三个函数完成:</p><ul><li>由<code>objc_autoreleasePoolPush</code>作为自动释放池作用域的第一个函数</li><li>使用<code>objc_autorelease</code>将对象加入自动释放池</li><li>由<code>objc_autoreleasePoolPop</code>作为自动释放池作用域的最后一个函数</li></ul></blockquote></li><li><p><code>autorelease</code>的对象何时被释放</p><blockquote><p><code>RunLoop</code>在每个事件循环结束后会去自动释放池将所有自动释放对象的引用计数减一,若引用计数变成了0,则会将对象真正销毁掉,回收内存。<br>在没有手动添加<code>Autorelease Pool</code>的情况下,<code>autorelease</code>的对象是在每个事件循环结束后,自动释放池才会对所有自动释放的对象的引用计数减一,若引用计数变成了0,则释放对象,回收内存。因此,若想要早一点释放掉<code>autorelease</code>对象,那么我们可以在对象外加一个自动释放池。比如,在循环处理数据时,临时变量要快速释放,就应该采用这种方式:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// 通过alloc创建的对象,直接加入@autoreleasepool没有作用,需在创建对象后面显式添加autorelease</span><br><span class="line">// 通过类方法创建的对象不需要显式添加autorelease,原因是类方法创建的对象系统会自动添加autorelease</span><br><span class="line">for (int i = 0; i < 1000000; i++) {</span><br><span class="line"> @autoreleasepool {</span><br><span class="line"> NSString *str = @"Abc";</span><br><span class="line"> str = [str lowercaseString];</span><br><span class="line"> str = [str stringByAppendingString:@"xyz"];</span><br><span class="line"> NSLog(@"%@", str);</span><br><span class="line"> } // 出了这里,就会去遍历该自动释放池了</span><br><span class="line">}</span><br></pre></td></tr></table></figure></blockquote></li><li><p>简述内存管理基本原则</p><blockquote><p>OC内存管理遵循<code>谁创建,谁释放,谁引用,谁管理</code>的机制,当使用<code>alloc、copy(mutableCopy)或者retian</code>一个对象时,你就有义务向它发送一条<code>release或者autorelease</code>消息释放该对象,其他方法创建的对象,不需要由你来管理内存,当对象引用计数为0时,系统将释放该对象,这是OC的手动管理机制(<code>MRC</code>)<br>向一个对象发送一条<code>autorelease</code>消息,这个对象并不会立即销毁,而是将这个对象放入了自动释放池,待池子释放时,它会向池中每一个对象发送一条<code>release</code>消息,以此来释放对象<br>向一个对象发送<code>release</code>消息,并不意味着这个对象被销毁了,而是当这个对象的引用计数为0时,系统才会调用<code>dealloc</code>方法释放该对象和对象本身所拥有的实例</p></blockquote></li><li><p><code>sizeof</code>关键字</p><blockquote><p><code>sizeof</code>是在编译阶段处理,且不能被编译为机器码。<code>sizeof</code>的结果等于对象或类型所占的内存字节数。<code>sizeof</code>的返回值类型为<code>size_t</code><br>变量:<code>int a; sizeof(a)</code>为4;<br>指针:<code>int *p; sizeof(p)</code>为4;<br>数组:<code>int b[10]; sizeof(b)</code>为数组的大小4<em>10;<code>int c[0]; sizeof(c)</code>等于0<br><code>sizeof(void)</code>等于1<br>`sizeof(void </em>)`等于4</p></blockquote></li><li><p>什么是离屏渲染?什么情况下会触发?离屏渲染消耗性能的原因</p><blockquote><p>离屏渲染就是在当前屏幕缓冲区以外,新开辟一个缓冲区进行操作<br>离屏渲染触发的场景有以下:</p><ul><li>圆角(同时设置<code>layer.masksToBounds = YES、layer.cornerRadius</code>大于0)</li><li>图层蒙版</li><li>阴影,<code>layer.shadowXXX</code>,如果设置了<code>layer.shadowPath</code>就不会产生离屏渲染</li><li>遮罩,<code>layer.mask</code></li><li>光栅化,<code>layer.shouldRasterize = YES</code></li></ul></blockquote></li></ol><blockquote><p>离屏渲染消耗性能的原因<br>需要创建新的缓冲区,离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(<code>On-Screen</code>)切换到离屏(<code>Off-Screen</code>)等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕</p></blockquote><ol start="19"><li><p>ARC 下,不显式指定任何属性关键字时,默认的关键字都有哪些</p><blockquote><p>基本数据类型默认关键字是:<code>atomic, readwrite, assign</code><br>普通<code>Objective-C</code>对象默认关键字是:<code>atomic, readwrite, strong</code></p></blockquote></li><li><p>OC中的类方法和实例方法有什么本质区别和联系</p><blockquote><p>类方法:</p><ul><li>类方法是属于类对象的</li><li>类方法只能通过类对象调用</li><li>类方法中的 self 是类对象</li><li>类方法可以调用其他的类方法</li><li>类方法中不能访问成员变量</li><li>类方法中不能直接调用对象方法</li></ul></blockquote></li></ol><blockquote><p>实例方法:</p><ul><li>实例方法是属于实例对象的</li><li>实例方法只能通过实例对象调用</li><li>实例方法中的 self 是实例对象</li><li>实例方法中可以访问成员变量</li><li>实例方法中直接调用实例方法</li><li>实例方法中也可以调用类方法(通过类名)</li></ul></blockquote><ol start="21"><li><p>能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?</p><blockquote><ul><li>不能向编译后得到的类中增加实例变量</li><li>能向运行时创建的类中添加实例变量</li><li>因为编译后的类已经注册在<code>runtime</code>中,类结构体中的<code>objc_ivar_list</code>实例变量的链表和<code>instance_size</code>实例变量的内存大小已经确定,同时<code>runtime</code>会调用<code>class_setIvarLayout</code>或<code>class_setWeakIvarLayout</code>来处理<code>strong weak</code>引用,所以不能向存在的类中添加实例变量<br>运行时创建的类是可以添加实例变量,调用<code>class_addIvar</code>函数。但是得在调用<code>objc_allocateClassPair</code>之后,<code>objc_registerClassPair</code>之前,原因同上</li></ul></blockquote></li><li><p><code>runtime</code>如何通过<code>selector</code>找到对应的<code>IMP</code>地址(分别考虑实例方法和类方法)<code>Selector、Method 和 IMP</code>的有什么区别与联系</p><blockquote><p>对于实例方法,每个实例的<code>isa</code>指针指向着对应类对象,而每一个类对象中都有一个对象方法列表。对于类方法,每个类对象的<code>isa</code>指针都指向着对应的元类对象,而每一个元类对象中都有一个类方法列表。方法列表中记录着方法的名称,方法实现,以及参数类型,其实<code>selector</code>本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现<br><code>Selector、Method 和 IMP</code>的关系可以这样描述:在运行期分发消息,方法列表中的每一个实体都是一个方法(<code>Method</code>)它的名字叫做选择器(<code>SEL</code>)对应着一种方法实现(<code>IMP</code>)</p></blockquote></li><li><p><code>objc_msgSend、_objc_msgForward</code>都是做什么的?OC 中的消息调用流程是怎样的</p><blockquote><ul><li><code>objc_msgSend</code>是用来做消息发送的。在<code>OC</code>中,对方法的调用都会被转换成内部的消息发送执行</li><li><code>_objc_msgForward</code>是<code>IMP</code>类型(函数指针)用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,<code>_objc_msgForward</code>会尝试做消息转发</li><li>在消息调用的过程中,<code>objc_msgSend</code>的动作比较清晰:首先在<code>Class</code>中的缓存查找<code>IMP</code>(没缓存则初始化缓存)如果没找到,则向父类的<code>Class</code>查找。如果一直查找到根类仍旧没有实现,则用<code>_objc_msgForward</code>函数指针代替<code>IMP</code>。最后,执行这个<code>IMP</code>。当调用一个<code>NSObject</code>对象不存在的方法时,并不会马上抛出异常,而是会经过多层转发,层层调用对象的<code>-resolveInstanceMethod:、-forwardingTargetForSelector:、-methodSignatureForSelector:、-forwardInvocation:</code>等方法。其中最后<code>-forwardInvocation:</code>是会有一个<code>NSInvocation</code>对象,这个<code>NSInvocation</code>对象保存了这个方法调用的所有信息,包括<code>Selector名,参数和返回值类型</code>,可以从这个<code>NSInvocation</code>对象里拿到调用的所有参数值<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.12.18.01.jpg" alt=""></li></ul></blockquote></li><li><p><code>class</code>方法和<code>objc_getClass</code>方法有什么区别</p><blockquote><p><code>object_getClass(obj)</code>返回的是<code>obj</code>中的<code>isa</code>指针,即指向类对象的指针;而<code>[obj class]</code>则分两种情况:一是当<code>obj</code>为实例对象时,<code>[obj class]</code>中<code>class</code>是实例方法,返回的是<code>obj</code>对象中的<code>isa</code>指针;二是当<code>obj</code>为类对象(包括元类和根类以及根元类)时,调用的是类方法,返回的结果为其本身</p></blockquote></li><li><p>OC中向一个<code>nil</code>对象发送消息将会发生什么</p><blockquote><p>在<code>OC</code>中向<code>nil</code>发送消息是完全有效的,只是在运行时不会有任何作用;向一个<code>nil</code>对象发送消息,首先在寻找对象的<code>isa</code>指针时就是<code>0地址</code>返回了,所以不会出现任何错误,也不会崩溃</p></blockquote></li><li><p><code>_objc_msgForward</code>函数是做什么的?直接调用它将会发生什么</p><blockquote><p><code>_objc_msgForward</code>是一个函数指针(和<code>IMP</code>的类型一样)用于消息转发;当向一个对象发送一条消息,但它并没有实现的时候,<code>_objc_msgForward</code>会尝试做消息转发<br><code>objc_msgSend</code>在<code>消息传递</code>中的作用。在<code>消息传递</code>过程中,<code>objc_msgSend</code>的动作比较清晰:首先在<code>Class</code>中的缓存查找<code>IMP</code>(<code>没有缓存则初始化缓存</code>)如果没找到,则向<code>父类的Class</code>查找。如果一直查找到<code>根类</code>仍旧没有实现,则用<code>_objc_msgForward</code>函数指针代替<code>IMP</code>,最后执行这个<code>IMP</code><br>一旦调用了<code>_objc_msgForward</code>,将跳过查找<code>IMP</code>的过程,直接触发<code>消息转发</code>,如果调用了<code>_objc_msgForward</code>,即使这个对象确实已经实现了这个方法,你也会告诉<code>objc_msgSend</code>,我没有在这个对象里找到这个方法的实现,如果用不好会直接导致程序<code>Crash</code></p></blockquote></li><li><p>什么时候会报<code>unrecognized selector</code>的异常</p></li></ol><ul><li>当调用该对象上某个方法,而该对象上没有实现这个方法的时候。可以通过<code>消息转发</code>进行解决,流程见下图<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.12.18.01.jpg" alt=""></li><li><code>OC</code>在向一个对象发送消息时,<code>runtime</code>库会根据对象的<code>isa</code>指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常<code>unrecognized selector sent to XXX</code><br><strong>但是在这之前,OC的运行时会给出三次拯救程序崩溃的机会</strong></li><li>Method resolution(消息动态解析)<br><code>OC</code>运行时会调用<code>+resolveInstanceMethod:</code>或者<code>+resolveClassMethod:</code>,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则,运行时就会移到下一步,消息转发(<code>Message Forwarding</code>)<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">// 重写 resolveInstanceMethod: 添加对象方法实现</span><br><span class="line">+ (BOOL)resolveInstanceMethod:(SEL)sel {</span><br><span class="line"> // 如果是执行 run 函数,就动态解析,指定新的 IMP</span><br><span class="line"> if (sel == NSSelectorFromString(@"run:")) {</span><br><span class="line"> // class: 给哪个类添加方法</span><br><span class="line"> // SEL: 添加哪个方法</span><br><span class="line"> // IMP: 方法实现 => 函数 => 函数入口 => 函数名</span><br><span class="line"> // type: 方法类型:void用v来表示,id参数用@来表示,SEL用:来表示</span><br><span class="line"> class_addMethod(self, sel, (IMP)runMethod, "v@:@");</span><br><span class="line"> return YES;</span><br><span class="line"> }</span><br><span class="line"> return [super resolveInstanceMethod:sel];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//新的 run 函数</span><br><span class="line">void runMethod(id self, SEL _cmd, NSNumber *meter) {</span><br><span class="line"> NSLog(@"跑了%@", meter);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>Fast forwarding(消息接受者重定向)<br>如果目标对象实现了<code>-forwardingTargetForSelector:</code>,<code>Runtime</code>这时就会调用这个方法,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是<code>nil</code>和<code>self</code>,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续<code>Normal Fowarding</code>。 这里叫<code>Fast</code>,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个<code>NSInvocation</code>对象,所以相对更快点<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 消息接受者重定向</span><br><span class="line">- (id)forwardingTargetForSelector:(SEL)aSelector{</span><br><span class="line"> if (aSelector == @selector(run:)) {</span><br><span class="line"> return [[Person alloc] init];</span><br><span class="line"> // 返回 Person 对象,让 Person 对象接收这个消息</span><br><span class="line"> }</span><br><span class="line"> return [super forwardingTargetForSelector:aSelector];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>Normal forwarding(消息重定向)<br>这一步是<code>Runtime</code>最后一次给你挽救的机会。首先它会发送<code>-methodSignatureForSelector:</code>消息获得函数的参数和返回值类型。如果<code>-methodSignatureForSelector:</code>返回<code>nil</code>,<code>Runtime</code>则会发出<code>-doesNotRecognizeSelector:</code>消息,程序这时也就挂掉了。如果返回了一个函数签名,<code>Runtime</code>就会创建一个<code>NSInvocation</code>对象并发送<code>-forwardInvocation:</code>消息给目标对象<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">// 获取函数的参数和返回值类型,返回签名</span><br><span class="line">- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {</span><br><span class="line"> if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {</span><br><span class="line"> return [NSMethodSignature signatureWithObjCTypes:"v@:@"];</span><br><span class="line"> }</span><br><span class="line"> return [super methodSignatureForSelector:aSelector];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 消息重定向</span><br><span class="line">- (void)forwardInvocation:(NSInvocation *)anInvocation {</span><br><span class="line"> // 从 anInvocation 中获取消息</span><br><span class="line"> SEL sel = anInvocation.selector;</span><br><span class="line"> if (sel == NSSelectorFromString(@"run:")) {</span><br><span class="line"> // 1. 指定当前类的一个方法作为IMP</span><br><span class="line"> // anInvocation.selector = @selector(readBook:);</span><br><span class="line"> // [anInvocation invoke];</span><br><span class="line"> </span><br><span class="line"> // 2. 指定其他类来执行这个IMP</span><br><span class="line"> Person *p = [[Person alloc] init];</span><br><span class="line"> // 判断 Person 对象方法是否可以响应 sel</span><br><span class="line"> if([p respondsToSelector:sel]) {</span><br><span class="line"> // 若可以响应,则将消息转发给其他对象处理</span><br><span class="line"> [anInvocation invokeWithTarget:p];</span><br><span class="line"> } else {</span><br><span class="line"> // 若仍然无法响应,则报错:找不到响应方法</span><br><span class="line"> [self doesNotRecognizeSelector:sel];</span><br><span class="line"> }</span><br><span class="line"> }else{</span><br><span class="line"> [super forwardInvocation:anInvocation];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)doesNotRecognizeSelector:(SEL)aSelector {</span><br><span class="line"> [super doesNotRecognizeSelector:aSelector];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><strong>既然<code>-forwardingTargetForSelector:</code>和<code>-forwardInvocation:</code>都可以将消息转发给其他对象处理,那么两者的区别在哪?</strong><br>区别就在于<code>-forwardingTargetForSelector:</code>只能将消息转发给一个对象。而<code>-forwardInvocation:</code>可以把消息存储,在你觉得合适的时机转发出去,或者不处理这个消息。修改消息的target,selector,参数等。将消息转发给多个对象</li></ul><ol start="28"><li><p><code>iOS layoutSubviews</code>什么时候会被调用</p><blockquote><ul><li><code>init</code>方法不会调用<code>layoutSubviews</code>,但是是用<code>initWithFrame</code>进行初始化时,当<code>rect</code>的值不为<code>CGRectZero</code>时,会触发</li><li><code>addSubview</code>会触发<code>layoutSubviews</code>方法</li><li><code>setFrame</code>只有当设置的<code>frame</code>的参数的<code>size</code>与原来的<code>size</code>不同,才会触发其<code>view</code>的<code>layoutSubviews</code>方法</li><li>滑动<code>UIScrollView</code>会调用<code>scrollview</code>及<code>scrollview</code>上的<code>view</code>的<code>layoutSubviews</code>方法</li><li>旋转设备只会调用<code>VC</code>的<code>view</code>的<code>layoutSubviews</code>方法</li><li>直接调用<code>[self setNeedsLayout];</code>(这个在上面苹果官方文档里有说明)<br><code>-layoutSubviews</code>方法:这个方法默认没有做任何事情,需要子类进行重写<br><code>-setNeedsLayout</code>方法:标记为需要重新布局,异步调用<code>layoutIfNeeded</code>刷新布局,不立即刷新,但<code>layoutSubviews</code>一定会被调用<br><code>-layoutIfNeeded</code>方法:如果有需要刷新的标记,立即调用<code>layoutSubviews</code>进行布局(如果没有标记,不会调用<code>layoutSubviews</code>)<br>如果要立即刷新,要先调用<code>[view setNeedsLayout]</code>,把标记设为需要布局,然后马上调用<code>[view layoutIfNeeded]</code>,实现布局<br>在视图第一次显示之前,标记总是<code>需要刷新</code>的,可以直接调用<code>[view layoutIfNeeded]</code></li></ul></blockquote></li><li><p>下面代码会发生什么问题</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">@property (nonatomic, strong) NSString *str;</span><br><span class="line"></span><br><span class="line">dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);</span><br><span class="line">for (int i = 0; i < 1000000 ; i++) {</span><br><span class="line"> dispatch_async(queue, ^{</span><br><span class="line"> self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>会<code>crash</code>。因为在并行队列<code>DISPATCH_QUEUE_CONCURRENT</code>中异步<code>dispatch_async</code>对<code>str</code>属性进行赋值,就会导致<code>str</code>已经被<code>release</code>了,还会执行<code>release</code>。这就是向已释放内存的对象发送消息而发生<code>crash</code><br>详细解析:对<code>str</code>属性<code>strong</code>修饰进行赋值,相当与<code>MRC</code>中的</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (void)setStr:(NSString *)str{</span><br><span class="line"> if (str == _str) return;</span><br><span class="line"> id pre = _str;</span><br><span class="line"> [str retain];//1.先保留新值</span><br><span class="line"> _str = str;//2.再进行赋值</span><br><span class="line"> [pre release];//3.释放旧值</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那么假如<code>并发队列</code>里调度的<code>线程A</code>执行到<code>步骤1</code>,还没到<code>步骤2</code>时,<code>线程B</code>执行到<code>步骤3</code>,那么当<code>线程A</code>再执行<code>步骤3</code>时,旧值就会被<code>过度释放</code>,导致向已释放内存的对象发送消息而崩溃</p></li></ol><ul><li><p>追问:怎么修改这段代码变为不崩溃呢</p><blockquote><p>1、使用串行队列<br>将<code>set</code>方法改成在串行队列中执行就行,这样即使异步,但所有<code>block</code>操作追加在队列最后依次执行<br>2、使用<code>atomic</code><br><code>atomic</code>关键字相当于在<code>setter</code>方法加锁,这样每次执行<code>setter</code>都是线程安全的,但这只是单独针对<code>setter</code>方法而言的狭义的线程安全<br>3、使用<code>weak</code>关键字<br><code>weak</code>的<code>setter</code>没有<code>保留新值</code>的操作,所以不会引发重复释放。当然这个时候要看具体情况能否使用<code>weak</code>,可能值并不是所需要的值<br>4、使用互斥锁,保证数据访问的唯一性<code>@synchronized (self) {self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];}</code><br>5、使用<code>Tagged Pointer</code><br><code>Tagged Pointer</code>是苹果在64位系统引入的内存技术。简单来说就是对于<code>NSString</code>(内存小于60位的字符串)或<code>NSNumber</code>(小于2^31),64位的指针有8个字节,完全可以直接用这个空间来直接表示值,这样的话其实会将<code>NSString</code>和<code>NSNumber</code>对象由一个<code>指针</code>转换成一个<code>值类型</code>,而值类型的<code>setter和getter</code>又是原子的,从而线程安全</p></blockquote></li><li><p>发散:下面代码会<code>crash</code>吗</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">@property (nonatomic, strong) NSString *str;</span><br><span class="line"></span><br><span class="line">dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);</span><br><span class="line">for (int i = 0; i < 1000000 ; i++) {</span><br><span class="line"> dispatch_async(queue, ^{</span><br><span class="line"> // 相比上面,仅字符串变短了</span><br><span class="line"> self.str = [NSString stringWithFormat:@"%d",i];</span><br><span class="line"> NSLog(@"%d, %s, %p", i, object_getClassName(self.str), self.str);</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不会<code>crash</code>。而且发现<code>str</code>这个字符串类型是<code>NSTaggedPointerString</code><br><code>Tagged Pointer</code>是一个能够提升性能、节省内存的有趣的技术<br><code>Tagged Pointer</code>专门用来存储小的对象,例如<code>NSNumber</code>和<code>NSDate</code>(后来可以存储小字符串)<br><code>Tagged Pointer指针的值</code>不再是<code>地址</code>了,而是<code>真正的值</code>。所以,实际上它不再是一个<code>对象</code>了,它只是一个披着对象皮的<code>普通变量</code>而已<br>它的内存并不存储在<code>堆</code>中,也不需要<code>malloc和free</code>,所以拥有极快的读取和创建速度</p></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 关于Other Linker Flags的作用</title>
<link href="/2019/12/11/iOS%E5%85%B3%E4%BA%8EOtherLinkerFlags%E7%9A%84%E4%BD%9C%E7%94%A8/"/>
<url>/2019/12/11/iOS%E5%85%B3%E4%BA%8EOtherLinkerFlags%E7%9A%84%E4%BD%9C%E7%94%A8/</url>
<content type="html"><![CDATA[<p>在用第三方库时,我们常常在Xcode的<code>Build Settings</code>下<code>Other Linker Flags</code>里面加入<code>-ObjC</code>标志,它和<code>Objective-C</code>的一个重要特性:类别(<code>category</code>)有关</p><blockquote><p>根据官方的解释,<code>Unix</code>的标准静态库实现和<code>Objective-C</code>的动态特性之间有一些冲突:<code>Objective-C</code>没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时,出现<code>"selector not recognized"</code>,也就是找不到方法定义的错误。</p></blockquote><p>为了解决这个问题,引入了<code>-ObjC</code>标志,它的作用就是将静态库中所有的 <code>Objective-C</code>代码都加载进来。可以看出,使用<code>-ObjC</code>可能会链接很多静态库中未被使用的<code>Objective-C</code>代码,极大的增加APP的代码体积。<br>不要以为这样就可以解决所有问题了,在64位的Mac系统或者iOS系统下,链接器有一个bug,会导致只包含有类别的静态库无法使用<code>-ObjC</code>标志来加载文件。<br>变通方法是使用<code>-all_load</code>或者<code>-force_load</code>标志,它们的作用都是强制链接器把目标文件都加载进来,即使没有objc代码,不过<code>-all_load</code>作用于所有的库,而<code>-force_load</code>后面必须要指定具体文件加载的位置</p><table><thead><tr><th>Flags</th><th>位置</th><th>作用</th></tr></thead><tbody><tr><td><code>-ObjC</code></td><td><code>Other Linker Flags</code></td><td>链接静态库中所有的<code>Objective-C</code>代码到APP</td></tr><tr><td><code>-all_load</code></td><td><code>Other Linker Flags</code></td><td>全加载,链接静态库中所有的代码到APP,无论是<code>c</code>、<code>c++</code>还是<code>oc</code></td></tr><tr><td><code>-force_load</code></td><td><code>Other Linker Flags</code></td><td>链接指定静态库中所有的代码到APP,无论是<code>c</code>、<code>c++</code>还是<code>oc</code></td></tr></tbody></table>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 使用Jenkins持续集成(简称CI)</title>
<link href="/2019/11/27/iOS%E4%BD%BF%E7%94%A8Jenkins%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
<url>/2019/11/27/iOS%E4%BD%BF%E7%94%A8Jenkins%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/</url>
<content type="html"><![CDATA[<ol><li>安装<code>jenkins</code><br>1.1. 直接到<a href="https://link.jianshu.com/?t=http://jenkins-ci.org/">官网</a>下载安装包,通过安装包安装<br>1.2. 通过<code>Homebrew</code>使用命令行安装<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. 安装Homebrew</span><br><span class="line">$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"</span><br><span class="line">2. 安装Jenkins</span><br><span class="line">$ brew install jenkins</span><br><span class="line">3、启动Jenkins</span><br><span class="line">$ jenkins</span><br></pre></td></tr></table></figure></li></ol><ul><li><code>jenkins</code>需要<code>java</code>环境,如果没有安装会有提示,<a href="https://links.jianshu.com/go?to=http%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Fjava%2Fjavase%2Fdownloads%2Fjdk8-downloads-2133151.html">java安装地址</a></li></ul><p>一切顺利的话,打开浏览器输入:<code>http://localhost:8080/</code>就能看到<code>jenkins</code>已经运行起来了,如果你更换了端口就是你后来设置的端口。接下来打开<code>Jenkins</code>后会让去一个填写<code>password</code>的页面如下图,存储<code>password</code>的地方就是图片上那行红色字体目录<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.01.png" alt=""></p><p>然后将我们得到的<code>password</code>输入到<code>Administrator password</code>中,即可进入如下界面,接着安装一些建议的插件<code>左边的</code>,安装过程中,有的插件可能会安装失败,强烈建议点击右下角的重试,直到把建议安装的都装好<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.02.png" alt=""></p><p>插件安装完成后,可能不会自动跳转,刷新下界面即可,在刷新后的界面中注册,输入用户名和密码,建议输入后点蓝色按钮保存完成</p><ol start="2"><li><p>安装<code>jenkins</code>插件<br>如果要使用<code>Jenkins</code>的插件构建工程的,需要在开始新建工程前安装一些<code>Jenkins</code>插件,在可选插件中选择我们需要的插件进行安装</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1. Xcode integration</span><br><span class="line">2. GIT plugin</span><br><span class="line">3. GitLab Plugin</span><br><span class="line">4. Gitlab Hook Plugin</span><br><span class="line">5. Keychains and Provisioning Profiles Management</span><br></pre></td></tr></table></figure><p>我们今天使用<code>Execute shell</code> <code>Shell脚本</code>构建工程</p></li><li><p><code>jenkins</code>的使用<br>3.1. 构建一个自由风格的软件项目<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.03.png" alt=""></p></li></ol><p>3.2. <code>General</code>参数<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.04.JPG" alt=""><br>可以设置包的保留天数和最大保留个数,这些可以根据需要进行调整,可以不要选</p><ul><li><code>jenkins</code>插件配置多个项目<code>extended choice parameter</code>插件主要是构建的时候可以多选框来选择要构建的项目模块</li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.05.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.06.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.07.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.08.png" alt="($+上面的Name)就可以获取该值"></p><p>3.3. 源码管理<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.09.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.10.png" alt=""></p><p>3.4. 构建触发器设置<br>触发器可自定义的地方很多,可以根据项目需要选择<code>可省略</code></p><ul><li>定时构建:不管<code>SVN</code>或<code>Git</code>中数据有无变化,均执行定时化的构建任务</li><li>轮询<code>SCM</code>:只要<code>SVN</code>或<code>Git</code>中数据有更新,则执行构建任务<br>日程表的填写内容有<code>5</code>个参数,从左到右的参数含义如下:<br>⦁ 第<code>1</code>个参数:分钟<code>minute</code>,取值<code>0~59</code><br>⦁ 第<code>2</code>个参数:小时<code>hour</code>,取值<code>0~23</code><br>⦁ 第<code>3</code>个参数:天<code>day</code>,取值<code>1~31</code><br>⦁ 第<code>4</code>个参数:月<code>month</code>,取值<code>1~12</code><br>⦁ 第<code>5</code>个参数:星期<code>week</code>,取值<code>0~7</code>,<code>0</code>和<code>7</code>都是表示星期天<br><code>5</code>个参数可选择性设定,不写死的参数用<code>*</code>号代替,参数之间用空格隔开。例如:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">"0 21 * * *"表示每晚21点0分自动化构建一次</span><br><span class="line">"0 * * * *"表示每个小时的第0分钟执行一次构建</span><br><span class="line">"H/5 * * * *"每隔5分钟构建一次</span><br><span class="line">"H H/2 * * *"每两小时构建一次</span><br><span class="line">"H H 30 * *"每月30号构建一次</span><br><span class="line">"H(0-29)/10 * * * *"每个小时的前半个小时内的每10分钟</span><br><span class="line">"0 8-17/2 * * 1-5"周一到周五,8点~17点,两小时构建一次</span><br><span class="line">"H H 1,15 1-11 *"每月1号、15号各构建一次,除12月等</span><br></pre></td></tr></table></figure><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.11.png" alt=""></li></ul><p>3.5. 构建环境设置<br>本文使用的是<code>shell</code>脚本构建工程,所以该项可以省去<br>3.6. 构建<br>有两种方式打包,一是用<code>Xcode</code>插件打包,二是用<code>Shell</code>脚本打包,本文选择第二种<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.12.png" alt=""></p><ul><li><code>iOS</code>自动打包—<code>Jenkins Shell</code>如下:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line">## !/bin/sh</span><br><span class="line">## 项目名</span><br><span class="line">TARGET_NAME=NNAlgorithm</span><br><span class="line">## Scheme名</span><br><span class="line">SCHEME=NNAlgorithm</span><br><span class="line">##=======================</span><br><span class="line">## 编译类型</span><br><span class="line">BUILD_TYPE=Release</span><br><span class="line">## 当前目录</span><br><span class="line">SORCEPATH=${WORKSPACE}</span><br><span class="line">## workspace名</span><br><span class="line">SPACE=${WORKSPACE}/${TARGET_NAME}.xcodeproj</span><br><span class="line">##xcarchive文件的存放路径</span><br><span class="line">ARCHIVEPATH=$SORCEPATH/build/$SCHEME.xcarchive</span><br><span class="line">## ipa文件的存放路径</span><br><span class="line">EXPORTPATH=$SORCEPATH/build/$SCHEME</span><br><span class="line">## ExportOptions.plist文件的存放路径,该文件要存放在这个路径下内容如下</span><br><span class="line">EXPORTOPTIONSPLIST=$SORCEPATH/build/ExportOptions.plist</span><br><span class="line">## 导出后的ipa路径</span><br><span class="line">EXPORTPATHIPA=$SORCEPATH/build/$SCHEME/$SCHEME.ipa</span><br><span class="line"></span><br><span class="line">echo -e "============First Build Clean============"</span><br><span class="line">## 清理缓存</span><br><span class="line">## 如果工程使用的是cocoapods,则'-project %s.xcodeproj'替换为'-workspace %s.xcworkspace'</span><br><span class="line">xcodebuild clean -project $SPACE -scheme ${SCHEME} -configuration ${BUILD_TYPE}</span><br><span class="line">echo -e "============Build Clean============"</span><br><span class="line">## 输出关键信息</span><br><span class="line">echo -e " TARGET_NAME : ${TARGET_NAME}"</span><br><span class="line">echo -e " BUILD_TYPE : ${BUILD_TYPE}"</span><br><span class="line">echo -e " SORCEPATH : ${SORCEPATH}"</span><br><span class="line">echo -e " ARCHIVEPATH : ${ARCHIVEPATH}"</span><br><span class="line">echo -e " EXPORTPATH : ${EXPORTPATH}"</span><br><span class="line">echo -e " EXPORTOPTIONSPLIST : ${EXPORTOPTIONSPLIST}"</span><br><span class="line">echo -e "============Build Archive============"</span><br><span class="line"></span><br><span class="line">## 导出archive包</span><br><span class="line">xcodebuild archive -project ${SPACE} -scheme ${SCHEME} -archivePath $ARCHIVEPATH</span><br><span class="line">echo -e "============Build Archive Success============"</span><br><span class="line"></span><br><span class="line">echo -e "============Export IPA============"</span><br><span class="line">## 导出IPA包</span><br><span class="line">xcodebuild -exportArchive -archivePath $ARCHIVEPATH -exportPath ${EXPORTPATH} -exportOptionsPlist ${EXPORTOPTIONSPLIST}</span><br><span class="line">echo -e "============Export IPA SUCCESS============"</span><br><span class="line"></span><br><span class="line">## 编译完成时间 20181030_0931</span><br><span class="line">BUILD_DATE="$(date +'%Y%m%d_%H%M')"</span><br><span class="line"></span><br><span class="line">## info.plist路径</span><br><span class="line">PROJECT_INFOPLIST_PATH="${SORCEPATH}/${TARGET_NAME}/Info.plist"</span><br><span class="line">## 取版本号</span><br><span class="line">BUNDLESHORTVERSION=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${PROJECT_INFOPLIST_PATH}")</span><br><span class="line">## 取build值</span><br><span class="line">VERSION=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" "${PROJECT_INFOPLIST_PATH}")</span><br><span class="line">## ipa更名规则 项目名V版本_年月日_时分</span><br><span class="line">IPANAME="${TARGET_NAME}V${BUNDLESHORTVERSION}_${BUILD_DATE}.ipa"</span><br><span class="line">## 更名后ipa路径</span><br><span class="line">EXPORTPATHNEWIPA=$EXPORTPATH/$IPANAME</span><br><span class="line"></span><br><span class="line">echo -e "============Export end :${BUILD_DATE}============"</span><br><span class="line">echo -e "============IPA Old Name: ${EXPORTPATHIPA}============"</span><br><span class="line">echo -e "============IPA New Name: ${EXPORTPATHNEWIPA}============"</span><br><span class="line"></span><br><span class="line">## IPA更名</span><br><span class="line">cp $EXPORTPATHIPA $EXPORTPATHNEWIPA</span><br><span class="line">echo -e "============Create New Name Success============"</span><br><span class="line">## 删除老IPA</span><br><span class="line">rm $EXPORTPATHIPA</span><br><span class="line">echo -e "============Delete Old Name Success============"</span><br><span class="line"></span><br><span class="line">#userKey和apiKey需要在蒲公英的账号设置中查找</span><br><span class="line">userKey="xxx"</span><br><span class="line">apiKey="xxx"</span><br><span class="line">#蒲公英打包</span><br><span class="line">curl -F "file=@${EXPORTPATHNEWIPA}" \</span><br><span class="line">-F "uKey=${userKey}" \</span><br><span class="line">-F "_api_key=${apiKey}" \</span><br><span class="line">-F "isPublishToPublic=2" \</span><br><span class="line">http://www.pgyer.com/apiv1/app/upload</span><br></pre></td></tr></table></figure></li><li><code>ExportOptions.plist</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span><br><span class="line"><plist version="1.0"></span><br><span class="line"><dict></span><br><span class="line"> <key>provisioningProfiles</key></span><br><span class="line"> <dict></span><br><span class="line"> <key>com.Y***ane</key></span><br><span class="line"> <string>azur***_dev</string></span><br><span class="line"> </dict></span><br><span class="line"> <key>method</key></span><br><span class="line"> <string>development</string></span><br><span class="line"> <key>signingCertificate</key></span><br><span class="line"> <string>iPhone Developer</string></span><br><span class="line"> <key>signingStyle</key></span><br><span class="line"> <string>manual</string></span><br><span class="line"> <key>teamID</key></span><br><span class="line"> <string>42***ZL</string></span><br><span class="line"> <key>compileBitcode</key></span><br><span class="line"> <false/></span><br><span class="line"> <key>uploadSymbols</key></span><br><span class="line"> <false/></span><br><span class="line"></dict></span><br><span class="line"></plist></span><br></pre></td></tr></table></figure></li><li>其中<code>plist</code>文件中的<code>method</code>参数有如下几个方法:<code>app-store, ad-hoc, enterprise, development</code><br>3.7. 构建后操作</li><li>邮件通知系统,通过<code>系统管理</code>→<code>系统设置</code>,进行邮件配置</li><li><p>设置<code>jenkins</code>地址和管理员邮箱地址<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.13.JPG" alt=""></p></li><li><p>设置发件人等信息<br>这里的发件人邮箱地址切记要和系统管理员邮件地址保持一致<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.14.png" alt=""></p></li><li><p><strong>注:上图的Password为邮箱的SMTP授权秘钥,至此系统管理处的内容已配置完成</strong></p></li><li>配置<code>Jenkins</code>自带的邮件功能(测试邮件功能是否正常使用,可以不配置,不影响)<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.15.png" alt=""></li></ul><p>和上面<code>Extended E-mail Notification</code>配置一样即可,点击<code>Test configuration</code>,收到邮件并且显示<code>Email was successfully sent</code>,代表邮件配置成功,接下来可以去项目中具体配置就可以使用了</p><ul><li><p>进入项目,然后找到构建后操作,点击<code>增加构建后的操作步骤</code>,点击<code>Editable Email Notification</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.16.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.17.png" alt=""></p></li><li><p>至此所有的配置已完成,点击应用后保存,<code>enjoy it!</code></p></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Project Recipient List:这个项目的需要发送邮件给哪些人,可以在这里输入多个邮箱,中间以英文逗号隔开</span><br><span class="line">Project Reply-To List:保持默认即可,这个是收到邮件的人回复邮件时候回复给谁用的,一般不会回复邮件</span><br><span class="line">Content Type:可以选择Html或者Default也行,因为我们在jenkins系统设置中的默认格式就是html</span><br><span class="line">Default Subject: 邮件主题,可以书写成:XXX项目iOS打包通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS! 分析下这几个参数什么意思:$PROJECT_NAME 构建项目的名称;$BUILD_NUMBER 构建的号码;$BUILD_STATUS 构建状态,这几个参数,它会自动读取,按照这种格式书写即可</span><br><span class="line">Default Content:邮件内容,以下内容为模板,可直接复制修改使用:</span><br><span class="line"></span><br><span class="line"><hr/></span><br><span class="line">本邮件是程序自动下发的,请勿回复!<br/><hr/></span><br><span class="line">项目名称:$PROJECT_NAME<br/><hr/></span><br><span class="line">构建编号:$BUILD_NUMBER<br/><hr/></span><br><span class="line">构建状态:$BUILD_STATUS<br/><hr/></span><br><span class="line">触发原因:${CAUSE}<br/><hr/></span><br><span class="line">构建日志地址:<a href="${BUILD_URL}console">${BUILD_URL}console/</a><br/><hr/></span><br><span class="line">构建地址:<a href="$BUILD_URL">$BUILD_URL</a><br/><hr/></span><br><span class="line">构建报告:<a href="${BUILD_URL}testReport">${BUILD_URL}testReport/</a><br/><hr/></span><br><span class="line">变更集:${JELLY_SCRIPT,template="html"}<br/><hr/></span><br></pre></td></tr></table></figure><ol start="4"><li><a href="https://www.cnblogs.com/EasonJim/p/6277708.html">Jenkins卸载方法(Windows/Linux/MacOS)</a></li></ol><ul><li>如果使用<code>brew</code>安装的,可以执行以下命令<code>$ brew uninstall jenkins</code></li><li><strong>注:Jenkins修改工程的工作空间</strong><br>在项目的配置中点击高级,选择使用自定义工作空间,输入工作空间路径即可<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.18.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.27.19.png" alt=""></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 插件化开发(动态库研究)</title>
<link href="/2019/11/13/iOS%E6%8F%92%E4%BB%B6%E5%8C%96%E5%BC%80%E5%8F%91/"/>
<url>/2019/11/13/iOS%E6%8F%92%E4%BB%B6%E5%8C%96%E5%BC%80%E5%8F%91/</url>
<content type="html"><![CDATA[<blockquote><p>framework是一种优秀的资源打包方式,我们平时看到的第三方发布的framework大部分都是静态库,苹果对iOS允许使用动态库,但是要利用动态库热更新,由于苹果的审核和签名技术,暂时还是不行,内部使用还是可行的</p><ul><li>思路:在用户想使用某个功能的时候让其从服务器上将动态库文件下载到本地,然后手动加载动态库,实现功能的的插件化</li></ul></blockquote><ol><li>创建动态库<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"># 头文件部分</span><br><span class="line">#import <Foundation/Foundation.h></span><br><span class="line"></span><br><span class="line">@interface DynamicLlib : NSObject</span><br><span class="line"></span><br><span class="line">- (void)doSomething;</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line"># 实现部分</span><br><span class="line">#import "DynamicLlib.h"</span><br><span class="line"></span><br><span class="line">@implementation DynamicLlib</span><br><span class="line"></span><br><span class="line">- (void)doSomething{</span><br><span class="line"> NSLog(@"doSomething!");</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure></li><li>使用动态库</li></ol><ul><li>实际过程中动态库是需要从服务器下载并且保存到app的沙盒中的,这边直接模拟已经下载好了动态库并且保存到沙盒中</li></ul><p>2.1. 使用NSBundle加载动态库<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">- (IBAction)loadFrameWorkByBundle:(id)sender {</span><br><span class="line"> //从服务器去下载并且存入Documents下(只要知道存哪里即可),事先要知道framework名字,然后去加载</span><br><span class="line"> NSString *frameworkPath = [NSString stringWithFormat:@"%@/Documents/DynamicLlib.framework",NSHomeDirectory()];</span><br><span class="line"> </span><br><span class="line"> NSError *err = nil;</span><br><span class="line"> NSBundle *bundle = [NSBundle bundleWithPath:frameworkPath];</span><br><span class="line"> NSString *str = @"加载动态库失败!";</span><br><span class="line"> if ([bundle loadAndReturnError:&err]) {</span><br><span class="line"> NSLog(@"bundle load framework success.");</span><br><span class="line"> str = @"加载动态库成功!";</span><br><span class="line"> } else {</span><br><span class="line"> NSLog(@"bundle load framework err:%@",err);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>2.2. 使用dlopen加载动态库<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// 动态库中真正的可执行代码为DynamicLlib.framework/DynamicLlib文件,因此使用dlopen时指定加载动态库的路径为DynamicLlib.framework/DynamicLlib</span><br><span class="line">NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/DynamicLlib.framework/DynamicLlib",NSHomeDirectory()];</span><br><span class="line">[self dlopenLoadDylibWithPath:documentsPath];</span><br><span class="line"> if (dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW) == NULL) { </span><br><span class="line"> char *error = dlerror(); </span><br><span class="line"> NSLog(@"dlopen error: %s", error); </span><br><span class="line"> } else { </span><br><span class="line"> NSLog(@"dlopen load framework success."); </span><br><span class="line"> } </span><br></pre></td></tr></table></figure><br>2.3. 调用动态库中的方法<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//调用framework的方法,利用runtime运行时</span><br><span class="line">- (IBAction)callMethodOfFrameWork:(id)sender {</span><br><span class="line"> Class DynamicLlibClass = NSClassFromString(@"DynamicLlib");</span><br><span class="line"> if(DynamicLlibClass){</span><br><span class="line"> //事先要知道有什么方法在这个framework中</span><br><span class="line"> id object = [[DynamicLlibClass alloc] init];</span><br><span class="line"> //由于没有引入相关头文件故通过performSelector调用</span><br><span class="line"> [object performSelector:@selector(doSomething)];</span><br><span class="line"> }else {</span><br><span class="line"> NSLog(@"调用方法失败!");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 唤起APP之Universal Link(通用链接)</title>
<link href="/2019/11/06/iOS%E5%94%A4%E8%B5%B7APP%E4%B9%8BUniversal%20Link/"/>
<url>/2019/11/06/iOS%E5%94%A4%E8%B5%B7APP%E4%B9%8BUniversal%20Link/</url>
<content type="html"><![CDATA[<blockquote><p><code>iOS 9</code>之前,一直使用的是<code>URL Schemes</code>技术来从外部对<code>App</code>进行跳转,但是<code>iOS</code>系统中进行<code>URL Schemes</code>跳转的时候如果没有安装<code>App</code>,会提示<code>Cannot open Page</code>的提示,而且当注册有多个<code>scheme</code>相同的时候,目前没有办法区分,但是从<code>iOS 9</code>起可以使用<code>Universal Links</code>技术进行跳转页面,这是一种体验更加完美的解决方案</p></blockquote><ul><li><p>什么是<code>Universal Link</code>(通用链接)<br><code>Universal Link</code>是<code>Apple</code>在<code>iOS 9</code>推出的一种能够方便的通过传统<code>HTTPS</code>链接来启动<code>APP</code>的功能。如果你的应用支持<code>Universal Link</code>,当用户点击一个链接时可以跳转到你的网站并获得无缝重定向到对应的<code>APP</code>,且不需要通过<code>Safari</code>浏览器。如果你的应用不支持的话,则会在<code>Safari</code>中打开该链接</p></li><li><p>支持<code>Universal Link</code>(通用链接)<br>先决条件:必须有一个支持<code>HTTPS</code>的域名,并且拥有该域名下上传到根目录的权限(为了上传<code>Apple</code>指定文件)</p></li><li><strong>集成步骤</strong></li></ul><ol><li><p>开发者中心配置<br>找到对应的<code>App ID</code>,在<code>Application Services</code>列表里有<code>Associated Domains</code>一条,把它变为<code>Enabled</code>就可以了<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.06.01.png" alt="配置App ID支持Associated Domains"></p></li><li><p>工程配置<br><code>targets->Capabilites->Associated Domains</code>,在其中的<code>Domains</code>中填入你想支持的域名,必须以<code>applinks:</code>为前缀,如:<code>applinks:domain</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.11.06.02.png" alt="配置项目中的Associated Domains"></p></li><li><p>配置指定文件<br>创建一个内容为<code>json</code>格式的文件,苹果将会在合适的时候,从我们在项目中填入的域名请求这个文件。这个文件名必须为<code>apple-app-site-association</code>,切记没有<code>后缀名</code>,文件内容大概是这样子:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "applinks": {</span><br><span class="line"> "apps": [],</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "appID": "9JA89QQLNQ.com.apple.wwdc",</span><br><span class="line"> "paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "appID": "ABCD1234.com.apple.wwdc",</span><br><span class="line"> "paths": [ "*" ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>appID</code>:组成方式是<code>TeamID.BundleID</code>。如上面的<code>9JA89QQLNQ</code>就是<code>teamId</code>。登陆开发者中心,在<code>Account -> Membership</code>里面可以找到<code>Team ID</code><br><code>paths</code>:设定你的<code>app</code>支持的路径列表,只有这些指定路径的链接,才能被<code>app</code>所处理。<code>*</code>的写法代表了可识别域名下所有链接</p></li><li><p>上传该文件<br>上传该文件到你的域名所对应的<code>根目录</code>或者<code>.well-known目录</code>下,这是为了苹果能获取到你上传的文件。上传完后,先访问一下,看看是否能够获取到,当你在浏览器中输入这个文件链接后,应该是直接下载<code>apple-app-site-association</code>文件</p></li><li><p>代码中的相关支持<br>当点击某个链接,可以直接进我们的<code>app</code>,但是我们的目的是要能够获取到用户进来的链接,根据链接来展示给用户相应的内容,我们需要在工程里实现<code>AppDelegate</code>对应的方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {</span><br><span class="line"> // NSUserActivityTypeBrowsingWeb 由Universal Links唤醒的APP</span><br><span class="line"> if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]){</span><br><span class="line"> NSURL *webpageURL = userActivity.webpageURL;</span><br><span class="line"> NSString *host = webpageURL.host;</span><br><span class="line"> if ([host isEqualToString:@"api.r2games.com.cn"]){</span><br><span class="line"> //进行我们的处理</span><br><span class="line"> NSLog(@"TODO....");</span><br><span class="line"> }else{</span><br><span class="line"> NSLog(@"openurl");</span><br><span class="line"> [[UIApplication sharedApplication] openURL:webpageURL options:nil completionHandler:nil];</span><br><span class="line"> // [[UIApplication sharedApplication] openURL:webpageURL];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return YES;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>苹果为了方便开发者,提供了一个<a href="https://search.developer.apple.com/appsearch-validation-tool/">网页验证</a>我们编写的这个<code>apple-app-site-association</code>是否合法有效</p></li></ol><ul><li><strong>Universal Link(通用链接)注意点</strong></li></ul><ol><li><code>Universal Link</code>跨域<br><code>Universal Link</code>有跨域问题,<code>Universal Link</code>必须要求跨域,如果不跨域,就不会跳转(<code>iOS 9.2</code>之后的改动)<br>假如当前网页的域名是<code>A</code>,当前网页发起跳转的域名是<code>B</code>,必须要求<code>B</code>和<code>A</code>是不同域名才会触发<code>Universal Link</code>,如果<code>B</code>和<code>A</code>是相同域名,只会继续在当前<code>WebView</code>里面进行跳转,哪怕你的<code>Universal Link</code>一切正常,根本不会打开<code>App</code></li><li><code>Universal Link</code>请求<code>apple-app-site-association</code>时机</li></ol><ul><li>当我们的<code>App</code>在设备上第一次运行时,如果支持<code>Associated Domains</code>功能,那么<code>iOS</code>会自动去<code>GET</code>定义的<code>Domain</code>下的<code>apple-app-site-association</code>文件</li><li><code>iOS</code>会先请求<code>https://domain.com/.well-known/apple-app-site-association</code>,如果此文件请求不到,再去请求<code>https://domain.com/apple-app-site-association</code>,所以如果想要避免服务器接收过多<code>GET</code>请求,可以直接把<code>apple-app-site-association</code>放在<code>./well-known</code>目录下</li><li><p>服务器上<code>apple-app-site-association</code>的更新不会让<code>iOS</code>本地的<code>apple-app-site-association</code>同步更新,即<code>iOS</code>只会在<code>App</code>第一次启动时请求一次,以后除非<code>App</code>更新或重新安装,否则不会在每次打开时请求<code>apple-app-site-association</code></p></li><li><p><strong>Universal Link的好处</strong></p></li></ul><ol><li>之前的<code>Custom URL scheme</code>是自定义的协议,因此在没有安装该<code>app</code>的情况下是无法直接打开的。而<code>Universal Links</code>本身就是一个能够指向<code>web</code>页面或者<code>app</code>内容页的标准<code>web link</code>,因此能够很好的兼容其他情况</li><li><code>Universal links</code>是从服务器上查询是哪个<code>app</code>需要被打开,因此不存在<code>Custom URL scheme</code>那样名字被抢占、冲突的情况</li><li><code>Universal links</code>支持从其他<code>app</code>中的<code>UIWebView</code>中跳转到目标<code>app</code></li><li>提供<code>Universal link</code>给别的<code>app</code>进行<code>app</code>间的交流时,对方并不能够用这个方法去检测你的<code>app</code>是否被安装(之前的<code>custom scheme URL</code>的<code>canOpenURL</code>方法可以)</li></ol><p><a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12-SW1">附:[官方文档] Support Universal Links</a></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 归档缓存</title>
<link href="/2019/10/30/iOS%E5%BD%92%E6%A1%A3%E7%BC%93%E5%AD%98/"/>
<url>/2019/10/30/iOS%E5%BD%92%E6%A1%A3%E7%BC%93%E5%AD%98/</url>
<content type="html"><![CDATA[<p>代码如下:</p><ul><li>头文件定义<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 归档缓存内容</span><br><span class="line">+ (void)archiverObject:(id)object byKey:(NSString *)key withPath:(NSString *)path;</span><br><span class="line">// 解归档缓存内容</span><br><span class="line">+ (id)unarchiverObjectByKey:(NSString *)key withPath:(NSString *)path;</span><br></pre></td></tr></table></figure></li><li>方法实现<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">+ (void)archiverObject:(id)object byKey:(NSString *)key withPath:(NSString *)path{</span><br><span class="line"> //初始化存储对象信息的data</span><br><span class="line"> NSMutableData *data = [NSMutableData data];</span><br><span class="line"> //创建归档工具对象</span><br><span class="line"> NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];</span><br><span class="line"> //开始归档</span><br><span class="line"> [archiver encodeObject:object forKey:key];</span><br><span class="line"> //结束归档</span><br><span class="line"> [archiver finishEncoding];</span><br><span class="line"> //写入本地地址</span><br><span class="line"> NSString *resultStr = [self destPath:path];</span><br><span class="line"> [data writeToFile:resultStr atomically:YES];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (NSString *)destPath:(NSString *)path{</span><br><span class="line"> NSString *docPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).lastObject;</span><br><span class="line"> NSString *destPath = [[docPath stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:path];</span><br><span class="line"> NSLog(@"%@", destPath);</span><br><span class="line"> return destPath;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (id)unarchiverObjectByKey:(NSString *)key withPath:(NSString *)path{</span><br><span class="line"> NSString *resultStr = [self destPath:path];</span><br><span class="line"> NSData *data = [NSData dataWithContentsOfFile:resultStr];</span><br><span class="line"> //创建反归档对象</span><br><span class="line"> NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];</span><br><span class="line"> //接收反归档得到的对象</span><br><span class="line"> id object = [unarchiver decodeObjectForKey:key];</span><br><span class="line"> return object;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>使用实例<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">NSDictionary *dict = @{@"1":@"ding", @"2":@"guan", @"3":@"xiong"};</span><br><span class="line">[TestObj archiverObject:dict byKey:@"cache" withPath:@"cache.plist"];</span><br><span class="line"></span><br><span class="line">NSDictionary *result = [TestObj unarchiverObjectByKey:@"cache" withPath:@"cache.plist"];</span><br><span class="line">NSLog(@"***::%@", result);</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS UIView分类</title>
<link href="/2019/10/23/iOSUIView%E5%88%86%E7%B1%BB/"/>
<url>/2019/10/23/iOSUIView%E5%88%86%E7%B1%BB/</url>
<content type="html"><![CDATA[<ol><li><code>.h内容</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">- (CGPoint)origin;</span><br><span class="line">- (void)setOrigin:(CGPoint)point;</span><br><span class="line"></span><br><span class="line">- (CGSize)size;</span><br><span class="line">- (void)setSize:(CGSize)size;</span><br><span class="line"></span><br><span class="line">- (CGFloat)x;</span><br><span class="line">- (void)setX:(CGFloat)x;</span><br><span class="line"></span><br><span class="line">- (CGFloat)y;</span><br><span class="line">- (void)setY:(CGFloat)y;</span><br><span class="line"></span><br><span class="line">- (CGFloat)width;</span><br><span class="line">- (void)setWidth:(CGFloat)width;</span><br><span class="line"></span><br><span class="line">- (CGFloat)height;</span><br><span class="line">- (void)setHeight:(CGFloat)height;</span><br><span class="line"></span><br><span class="line">- (CGFloat)bottom;</span><br><span class="line">- (void)setBottom:(CGFloat)bottom;</span><br><span class="line"></span><br><span class="line">- (CGFloat)right;</span><br><span class="line">- (void)setRight:(CGFloat)right;</span><br></pre></td></tr></table></figure></li><li><code>.m内容</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">- (CGPoint)origin{</span><br><span class="line"> return self.frame.origin;</span><br><span class="line">}</span><br><span class="line">- (void)setOrigin:(CGPoint)origin{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.origin = origin;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGSize)size{</span><br><span class="line"> return self.frame.size;</span><br><span class="line">}</span><br><span class="line">- (void)setSize:(CGSize)size{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.size = size;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGFloat)x{</span><br><span class="line"> return self.frame.origin.x;</span><br><span class="line">}</span><br><span class="line">- (void)setX:(CGFloat)x{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.origin.x = x;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGFloat)y{</span><br><span class="line"> return self.frame.origin.y;</span><br><span class="line">}</span><br><span class="line">- (void)setY:(CGFloat)y{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.origin.y = y;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGFloat)width{</span><br><span class="line"> return self.frame.size.width;</span><br><span class="line">}</span><br><span class="line">- (void)setWidth:(CGFloat)width{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.size.width = width;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGFloat)height{</span><br><span class="line"> return self.frame.size.height;</span><br><span class="line">}</span><br><span class="line">- (void)setHeight:(CGFloat)height{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.size.height = height;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGFloat)bottom{</span><br><span class="line"> return self.frame.origin.y + self.frame.size.height;</span><br><span class="line">}</span><br><span class="line">- (void)setBottom:(CGFloat)bottom{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.origin.y = bottom - self.frame.size.height;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (CGFloat)right{</span><br><span class="line"> return self.frame.origin.x + self.frame.size.width;</span><br><span class="line">}</span><br><span class="line">- (void)setRight:(CGFloat)right{</span><br><span class="line"> CGRect frame = self.frame;</span><br><span class="line"> frame.origin.x = right - self.frame.size.width;</span><br><span class="line"> self.frame = frame;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 发布CocoaPods私有库</title>
<link href="/2019/10/16/iOS%E5%8F%91%E5%B8%83CocoaPods%E7%A7%81%E6%9C%89%E5%BA%93/"/>
<url>/2019/10/16/iOS%E5%8F%91%E5%B8%83CocoaPods%E7%A7%81%E6%9C%89%E5%BA%93/</url>
<content type="html"><![CDATA[<ul><li><strong>需要做的工作包括以下几点</strong></li></ul><ol><li>创建一个本地的仓库,将自己的代码搞进去</li><li>将自己的代码上传到远程私有仓库中去</li><li>创建一个pods 的描述文件 .podspec</li><li>修改.podspec描述文件中的相关的描述信息</li><li>创建远程内部私有Spec Repo仓库</li><li>向私有的Spec Repo仓库中提交.podspec</li><li>在个人项目中的Podfile中增加刚刚制作的好的Pod并使用</li><li>后期的升级维护</li></ol><ul><li><strong>具体详细的步骤如下</strong></li></ul><ol><li><ol start="2"><li>创建远程仓库注意点</li></ol></li></ol><ul><li>正规的仓库都有一个license文件,Pods依赖库对这个文件要求比较严格,需要有这个文件,建议使用<strong>MIT</strong>类型的license</li><li>代码版本要打tag(要在代码版本上传以后打tag)</li><li>pod 支持 .a静态库、.framework 以及文件,不一定要是可运行的工程里面的某个组件</li><li>放代码的仓库不一定非要是Git仓库,只要是可以获取到相关代码文件就可以,可以是SVN的,也可以是zip包,区别就是在podspec中的source项填写的内容不同</li></ul><ol start="3"><li>创建一个pods 的描述文件 .podspec</li></ol><ul><li>如果你已经有先有工程可以使用如下命令直接创建.podspec文件<code>$ pod spec create MyViewExtension<这个名称一般和创建的项目名称一样就可以></code></li><li>或者使用如下命令创建完整项目工程目录<code>$ pod lib create MyViewExtension <这个名称一般和创建的项目名称一样就可以></code>使用这个命令会询问如下问题,根据项目情况选择即可<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.10.16.01.png" alt=""></li></ul><ol start="4"><li>修改.podspec描述文件中的相关的描述信息<br>详情可参考CocoaPods的官网的<a href="https://link.jianshu.com/?t=http://guides.cocoapods.org/syntax/podspec.html#group_root_specification">PodSpec语法</a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">Pod::Spec.new do |s|</span><br><span class="line"> # 项目的名称</span><br><span class="line"> s.name = "MyViewExtension" </span><br><span class="line"> # 项目的版本号,通过项目git的tag标签进行对应,这里的标签代表的版本 </span><br><span class="line"> s.version = "0.0.1" </span><br><span class="line"> # 项目简单的描述信息 </span><br><span class="line"> s.summary = "Just Testing." </span><br><span class="line"> # 项目的详细描述信息,注意,这里的文字的长度,一定要比上面的s.summary长,不然会认为格式不合格</span><br><span class="line"> s.description = <<-DESC</span><br><span class="line"> this project provide all kind of KeychainDeviceID for iOS developer </span><br><span class="line"> DESC</span><br><span class="line"> # 项目的网页主页信息,这里可以直接写自己的远程仓库的主页的地址</span><br><span class="line"> s.homepage = "https://github.com/RunOfTheSnail/MyViewExtension"</span><br><span class="line"> # 开源协议</span><br><span class="line"> s.license = "MIT" </span><br><span class="line"> # 作者信息 </span><br><span class="line"> s.author = { "zhangyan" => "17***[email protected]" } </span><br><span class="line"> # 这个比较重要,指的就是git的对应的远程仓库的地址以及版本号,版本号直接获取的是上面的s.version</span><br><span class="line"> # 项目地址,这里不支持ssh的地址,验证不通过,只支持HTTP和HTTPS,最好使用HTTPS</span><br><span class="line"> # Supported Keys:</span><br><span class="line"> # :git => :tag, :branch, :commit, :submodules</span><br><span class="line"> # :svn => :folder, :tag, :revision</span><br><span class="line"> # :hg => :revision</span><br><span class="line"> # :http => :flatten, :type, :sha256, :sha1</span><br><span class="line"> s.source = { :git => "https://github.com/RunOfTheSnail/MyViewExtension.git", :tag => s.version } </span><br><span class="line"> # 支持的平台及版本</span><br><span class="line"> s.platform = :ios, "11.0"</span><br><span class="line"> # 支持的ios最低版本</span><br><span class="line"> s.ios.deployment_target = "7.0"</span><br><span class="line"> # 如果是 Swift 的话指定 Swift 编译版本</span><br><span class="line"> # s.swift_version = "4.0"</span><br><span class="line"> # 必备项,代码源文件地址,如果有多个目录下则用逗号分开,否则"public_header_files"等不可用</span><br><span class="line"> s.source_files = "GSLXYKeychainDeviceID/KeychainDeviceID/**/*.{h,m}" </span><br><span class="line"> # 公开头文件地址</span><br><span class="line"> # s.public_header_files = "Pod/Classes/**/*.h"</span><br><span class="line"> # 所需的系统framework,多个用逗号隔开,不需要后缀名</span><br><span class="line"> # s.framework = "SomeFramework"</span><br><span class="line"> s.frameworks = "UIKit", "AnotherFramework"</span><br><span class="line"> # 需要弱链接的框架</span><br><span class="line"> # s.weak_framework = "Twitter"</span><br><span class="line"> # s.weak_frameworks = "Twitter", "SafariServices"</span><br><span class="line"> #项目依赖的库文件(这个是系统的库文件),不需要后缀名,比如sqlite,libz等.以lib开头的需要省略掉lib这三个字母.例如:libz需要简写为z否则报错</span><br><span class="line"> # s.library = "iconv"</span><br><span class="line"> # s.libraries = "iconv", "xml2"</span><br><span class="line"> # 第三方或自己创建的 .Framework的名称</span><br><span class="line"> # s.vendored_frameworks = "YostarLib.framework"</span><br><span class="line"> # 第三方或自己创建的 .a静态库的名称</span><br><span class="line"> # s.vendored_libraries = "libYostarStaticLib.a"</span><br><span class="line"> # 添加资源文件</span><br><span class="line"> # s.resource = "XXX/XXXX/**/*.bundle"</span><br><span class="line"> # s.resources = "XXX/XXXX/**/*.bundle"</span><br><span class="line"> # CocoaPods会把这个库配置成static framework,同时支持Swift和Objective-C</span><br><span class="line"> # s.static_framework = true</span><br><span class="line"> # 依赖关系,该项目所依赖的其他,当在加载的时候也会一块把相关的依赖的库加载下来,如果有多个需要填写多个</span><br><span class="line"> # s.dependency "JSONKit", "~> 1.4" </span><br><span class="line"> # 是否使用ARC,如果指定具体文件,则具体的文件使用ARC </span><br><span class="line"> s.requires_arc = true</span><br><span class="line"> # 指定项目配置,如HEADER_SEARCH_PATHS、OTHER_LDFLAGS等</span><br><span class="line"> # s.xcconfig = {"OTHER_LDFLAGS" => "-ObjC"} </span><br><span class="line">end</span><br></pre></td></tr></table></figure></li></ol><ul><li>修改完毕之后进行检验一下.podspec的格式有木有问题<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ pod lib lint</span><br><span class="line">完整lint格式</span><br><span class="line">$ pod lib lint --allow-warnings --use-libraries --verbose --no-clean --sources='http://10.11.180.29/mobileDevelopers/YZT-Loan-Pod-Spec.git'</span><br><span class="line">--verbose:打印错误</span><br><span class="line">--allow-warnings:允许警告,默认有警告的podspec会验证失败</span><br><span class="line">--fail-fast:遇到错误马上停止,默认会完成全过程再停止</span><br><span class="line">--use-libraries:如果自己私有库包含library,引用了.a、.framework,在验证和提交时需要加</span><br><span class="line">--no-clean:检查问题</span><br><span class="line">--sources:如果依赖了其他不包含在官方specs里的pod,则用它来指明源,比如依赖了某个私有库。多个值以逗号分隔</span><br></pre></td></tr></table></figure></li></ul><ol start="5"><li><p>创建远程内部私有Spec Repo仓库<br>创建远程内部私有Spec Repo仓库, 需要到<a href="https://github.com/">Github</a>或其他代码托管平台创建远程仓库, 之后将远程仓库克隆到本地,终端执行如下命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// 这里可以用https或ssh地址方式克隆</span><br><span class="line">$ pod repo add WBSpecs https://github.com/G***00/TestPodspec.git</span><br></pre></td></tr></table></figure><p>注意:<strong>代码仓库和Spec Repo是需要分开存储的</strong><br>克隆成功之后,我们可以查看一下:<code>$ open ~/.cocoapods/repos</code><br>本地cocoapods目录如下:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.10.16.02.jpg" alt=""></p></li><li><p>向私有的Spec Repo仓库中提交.podspec</p></li></ol><ul><li>首先将本地.podspec推送到远程私有repo spec仓库和本地repo spec仓库,终端执行如下命令:<code>$ pod repo push WBSpecs WBAvoidCrash.podspec 参数解析:repo spec仓库名称 .podspec名称</code></li><li>验证远程是否通过<br>推送成功之后,终端输入如下命令进行验证<code>$ pod spec lint WBAvoidCrash.podspec</code></li><li>验证私有仓库是否可用<br>用pod命令进行搜索,看能否搜索到:<code>$ pod search WBAvoidCrash</code><br>如果搜索不到,在终端执行如下命令<code>$ rm ~/Library/Caches/CocoaPods/search_index.json</code>或者更新本地仓库<code>$ pod repo update</code>然后重新search</li></ul><ol start="7"><li>在个人项目中增加刚刚制作好的Podfile并使用<br>新建一个测试工程测试,用CocoaPods初始化项目,编辑Podfile文件:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#CocoaPods官方spec仓库</span><br><span class="line">source 'https://github.com/CocoaPods/Specs.git'</span><br><span class="line">#自己私有spec仓库</span><br><span class="line">source 'https://github.com/wenmobo/WBSpecs.git'</span><br><span class="line"></span><br><span class="line">platform :ios, '8.0'</span><br><span class="line"></span><br><span class="line">target 'TestDemo' do</span><br><span class="line"> #防Crash库</span><br><span class="line"> pod 'WBAvoidCrash'</span><br><span class="line">end</span><br></pre></td></tr></table></figure>编辑好podfile文件之后,终端执行:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ pod install 安装时使用,更新库使用update命令</span><br><span class="line">或</span><br><span class="line">$ pod update 更新时使用</span><br></pre></td></tr></table></figure></li><li>后期的升级维护<br>8.1. 更新远程私有库中的代码<br>8.2. 修改.podspec中的配置,version升级一个版本<br>8.3. 给当前的远程仓库的代码,重新打个tag,tag和.podspec的version一样<br>8.4. 远程仓库的代码更新完毕,接下来执行上面的 6.将当前本地的spec文件传到私有Spec Repo仓库的索引库中<br>8.5. 检查测试一下,有没有上传到私有Spec Repo仓库的索引库中</li></ol><ul><li><strong>删除私有的Spec Repo<code>$ pod repo remove [name]</code></strong><br>其实直接找到以后,手动删除就好了,然后在将Git的变动push到远端仓库即可</li><li><strong>清理CocoaPods本地缓存</strong><br>特殊情况下,由于网络或者别的原因,通过CocoaPods下载的文件可能会有问题</li></ul><ol><li><p>手动删除(<code>~/Library/Caches/CocoaPods/Pods/Release</code>目录)</p></li><li><p>打开终端,输入<code>$ pod cache list</code>,会列出所有本地已经缓存的第三方库,在终端中输入<code>$ pod cache clean AAA</code>会删除AAA缓存库,使用<code>$ pod cache clean --all</code>清除所有缓存</p></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>Mac OS 安装与恢复</title>
<link href="/2019/10/09/MacOS%E5%AE%89%E8%A3%85%E4%B8%8E%E6%81%A2%E5%A4%8D/"/>
<url>/2019/10/09/MacOS%E5%AE%89%E8%A3%85%E4%B8%8E%E6%81%A2%E5%A4%8D/</url>
<content type="html"><![CDATA[<ol><li>通过 macOS 恢复功能启动<br>要通过 macOS 恢复功能启动,请开启 Mac 并立即按住键盘上的以下组合键之一。通常建议您使用 Command-R-电源键</li></ol><ul><li>Command (⌘)-R-电源键<br>安装您的 Mac 上装有的最新 macOS</li><li>Option-⌘-R-电源键<br>升级到与您的 Mac 兼容的最新 macOS</li><li><p>Shift-Option-⌘-R-电源键<br>安装 Mac 随附的 macOS 或与它最接近且仍在提供的版本<br>当您看到 Apple 标志、旋转的地球的提示时,请松开这些按键。当您看到“实用工具”窗口时,即表示您已通过 macOS 恢复功能启动<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.10.09.01.png" alt=""></p></li><li><p>如果您在安装 macOS 之前需要抹掉磁盘,请从“实用工具”窗口中选择“磁盘工具”,然后点按“继续”。除非您要出售或赠送您的 Mac,或者遇到一个需要您抹掉磁盘的问题,否则您可能不需要抹掉磁盘</p></li><li>从“实用工具”窗口中选取“重新安装 macOS”(或“重新安装 OS X”)点按“继续”,然后按照屏幕上的说明来选取磁盘并开始安装</li></ul><ol start="2"><li>制作启动盘来恢复安装</li></ol><ul><li>首先准备一个8GB或更大容量的U盘,下载好macOS Mojave正式版的安装程序,并把它放在Mac的「应用程序」里备用</li><li><p>打开 “应用程序 → 实用工具 → 磁盘工具”,将U盘「抹掉」(格式化) 成「Mac OS X 扩展(日志式)」格式、(GUID 分区图可选项),并将 U 盘命名为「Mojave」(下图序号3处)。注意:这个盘符名称必须与后面的命令里的名称一致,需要认真看清楚<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.10.09.02.png" alt=""></p></li><li><p>在“终端”中键入或粘贴以下命令之一。这些命令假设安装器仍位于您的“应用程序”文件夹中,并且 MyVolume 是 USB 闪存驱动器或您正在使用的其他宗卷的名称。如果不是这个名称,请相应地替换为 MyVolume</p></li><li>制作 macOS Mojave 启动盘,U盘名称(必须与下面命令对应),然后拷贝这段命令:<code>sudo /Applications/Install\ macOS\ Mojave.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume</code></li><li>制作 macOS High Sierra 启动盘,U盘名称(要与下面命令对应),拷贝这段命令:<code>sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume</code></li><li>制作旧版本的 macOS Sierra,拷贝这段命令:<code>sudo /Applications/Install\ macOS\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume --applicationpath /Applications/Install\ macOS\ Sierra.app</code></li><li>制作El Capitan,拷贝这段命令:<code>sudo /Applications/Install\ OS\ X\ El\ Capitan.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume --applicationpath /Applications/Install\ OS\ X\ El\ Capitan.app</code></li><li><p>当“终端”显示这个操作已完成时,该宗卷的名称将与您下载的安装器名称相同,例如“Install macOS Mojave”。您现在可以退出“终端”并弹出宗卷<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.10.09.03.png" alt=""></p></li><li><p>当你制作好 macOS Mojave 的安装盘 U 盘之后,你就可以利用它来给Mac电脑格式化重装 (抹盘安装)了。操作的方法非常简单:按下电源键开机,按住 Option 键不放,直到出现启动菜单选项,这时选择安装U盘 (黄色图标) 并回车,就可以开始安装了,在过程中你可以直接覆盖安装系统(升级),也可以通过“磁盘工具”对 Mac 的磁盘式化或者重新分区等操作实现全新干净的安装。之后就是一步一步的安装直到完成了</p></li></ul><p><a href="https://www.iplaysoft.com/macos-usb-install-drive.html">附:制作 macOS Mojave U盘USB启动安装盘方法教程 (全新安装 Mac 系统)</a><br><a href="https://support.apple.com/zh-cn/HT204904">附:[官方文档] 如何通过 macOS 恢复功能重新安装 macOS</a><br><a href="https://support.apple.com/zh-cn/HT201372">附:[官方文档] 如何创建可引导的 macOS 安装器</a></p>]]></content>
<tags>
<tag> 随笔 </tag>
</tags>
</entry>
<entry>
<title>iOS 指针详解</title>
<link href="/2019/09/25/iOS%E6%8C%87%E9%92%88%E8%AF%A6%E8%A7%A3/"/>
<url>/2019/09/25/iOS%E6%8C%87%E9%92%88%E8%AF%A6%E8%A7%A3/</url>
<content type="html"><![CDATA[<ol><li><code>指针数组</code>与<code>数组指针</code><br>在这里<code>数组指针</code>是指向数组的<code>指针</code>,其本质为<code>指针</code>,指向的对象是<code>数组</code>。由于数组的形式多样所以数组指针的表达也十分多样。同理,<code>指针数组</code>就是存放指针的<code>数组</code>,其本质为<code>数组</code>。由于<code>[ ]</code>的优先级高于<code>*</code>的优先级,<code>指针数组</code>与<code>数组指针</code>的表达可做如下表示:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">int * p1 [10]; // 指针数组 p1先与“[ ]”结合构成一个包含10个元素的数组,int*表示的则是数组的内容</span><br><span class="line">int (* p2)[10]; // 数组指针 p2先与“ * ”构成指针定义,int表示数组内容,[10]表示数组内元素个数</span><br></pre></td></tr></table></figure></li></ol><ul><li><p>由于<code>指向数组的指针</code>与<code>指向普通整型变量的指针</code>不同,在这里可以再对<code>数组名</code>与<code>&数组名</code>的关系进行理解<br>在一维数组中,<code>数组名</code>表示<code>指向首元素的首地址</code>,是一个<code>指向普通变量的指针常量</code>,当对其+1时偏移量是<code>一个普通数据类型的内存大小</code>。而在<code>数组名前加上取地址符&后</code>,表示的就是一个<code>指向数组的指针常量</code>,对其+1时偏移量是一个<code>数组的内存大小</code></p></li><li><p><code>int p;</code> //这是一个普通的<code>整型变量</code></p></li><li><code>int *p;</code> //首先从<code>P</code>处开始,先与<code>*</code>结合,所以说明<code>P</code>是一个<code>指针</code>,然后再与<code>int</code>结合,说明指针所指向的内容的类型为<code>int型</code>,所以<code>P</code>是一个返回<code>整型数据的指针</code></li><li><code>int p[3];</code> //首先从<code>P</code>处开始,先与<code>[]</code>结合,说明<code>P</code>是一个<code>数组</code>,然后与<code>int</code>结合,说明数组里的元素是<code>整型的</code>,所以<code>P</code>是一个由<code>整型数据组成的数组</code></li><li><code>int *p[3];</code> //首先从<code>P</code>处开始,先与<code>[]</code>结合,因为其优先级比<code>*</code>高,所以<code>P</code>是一个<code>数组</code>,然后再与<code>*</code>结合,说明数组里的元素是<code>指针类型</code>,然后再与<code>int</code>结合,说明指针所指向的内容的类型是<code>整型的</code>,所以<code>P</code>是一个由返回<code>整型数据的指针所组成的数组</code></li><li><code>int (*p)[3];</code> //首先从<code>P</code>处开始,先与<code>*</code>结合,说明<code>P</code>是一个<code>指针</code>,然后再与<code>[]</code>结合,说明指针所指向的内容是<code>一个数组</code>,然后再与<code>int</code>结合,说明数组里的元素是<code>整型的</code>,所以<code>P</code>是一个指向由<code>整型数据组成的数组的指针</code></li></ul><ol start="2"><li>可以通过如下题目,进一步理解上面的讲解<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">int arrayName[4] = {10, 20, 30, 40};</span><br><span class="line">int *p = (int *)(&arrayName + 1);</span><br><span class="line">NSLog(@"%d", *(p - 1));</span><br></pre></td></tr></table></figure></li></ol><ul><li>答案解析<blockquote><ol><li><code>(&arrayName + 1)</code>:<code>&arrayName</code>是数组的地址(等价于指向<code>arrayName数组的指针</code>)</li><li>增加<code>1</code>会往后移动<code>16个字节</code>,开始是<code>4个字节的位置</code>,移动后就是<code>16个字节后面的位置</code>(也就是目前位置是<code>20个字节</code>)</li><li>最后又赋值给<code>int类型</code>的指针<code>p</code>(<code>int类型占4个字节</code>)</li><li>所以<code>(p - 1)</code>就是<code>减去4个字节</code>,变成为<code>16个字节的位置</code>,输出的<code>*(p - 1)</code>值为<code>40</code></li></ol></blockquote></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS KVO</title>
<link href="/2019/09/18/iOSKVO/"/>
<url>/2019/09/18/iOSKVO/</url>
<content type="html"><![CDATA[<p><code>KVO</code>和<code>NSNotificationCenter</code>都是<code>iOS</code>中<code>观察者模式</code>的一种实现。区别在于,相对于被观察者和观察者之间的关系,<code>KVO是一对一</code>的,而<code>NSNotificationCenter是一对多</code>的。<code>KVO</code>对被监听对象无侵入性,不需要修改其内部代码即可实现监听。</p><ol><li><code>KVO</code>底层实现<br><code>KVO</code>是基于<code>runtime</code>机制实现的,运用了一个<code>isa-swizzling</code>技术。<code>isa-swizzling</code>就是<code>类型混合指针机制</code>, 将2个对象的<code>isa</code>指针互相调换, 就是俗称的黑魔法</li></ol><ul><li>当某个类的属性对象<code>第一次被观察</code>时,系统就会在运行期<code>动态</code>地创建<code>该类的一个派生类</code>,在这个派生类中重写基类中任何被观察属性的<code>setter</code>方法。派生类在被重写的<code>setter</code>方法内实现真正的<code>通知机制</code></li><li>如果原类为<code>Person</code>,那么生成的派生类名为<code>NSKVONotifying_Person</code>,每个类对象中都有一个<code>isa指针</code>指向当前类,当一个类对象第一次被观察,那么系统会偷偷将<code>isa指针</code>指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的<code>setter</code>方法</li><li>键值观察通知依赖于<code>NSObject</code>的两个方法:<code>willChangeValueForKey:</code>和<code>didChangevlueForKey:</code>;在一个被观察属性发生改变之前,<code>willChangeValueForKey:</code>一定会被调用,这就会记录旧的值。而当改变发生后,<code>didChangeValueForKey:</code>会被调用,继而<code>observeValueForKey:ofObject:change:context:</code>也会被调用</li><li>补充:<code>KVO</code>的这套实现机制中苹果还偷偷重写了<code>class</code>方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类</li></ul><ol start="2"><li>如何手动触发<code>KVO</code><br>手动调用<code>willChangeValueForKey:</code>和<code>didChangeValueForKey:</code><br>示例代码如下:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">1、自动</span><br><span class="line">//默认返回YES</span><br><span class="line">+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{</span><br><span class="line"> if ([key isEqualToString:@"age"]) {</span><br><span class="line"> return NO;//不观察age属性值得变化</span><br><span class="line"> }</span><br><span class="line"> return YES;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">2、手动</span><br><span class="line">- (void)setName:(NSString *)name{</span><br><span class="line"> [self willChangeValueForKey:@"name"];</span><br><span class="line"> _name = name;</span><br><span class="line"> [self didChangeValueForKey:@"name"];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>直接修改成员变量会触发<code>KVO</code>吗<br>不会触发<code>KVO</code>,添加<code>KVO</code>的<code>Person</code>实例,其实是<code>NSKVONotyfing_Person</code>类在调用<code>setter</code>方法,不是调用<code>Person</code>的<code>setter</code>方法,而是<code>NSKVONotyfing_Person</code>的<code>setter</code>方法,因为修改成员变量不是<code>setter</code>方法赋值</li><li>如果在项目中对<code>Person</code>类进行了监听,也创建了一个<code>NSKVONotifying_Person</code>类,那么会编译通过么<br>编译通过,因为<code>KVO</code>是运行时刻创建的,并不在编译时刻,在编译时刻只有一个<code>NSKVONotifying_Person</code>,所以不报错,可以通过,但是此时<code>KVO</code>起不了作用<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.09.18.01.png" alt=""></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS python自动化出包脚本</title>
<link href="/2019/09/11/iOSPython%E8%87%AA%E5%8A%A8%E5%8C%96%E5%87%BA%E5%8C%85%E8%84%9A%E6%9C%AC/"/>
<url>/2019/09/11/iOSPython%E8%87%AA%E5%8A%A8%E5%8C%96%E5%87%BA%E5%8C%85%E8%84%9A%E6%9C%AC/</url>
<content type="html"><![CDATA[<p>脚本代码如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/python</span><br><span class="line"># -*- coding:UTF-8 -*-</span><br><span class="line"></span><br><span class="line">import os</span><br><span class="line">import sys</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"># 发邮件所用</span><br><span class="line">from email import encoders</span><br><span class="line">from email.header import Header</span><br><span class="line">from email.mime.text import MIMEText</span><br><span class="line">from email.utils import parseaddr, formataddr</span><br><span class="line">import smtplib</span><br><span class="line"></span><br><span class="line"># 需要配置分割线 ===================================================================</span><br><span class="line"></span><br><span class="line"># fir token</span><br><span class="line">fir_api_token = '34d6f526c9fdcf9afe90753cdb9bb837' #firm的api token</span><br><span class="line">download_address = "https://fir.im/xxxxxxxxx" #firm 下载地址</span><br><span class="line"></span><br><span class="line"># pgyer</span><br><span class="line">pgyer_uKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"</span><br><span class="line">pgyer_apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"</span><br><span class="line">pgyer_appQRCodeURL = "http://www.pgyer.com/xxxxxxxxx" # 下载地址</span><br><span class="line">pgyer_password = "12345"</span><br><span class="line">pgyer_updateDescription = "test版本" # 更新描述</span><br><span class="line"></span><br><span class="line"># 项目配置</span><br><span class="line">project_Name = 'Unity-iPhone' #工程名</span><br><span class="line">scheme = 'Unity-iPhone' #scheme</span><br><span class="line">isDistribution = False #生成dev包或者dis包类型</span><br><span class="line">isWorkspace = False #工程类型 pod工程 -workspace 普通工程 -project</span><br><span class="line"></span><br><span class="line"># 项目根目录</span><br><span class="line">project_path = '/Users/yostar/Desktop/ProjectiOSTest'</span><br><span class="line">#当前autoIpa.py 以及 plist 所在文件夹位置</span><br><span class="line">#主执行文件的父级目录</span><br><span class="line">autoPythonRoot = sys.path[0]</span><br><span class="line"></span><br><span class="line"># 发邮件相关信息</span><br><span class="line">from_addr = '250***[email protected]'</span><br><span class="line">password = 'plgke***pzbjdice'</span><br><span class="line">smtp_host = 'smtp.qq.com'</span><br><span class="line">to_addr = ['250***[email protected]', '1728***[email protected]']</span><br><span class="line"></span><br><span class="line"># 需要配置分割线 ===================================================================</span><br><span class="line"></span><br><span class="line"># 编译模式 Debug,Release</span><br><span class="line">def configuration():</span><br><span class="line"> if isDistribution:</span><br><span class="line"> return 'Release'</span><br><span class="line"> else:</span><br><span class="line"> return 'Debug'</span><br><span class="line"></span><br><span class="line"># 编译成功后.xcarchive所在目录</span><br><span class="line">archive_dir = project_path + '/archive'</span><br><span class="line"># 打包后ipa存储目录</span><br><span class="line">targerIPA_dir = project_path + '/ipaDir'</span><br><span class="line"></span><br><span class="line">#CA certificate</span><br><span class="line">#发布包相关的plist</span><br><span class="line">DistributionExportFileName = "Distribution_ExportOptions.plist"</span><br><span class="line"></span><br><span class="line">#测试包相关的plist</span><br><span class="line">DeveloperExportFileName = "Develop_ExportOptions.plist"</span><br><span class="line"></span><br><span class="line">#时间字符串</span><br><span class="line">time_Tag = '%s'%(time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time())))</span><br><span class="line"></span><br><span class="line">#xcodebuild export ipa包命令时需要用到</span><br><span class="line">def export_OptionsPlist():</span><br><span class="line"> if isDistribution:</span><br><span class="line"> return autoPythonRoot + '/' + DistributionExportFileName</span><br><span class="line"> else:</span><br><span class="line"> return autoPythonRoot + '/' + DeveloperExportFileName</span><br><span class="line"></span><br><span class="line">#打包名字</span><br><span class="line">def archiveName():</span><br><span class="line"> return project_Name + '_' + time_Tag + '.xcarchive'</span><br><span class="line"></span><br><span class="line">#archive地址</span><br><span class="line">def archivePath():</span><br><span class="line"> return '%s/%s'%(archive_dir, archiveName())</span><br><span class="line"></span><br><span class="line">#ipa包名</span><br><span class="line">def ipaFileName():</span><br><span class="line"> return '%s_%s'%(project_Name, time_Tag)</span><br><span class="line"></span><br><span class="line">#ipa导出地址</span><br><span class="line">def exportPath():</span><br><span class="line"> if isDistribution:</span><br><span class="line"> return '%s/%s/%s'%(targerIPA_dir, 'Distribution', ipaFileName())</span><br><span class="line"> else:</span><br><span class="line"> return '%s/%s/%s'%(targerIPA_dir, 'development', ipaFileName())</span><br><span class="line"></span><br><span class="line"># 清理项目</span><br><span class="line">def clean_project():</span><br><span class="line"> os.system('rm -rf %s'%(archive_dir))</span><br><span class="line"> print(project_path + '******' + project_Name + '******' + '******' + scheme + '******' + configuration())</span><br><span class="line"> if isWorkspace:</span><br><span class="line"> os.system('cd %s; xcodebuild clean -workspace %s.xcworkspace -scheme %s -configuration %s'%(project_path, project_Name, scheme, configuration()))</span><br><span class="line"> else:</span><br><span class="line"> os.system('cd %s; xcodebuild clean -project %s.xcodeproj -scheme %s -configuration %s'%(project_path, project_Name, scheme, configuration()))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#archive 打包</span><br><span class="line">def archive_project():</span><br><span class="line"> print('======archive_project start')</span><br><span class="line"> print(archiveName())</span><br><span class="line"> if isWorkspace:</span><br><span class="line"> os.system('cd %s; xcodebuild archive -workspace %s.xcworkspace -scheme %s -archivePath %s'%(project_path, project_Name, scheme, archivePath()))</span><br><span class="line"> else:</span><br><span class="line"> os.system('cd %s; xcodebuild archive -project %s.xcodeproj -scheme %s -archivePath %s'%(project_path, project_Name, scheme, archivePath()))</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"># 打包ipa 并且保存在桌面</span><br><span class="line">def export_ipa():</span><br><span class="line"> print('export_ipa start')</span><br><span class="line"> print(ipaFileName())</span><br><span class="line"> print(export_OptionsPlist())</span><br><span class="line"> os.system('cd %s; xcodebuild -exportArchive -archivePath %s/ -exportOptionsPlist %s -exportPath %s'%(project_path, archivePath(), export_OptionsPlist(), exportPath()))</span><br><span class="line"></span><br><span class="line">##上传到fir</span><br><span class="line">def upload_fir():</span><br><span class="line"> p = exportPath() + '/' + scheme + '.ipa'</span><br><span class="line"> if os.path.exists(p):</span><br><span class="line"> print('watting===%s...上传到fir'%p)</span><br><span class="line"> # 直接使用fir 有问题 这里使用了绝对地址 在终端通过 which fir 获得</span><br><span class="line"> ret = os.system('fir publish %s -T %s'%(p, fir_api_token))</span><br><span class="line"> print('watting...上传结束')</span><br><span class="line"> return True</span><br><span class="line"> else:</span><br><span class="line"> print('没有找到IPA文件')</span><br><span class="line"> return False</span><br><span class="line"></span><br><span class="line"># 发邮件</span><br><span class="line">def send_mail():</span><br><span class="line"> msg = MIMEText('【%s】'%scheme + 'iOS 测试项目完成,请下载测试!如有问题,请联系iOS相关人员,我们会及时解决,谢谢!', 'plain', 'utf-8') #发邮件内容</span><br><span class="line"> msg['From'] = Header('自动打包系统<%s>' % from_addr, 'utf-8') #发件人</span><br><span class="line"> msg['To'] = Header('测试人员', 'utf-8') #收件人</span><br><span class="line"> msg['Subject'] = Header('【%s】'%scheme + 'iOS客户端测试包构建完成, 构建时间:%s'%(time_Tag), 'utf-8').encode() #邮件主题</span><br><span class="line"> </span><br><span class="line"> try:</span><br><span class="line"> server = smtplib.SMTP(smtp_host, 25)</span><br><span class="line"> server.set_debuglevel(1)</span><br><span class="line"> server.login(from_addr, password)</span><br><span class="line"> server.sendmail(from_addr, to_addr, msg.as_string())</span><br><span class="line"> print('邮件发送成功')</span><br><span class="line"> except smtplib.SMTPException:</span><br><span class="line"> print('Error:无法发送邮件')</span><br><span class="line"> finally:</span><br><span class="line"> server.quit() # 发送完毕后退出smtp</span><br><span class="line"></span><br><span class="line">def main():</span><br><span class="line"> # 执行</span><br><span class="line"> # 清理目录</span><br><span class="line"> clean_project()</span><br><span class="line"> # 编译coocaPods项目文件并 执行编译目录</span><br><span class="line"> archive_project()</span><br><span class="line"> # 导出ipa</span><br><span class="line"> export_ipa()</span><br><span class="line"></span><br><span class="line"> if not isDistribution:</span><br><span class="line"> # 上传fir</span><br><span class="line"> success = upload_fir()</span><br><span class="line"> # 发邮件</span><br><span class="line"> if success:</span><br><span class="line"> send_mail()</span><br><span class="line"></span><br><span class="line">main()</span><br></pre></td></tr></table></figure></p><ul><li>该脚本是把<code>Distribution_ExportOptions.plist</code>、<code>Develop_ExportOptions.plist</code>和脚本放在同一目录里面的</li><li>该脚本是针对<code>xcode 8</code>及以上版本的,低版本会出包失败</li><li><p><code>plist</code>文件内容如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span><br><span class="line"><plist version="1.0"></span><br><span class="line"><dict></span><br><span class="line"> <key>provisioningProfiles</key></span><br><span class="line"> <dict></span><br><span class="line"> <key>com.Y***ane</key></span><br><span class="line"> <string>azur***_dev</string></span><br><span class="line"> </dict></span><br><span class="line"> <key>method</key></span><br><span class="line"> <string>development</string></span><br><span class="line"> <key>signingCertificate</key></span><br><span class="line"> <string>iPhone Developer</string></span><br><span class="line"> <key>signingStyle</key></span><br><span class="line"> <string>manual</string></span><br><span class="line"> <key>teamID</key></span><br><span class="line"> <string>42***ZL</string></span><br><span class="line"> <key>compileBitcode</key></span><br><span class="line"> <false/></span><br><span class="line"> <key>uploadSymbols</key></span><br><span class="line"> <false/></span><br><span class="line"></dict></span><br><span class="line"></plist></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.09.17.01.jpeg" alt=""></p></li><li><p>其中<code>plist</code>文件中的<code>method</code>参数有如下几个方法:<code>{app-store, ad-hoc, enterprise, development}</code></p></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>Chrome 浏览器扩展插件</title>
<link href="/2019/08/28/Chrome%E6%B5%8F%E8%A7%88%E5%99%A8%E6%89%A9%E5%B1%95%E6%8F%92%E4%BB%B6/"/>
<url>/2019/08/28/Chrome%E6%B5%8F%E8%A7%88%E5%99%A8%E6%89%A9%E5%B1%95%E6%8F%92%E4%BB%B6/</url>
<content type="html"><![CDATA[<p>Chrome扩展可以在<a href="https://chrome.google.com/webstore/category/extensions?hl=zh-CN">Google应用商店下载</a>这里可以搜索安装你喜欢的各种扩展<br>扩展这么多,推荐以下扩展</p><ul><li><a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=zh-CN">油猴Tampermonkey</a></li><li><a href="https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag?hl=zh-CN">暴力猴</a></li><li><a href="https://chrome.google.com/webstore/detail/adguard-adblocker/bgnkhhnnamicmpeenaelnjfhikgbkllg?hl=zh-CN">AdGuard广告拦截器</a></li><li><a href="https://chrome.google.com/webstore/detail/%E5%B9%BF%E5%91%8A%E7%BB%88%E7%BB%93%E8%80%85/fpdnjdlbdmifoocedhkighhlbchbiikl?hl=zh-CN">广告终结者</a></li><li><a href="https://chrome.google.com/webstore/detail/json-handle/iahnhfdhidomcpggpaimmmahffihkfnj?hl=zh-CN">JSON-handle</a></li><li><a href="https://chrome.google.com/webstore/detail/postwoman-browser-extensi/amknoiejhlmhancpahfcfcfhllgkpbld?hl=zh-CN">Postwoman Browser Extension</a></li><li><a href="https://chrome.google.com/webstore/detail/postwoman-http%E6%8E%A5%E5%8F%A3%E8%B0%83%E8%AF%95%E6%8F%92%E4%BB%B6/lboaigfphnnfclelldpoladgpldgbcgn?hl=zh-CN">PostWoman Http接口调试插件</a></li><li><a href="https://chrome.google.com/webstore/detail/fehelper%E5%89%8D%E7%AB%AF%E5%8A%A9%E6%89%8B/pkgccpejnmalmdinmhkkfafefagiiiad?hl=zh-CN">FeHelper(前端助手)</a></li></ul><p>其中的<code>油猴Tampermonkey</code>和<code>暴力猴</code>是必须推荐的,它是扩展中的王者,最强大的浏览器扩展,下面讲解下安装和使用方法</p><ol><li>安装<code>油猴和暴力猴</code>扩展<br>如果能科学上网直接点击上面链接即可直接安装。上不了<code>Google</code>,可以在国内第三方<code>Chrome</code>插件网站搜索下载</li></ol><ul><li><a href="https://www.crx4chrome.com/">crx4chrome</a></li><li><a href="http://chromecj.com/">chromecj</a> </li><li><a href="http://www.cnplugins.com/">cnplugins</a></li><li><a href="https://www.chromefor.com/">chromefor</a></li><li><a href="https://www.gugeapps.net/">gugeapps</a><br>下载的是个<code>crx</code>文件,然后打开<code>Chrome扩展程序</code>,打开<code>开发者模式</code>,将下载的<code>crx</code>文件<code>拖进去</code>,如果出错提示程序包无效。将<code>crx</code>后缀改为<code>zip</code>再<code>拖进去</code>就能安装成功了,浏览器右上角看到图标,即安装完成</li></ul><p><strong>注:</strong>一定要打开<code>开发者模式</code>,否则会安装失败。用该种方法安装的扩展,在<code>Mac</code>系统重启后,扩展会消失,需要重新安装,所以推荐<code>Google</code>商店直装,此方法首先需要能连上谷歌<code>Chrome</code>商店,必须先安装上一个直连谷歌的插件,安装方法按上面即可,然后就可以商店直装,永久使用<br>以下3款插件可以直连谷歌商店,任选一个使用</p><ul><li><a href="https://chrome.zzzmh.cn/info?token=jkicnibdkfemnfhojeajbldjgdddpajk">谷歌学术助手:学术文献资料查询</a></li><li><a href="https://chrome.zzzmh.cn/info?token=gocklaboggjfkolaknpbhddbaopcepfp">谷歌访问助手:有12小时试用时间,设置浏览器首页可免费激活,永久使用</a></li><li><a href="https://chrome.zzzmh.cn/info?token=kahndhhhcnignmbbpiobmdlgjhgfkfil">PP谷歌访问助手:永久免费,不限时</a></li></ul><ol start="2"><li>有了<code>油猴和暴力猴</code>扩展,还需要配上脚本才能使用<br>安装完扩展后点击<code>图标</code>,选择<code>获取新脚本</code>会<a href="https://www.tampermonkey.net/scripts.php">进入网站</a>,这里可以获取脚本来源,有3个来源网站</li></ol><ul><li><a href="http://userscripts-mirror.org/">userscripts-mirror</a> </li><li><a href="https://openuserjs.org/">openuserjs</a></li><li><a href="https://greasyfork.org/">greasyfork</a><br>这里推荐<code>greasyfork</code>,因为它支持中文,打开网站首页可以看到很多脚本,选择需要的脚本安装即可</li><li><code>暴力猴</code>是一个可以加载<code>油猴</code>脚本的<code>Chrome</code>扩展,它支持<code>Userscripts.org、GreasyFork、OpenUserJS</code>三大脚本下载源。打开对应网站可以搜索到相应的油猴脚本,相比油猴就是它帮你搜索了,所以使用更方便点</li></ul>]]></content>
<tags>
<tag> 随笔 </tag>
</tags>
</entry>
<entry>
<title>iOS 13-Sign In with Apple</title>
<link href="/2019/08/21/iOS13SignInwithApple/"/>
<url>/2019/08/21/iOS13SignInwithApple/</url>
<content type="html"><![CDATA[<p>最近了解了<code>iOS 13</code>新增功能之<code>Sign In with Apple</code>,<code>Sign In with Apple</code>是跨平台的,可以支持<code>iOS、macOS、watchOS、tvOS、JS</code>。本文主要内容为<code>Sign In with Apple</code>在<code>iOS</code>上的基础使用。<a href="https://developer.apple.com/videos/play/wwdc2019/706/">详情参考WWDC 2019</a></p><ul><li>审核备注<blockquote><p>New Guidelines for Sign in with Apple<br>We’ve updated the App Store Review Guidelines to provide criteria for when apps are required to use Sign in with Apple. Starting today, new apps submitted to the App Store must follow these guidelines. Existing apps and app updates must follow them by April 2020. We’ve also provided new guidelines for using Sign in with Apple on the web and other platforms.<br>September 12, 2019<br>也就是说,所有已接入其它第三方登录的 App,Sign In with Apple 将被要求作为一种登录选择,否则就不给过。从今天开始(2019-9-12),提交到App Store的新应用必须遵循这些准则,现有应用程序和应用程序更新必须在2020年4月之前进行。<a href="https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple">详情参考App Store审核指南</a></p></blockquote></li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.01.png" alt=""></p><ul><li><p>开发<code>Sign In with Apple</code>的注意事项<br>需要在苹果后台打开该选项,并且重新生成<code>Profiles</code>配置文件,并安装到<code>Xcode</code>,如下图<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.02.png" alt=""></p></li><li><p>服务端验证需要的文件,一个是私钥文件,一个是<code>config.json</code>文件</p></li><li>创建用于客户端身份验证的私钥<br>返回<code>Certificates, Identifiers & Profiles</code>主屏幕,从侧面导航中选择<code>Keys</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.03.png" alt=""></li></ul><p>单击<code>Configure</code>按钮,然后选择你先前创建的<code>Primary App ID</code>,保存之后,<code>Apple</code>将为你生成一个新的私钥,并让你仅下载一次,<code>请确保你保存了此文件,因为以后你将无法再次将其取回!</code>你下载的文件将以<code>.p8</code>结尾,可以将其重命名为<code>key.txt</code>以便在后续步骤中更轻松地使用</p><ul><li><p>创建<code>config.json</code>新文件,格式、内容和参数说明如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "client_id": "实际上被称为“Service ID”,您将在“Identifiers”部分创建它,其实就是应用的bundleID",</span><br><span class="line"> "team_id": "后台账号的teamID",</span><br><span class="line"> "redirect_uri": "重定向url,网页登录需要,只是客服端登录可以不写",</span><br><span class="line"> "key_id": "在苹果后台获取,如下图",</span><br><span class="line"> "scope": "设置我们要从用户那里收集什么信息,我们可以设置email和name,或者也可以不写</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.04.png" alt="获取key_id"></p></li><li><p><code>web</code>使用<code>Sign In with Apple</code>的相关配置,<code>不需要web登录的,以下配置可以忽略</code></p></li><li>创建<code>Services ID</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.05.png" alt=""></li></ul><p>在下一步中,你将定义用户在登录流程中将看到的应用程序的名称,并定义成为<code>OAuth</code>的标识符<code>client_id</code>,确保还选中<code>Sign In with Apple</code>复选框<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.06.png" alt=""></p><ul><li>创建<code>web Authentication Configuration</code>,定义应用程序的重定向<code>URL</code></li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.07.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.08.png" alt=""></p><ul><li><p><code>iOS</code>使用<code>Sign In with Apple</code>在<code>Xcode</code>的准备工作<br>在<code>Xcode11 Signing & Capabilities</code>中添加<code>Sign In With Apple</code>,如下图<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.21.09.jpeg" alt=""></p></li><li><p><code>iOS Sign In with Apple</code>流程</p><blockquote><ol><li>导入系统头文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登录按钮,设置ASAuthorizationAppleIDButton相关布局,并添加按钮点击响应事件</li><li>获取授权码</li><li>验证</li></ol></blockquote></li></ul><ol><li>导入系统头文件<code>#import <AuthenticationServices/AuthenticationServices.h></code>,添加<code>Sign In with Apple</code>登录按钮,设置<code>ASAuthorizationAppleIDButton</code>相关布局,并添加按钮点击响应事件。当然苹果也允许自定义苹果登录按钮的样式,样式要求详见这个文档:<a href="https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/">Human Interface Guidelines</a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">- (void)configUI{</span><br><span class="line"> // 使用系统提供的按钮,要注意不支持系统版本的处理</span><br><span class="line"> if (@available(iOS 13.0, *)) {</span><br><span class="line"> // Sign In With Apple Button</span><br><span class="line"> ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];</span><br><span class="line"> appleIDBtn.frame = CGRectMake(30, self.view.bounds.size.height - 180, self.view.bounds.size.width - 60, 100);</span><br><span class="line"> // appleBtn.cornerRadius = 22.f;</span><br><span class="line"> [appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside];</span><br><span class="line"> [self.view addSubview:appleIDBtn];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> // 或者自己用UIButton实现按钮样式</span><br><span class="line"> UIButton *addBtn = [UIButton buttonWithType:UIButtonTypeCustom];</span><br><span class="line"> addBtn.frame = CGRectMake(30, 80, self.view.bounds.size.width - 60, 44);</span><br><span class="line"> addBtn.backgroundColor = [UIColor orangeColor];</span><br><span class="line"> [addBtn setTitle:@"Sign in with Apple" forState:UIControlStateNormal];</span><br><span class="line"> [addBtn addTarget:self action:@selector(didCustomBtnClicked) forControlEvents:UIControlEventTouchUpInside];</span><br><span class="line"> [self.view addSubview:addBtn];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 自己用UIButton按钮调用处理授权的方法</span><br><span class="line">- (void)didCustomBtnClicked{</span><br><span class="line"> // 封装Sign In with Apple 登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行</span><br><span class="line"> self.signInApple = [[SignInApple alloc] init];</span><br><span class="line"> [self.signInApple handleAuthorizationAppleIDButtonPress];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 使用系统提供的按钮调用处理授权的方法</span><br><span class="line">- (void)didAppleIDBtnClicked{</span><br><span class="line"> // 封装Sign In with Apple 登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行</span><br><span class="line"> self.signInApple = [[SignInApple alloc] init];</span><br><span class="line"> [self.signInApple handleAuthorizationAppleIDButtonPress];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 处理授权</span><br><span class="line">- (void)handleAuthorizationAppleIDButtonPress{</span><br><span class="line"> NSLog(@"////////");</span><br><span class="line"> </span><br><span class="line"> if (@available(iOS 13.0, *)) {</span><br><span class="line"> // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制</span><br><span class="line"> ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];</span><br><span class="line"> // 创建新的AppleID 授权请求</span><br><span class="line"> ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];</span><br><span class="line"> // 在用户授权期间请求的联系信息</span><br><span class="line"> appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];</span><br><span class="line"> // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器</span><br><span class="line"> ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];</span><br><span class="line"> // 设置授权控制器通知授权请求的成功与失败的代理</span><br><span class="line"> authorizationController.delegate = self;</span><br><span class="line"> // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户</span><br><span class="line"> authorizationController.presentationContextProvider = self;</span><br><span class="line"> // 在控制器初始化期间启动授权流</span><br><span class="line"> [authorizationController performRequests];</span><br><span class="line"> }else{</span><br><span class="line"> // 处理不支持系统版本</span><br><span class="line"> NSLog(@"该系统版本不可用Apple登录");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><ul><li><strong>注意:封装Sign In with Apple 登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行</strong></li><li>已经使用<code>Sign In with Apple</code>登录过<code>app</code>的用户<br>如果设备中存在<code>iCloud Keychain</code>凭证或者<code>AppleID</code>凭证,提示用户直接使用<code>TouchID</code>或<code>FaceID</code>登录即可,代码如下<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">// 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户</span><br><span class="line">- (void)perfomExistingAccountSetupFlows{</span><br><span class="line"> NSLog(@"///已经认证过了/////");</span><br><span class="line"> </span><br><span class="line"> if (@available(iOS 13.0, *)) {</span><br><span class="line"> // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制</span><br><span class="line"> ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];</span><br><span class="line"> // 授权请求AppleID</span><br><span class="line"> ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];</span><br><span class="line"> // 为了执行钥匙串凭证分享生成请求的一种机制</span><br><span class="line"> ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];</span><br><span class="line"> ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];</span><br><span class="line"> // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器</span><br><span class="line"> ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];</span><br><span class="line"> // 设置授权控制器通知授权请求的成功与失败的代理</span><br><span class="line"> authorizationController.delegate = self;</span><br><span class="line"> // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户</span><br><span class="line"> authorizationController.presentationContextProvider = self;</span><br><span class="line"> // 在控制器初始化期间启动授权流</span><br><span class="line"> [authorizationController performRequests];</span><br><span class="line"> }else{</span><br><span class="line"> // 处理不支持系统版本</span><br><span class="line"> NSLog(@"该系统版本不可用Apple登录");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><ol start="2"><li>获取授权码<br>获取授权码需要在代码中实现两个代理回调<code>ASAuthorizationControllerDelegate、ASAuthorizationControllerPresentationContextProviding</code>分别用于处理授权登录成功和失败、以及提供用于展示授权页面的<code>Window</code>,代码如下<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line">#pragma mark - delegate</span><br><span class="line">//@optional 授权成功地回调</span><br><span class="line">- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){</span><br><span class="line"> NSLog(@"授权完成:::%@", authorization.credential);</span><br><span class="line"> NSLog(@"%s", __FUNCTION__);</span><br><span class="line"> NSLog(@"%@", controller);</span><br><span class="line"> NSLog(@"%@", authorization);</span><br><span class="line"> </span><br><span class="line"> if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {</span><br><span class="line"> // 用户登录使用ASAuthorizationAppleIDCredential</span><br><span class="line"> ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;</span><br><span class="line"> NSString *user = appleIDCredential.user;</span><br><span class="line"> // 使用过授权的,可能获取不到以下三个参数</span><br><span class="line"> NSString *familyName = appleIDCredential.fullName.familyName;</span><br><span class="line"> NSString *givenName = appleIDCredential.fullName.givenName;</span><br><span class="line"> NSString *email = appleIDCredential.email;</span><br><span class="line"> </span><br><span class="line"> NSData *identityToken = appleIDCredential.identityToken;</span><br><span class="line"> NSData *authorizationCode = appleIDCredential.authorizationCode;</span><br><span class="line"> </span><br><span class="line"> // 服务器验证需要使用的参数</span><br><span class="line"> NSString *identityTokenStr = [[NSString alloc] initWithData:identityToken encoding:NSUTF8StringEncoding];</span><br><span class="line"> NSString *authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding];</span><br><span class="line"> NSLog(@"%@\n\n%@", identityTokenStr, authorizationCodeStr);</span><br><span class="line"> </span><br><span class="line"> // Create an account in your system.</span><br><span class="line"> // For the purpose of this demo app, store the userIdentifier in the keychain.</span><br><span class="line"> // 需要使用钥匙串的方式保存用户的唯一信息</span><br><span class="line">// [YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];</span><br><span class="line"> </span><br><span class="line"> }else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){</span><br><span class="line"> // 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性,如果不支持,可以忽略</span><br><span class="line"> // Sign in using an existing iCloud Keychain credential.</span><br><span class="line"> // 用户登录使用现有的密码凭证</span><br><span class="line"> ASPasswordCredential *passwordCredential = authorization.credential;</span><br><span class="line"> // 密码凭证对象的用户标识 用户的唯一标识</span><br><span class="line"> NSString *user = passwordCredential.user;</span><br><span class="line"> // 密码凭证对象的密码</span><br><span class="line"> NSString *password = passwordCredential.password;</span><br><span class="line"> </span><br><span class="line"> }else{</span><br><span class="line"> NSLog(@"授权信息均不符");</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">- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){</span><br><span class="line"> // Handle error.</span><br><span class="line"> NSLog(@"Handle error:%@", error);</span><br><span class="line"> NSString *errorMsg = nil;</span><br><span class="line"> switch (error.code) {</span><br><span class="line"> case ASAuthorizationErrorCanceled:</span><br><span class="line"> errorMsg = @"用户取消了授权请求";</span><br><span class="line"> break;</span><br><span class="line"> case ASAuthorizationErrorFailed:</span><br><span class="line"> errorMsg = @"授权请求失败";</span><br><span class="line"> break;</span><br><span class="line"> case ASAuthorizationErrorInvalidResponse:</span><br><span class="line"> errorMsg = @"授权请求响应无效";</span><br><span class="line"> break;</span><br><span class="line"> case ASAuthorizationErrorNotHandled:</span><br><span class="line"> errorMsg = @"未能处理授权请求";</span><br><span class="line"> break;</span><br><span class="line"> case ASAuthorizationErrorUnknown:</span><br><span class="line"> errorMsg = @"授权请求失败未知原因";</span><br><span class="line"> break;</span><br><span class="line"> </span><br><span class="line"> default:</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> NSLog(@"%@", errorMsg);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 告诉代理应该在哪个window 展示内容给用户</span><br><span class="line">- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){</span><br><span class="line"> NSLog(@"88888888888");</span><br><span class="line"> // 返回window</span><br><span class="line"> return [UIApplication sharedApplication].windows.lastObject;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>在授权登录成功回调中,我们可以拿到以下几类数据</li></ol><ul><li><strong>UserID:</strong><code>Unique, stable, team-scoped user ID</code>,苹果用户唯一标识符,该值在同一个开发者账号下的所有<code>App</code>下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来(这与国内的<code>微信、QQ、微博</code>等第三方登录流程基本一致)</li><li><strong>Verification data:</strong><code>Identity token, code</code>,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证,本次授权登录请求数据的有效性和真实性,详见<a href="https://developer.apple.com/documentation/signinwithapplerestapi">Sign In with Apple REST API</a></li><li><strong>Account information:</strong><code>Name, verified email</code>,苹果用户信息,包括全名、邮箱等,注意:<strong>如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号,而且记录过的苹果账号再次登录这些参数拿不到</strong></li></ul><ol start="3"><li>验证<br>关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果<code>API</code>去校验授权码<a href="https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens">Generate and validate tokens</a>。如果验证成功,可以根据<code>userIdentifier</code>判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录状态给<code>App</code></li></ol><ul><li>推荐验证步骤为:</li><li>服务端拿<code>authorizationCode</code>去苹果后台验证,验证地址<code>https://appleid.apple.com/auth/token</code>,苹果返回<code>id_token</code>,与客户端获取的<code>identityToken</code>值一样,格式如下<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "access_token": "一个token",</span><br><span class="line"> "token_type": "Bearer",</span><br><span class="line"> "expires_in": 3600,</span><br><span class="line"> "refresh_token": "一个token",</span><br><span class="line"> "id_token": "结果是JWT,字符串形式,identityToken"</span><br><span class="line">}</span><br></pre></td></tr></table></figure>另外授权<code>code</code>是有时效性的,且使用一次即失效</li><li>服务器拿到相应结果后,其中<code>id_token</code>是<code>JWT</code>数据,解码<code>id_token</code>,得到如下内容<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "iss":"https://appleid.apple.com",</span><br><span class="line"> "aud":"这个是你的app的bundle identifier",</span><br><span class="line"> "exp":1567482337,</span><br><span class="line"> "iat":1567481737,</span><br><span class="line"> "sub":"这个字段和客户端获取的user字段是完全一样的",</span><br><span class="line"> "c_hash":"8KDzfalU5kygg5zxXiX7dA",</span><br><span class="line"> "auth_time":1567481737</span><br><span class="line">}</span><br></pre></td></tr></table></figure>其中<code>aud</code>与你<code>app</code>的<code>bundleID</code>一致,<code>sub</code>就是授权用户的唯一标识,与手机端获得的<code>user</code>一致,服务器端通过对比<code>sub</code>字段信息是否与手机端上传的<code>user</code>信息一致来确定是否成功登录<br>该<code>token</code>的有效期是<code>10</code>分钟,具体后端验证参考附录</li></ul><p><a href="https://developer.apple.com/documentation/authenticationservices/adding_the_sign_in_with_apple_flow_to_your_app">附:官方示例代码 Swift 版</a><br><a href="https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple">附:What the Heck is Sign In with Apple?</a><br><a href="https://www.yuque.com/zhanglong/bb0s5d/cxbh7n">附:Sign In with Apple 从登陆到服务器验证</a><br><a href="https://blog.csdn.net/wpf199402076118/article/details/99677412">附:苹果授权登陆后端验证</a><br><a href="https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens">附:[官方文档] Generate and validate tokens</a><br><a href="https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple">附:[官方文档] App Store审核指南</a><br><a href="https://github.com/Gsl201600/SignInAppleDemo.git">附:SignInAppleDemo</a></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>mac 搭建基于Hexo-Github的Blog</title>
<link href="/2019/08/14/mac%E6%90%AD%E5%BB%BA%E5%9F%BA%E4%BA%8EHexo-Github%E7%9A%84Blog/"/>
<url>/2019/08/14/mac%E6%90%AD%E5%BB%BA%E5%9F%BA%E4%BA%8EHexo-Github%E7%9A%84Blog/</url>
<content type="html"><![CDATA[<ul><li>GitHubPages + Hexo:免费,使用简单,使用者众多</li></ul><p><strong>博客搭建</strong></p><ol><li><strong>创建 GitHub 仓库</strong><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.01.jpeg" alt=""></li></ol><ul><li>注意 Respository name 中一定要输入:<code>你的用户名.github.io</code>其他地方不用修改,然后直接点 ”Create repository“ 按钮完成创建即可</li></ul><ol start="2"><li><strong>安装博客需要的框架</strong></li></ol><ul><li>安装 Homebrew<br><code>$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"</code></li><li>查询Homebrew是否安装成功的命令<br><code>$ brew -v</code></li><li>安装 git<br><code>$ brew install git</code></li><li>查询git是否安装成功的命令<br><code>$ git –version</code></li><li>安装 node.js<br><code>$ brew install node</code></li><li>查询node是否安装成功的命令<br><code>$ node -v</code></li><li>安装 hexo<br><code>$ npm install -g hexo</code></li><li>查询hexo是否安装成功的命令<br><code>$ hexo -v</code></li></ul><ol start="3"><li><strong>安装博客相关插件</strong></li></ol><ul><li>自动部署到Github上的插件<br><code>$ npm install hexo-deployer-git --save</code></li><li>安装atom生成插件,便于感兴趣的小伙伴们订阅(RSS订阅)<br><code>$ npm install hexo-generator-feed --save</code><br>然后在本地Blog根目录下的_config.yml文件中,添加以下配置<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># Extensions</span><br><span class="line">## Plugins: http://hexo.io/plugins/</span><br><span class="line">#RSS订阅</span><br><span class="line">plugin: -hexo-generator-feed</span><br><span class="line">#Feed Atom</span><br><span class="line">feed:</span><br><span class="line"> type: atom</span><br><span class="line"> path: atom.xml</span><br><span class="line"> limit: 20</span><br></pre></td></tr></table></figure>在主题目录下的_config.yml目录下,添加如下配置<br><code>rss: /atom.xml</code></li><li>安装博客首页生成插件<br><code>$ npm install hexo-generator-index --save</code></li><li>安装tag生成插件<br><code>$ npm install hexo-generator-tag --save</code><br><strong>到此为止,安装完毕</strong></li></ul><ol start="4"><li><strong>创建博客、调试、发布</strong></li></ol><ul><li>创建本地一个目录,用来创建博客<br><code>$ hexo init Blog</code><br>执行成功后,会创建出一个名为Blog的文件夹</li><li>撰写博客内容<br>cd到Blog文件夹下,执行命令<br><code>$ hexo new firstBlog</code><br>在Blog/source/_posts中就会新建一个firstBlog.md的文件,然后你就可以编辑你的博客内容了,Visual Studio Code编辑器支持预览,还可以和印象笔记同步</li><li>本地调试<br><code>$ sudo hexo server 或 $ sudo hexo s</code><br>然后可以访问<code>http://localhost:4000</code>来查看结果</li><li>安装发布插件<br>在博客文件夹运行下面命令<br><code>$ npm install hexo-deployer-git --save</code><br>然后在_config.yml文件修改发布的配置,最后一行改为(注意替换yourusername)<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># Site</span><br><span class="line">title: 博客的名字</span><br><span class="line">subtitle: 博客副标题</span><br><span class="line">description: 博客描述</span><br><span class="line">author: 博客作者</span><br><span class="line">language: zh-Hans</span><br><span class="line">theme: next //安装的主题名称</span><br><span class="line">deploy:</span><br><span class="line"> type: git //使用Git 发布</span><br><span class="line"> repo: https://github.com/yourusername/yourusername.github.com.git //自己的Github仓库地址</span><br><span class="line"> branch: master</span><br></pre></td></tr></table></figure></li><li>运行生成发布<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo hexo g</span><br><span class="line">$ sudo hexo d</span><br></pre></td></tr></table></figure></li><li>如果改动了站点的源码,需要在发布之前<br><code>$ sudo hexo clean</code></li><li>如果成功了就可以通过<code>yourusername.github.com</code>或者<code>yourusername.github.io</code>来访问你的博客了</li></ul><ol start="5"><li><strong>配置博客分类内容</strong></li></ol><ul><li>新增资源分类页面<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">hexo new page about</span><br><span class="line">INFO Created: ~/blog/source/about/index.md</span><br><span class="line">// 打开修改为如下</span><br><span class="line">---</span><br><span class="line">title: about</span><br><span class="line">date: 2016-09-17 13:21:20</span><br><span class="line">tags: 代码库 // 标签</span><br><span class="line">comment: false // 添加这行关闭评论</span><br><span class="line">---</span><br><span class="line">here is something about me</span><br></pre></td></tr></table></figure></li><li>新添加的菜单需要翻译对应的中文<br>打开<code>\themes\next\languages</code>文件夹,创建主站配置语言的对应文件,如:zh-Hans.yml</li></ul><ol start="6"><li><strong>自定义博客</strong></li></ol><ul><li>更换主题<br>如果你对默认的主题不满意,可以通过克隆的方式,把别人的主题克隆到项目”/themes”路径下,喵神的主题在<a href="https://link.jianshu.com?t=https://github.com/monniya/hexo-theme-new-vno">这里</a>,Hexo也有<a href="https://link.jianshu.com?t=https://hexo.io/themes/">更多主题</a>,比如使用这一套<br><code>git clone https://github.com/iissnan/hexo-theme-next.git themes/next</code><br>然后在blog/themes文件夹下,会看到一个next文件夹,在next中有一个_config.yml文件,它就是主题配置文件,这里略过<a href="https://link.jianshu.com?t=http://theme-next.iissnan.com/getting-started.html#theme-settings">设置详情</a>;要设置博客主题的话,还是要回到根目录下(Blog文件夹下)的_config.yml文件中,将theme属性由landscape修改为next,还有注意配置的键值之间一定要有<strong>空格</strong>。<a href="https://link.jianshu.com?t=https://hexo.io/zh-cn/docs/configuration.html">更多设置</a><br>修改并保存之后,我们再执行命令<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g</span><br><span class="line">$ hexo d</span><br></pre></td></tr></table></figure>执行完毕以后,就可以在本地或者git上看到博客的新主题了,更多的主题可以<a href="https://link.jianshu.com/?t=https://www.zhihu.com/question/24422335">参考知乎</a></li><li><p>设置网页浏览次数<br>打开themes/你的主题/layout/_partial/footer.ejs添加即可</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># 脚本</span><br><span class="line"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></span><br><span class="line"></span><br><span class="line"># 标签</span><br><span class="line"># pv的方式,单个用户连续点击n篇文章,记录n次访问量</span><br><span class="line"><span id="busuanzi_container_site_pv"></span><br><span class="line"> 本站总访问量<span id="busuanzi_value_site_pv"></span>次</span><br><span class="line"></span></span><br><span class="line"># uv的方式,单个用户连续点击n篇文章,只记录1次访客数</span><br><span class="line"><span id="busuanzi_container_site_uv"></span><br><span class="line"> 总访客数<span id="busuanzi_value_site_uv"></span>人</span><br><span class="line"></span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.02.jpg" alt=""></p></li><li><p>评论功能<br>使用韩国来必力评论,需要注册账号<a href="https://www.livere.com/">详见官网</a><br>在主题配置文件下添加<br><code>page_comment: true</code><br>在<code>\themes\random\layout\_partial</code>文件夹中新建<code>livere.ejs</code>文件并添加如下代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><section class="livere" id="comments"></span><br><span class="line"> <!-- 来必力City版安装代码 --></span><br><span class="line"> <div id="lv-container" data-id="city" data-uid="MTAyM***注册成功官方返回的uid****xNzkwMw=="></span><br><span class="line"> <script type="text/javascript"></span><br><span class="line"> (function(d, s) {</span><br><span class="line"> var j, e = d.getElementsByTagName(s)[0];</span><br><span class="line"></span><br><span class="line"> if (typeof LivereTower === 'function') { return; }</span><br><span class="line"></span><br><span class="line"> j = d.createElement(s);</span><br><span class="line"> j.src = 'https://cdn-city.livere.com/js/embed.dist.js';</span><br><span class="line"> j.async = true;</span><br><span class="line"></span><br><span class="line"> e.parentNode.insertBefore(j, e);</span><br><span class="line"> })(document, 'script');</span><br><span class="line"> </script></span><br><span class="line"> <noscript> 为正常使用来必力评论功能请激活JavaScript</noscript></span><br><span class="line"> </div></span><br><span class="line"> <!-- City版安装代码已完成 --></span><br><span class="line"></section></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.03.jpeg" alt=""></p></li></ul><p>在<code>\themes\random\layout\post.ejs</code>文件中添加或修改如下代码<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><% if (page.comment || theme.page_comment) { %></span><br><span class="line"> <%- partial('_partial/livere',{}) %></span><br><span class="line"><% } %></span><br></pre></td></tr></table></figure></p><ul><li><p>顶部加载条<br>在<code>\themes\random\layout\_partial head.swig或head.ejs</code>文件中添加如下代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><script src="//cdn.bootcss.com/pace/1.0.2/pace.min.js"></script></span><br><span class="line"><link href="//cdn.bootcss.com/pace/1.0.2/themes/pink/pace-theme-flash.css" rel="stylesheet"></span><br><span class="line"> <style></span><br><span class="line"> .pace .pace-progress {</span><br><span class="line"> background: #1E92FB; /*进度条颜色*/</span><br><span class="line"> height: 3px;</span><br><span class="line"> }</span><br><span class="line"> .pace .pace-progress-inner {</span><br><span class="line"> box-shadow: 0 0 10px #1E92FB, 0 0 5px #1E92FB; /*阴影颜色*/</span><br><span class="line"> }</span><br><span class="line"> .pace .pace-activity {</span><br><span class="line"> border-top-color: #1E92FB; /*上边框颜色*/</span><br><span class="line"> border-left-color: #1E92FB; /*左边框颜色*/</span><br><span class="line"> }</span><br><span class="line"> </style></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.04.png" alt="添加在head里面"></p></li><li><p>安全运行天数<br>在<code>\themes\random\layout\_partial footer.swig或footer.ejs</code>页脚部分添加如下代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"># 标签</span><br><span class="line"><span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span></span><br><span class="line"># 脚本</span><br><span class="line"><script></span><br><span class="line"> var now = new Date(); </span><br><span class="line"> function createtime() { </span><br><span class="line"> var grt= new Date("07/08/2018 12:00:00");//此处修改你的建站时间或者网站上线时间 </span><br><span class="line"> now.setTime(now.getTime()+250); </span><br><span class="line"> days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); </span><br><span class="line"> hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours); </span><br><span class="line"> if(String(hnum).length ==1 ){hnum = "0" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum); </span><br><span class="line"> mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = "0" + mnum;} </span><br><span class="line"> seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum); </span><br><span class="line"> snum = Math.round(seconds); if(String(snum).length ==1 ){snum = "0" + snum;} </span><br><span class="line"> document.getElementById("timeDate").innerHTML = "本站已安全运行 "+dnum+" 天 "; </span><br><span class="line"> document.getElementById("times").innerHTML = hnum + " 小时 " + mnum + " 分 " + snum + " 秒"; </span><br><span class="line"> } </span><br><span class="line"> setInterval("createtime()",250);</span><br><span class="line"></script></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.05.png" alt=""></p></li><li><p>在每篇文章末尾统一添加“本文结束”标记<br>接着打开<code>\themes\vno\layout\post.ejs</code>文件,在post-body之后, post-footer之前添加如下代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><div></span><br><span class="line"> <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div></span><br><span class="line"></div> </span><br></pre></td></tr></table></figure></li><li>添加打赏功能,使用iframe嵌入打赏<br>把下面代码添加到你想要的位置,一般是在<code><article></code>标签内,如<code>\themes\vno\layout\post.ejs</code>的<article>标签内</li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.06.png" alt=""></p><p><code><iframe src="https://gsl201600.github.io/RewardCode" style="overflow-x:hidden;overflow-y:hidden; border:0xp none #fff; min-height:240px; width:100%;" frameborder="0" scrolling="no" allowtransparency="true"></iframe></code><br>把该项目添加到你的网站,修改相关参数为你的打赏码,即可实现<a href="https://github.com/Gsl201600/RewardCode">项目地址</a></p><ul><li>在Hexo博客上添加可爱的Live 2D模型<br>安装npm包<br><code>$ npm install --save hexo-helper-live2d</code><br>然后在Blog的配置文件<code>_config.yml</code>中添加如下配置,详细配置可以参考<a href="https://github.com/EYHN/hexo-helper-live2d/blob/master/README.zh-CN.md">文档</a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">live2d:</span><br><span class="line"> enable: true</span><br><span class="line"> scriptFrom: local</span><br><span class="line"> pluginRootPath: live2dw/</span><br><span class="line"> pluginJsPath: lib/</span><br><span class="line"> pluginModelPath: assets/</span><br><span class="line"> tagMode: false</span><br><span class="line"> debug: false</span><br><span class="line"> model:</span><br><span class="line"> use: live2d-widget-model-shizuku</span><br><span class="line"> display:</span><br><span class="line"> position: right</span><br><span class="line"> width: 150</span><br><span class="line"> height: 300</span><br><span class="line"> mobile:</span><br><span class="line"> show: true</span><br></pre></td></tr></table></figure>然后下载模型,模型名称可以到<a href="https://github.com/xiazeyu/live2d-widget-models">这里</a>参考,一些模型的预览可以在<a href="https://huaji8.top/post/live2d-plugin-2.0/">这里</a><br><code>$ npm install live2d-widget-model-shizuku</code><br>下载完之后,在Blog根目录中新建文件夹live2d_models,然后在node_modules文件夹中找到刚刚下载的live2d模型,将其复制到live2d_models中,然后编辑配置文件中的model.use项,将其修改为live2d_models文件夹中的模型文件夹名称<br>一切就绪之后,用hexo server命令启动服务器,稍等一下就可以看到右下角出现了一个可爱的萌萌哒的妹纸!不过因为所有东西都在Github上托管的原因,可能Live2D不能马上加载出来</li><li>Hexo操作指令一览<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean #清理缓存</span><br><span class="line">$ hexo generate #生成静态文件</span><br><span class="line">$ hexo server #启动本地服务器</span><br><span class="line">$ hexo deploy #部署</span><br><span class="line">或者</span><br><span class="line">$ hexo clean #清理缓存</span><br><span class="line">$ hexo g #生成静态文件</span><br><span class="line">$ hexo s #启动本地服务器</span><br><span class="line">$ hexo d #部署</span><br></pre></td></tr></table></figure></li><li>homebrew操作指令一览<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">// 安装 homebrew</span><br><span class="line">$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"</span><br><span class="line">// 卸载 homebrew</span><br><span class="line">$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"</span><br><span class="line">// 查看版本</span><br><span class="line">$ brew -v</span><br><span class="line">// 下载软件:brew install 软件名</span><br><span class="line">如:$ brew install htop, 安装htop</span><br><span class="line">// 如需要图形安装的软件 要加cask</span><br><span class="line">如:$ brew cask install google-chrome</span><br><span class="line">// 卸载软件:brew uninstall 软件名</span><br><span class="line">如: $ brew cask uninstall google-chrome</span><br><span class="line">// 软件搜索:brew search 软件名</span><br><span class="line">如: $ brew search google</span><br><span class="line">// 列出已安装的包</span><br><span class="line">$ brew list</span><br><span class="line">// 查看软件相关信息:brew info 软件名</span><br><span class="line">如:$ brew info google-chrome</span><br><span class="line">// 删除 Homebrew下载的包</span><br><span class="line">$ brew cleanup</span><br><span class="line">// 更新 Homebrew</span><br><span class="line">$ brew update</span><br></pre></td></tr></table></figure></li><li><strong>附:给Hexo搭建的博客增加百度和谷歌的搜索引擎验证</strong></li></ul><ol><li>验证站点<br>搜索引擎验证的方法有好几种,下面选择 <code>HTML标签验证</code> 验证方法,其他的方法有兴趣可以自己去试一下</li></ol><ul><li>首先打开 <a href="https://link.jianshu.com?t=http://www.baidu.com/search/url_submit.htm">百度搜索引擎验证</a> ,点击 <code>添加网站</code> ,输入自己的 <code>博客</code> 地址</li><li>输完后选择 <code>HTML标签验证</code> ,然后将下方的 <code>meta</code> 代码复制下来,网页先不要关</li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.07.png" alt=""></p><ul><li>重新开一个页面,打开<a href="https://link.jianshu.com/?t=https://www.google.com/webmasters/tools/home?hl=zh-CN">谷歌搜索引擎验证</a>,点击<code>添加属性</code>,一样输入自己的<code>博客</code>地址</li><li>输完后选择<code>备用方法</code>下的<code>HTML 标记</code>,然后将下方的<code>meta</code>代码复制下来,网页也不要关</li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.08.png" alt=""></p><ul><li>打开本地博客主题下的layout/_partial 文件夹,有一个名为head的文件,使用HTML编辑器打开,将刚才复制的两句meta代码粘贴进去</li><li>保存文件后,输入以下命令将博客重新部署到GitHub服务器<br><code>hexo clean && hexo g && hexo d</code></li><li>然后分别点击刚才百度、谷歌验证页面的验证按钮进行站点验证</li></ul><ol start="2"><li>生成站点地图</li></ol><ul><li>打开终端cd到本地博客目录下,输入以下命令安装sitmap插件<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-generator-sitemap --save</span><br><span class="line">npm install hexo-generator-baidu-sitemap --save</span><br></pre></td></tr></table></figure></li><li>打开本地博客目录下的_config.yml文件,修改url参数为你博客的首页地址,这样是为了保证能正确生成sitemap.xml文件中的地址<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">url: http://jonzzs.cn # 修改成你博客的首页地址</span><br><span class="line">root: /</span><br><span class="line">permalink: :year/:month/:day/:title/</span><br><span class="line">permalink_defaults:</span><br></pre></td></tr></table></figure></li><li>添加以下配置<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 自动生成sitemap</span><br><span class="line">sitemap: </span><br><span class="line"> path: sitemap.xml</span><br><span class="line">baidusitemap: </span><br><span class="line"> path: baidusitemap.xml</span><br></pre></td></tr></table></figure></li><li>输入以下命令重新部署博客<br><code>hexo clean && hexo g && hexo d</code></li></ul><ol start="3"><li>将站点地图提交谷歌</li></ol><ul><li>打开<a href="https://link.jianshu.com/?t=https://www.google.com/webmasters/tools/home?hl=zh-CN">谷歌站点控制台</a>进入站点控制台,先点击<code>测试</code>站点地图,测试通过后再点击<code>提交</code>站点地图</li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.14.09.png" alt=""></p><ul><li>百度主动推送<br>首先,在Hexo根目录下,安装本插件:<code>$ npm install hexo-baidu-url-submit --save</code><br>然后,同样在根目录下,把以下内容配置到_config.yml文件中:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">baidu_url_submit:</span><br><span class="line"> count: 3 ## 提交最新的3个链接</span><br><span class="line"> host: www.hui-wang.info ## 在百度站长平台中注册的域名</span><br><span class="line"> token: your_token ## 请注意这是您的秘钥, 所以请不要把博客源代码发布在公众仓库里!</span><br><span class="line"> path: baidu_urls.txt ## 文本文档的地址, 新链接会保存在此文本文档里</span><br></pre></td></tr></table></figure>其次,记得查看_config.ym文件中url的值, 必须包含是百度站长平台注册的域名,比如:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># URL</span><br><span class="line">url: http://www.hui-wang.info</span><br><span class="line">root: /</span><br><span class="line">permalink: :year/:month/:day/:title/</span><br></pre></td></tr></table></figure>最后,加入新的deployer:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">deploy:</span><br><span class="line">- type: git ## 这是原来的deployer</span><br><span class="line"> repo: https://github.com/yourusername/yourusername.github.com.git //自己的Github仓库地址</span><br><span class="line"> branch: master</span><br><span class="line">- type: baidu_url_submitter ## 这是新加的</span><br></pre></td></tr></table></figure>执行hexo deploy的时候,新的连接就会被推送了</li><li>百度自动推送<br>安装自动推送JS代码的网页,在页面被访问时,页面URL将立即被推送给百度,修改主题目录下的layout/post.swig文件,末尾添加自动推送代码,代码如下<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><script></span><br><span class="line">(function(){</span><br><span class="line"> var bp = document.createElement('script');</span><br><span class="line"> var curProtocol = window.location.protocol.split(':')[0];</span><br><span class="line"> if (curProtocol === 'https') {</span><br><span class="line"> bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; </span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> bp.src = 'http://push.zhanzhang.baidu.com/push.js';</span><br><span class="line"> }</span><br><span class="line"> var s = document.getElementsByTagName("script")[0];</span><br><span class="line"> s.parentNode.insertBefore(bp, s);</span><br><span class="line">})();</span><br><span class="line"></script></span><br></pre></td></tr></table></figure></li></ul><ol start="4"><li>用<code>site:域名</code>测试是否成功</li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 发布个人开源框架到CocoaPods</title>
<link href="/2019/08/09/iOS%E5%8F%91%E5%B8%83%E4%B8%AA%E4%BA%BA%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E5%88%B0CocoaPods/"/>
<url>/2019/08/09/iOS%E5%8F%91%E5%B8%83%E4%B8%AA%E4%BA%BA%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E5%88%B0CocoaPods/</url>
<content type="html"><![CDATA[<ul><li><strong>需要做的工作包括以下几点</strong></li></ul><ol><li>创建一个本地的仓库,将自己想要公开的代码搞进去</li><li>将自己的代码上传到远程公开仓库中去</li><li>创建一个pods 的描述文件 .podspec</li><li>修改.podspec描述文件中的相关的描述信息</li><li>将当前本地的.podspec文件传到CocoaPods官方的索引库中</li><li>测试一下,有没有上传到CocoaPods的索引库中</li><li>后期的升级维护</li></ol><ul><li><strong>具体详细的步骤如下</strong></li></ul><ol><li><ol start="2"><li>创建远程仓库注意点</li></ol></li></ol><ul><li>正规的仓库都有一个license文件,Pods依赖库对这个文件要求比较严格,需要有这个文件,建议使用<strong>MIT</strong>类型的license</li><li>代码版本要打tag(要在代码版本上传以后打tag)</li><li>pod 支持 .a静态库、.framework 以及文件,不一定要是可运行的工程里面的某个组件</li><li>放代码的仓库不一定非要是Git仓库,只要是可以获取到相关代码文件就可以,可以是SVN的,也可以是zip包,区别就是在podspec中的source项填写的内容不同</li></ul><ol start="3"><li>创建一个pods 的描述文件 .podspec<br><code>$ pod spec create MyViewExtension<这个名称一般和创建的项目名称一样就可以></code></li><li>修改.podspec描述文件中的相关的描述信息<br>详情可参考CocoaPods的官网的<a href="https://link.jianshu.com/?t=http://guides.cocoapods.org/syntax/podspec.html#group_root_specification">PodSpec语法</a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">Pod::Spec.new do |s|</span><br><span class="line"> # 项目的名称</span><br><span class="line"> s.name = "MyViewExtension" </span><br><span class="line"> # 项目的版本号,通过项目git的tag标签进行对应,这里的标签代表的版本 </span><br><span class="line"> s.version = "0.0.1" </span><br><span class="line"> # 项目简单的描述信息 </span><br><span class="line"> s.summary = "Just Testing." </span><br><span class="line"> # 项目的详细描述信息,注意,这里的文字的长度,一定要比上面的s.summary长,不然会认为格式不合格</span><br><span class="line"> s.description = <<-DESC</span><br><span class="line"> this project provide all kind of KeychainDeviceID for iOS developer </span><br><span class="line"> DESC</span><br><span class="line"> # 项目的网页主页信息,这里可以直接写自己的远程仓库的主页的地址</span><br><span class="line"> s.homepage = "https://github.com/RunOfTheSnail/MyViewExtension"</span><br><span class="line"> # 开源协议</span><br><span class="line"> s.license = "MIT" </span><br><span class="line"> # 作者信息 </span><br><span class="line"> s.author = { "zhangyan" => "17***[email protected]" } </span><br><span class="line"> # 这个比较重要,指的就是git的对应的远程仓库的地址以及版本号,版本号直接获取的是上面的s.version</span><br><span class="line"> # 项目地址,这里不支持ssh的地址,验证不通过,只支持HTTP和HTTPS,最好使用HTTPS</span><br><span class="line"> # Supported Keys:</span><br><span class="line"> # :git => :tag, :branch, :commit, :submodules</span><br><span class="line"> # :svn => :folder, :tag, :revision</span><br><span class="line"> # :hg => :revision</span><br><span class="line"> # :http => :flatten, :type, :sha256, :sha1</span><br><span class="line"> s.source = { :git => "https://github.com/RunOfTheSnail/MyViewExtension.git", :tag => s.version } </span><br><span class="line"> # 支持的平台及版本</span><br><span class="line"> s.platform = :ios, "11.0"</span><br><span class="line"> # 支持的ios最低版本</span><br><span class="line"> s.ios.deployment_target = "7.0"</span><br><span class="line"> # 如果是 Swift 的话指定 Swift 编译版本</span><br><span class="line"> # s.swift_version = "4.0"</span><br><span class="line"> # 必备项,代码源文件地址,如果有多个目录下则用逗号分开,否则"public_header_files"等不可用</span><br><span class="line"> s.source_files = "GSLXYKeychainDeviceID/KeychainDeviceID/**/*.{h,m}" </span><br><span class="line"> # 公开头文件地址</span><br><span class="line"> # s.public_header_files = "Pod/Classes/**/*.h"</span><br><span class="line"> # 所需的系统framework,多个用逗号隔开,不需要后缀名</span><br><span class="line"> # s.framework = "SomeFramework"</span><br><span class="line"> s.frameworks = "UIKit", "AnotherFramework"</span><br><span class="line"> # 需要弱链接的框架</span><br><span class="line"> # s.weak_framework = "Twitter"</span><br><span class="line"> # s.weak_frameworks = "Twitter", "SafariServices"</span><br><span class="line"> #项目依赖的库文件(这个是系统的库文件),不需要后缀名,比如sqlite,libz等.以lib开头的需要省略掉lib这三个字母.例如:libz需要简写为z否则报错</span><br><span class="line"> # s.library = "iconv"</span><br><span class="line"> # s.libraries = "iconv", "xml2"</span><br><span class="line"> # 第三方或自己创建的 .Framework的名称</span><br><span class="line"> # s.vendored_frameworks = "YostarLib.framework"</span><br><span class="line"> # 第三方或自己创建的 .a静态库的名称</span><br><span class="line"> # s.vendored_libraries = "libYostarStaticLib.a"</span><br><span class="line"> # 添加资源文件</span><br><span class="line"> # s.resource = "XXX/XXXX/**/*.bundle"</span><br><span class="line"> # s.resources = "XXX/XXXX/**/*.bundle"</span><br><span class="line"> # CocoaPods会把这个库配置成static framework,同时支持Swift和Objective-C</span><br><span class="line"> # s.static_framework = true</span><br><span class="line"> # 依赖关系,该项目所依赖的其他,当在加载的时候也会一块把相关的依赖的库加载下来,如果有多个需要填写多个</span><br><span class="line"> # s.dependency "JSONKit", "~> 1.4" </span><br><span class="line"> # 是否使用ARC,如果指定具体文件,则具体的文件使用ARC </span><br><span class="line"> s.requires_arc = true</span><br><span class="line"> # 指定项目配置,如HEADER_SEARCH_PATHS、OTHER_LDFLAGS等</span><br><span class="line"> # s.xcconfig = {"OTHER_LDFLAGS" => "-ObjC"} </span><br><span class="line">end</span><br></pre></td></tr></table></figure></li></ol><ul><li>修改完毕之后进行检验一下.podspec的格式有木有问题<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ pod lib lint</span><br><span class="line">完整lint格式</span><br><span class="line">$ pod lib lint --allow-warnings --use-libraries --verbose --no-clean --sources='http://10.11.180.29/mobileDevelopers/YZT-Loan-Pod-Spec.git'</span><br><span class="line">--verbose:打印错误</span><br><span class="line">--allow-warnings:允许警告,默认有警告的podspec会验证失败</span><br><span class="line">--fail-fast:遇到错误马上停止,默认会完成全过程再停止</span><br><span class="line">--use-libraries:如果自己私有库包含library,引用了.a、.framework,在验证和提交时需要加</span><br><span class="line">--no-clean:检查问题</span><br><span class="line">--sources:如果依赖了其他不包含在官方specs里的pod,则用它来指明源,比如依赖了某个私有库。多个值以逗号分隔</span><br></pre></td></tr></table></figure></li></ul><ol start="5"><li>将当前本地的podspec文件传到CocoaPods官方的索引库中<br>5.1. 第一次需要上传需要注册<br><code>$ pod trunk register zy_iOS2163.com 'zy' --description='macbook air' --verbose</code><br>你注册的时候需要替换邮箱和名字,加上 –verbose 可以看到详细信息,然后你会收到一份邮件,需要点击验证<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.09.01.png" alt="验证完成"></li></ol><p>5.2. 使用<code>$ pod trunk me</code>查看注册信息是否注册成功<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.09.02.jpeg" alt=""></p><p>5.3. 如果你的pod是由多人维护的,你也可以添加其他维护者<br><code>$ pod trunk add-owner JLC [email protected](即:pod trunk add-owner 库名 邮箱)</code><br>移除某个管理员<br><code>$ pod trunk remove-owner TTLockSDK 邮箱地址(即:pod trunk remove-owner 库名 邮箱)</code><br><code>$ pod trunk info TTLockSDK</code>查看库当前状态,版本,所有人等<br>5.4. 在提交 spec 文件前,如果发布的是 Swift 框架,需要指定 Swift 的版本,否则会报错,执行<code>$ echo "4.0" > .swift-version</code>即可指定版本为 4.0<br>5.5. 执行命令,将 .podspec push到cocopods的trunk中<br><code>$ pod trunk push MyViewExtension.podspec</code></p><ol start="6"><li>测试一下,有没有上传到CocoaPods的索引库中</li></ol><ul><li><code>$ pod search SGExtension</code>如果没有搜到,可能就是本地仓库没有更新</li><li>更新本地仓库<code>$ pod repo update</code>再次执行<code>$ pod search SGExtension</code></li><li>如果还搜索不到,执行<code>$ rm ~/Library/Caches/CocoaPods/search_index.json</code>这句话是移除已经生成的搜索目录缓存文件,移除之后,执行pod search会重新生成一份最新的缓存列表</li><li>查看本地索引库:<code>$ open ~/.cocoapods/repos</code></li></ul><ol start="7"><li>在个人项目中增加刚刚制作好的Podfile并使用<br>新建一个测试工程测试,用CocoaPods初始化项目,编辑Podfile文件:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#CocoaPods官方spec仓库</span><br><span class="line">source 'https://github.com/CocoaPods/Specs.git'</span><br><span class="line">#自己私有spec仓库</span><br><span class="line">source 'https://github.com/wenmobo/WBSpecs.git'</span><br><span class="line"></span><br><span class="line">platform :ios, '8.0'</span><br><span class="line"></span><br><span class="line">target 'TestDemo' do</span><br><span class="line"> #防Crash库</span><br><span class="line"> pod 'WBAvoidCrash'</span><br><span class="line">end</span><br></pre></td></tr></table></figure>编辑好podfile文件之后,终端执行:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ pod install 安装时使用,更新库使用update命令</span><br><span class="line">或</span><br><span class="line">$ pod update 更新时使用</span><br></pre></td></tr></table></figure></li><li>后期的升级维护<br>8.1. 更新远程公开库中的代码<br>8.2. 修改.podspec中的配置,version升级一个版本<br>8.3. 给当前的远程仓库的代码,重新打个tag,tag和.podspec的version一样<br>8.4. 远程仓库的代码更新完毕,接下来执行上面的 5.将当前本地的spec文件传到CocoaPods官方的索引库中<br>8.5. 检查使用上面的 6.测试一下,有没有上传到CocoaPods的索引库中</li></ol><ul><li><strong>移除索引版本,纠正意外推送</strong></li><li>移除该版本,然后重新push,在终端执行下面指令<br><code>$ pod trunk delete TTLockSDK 2.6.4(即:pod trunk delete 库名 版本号)</code></li><li>也可以放弃整个pod和所有版本<br><code>$ pod trunk deprecate TTLockSDK(即:pod trunk deprecate 库名)</code><br>确认时,回复一个”y”(小写字母 y)</li><li><strong>清理CocoaPods本地缓存</strong><br>特殊情况下,由于网络或者别的原因,通过CocoaPods下载的文件可能会有问题</li></ul><ol><li>手动删除(<code>~/Library/Caches/CocoaPods/Pods/Release</code>目录)</li><li>打开终端,输入<code>$ pod cache list</code>,会列出所有本地已经缓存的第三方库,在终端中输入<code>$ pod cache clean AAA</code>会删除AAA缓存库,使用<code>$ pod cache clean --all</code>清除所有缓存</li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS copy相关</title>
<link href="/2019/08/08/iOScopy%E7%9B%B8%E5%85%B3/"/>
<url>/2019/08/08/iOScopy%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<ol><li><p><strong>strong和copy的区别</strong><br>当我们用<code>@property</code>来声明属性变量时,编译器会自动为我们生成一个以下划线加属性名命名的实例变量<code>(@synthesize copyyStr = _copyyStr)</code>,并且生成其对应的<code>getter、setter</code>方法。当我们用<code>self.copyyStr = originStr</code>赋值时,会调用coppyStr的<code>setter</code>方法,而<code>_copyyStr = originStr</code>赋值时给_copyyStr实例变量直接赋值,并不会调用copyyStr的<code>setter</code>方法,而在<code>setter</code>方法中有一个非常关键的语句<br><code>_copyyStr = [copyyStr copy];</code></p><blockquote><p>结论:用self.copyyStr = originStr 赋值时,调用copyyStr的setter方法,setter方法对传入的copyyStr做了次深拷贝生成了一个新的对象赋值给_copyyStr,所以_copyyStr指向的地址和对象值都不再和originStr相同</p></blockquote></li><li><p><strong>assign与weak</strong></p></li></ol><ul><li>assign用来修饰基本数据类型,weak用来修饰OC对象</li><li>assign也能修饰OC对象,但是assign修饰的对象在该对象释放后,其指针依然存在,不会被置为nil,这就造成了一个很严重的问题:出现了野指针。当访问这个野指针时,指向了原地址,如果原地址被回收,就会造成程序的crash</li><li>用weak来修饰的话,对象释放的时候会把指针置为nil,从而避免了野指针的出现</li></ul><ol start="3"><li><p><strong>基本数据类型为什么可以使用assign</strong></p><blockquote><p>这就要扯到堆和栈的问题了,基本数据类型会被分配到栈空间,而栈空间是由系统自动管理分配和释放的,就不会造成野指针的问题</p></blockquote></li><li><p><strong>copy和mutableCopy</strong></p></li></ol><ul><li><p>容器类概念:NSArray、NSDictionary、NSSet为容器类型的对象</p></li><li><p>非容器类总结</p></li></ul><table><thead><tr><th>对象类型</th><th>不可变对象</th><th>可变对象</th></tr></thead><tbody><tr><td>copy</td><td>浅拷贝</td><td>深拷贝</td></tr><tr><td>mutableCopy</td><td>深拷贝</td><td>深拷贝</td></tr></tbody></table><ul><li>容器类型总结</li></ul><table><thead><tr><th>对象类型</th><th>不可变对象</th><th>可变对象</th></tr></thead><tbody><tr><td>copy</td><td>浅拷贝</td><td>深拷贝</td></tr><tr><td>mutableCopy</td><td>深拷贝</td><td>深拷贝</td></tr></tbody></table>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS UIButton之改变有效点击区域</title>
<link href="/2019/08/07/iOSUIButton%E4%B9%8B%E6%94%B9%E5%8F%98%E6%9C%89%E6%95%88%E7%82%B9%E5%87%BB%E5%8C%BA%E5%9F%9F/"/>
<url>/2019/08/07/iOSUIButton%E4%B9%8B%E6%94%B9%E5%8F%98%E6%9C%89%E6%95%88%E7%82%B9%E5%87%BB%E5%8C%BA%E5%9F%9F/</url>
<content type="html"><![CDATA[<ul><li>解决方案<br>通过重写<code>- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;</code><br>以改变按钮的有效点击区域<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{</span><br><span class="line"> if (_qi_clickAreaReduceValue > 0) {</span><br><span class="line"> if (_qi_clickAreaReduceValue >= CGRectGetWidth(self.bounds)/2) {</span><br><span class="line"> _qi_clickAreaReduceValue = CGRectGetWidth(self.bounds)/2;</span><br><span class="line"> }</span><br><span class="line"> CGRect bounds = CGRectInset(self.bounds, _qi_clickAreaReduceValue, _qi_clickAreaReduceValue);</span><br><span class="line"> return CGRectContainsPoint(bounds, point);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 获取bounds 实际大小</span><br><span class="line"> CGRect bounds = self.bounds;</span><br><span class="line"> // 若热区小于 44 * 44 则放大热区 否则保持原大小不变</span><br><span class="line"> CGFloat widthDelta = MAX(44.f - bounds.size.width, 0.f);</span><br><span class="line"> CGFloat heightDelta = MAX(44.f - bounds.size.height, 0.f);</span><br><span class="line"> // 扩大bounds</span><br><span class="line"> bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);</span><br><span class="line"> // 点击的点在新的bounds 中 就会返回YES</span><br><span class="line"> return CGRectContainsPoint(bounds, point);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS OTA无线分发安装App内测下载</title>
<link href="/2019/08/01/iOSOTA%E6%97%A0%E7%BA%BF%E5%88%86%E5%8F%91%E5%AE%89%E8%A3%85App%E5%86%85%E6%B5%8B%E4%B8%8B%E8%BD%BD/"/>
<url>/2019/08/01/iOSOTA%E6%97%A0%E7%BA%BF%E5%88%86%E5%8F%91%E5%AE%89%E8%A3%85App%E5%86%85%E6%B5%8B%E4%B8%8B%E8%BD%BD/</url>
<content type="html"><![CDATA[<p><strong>搭建步骤</strong></p><ol><li>应用<code>.ipa</code>文件,可以是<code>企业级签名</code>,也可以是<code>dev签名包</code></li><li><code>manifest.plist</code>文件,<code>plist</code>文件和<code>ipa</code>文件必须放在支持<code>https://</code>服务器上,而且必须是公网<code>ssl</code>,自签名及免费的<code>https</code>不可用(本文以<code>GitHub</code>为例)</li><li>下载应用的<code>html</code>页面</li></ol><hr><ul><li><code>manifest.plist</code>内容如下<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span><br><span class="line"><plist version="1.0"></span><br><span class="line"><dict></span><br><span class="line"> <key>items</key></span><br><span class="line"> <array></span><br><span class="line"> <dict></span><br><span class="line"> <key>assets</key></span><br><span class="line"> <array></span><br><span class="line"> <dict></span><br><span class="line"> <key>kind</key></span><br><span class="line"> <string>software-package</string></span><br><span class="line"> <key>url</key></span><br><span class="line"> <string>https://raw.githubusercontent.com/***/testIPA/master/Unity-iPhone.ipa</string></span><br><span class="line"> </dict></span><br><span class="line"> </array></span><br><span class="line"> <key>metadata</key></span><br><span class="line"> <dict></span><br><span class="line"> <key>bundle-identifier</key></span><br><span class="line"> <string>com.*****EN.****</string></span><br><span class="line"> <key>bundle-version</key></span><br><span class="line"> <string>0.1</string></span><br><span class="line"> <key>kind</key></span><br><span class="line"> <string>software</string></span><br><span class="line"> <key>title</key></span><br><span class="line"> <string>应用名称</string></span><br><span class="line"> </dict></span><br><span class="line"> </dict></span><br><span class="line"> </array></span><br><span class="line"></dict></span><br><span class="line"></plist></span><br></pre></td></tr></table></figure></li><li><p>将应用的<code>.ipa</code>文件,<code>manifest.plist</code>文件以及<code>html</code>下载页面,上传到<code>GitHub</code>上(支持<code>https://</code>服务器上)<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.01.01.png" alt=""></p></li><li><p><strong>注意:获取文件路径正确姿势</strong></p></li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.01.02.png" alt="获取.ipa和图片下载路径,他们的获取方式一样"><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.01.03.png" alt="获取manifest.plist文件路径"><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.08.01.04.png" alt="点击Raw按钮后,会进入以下页面"></p><ul><li>修改<code>manifest.plist</code>文件,将获取的下载路径,填写到<code>manifest.plist</code>文件中对应位置</li><li><code>html</code>简易下载页面,下载链接必须是这样的格式<code>itms-services://?action=download-manifest&url=一个plist文件的地址</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><!DOCTYPE HTML></span><br><span class="line"><html></span><br><span class="line"> <head></span><br><span class="line"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></span><br><span class="line"> <title>Install</title></span><br><span class="line"> </head></span><br><span class="line"> <body></span><br><span class="line"> <p align=center></span><br><span class="line"> <font size="10"></span><br><span class="line"> <a style="color:#69DEDA" href="itms-services://?action=download-manifest&url=https://raw.githubusercontent.com/***/testIPA/master/DownloadPlist.plist">点击安装</a></span><br><span class="line"> </font></span><br><span class="line"> </p></span><br><span class="line"> </body></span><br><span class="line"></html></span><br></pre></td></tr></table></figure></li><li><strong>附:如何直接在<code>github</code>上预览<code>html</code>网页效果</strong></li></ul><ol><li>将<code>github</code>上<code>demo</code>的<code>html</code>文件链接复制到,打开下面网址后出现的输入栏中,点击按钮即可。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://htmlpreview.github.io/</span><br></pre></td></tr></table></figure><a href="http://htmlpreview.github.io/">GitHub & BitBucket HTML Preview</a></li><li>在<code>HTML</code>文件的地址前面加上<code>htmlpreview.github.io/?</code></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">htmlpreview.github.io/?文件地址</span><br></pre></td></tr></table></figure><ol start="3"><li>在<code>github</code>上<code>demo</code>的仓库页面,点击<code>setting</code>按钮,找到<code>GitHub Pages</code>版块,选择<code>Source</code>为<code>master branch</code>,然后保存(或者在<code>Theme Chooser</code>处,点击<code>Change theme</code>,进入页面后选择主题保存,这样也可以)页面会出现一个新链接,在链接后面加上<code>demo</code>的<code>HTML</code>文件名,即可跳转到<code>demo</code>显示的页面</li></ol><ul><li><strong>附:Github如何上传超过100M的大文件</strong>(使用 <a href="https://github.com/git-lfs/git-lfs">Git LFS</a>)<br>使用方式:先安装<code>Git LFS</code>的客户端,然后在将要<code>push</code>的仓库里重新打开一个<code>bash</code>命令行:</li></ul><ol><li>只需设置1次<code>LFS</code>:<code>git lfs install</code></li><li>然后跟踪一下你要<code>push</code>的大文件的文件或指定文件类型<code>git lfs track "*.pdf"</code>当然还可以直接编辑<code>.gitattributes</code>文件</li><li>以上已经设置完毕,其余的工作就是按照正常的<code>add, commit, push</code>流程就可以了<br><strong>注:</strong><code>sourcetree</code>集成的有<code>LFS</code>使用也非常方便</li></ol><ul><li><strong>附:使用的SourceTree提交到git远程仓库的时候时出现了问题,一直处于这种状态<code>POST Git-receive-pack (chunked)</code></strong></li><li>解决办法<br>打开<code>SourceTree</code>右上角的,<code>设置 -> 高级 -> 编辑配置文件</code>,来打开配置文件,在配置文件中添加如下配置,最后保存,重新尝试推送到仓库就可以了<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[http] </span><br><span class="line"> postBuffer = 524288000</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 使用自定义字体</title>
<link href="/2019/07/24/iOS%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E4%BD%93/"/>
<url>/2019/07/24/iOS%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E4%BD%93/</url>
<content type="html"><![CDATA[<p><strong>1. 动态下载系统提供的中文字体</strong></p><ul><li>为了实现更好的字体效果,在应用中加入字体包问题:<blockquote><ol><li>字体文件比较大,会造成应用体积剧增</li><li>中文字体通常都是有版权的</li></ol></blockquote></li><li>动态下载中文字体的API可以动态的向iOS系统中添加字体文件,这些字体文件都是下载到系统的目录中,所以不会造成应用体积增加,字体文件下载后还可以在所有应用间共享。并且字体文件是iOS系统提供的,也免去了字体使用版权的问题</li><li><p>下载的时候需要使用的名字是 PostScript 名称,所以你要动态下载相应的字体的话,还需要使用 Mac 内自带的应用 “字体册 “来获得相应字体的 PostScript 名称。如下显示了从” 字体册 “中获取字体的 PostScript 名称的截图<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.24.01.png" alt=""></p></li><li><p>苹果提供的动态下载代码的 <a href="http://developer.apple.com/library/ios/#samplecode/DownloadFont/Listings/DownloadFont_ViewController_m.html">Demo 工程</a> 链接在这里。将此 Demo 工程下载下来,即可学习相应 API 的使用</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">// 1. 先判断该字体是否已经被下载下来了</span><br><span class="line">- (BOOL)isFontDownloaded:(NSString *)fontName {</span><br><span class="line"> UIFont* aFont = [UIFont fontWithName:fontName size:12.0];</span><br><span class="line"> if (aFont && ([aFont.fontName compare:fontName] == NSOrderedSame </span><br><span class="line"> || [aFont.familyName compare:fontName] == NSOrderedSame)) {</span><br><span class="line"> return YES;</span><br><span class="line"> } else {</span><br><span class="line"> return NO;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 2. 如果该字体下载过了,则可以直接使用。否则需要先准备下载字体 API 需要的一些参数</span><br><span class="line">// 用字体的 PostScript 名字创建一个 Dictionary</span><br><span class="line">NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys:fontName, kCTFontNameAttribute, nil];</span><br><span class="line">// 创建一个字体描述对象 CTFontDescriptorRef</span><br><span class="line">CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attrs);</span><br><span class="line">// 将字体描述对象放到一个 NSMutableArray 中</span><br><span class="line">NSMutableArray *descs = [NSMutableArray arrayWithCapacity:0];</span><br><span class="line">[descs addObject:(__bridge id)desc];</span><br><span class="line">CFRelease(desc);</span><br><span class="line"></span><br><span class="line">// 3. 准备好上面的descs变量后,则可以进行字体的下载了</span><br><span class="line">__block BOOL errorDuringDownload = NO;</span><br><span class="line">CTFontDescriptorMatchFontDescriptorsWithProgressHandler((CFArrayRef)descs, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef _Nonnull progressParameter){</span><br><span class="line"> </span><br><span class="line"> double progressValue = [[(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingPercentage] doubleValue];</span><br><span class="line"> </span><br><span class="line"> if (state == kCTFontDescriptorMatchingDidBegin) {</span><br><span class="line"> NSLog(@" 字体已经匹配 ");</span><br><span class="line"> } else if (state == kCTFontDescriptorMatchingDidFinish) { </span><br><span class="line"> if (!errorDuringDownload) {</span><br><span class="line"> NSLog(@" 字体 %@ 下载完成 ", fontName);</span><br><span class="line"> }</span><br><span class="line"> } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {</span><br><span class="line"> NSLog(@" 字体开始下载 ");</span><br><span class="line"> } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {</span><br><span class="line"> NSLog(@" 字体下载完成 ");</span><br><span class="line"> dispatch_async( dispatch_get_main_queue(), ^ {</span><br><span class="line"> // 可以在这里修改 UI 控件的字体</span><br><span class="line"> // self.label.font = [UIFont fontWithName:fontName size:12];</span><br><span class="line"> });</span><br><span class="line"> } else if (state == kCTFontDescriptorMatchingDownloading) {</span><br><span class="line"> NSLog(@" 下载进度 %.0f%%", progressValue);</span><br><span class="line"> } else if (state == kCTFontDescriptorMatchingDidFailWithError) {</span><br><span class="line"> NSError *error = [(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingError];</span><br><span class="line"> if (error != nil) {</span><br><span class="line"> _errorMessage = [error description];</span><br><span class="line"> } else {</span><br><span class="line"> _errorMessage = @"ERROR MESSAGE IS NOT AVAILABLE!";</span><br><span class="line"> }</span><br><span class="line"> // 设置标志</span><br><span class="line"> errorDuringDownload = YES;</span><br><span class="line"> NSLog(@" 下载错误: %@", _errorMessage);</span><br><span class="line"> }</span><br><span class="line"> return YES;</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p><strong>2. 导入TTF字体文件使用自定义字体</strong></p></li><li>下载字体导入工程(<strong>注意上文说的字体版权问题</strong>)</li><li><p>在 info.plist文件中告诉系统你想导入的字体文件<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.24.02.png" alt="方正兰亭纤黑"></p></li><li><p>设置字体到相应控件上(使用的字体名字是 PostScript 名称,上文已说过怎样获取 PostScript 名称)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 100, 300, 400)];</span><br><span class="line">label.text = @"汉体书写信息技术标准相容档案下载使用界面简单";</span><br><span class="line">label.numberOfLines = 0;</span><br><span class="line">UIFont *font = [UIFont fontWithName:@"FZLTXHK--GBK1-0" size:40];</span><br><span class="line">[self.view addSubview:label];</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iPhone 手机官方查询网站</title>
<link href="/2019/07/22/iPhone%E6%89%8B%E6%9C%BA%E5%AE%98%E6%96%B9%E6%9F%A5%E8%AF%A2%E7%BD%91%E7%AB%99/"/>
<url>/2019/07/22/iPhone%E6%89%8B%E6%9C%BA%E5%AE%98%E6%96%B9%E6%9F%A5%E8%AF%A2%E7%BD%91%E7%AB%99/</url>
<content type="html"><![CDATA[<p>今天给大家分享一些苹果官方网站,一定要收藏起来,以备不时之需。例如<code>激活查询、苹果服务器状态</code>等等。</p><ol><li>查看激活日期<br>当你新入手<code>iPhone 或其他苹果设备</code>,可以通过官方<code>查看保障状态</code>页面,在线查看激活日期,由此判断新设备是否被<code>提前激活</code>。<a href="https://checkcoverage.apple.com/cn/zh/">苹果官方保修服务查询页面</a><br>首先,打开手机<code>设置 → 通用 → 关于本机</code>,长按序列号,拷贝备用。然后,在Safari浏览器打开<code>苹果官方保修服务查询</code>页面,填入序列号进行查询。<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.22.01.jpeg" alt=""></li></ol><ul><li><p>查询结果包含你的<code>设备名称</code>,<code>购买日期</code>是否有效,<code>电话技术支持</code>是否有效。最重要的是<code>维修和服务保障情况</code>,会显示你的保修日期,这个日期往前一年,就是你设备的<code>激活日期</code>。<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.22.02.png" alt=""></p></li><li><p>示例图片保修日期是<code>2020年6月29日</code>,说明该设备激活日期是<code>2019 年6月29日</code></p></li></ul><ol start="2"><li>查看系统状态<br>使用iPhone过程中,有时候会遇到网络服务问题,例如App Store无法打开、iCloud无法同步等等。我们可以通过苹果的<code>系统状态</code>页面查看。<a href="https://www.apple.com/cn/support/systemstatus/">苹果官方服务系统状态</a><br>在苹果的<code>系统状态</code>页面,可以看到<code>可用服务、有故障的服务,以及已经修复的服务</code>。这些数据<code>实时更新</code>,可以清晰的了解当前<code>苹果服务状态</code>。<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.22.03.png" alt=""></li></ol><ul><li>链接为查看中国的苹果系统状态,你可以将链接中的cn换成其他国家代码,查看其他国家或地区的系统状态。例如cn换成us,查询美国的苹果系统状态</li></ul><ol start="3"><li>官方维修查询<br>如果你的iPhone出现了问题,需要送官方维修,在这之前,一定要在iPhone维修页面先了解一下情况。<a href="https://support.apple.com/zh-cn/iphone/repair/service">苹果官方iPhone 维修页面</a>,这里可以查看送修前的准备,以及一些项目的官方维修费用等等。<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.22.04.png" alt=""></li></ol>]]></content>
<tags>
<tag> 随笔 </tag>
</tags>
</entry>
<entry>
<title>iOS 后台运行方法</title>
<link href="/2019/07/19/iOS%E5%90%8E%E5%8F%B0%E8%BF%90%E8%A1%8C%E6%96%B9%E6%B3%95/"/>
<url>/2019/07/19/iOS%E5%90%8E%E5%8F%B0%E8%BF%90%E8%A1%8C%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<p>应用可以调用<code>UIApplication</code>的<code>beginBackgroundTaskWithExpirationHandler</code>方法,让应用最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存、发送统计数据等工作。代码如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">// AppDelegate.h文件</span><br><span class="line">@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundUpdateTask;</span><br><span class="line"></span><br><span class="line">// AppDelegate.m文件</span><br><span class="line">- (void)applicationDidEnterBackground:(UIApplication *)application {</span><br><span class="line"> [self beginBackgroundUpdateTask];</span><br><span class="line"> // 在这里加上你需要长久运行的代码</span><br><span class="line"> [self endBackgroundUpdateTask];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)beginBackgroundUpdateTask{</span><br><span class="line"> self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{</span><br><span class="line"> [self endBackgroundUpdateTask];</span><br><span class="line"> }];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)endBackgroundUpdateTask{</span><br><span class="line"> [[UIApplication sharedApplication] endBackgroundTask:self.backgroundUpdateTask];</span><br><span class="line"> self.backgroundUpdateTask = UIBackgroundTaskInvalid;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>Core Foundation对象的内存管理</title>
<link href="/2019/07/18/CoreFoundation%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"/>
<url>/2019/07/18/CoreFoundation%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/</url>
<content type="html"><![CDATA[<ul><li><code>Foundation</code>对象和<code>Core Foundation</code>对象重要的区别是<code>ARC</code>下的内存管理问题,在<code>非ARC</code>下两者都需要开发者手动管理内存,没有区别,但是在<code>ARC</code>下系统只会自动管理<code>Foundation</code>对象的释放,而不支持<code>Core Foundation</code>对象的管理,因此在<code>ARC</code>下两者进行转换后必须要确定对象是由开发者手动管理还是<code>ARC</code>系统管理,否则可能导致内存泄露</li><li>由于<code>ARC</code>不能管理<code>Core Foundation</code>对象的生命周期,所以<code>Core Foundation</code>对象和<code>Foundation</code>对象转换时,需要使用到<code>__bridge、__bridge_retained和__bridge_transfer</code>三个转换关键字</li></ul><ol><li><p><code>__bridge</code><br><code>CF</code>和<code>OC</code>对象转化时只涉及对象类型,不涉及对象所有权的转化,他的含义是,不改变对象的管理权所有者,本来由<code>ARC</code>管理的<code>Foundation</code>对象,转换成<code>Core Foundation</code>对象后依旧由<code>ARC</code>管理,本来有开发者手动管理的<code>Core Foundation</code>对象转换成<code>Foundation</code>对象后,继续由开发者手动管理</p></li><li><p><code>__bridge_transfer</code>也可以使用<code>CFBridgingRelease</code><br>用在将<code>Core Foundation</code>对象转换为<code>Foundation</code>对象,同时将对象内存管理权交给<code>ARC</code>,由<code>ARC</code>来代替我们管理内存</p></li><li><p><code>__bridge_retained</code>也可以使用<code>CFBridgingRetain</code><br>用在将<code>Foundation</code>对象转换为<code>Core Foundation</code>对象,同时将对象内存管理权交给我们,后续需要使用<code>CFRelease</code>或者相关方法来释放对象,需要我们手动来管理内存</p></li></ol><ul><li>如下例子为,网络请求中包含特殊字符的处理,报错信息为<br><code>Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL, NSUnderlyingError=0x7fa9b1d06120 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}}</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NSString * resultUrl = @"网络请求字段或者地址";</span><br><span class="line">// 处理请求中包含的特殊字符,如“+”</span><br><span class="line">NSString *endResutl = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)resultUrl, NULL, CFSTR("+"), kCFStringEncodingUTF8);</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>CocoaPods安装与使用步骤详解</title>
<link href="/2019/07/17/CocoaPods%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4%E8%AF%A6%E8%A7%A3/"/>
<url>/2019/07/17/CocoaPods%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4%E8%AF%A6%E8%A7%A3/</url>
<content type="html"><![CDATA[<blockquote><p><strong>目录</strong></p><ul><li>CocoaPods安装过程</li><li>CocoaPods的使用</li><li>删除cocoapods已导入项目的第三方库和移除项目中的cocoapods</li></ul></blockquote><ul><li><strong>CocoaPods安装过程</strong></li></ul><ol><li>安装并载入rvm环境</li></ol><ul><li><p>打开终端,输入指令<code>$ rvm -v</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.17.01.png" alt="rvm 环境检测"></p></li><li><p>安装rvm<br>安装指令是<code>$ curl -L https://get.rvm.io | bash -s stable</code><br>载入RVM环境:<code>$ source ~/.rvm/scripts/rvm</code><br>检查是否安装成功:<code>$ rvm -v</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.17.02.png" alt="安装rvm"></p></li><li><p>rvm命令安装Ruby环境<br>查看当前ruby版本<code>$ ruby -v</code>(检查当前版本,当ruby版本低于2.2.2时,安装cocoapods会报错)<br>查看所有ruby版本<code>$ rvm list known</code><br><code>$ rvm list known</code>命令会查询所有的ruby版本,找到最高版本号进行安装;若版本库里没有最新版本,输入:<code>$ rvm get head</code>升级到最新的存储库源版本<br>安装指定版本,输入指令:<code>$ rvm install 2.5.1</code> (选择较高版本)<br>等待漫长的下载,编译过程,完成以后,Ruby, Ruby Gems 就自动安装好了<br><strong>注意:如果安装失败请手动安装 Homebrew <code>$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"</code>再执行该<code>$ rvm install 2.5.1</code>命令,查询Homebrew是否安装成功的命令<code>$ brew -v</code></strong><br>查看已经安装的ruby版本<code>$ rvm list</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.17.03.png" alt="查看已安装的ruby版本"></p></li></ul><p>卸载一个已安装版本<code>$ rvm remove 2.5.1</code></p><ol start="2"><li>设置默认Ruby版本<br>安装好rvm之后可以指定特定Ruby版本为系统默认版本,输入命令:<code>$ rvm 2.5.1 --default</code>也可以指定其他版本号,前提是有用rvm install 安装过那个版本</li><li>检查更新ruby版本环境<br>cocoapods是用gem ruby实现的,想要使用它首先需要有gem ruby的环境。且Mac的macos系统默认已经可以运行ruby。建议gem ruby包环境升级到2.6.x以上</li></ol><ul><li>检查gem ruby版本号:<code>$ ruby -v</code> <code>$ gem -v</code><br>Gem是管理Ruby库和程序的标准包,如果它的版本过低也可能导致安装失败,解决的办法是更新gem版本</li><li>更新gem ruby版本<code>$ gem update --system</code></li></ul><ol start="4"><li>检查ruby源并移除</li></ol><ul><li>检查ruby源<code>$ gem sources -l</code><br>因为Ruby环境默认的的软件源<code>rubygems.org</code>被屏蔽了,国内那面永远需要翻越的墙,我们需要来修改更换源,把源切换至ruby-china</li><li>移除掉原有的源<code>$ gem sources --remove https://rubygems.org/</code></li><li>添加国内最新的源<code>$ gem sources -a https://gems.ruby-china.com</code></li><li>检查是否添加成功<code>$ gem sources -l</code><br>到这里就已经把Ruby环境安装成功</li></ul><ol start="5"><li>安装CocoaPods<br><code>$ gem install -n /usr/local/bin cocoapods</code></li><li>查看是否安装成功并更新</li></ol><ul><li>查看是否成功<code>$ pod --version</code></li><li>更新Podspec索引文件,创建本地索引库,如果没有报错,就说明一切安装成功了;这个过程需要一些时间<code>$ pod setup</code><br>查看本地索引库:<code>$ open ~/.cocoapods/repos</code></li><li><strong>CocoaPods的使用</strong></li></ul><ol><li>用Xcode创建一个工程,并创建podfile配置文件</li></ol><ul><li>进入项目目录<code>$ cd ~</code></li><li>创建Podfile文件<code>$ touch Podfile</code></li><li>打开编辑,使用<code>$ vi Podfile</code>输入i进入编辑,编辑完成后按 esc 然后输入<code>:wq</code>按回车键 ,保存并退出</li><li>编辑Podfile文件<br>我们可以在Podfile文件中写入需要用到的第三方库按如下格式:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">platform :ios, '9.0'</span><br><span class="line">use_frameworks!</span><br><span class="line">target 'TestDemo' do</span><br><span class="line">pod 'Alamofire', '~> 4.0.1'</span><br><span class="line">pod 'Kingfisher', '~> 3.1.1'</span><br><span class="line">end</span><br></pre></td></tr></table></figure>Swift的pod文件在于use_frameworks! 这一句是必须的,作用是把三方库打包成静态库,而oc是不需要的</li></ul><ol start="2"><li>安装依赖库<br><code>$ pod install</code> (后续添加框架可直接<code>pod update</code>)<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.17.04.png" alt=""></li></ol><ul><li><strong>删除cocoapods已导入项目的第三方库和移除项目中的cocoapods</strong></li></ul><ol><li>删除项目中已经由cocoapods配置好的第三方</li></ol><ul><li>打开项目中的Podfile文件</li><li>删除选中的pod Snapkit的命令行</li><li>打开终端cd到当前项目的根目录下重新执行<code>$ pod install --verbose --no-repo-update</code>或者直接<code>$ pod install</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.17.05.png" alt=""></li></ul><ol start="2"><li>删除项目中的cocoapods</li></ol><ul><li><p>手动删除</p><blockquote><ol><li>删除本地文件(Podfile、Podfile.lock、Pods文件夹、xcworkspace文件)</li><li>打开项目,在Frameworks文件夹下,删除Pods.xcconfig和libPods.a</li><li>进入项目Build Phases,删除Copy Pods Resources、Embed Pods Frameworks和Check Pods Manifest.lock 三项</li><li>删除了CocoaPod管理的第三方代码,在工程里面引用的第三方代码都会报错,需要删除对应的代码<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.07.17.06.jpg" alt=""></li></ol></blockquote></li><li><p>通过第三方插件删除</p><blockquote><ol><li>安装cocoapods-deintegrate命令:<code>$ sudo gem install cocoapods-deintegrate</code></li><li>然后到工程目录下面执行命令:<code>$ pod deintegrate</code></li><li>手动删除.xcworkspace,libPods.a,Podfile,Podfile.lock文件</li><li>如果想要重装的话保留Podfile,再执行命令:<code>$ pod install</code>就好了</li></ol></blockquote></li></ul><p><a href="https://www.jianshu.com/p/d298c21fc95a">附:最新CocoaPods安装与使用步骤详解</a><br><a href="https://gems.ruby-china.com">附:RubyGems 镜像</a></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS timer定时器正确使用方式</title>
<link href="/2019/07/11/iOStimer%E5%AE%9A%E6%97%B6%E5%99%A8%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F/"/>
<url>/2019/07/11/iOStimer%E5%AE%9A%E6%97%B6%E5%99%A8%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F/</url>
<content type="html"><![CDATA[<p><strong>1. 初始化,添加定时器前先移除</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[self.timer invalidate];</span><br><span class="line">self.timer = nil;</span><br><span class="line">self.timer = [NSTimer scheduledTimerWithTimeInterval:2.f target:self selector:@selector(lookforCard:) userInfo:nil repeats:YES];</span><br><span class="line">[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];</span><br></pre></td></tr></table></figure><br><strong>2. 释放timer</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[self.timer invalidate];</span><br><span class="line">self.timer = nil;</span><br></pre></td></tr></table></figure><br><strong>3. NSTimer不释放原因</strong></p><ul><li>原因是 Timer 添加到 Runloop 的时候,会被 Runloop 强引用;然后 Timer 又会有一个对 Target 的强引用(也就是 self )<blockquote><p>注意target参数的描述:<br>The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.<br>注意:文档中写的很清楚,timer对target会有一个强引用,直到timer is invalidated。也就是说,在timer调用 invalidate方法之前,timer对target一直都有一个强引用。这也是为什么控制器的dealloc 方法不会被调用的原因。<br>方法的文档介绍:<br>The receiver retains aTimer. To remove a timer from all run loop modes on which it is installed, send an invalidate message to the timer.<br>也就是说,runLoop会对timer有强引用,因此,timer修饰符是weak,timer还是不能释放,timer的target也就不能释放。</p></blockquote></li></ul><p><strong>4. 解决办法</strong> </p><ul><li><code>viewWillDisappear</code>或<code>viewDidDisappear</code>中 invalidate<br>这种方式是可以释放掉的,但如果我只是想在离开此页时要释放,进入下一页时不要释放,场景就不适用了<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- (void)viewWillDisappear:(BOOL)animated</span><br><span class="line">- (void)viewDidDisappear:(BOOL)animated</span><br></pre></td></tr></table></figure></li><li>添加一个NSTimer的分类,把target指给[NSTimer class],事件由加方法接收,然后把事件通过block传递出来<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">@interface NSTimer (Block)</span><br><span class="line"></span><br><span class="line">+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer *timer))block;</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation NSTimer (Block)</span><br><span class="line"></span><br><span class="line">+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer *timer))block{</span><br><span class="line"> NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(trigger:) userInfo:[block copy] repeats:repeats];</span><br><span class="line"> return timer;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (void)trigger:(NSTimer *)timer{</span><br><span class="line"> void(^block)(NSTimer *timer) = [timer userInfo];</span><br><span class="line"> if (block) {</span><br><span class="line"> block(timer);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure></li><li>使用示例<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">@interface SecondViewController ()</span><br><span class="line"></span><br><span class="line">@property (nonatomic, strong) NSTimer *timer;</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation SecondViewController</span><br><span class="line"></span><br><span class="line">- (void)viewDidLoad {</span><br><span class="line"> [super viewDidLoad];</span><br><span class="line"> __weak typeof(self) weakSelf = self;</span><br><span class="line"> self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {</span><br><span class="line"> [weakSelf doSomeThing];</span><br><span class="line"> }];</span><br><span class="line"> [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)dealloc {</span><br><span class="line"> [self.timer invalidate];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure><strong>5. invalidate方法注意事项</strong><blockquote><p>invalidate方法的介绍:<br>(1)This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.<br>(2)You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.<br>两点:<br>(1)invalidate方法是唯一能从runloop中移除timer的方式,调用invalidate方法后,runloop会移除对timer的强引用<br>(2)timer的添加和timer的移除(invalidate)需要在同一个线程中,否则timer可能不能正确的移除,线程不能正确退出</p></blockquote></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 简单日志系统</title>
<link href="/2019/07/03/iOS%E7%AE%80%E5%8D%95%E6%97%A5%E5%BF%97%E7%B3%BB%E7%BB%9F/"/>
<url>/2019/07/03/iOS%E7%AE%80%E5%8D%95%E6%97%A5%E5%BF%97%E7%B3%BB%E7%BB%9F/</url>
<content type="html"><![CDATA[<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#define YostarDebugLogLevel(level, fmt, ...) \</span><br><span class="line">[YostarDebugLog logLevel:level file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__ format:(fmt), ##__VA_ARGS__]</span><br><span class="line"></span><br><span class="line">#define YostarDebugLog(fmt, ...) \</span><br><span class="line">YostarDebugLogLevel(YostarDebugLogLevelInfo, (fmt), ##__VA_ARGS__)</span><br><span class="line"></span><br><span class="line">#define YostarDebugWarningLog(fmt, ...) \</span><br><span class="line">YostarDebugLogLevel(YostarDebugLogLevelWarning, (fmt), ##__VA_ARGS__)</span><br><span class="line"></span><br><span class="line">#define YostarDebugErrorLog(fmt, ...) \</span><br><span class="line">YostarDebugLogLevel(YostarDebugLogLevelError, (fmt), ##__VA_ARGS__)</span><br><span class="line"></span><br><span class="line">typedef NS_ENUM(NSUInteger, YostarDebugLogLevel) {</span><br><span class="line"> YostarDebugLogLevelInfo = 1,</span><br><span class="line"> YostarDebugLogLevelWarning,</span><br><span class="line"> YostarDebugLogLevelError</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">@interface YostarDebugLog : NSObject</span><br><span class="line"></span><br><span class="line">+ (BOOL)isDebugLogEnabled;</span><br><span class="line"></span><br><span class="line">+ (void)enableDebugLog:(BOOL)enableLog;</span><br><span class="line"></span><br><span class="line">+ (void)logLevel:(NSInteger)level file:(const char *)file function:(const char *)function line:(NSUInteger)line format:(NSString *)format, ...;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">static BOOL _enableLog;</span><br><span class="line">+ (void)initialize{</span><br><span class="line"> _enableLog = NO;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (BOOL)isDebugLogEnabled{</span><br><span class="line"> return _enableLog;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (void)enableDebugLog:(BOOL)enableLog{</span><br><span class="line"> _enableLog = enableLog;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">static id sharedInstance = nil;</span><br><span class="line">+ (instancetype)sharedInstance{</span><br><span class="line"> static dispatch_once_t onceToken;</span><br><span class="line"> dispatch_once(&onceToken, ^{</span><br><span class="line"> sharedInstance = [[self alloc] init];</span><br><span class="line"> });</span><br><span class="line"> return sharedInstance;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (void)logLevel:(NSInteger)level file:(const char *)file function:(const char *)function line:(NSUInteger)line format:(NSString *)format, ...{</span><br><span class="line"> @try {</span><br><span class="line"> //参数链表指针</span><br><span class="line"> va_list args;</span><br><span class="line"> //遍历开始</span><br><span class="line"> va_start(args, format);</span><br><span class="line"> NSString *message = [[NSString alloc] initWithFormat:format arguments:args];</span><br><span class="line"> [self.sharedInstance logMessage:message level:level file:file function:function line:line];</span><br><span class="line"> //结束遍历</span><br><span class="line"> va_end(args);</span><br><span class="line"> } @catch (NSException *exception) {</span><br><span class="line"> NSLog(@"⚠️WARN::%@", exception);</span><br><span class="line"> } @finally {</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">- (void)logMessage:(NSString *)message level:(NSInteger)level file:(const char *)file function:(const char *)function line:(NSUInteger)line{</span><br><span class="line"> NSString *logMessage = [NSString stringWithFormat:@"[YostarLog][%@][line:%lu]: %s %s %@", [self descriptionForLevel:level], (unsigned long)line, function, "", message];</span><br><span class="line"> if (_enableLog) {</span><br><span class="line"> NSLog(@"%@", logMessage);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (NSString *)descriptionForLevel:(YostarDebugLogLevel)level{</span><br><span class="line"> NSString *desc = nil;</span><br><span class="line"> switch (level) {</span><br><span class="line"> case YostarDebugLogLevelInfo:</span><br><span class="line"> desc = @"INFO";</span><br><span class="line"> break;</span><br><span class="line"> case YostarDebugLogLevelWarning:</span><br><span class="line"> desc = @"⚠️WARN";</span><br><span class="line"> break;</span><br><span class="line"> case YostarDebugLogLevelError:</span><br><span class="line"> desc = @"❌ERROR";</span><br><span class="line"> break;</span><br><span class="line"> default:</span><br><span class="line"> desc = @"UNKNOW";</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> return desc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS IAP安全性问题汇总</title>
<link href="/2019/06/26/iOSIAP%E5%AE%89%E5%85%A8%E6%80%A7%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/"/>
<url>/2019/06/26/iOSIAP%E5%AE%89%E5%85%A8%E6%80%A7%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/</url>
<content type="html"><![CDATA[<p><strong>1. 常用的攻击方式</strong></p><ul><li>劫持apple server攻击</li><li>重复验证攻击</li><li>跨app攻击</li><li>换价格攻击</li><li>歧义攻击</li><li>中间人攻击</li></ul><p><strong>2. 讲解攻击方式及处理</strong></p><ul><li>劫持apple server攻击</li></ul><blockquote><p>通过dns污染,让客户端通过假的apple_server进行verify,从而认为自己支付成功。这个主要针对客户端验证发货的方式,如果是服务端验证,就没效果了</p></blockquote><ul><li>重复验证攻击</li></ul><blockquote><p>因为同一个receipt,如果第一次验证成功,那么之后每次验证都会成功。如果服务端没有判重机制,就会导致一个receipt被当做多次充值处理。</p></blockquote><blockquote><p>为了预防这种情况,我们可以将receipt做一次md5得到receipt_md5, 每次发送充值请求的时候就按照receipt_md5判重,如果重复就停止商品发放;或者根据解析得到的信息进行判重</p></blockquote><ul><li>跨app攻击</li></ul><blockquote><p>通过在别的app中拿到receipt,然后发送到我们app中。因为这个receipt是合法的而且apple不会验证请求的源,所以这个receipt是可以验证通过的</p></blockquote><blockquote><p>对于这种情况,我们可以判断apple verify的返回值apple_callback_data中对应的bundle_id和我们app的bundle_id是否一样来进行验证</p></blockquote><ul><li>换价格攻击</li></ul><blockquote><p>在同一个app中,用低价商品的receipt伪造购买高价商品。这时候bundle_id和我们app的bundle_id是一致的</p></blockquote><blockquote><p>针对这种情况, 我们可以从apple verify的返回值apple_callback_data中拿到对应的product_id, 并按照product_id来进行充值。不要信任客户端的product_id</p></blockquote><ul><li>歧义攻击</li></ul><blockquote><p>在iOS6的时候,status=0表示此次支付成功,而现在变为status=0只表示receipt整体上合法。对iOS7即使是一个过期订单,也会返回status=0,如果还按照iOS6的逻辑处理,就会导致假充值</p></blockquote><blockquote><p>针对iOS7,我们应该不只通过status,还要通过in_app中的内容,来决定如何发放商品</p></blockquote><blockquote><p>For iOS 6 style transaction receipts, the status code reflects the status of the specific transaction’s receipt.<br>For iOS 7 style app receipts, the status code is reflects the status of the app receipt as a whole. For example, if you send a valid app receipt that contains an expired subscription, the response is 0 because the receipt as a whole is valid.</p></blockquote><ul><li>中间人攻击</li></ul><blockquote><p>伪造apple server,将我们的支付请求转发到真的apple_server,拿到合法的receipt,并弄个假的receipt给客户端。这样就拿到一个合法的凭证。利用这个合法的receipt,伪造别人充值的请求,从而达到帮别人充值的目的</p></blockquote><blockquote><p>针对中间人攻击,最重要的是保证a用户的支付receipt,不能被b用户使用。但是apple为了保护隐私,receipt中没有任何用户的个人信息,这就需要我们自己来保证。目前我们可以用加密的手段来做这个保证</p></blockquote><ul><li>最后给出一个apple_callback_data的例子<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "status": 0,</span><br><span class="line"> "environment": "Production",</span><br><span class="line"> "receipt": {</span><br><span class="line"> "download_id": 75017873837267,</span><br><span class="line"> "adam_id": 1149703708,</span><br><span class="line"> "request_date": "2017-01-13 06:57:20 Etc/GMT",</span><br><span class="line"> "app_item_id": 1149703708,</span><br><span class="line"> "original_purchase_date_pst": "2016-11-17 18:57:09 America/Los_Angeles",</span><br><span class="line"> "version_external_identifier": 820252187,</span><br><span class="line"> "receipt_creation_date": "2017-01-13 05:04:52 Etc/GMT",</span><br><span class="line"> "in_app": [</span><br><span class="line"> {</span><br><span class="line"> "is_trial_period": "false",</span><br><span class="line"> "purchase_date_pst": "2017-01-12 21:04:52 America/Los_Angeles",</span><br><span class="line"> "original_purchase_date_pst": "2017-01-12 21:04:52 America/Los_Angeles",</span><br><span class="line"> "product_id": "com.lucky917.live.gold.1.555",</span><br><span class="line"> "original_transaction_id": "350000191094279",</span><br><span class="line"> "original_purchase_date": "2017-01-13 05:04:52 Etc/GMT",</span><br><span class="line"> "original_purchase_date_ms": "1484283892000",</span><br><span class="line"> "purchase_date": "2017-01-13 05:04:52 Etc/GMT",</span><br><span class="line"> "purchase_date_ms": "1484283892000",</span><br><span class="line"> "transaction_id": "350000191094279",</span><br><span class="line"> "quantity": "1"</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> "original_purchase_date_ms": "1479437829000",</span><br><span class="line"> "original_application_version": "26",</span><br><span class="line"> "original_purchase_date": "2016-11-18 02:57:09 Etc/GMT",</span><br><span class="line"> "request_date_ms": "1484290640800",</span><br><span class="line"> "bundle_id": "com.lucky917.ios.Live",</span><br><span class="line"> "receipt_creation_date_pst": "2017-01-12 21:04:52 America/Los_Angeles",</span><br><span class="line"> "application_version": "32",</span><br><span class="line"> "request_date_pst": "2017-01-12 22:57:20 America/Los_Angeles",</span><br><span class="line"> "receipt_creation_date_ms": "1484283892000",</span><br><span class="line"> "receipt_type": "Production"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS IAP支付常见问题汇总与解决</title>
<link href="/2019/06/19/iOSIAP%E6%94%AF%E4%BB%98%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E5%86%B3/"/>
<url>/2019/06/19/iOSIAP%E6%94%AF%E4%BB%98%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E5%86%B3/</url>
<content type="html"><![CDATA[<p><strong>1. 获取不到商品信息的原因</strong></p><ul><li>沙盒的测试账号和你请求商品信息没有关系</li><li><code>iTunes Connect</code>里面对应账号的<code>协议、税务和银行业务</code>信息有没有填完整,填好的应该是这个样子<code>这个很容易疏忽,务必检查</code></li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.06.19.01.png" alt="银行税务信息填写完整状态"><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.06.19.02.png" alt="银行税务信息未填写"></p><ul><li>确认证书是否添加<code>IAP</code>支付功能<code>默认创建的证书是包含该项的</code></li><li>确定是真机测试且手机没有越狱<code>大部分越狱手机也可以测试,深度越狱破坏系统的可能无法调起支付</code></li><li>确定内购商品添加到了需要内购功能的<code>App</code>中</li><li>确定当前运行的<code>App</code>的<code>Bundle ID</code>和后台配置的<code>App</code>的<code>Bundle ID</code>是一致的</li><li>可以尝试先删除旧<code>App</code>,再重新编译生成新的,避免新<code>App</code>未覆盖错误</li><li>如果上线后发现线上包请求不到商品信息,一般发生于首次提交<code>App</code>或添加新商品,可能是苹果缓存的<code>bug</code>,当你的<code>App</code>通过审核以后,你发现在生产环境下获取不到商品,这是因为<code>App</code>虽然过审核了,但是内购商品还没有正式添加到苹果的服务器里,耐心等待一段时间就可以啦,或者去苹果后台刷新配置商品信息列表,然后等待一天左右时间大概就可以了</li></ul><p><strong>2. 如果请求到了商品信息,也发送了购买请求,但是监听购买结果的方法就是不执行</strong></p><ul><li>可以检查一下,是否在工具类初始化的时候,添加了监听,添加监听代码如下</li><li>注:支付工具类一般用单例模式,避免创建多个对象或者对象提前释放,导致苹果回调不会调用支付失败,或者使用<code>self</code>全局化支付工具类对象,不可使支付工具类对象局部变量化<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">#pragma mark - 单例方法</span><br><span class="line">static IAPPayManager* instance = nil;</span><br><span class="line">+ (instancetype)sharedInstance{</span><br><span class="line"> static dispatch_once_t onceToken = 0;</span><br><span class="line"> dispatch_once(&onceToken, ^{</span><br><span class="line"> instance = [[IAPPayManager alloc] init];</span><br><span class="line"> });</span><br><span class="line"> return instance;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#pragma mark - 重载初始化方法,注册用于处理支付回调的Observer</span><br><span class="line">- (instancetype)init{</span><br><span class="line"> self = [super init];</span><br><span class="line"> if (self) {</span><br><span class="line"> [self registerObserver];</span><br><span class="line"> }</span><br><span class="line"> return self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)registerObserver{</span><br><span class="line"> if(_isAddObserver){</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> [[SKPaymentQueue defaultQueue] addTransactionObserver:self];</span><br><span class="line"> _isAddObserver = true;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><strong>3. IAP审核环境</strong></li><li>苹果在审核<code>App</code>时,只会在<code>sandbox</code>环境购买,其产生的购买凭证,也只能连接苹果的测试验证服务器,审核时后台要保证沙盒测试环境开放,以免服务器无法验证通过<code>IAP</code>购买,造成<code>App</code>审核被拒</li><li><code>TestFlight</code>测试时也是走的<code>sandbox</code>环境购买</li></ul><p><strong>4. 只要不是红色的状态都是可以进行支付测试的,元数据丢失是因为,在增加内购项目的时候,没有填写完全,产品ID是唯一的,假如你删除了一个内购项目,那么这个产品ID就不能用了,所以填写要慎重</strong><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.06.19.03.png" alt="配置内购商品"></p><p><strong>5. 沙盒测试账号相关</strong><br>用沙盒账号测试支付的包,只能是<code>adhoc</code>签名证书或者<code>develop</code>签名证书打的包,不能是从<code>AppStore</code>或者<code>TestFlight</code>上下载的,还没上线之前<code>App</code>并没有地区之分,沙盒账号随便哪个地区都可以用来测试,弹出的购买提示框会根据当前沙盒账号<code>AppleID</code>的地区显示语言的</p><ul><li>注册沙盒测试账号时,提示报错<code>Unknown Errors while creating Sandbox Tester, Please check Error Log, email=a***[email protected]</code><br>解决方案:把你的密码设置的复杂点,比如包含数字、字母混大小写等</li></ul><p><strong>6. 支付时提示<code>您已购买此App内购买项目。此项目将免费恢复</code>问题</strong><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.06.19.04.png" alt=""></p><p>此提示说明<code>iTunes</code>订单被卡住,属于<code>苹果ID</code>支付问题,暂时可先选择其他额度进行支付,也可联系苹果的客服人员删除你异常的订单,打开浏览器进入<a href="http://www.apple.com/cn/support/contact">Apple官方支持</a><br>或者可能是<code>addTransactionObserver:</code>调用多次造成,注意要保证一个生命周期只能调用一次该方法</p><p><strong>7. 验证服务器地址和需要的参数说明</strong></p><table><thead><tr><th>Key</th><th>Value</th><th>是否必须</th></tr></thead><tbody><tr><td><code>receipt-data</code></td><td>The base64 encoded receipt data</td><td>是</td></tr><tr><td><code>password</code></td><td>Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string)</td><td>否,仅用于自动续订,获取方法见共享密钥附录</td></tr><tr><td><code>exclude-old-transactions</code></td><td>Only used for iOS7 style app receipts that contain auto-renewable or non-renewing subscriptions. If value is true, response includes only the latest renewal transaction for any subscriptions</td><td>否,仅用于自动续订或非续订订阅的iOS 7样式的应用收据</td></tr></tbody></table><ul><li>在测试服务器中,发送<code>receipt</code>到苹果的测试服务器<a href="https://sandbox.itunes.apple.com/verifyReceipt">https://sandbox.itunes.apple.com/verifyReceipt</a>验证</li><li>在正式服务器中<code>已上线Appstore</code>,发送<code>receipt</code>到苹果的正式服务器<a href="https://buy.itunes.apple.com/verifyReceipt">https://buy.itunes.apple.com/verifyReceipt</a>验证</li><li>当我们把应用提交给苹果审核时,苹果也是在<code>sandbox</code>环境购买,其产生的购买凭证,也只能连接苹果的测试验证服务器,所以我们可以先发到苹果的正式服务器验证,如果苹果返回<code>21007</code>,则再一次连接测试服务器进行验证</li></ul><p><strong>8. 苹果返回状态码</strong></p><table><thead><tr><th>Status</th><th>描述</th></tr></thead><tbody><tr><td>0</td><td>App Store 验证成功</td></tr><tr><td>21000</td><td>App Store不能读取你提供的JSON对象</td></tr><tr><td>21002</td><td>receipt-data属性中的数据格式错误或丢失</td></tr><tr><td>21003</td><td>receipt无法通过验证</td></tr><tr><td>21004</td><td>提供的共享密码与帐户的文件共享密码不匹配</td></tr><tr><td>21005</td><td>receipt服务器当前不可用</td></tr><tr><td>21006</td><td>该收据有效,但订阅已过期,当此状态代码返回到您的服务器时,收据数据也会被解码并作为响应的一部分返回,仅针对自动续订的iOS 6样式交易收据返回</td></tr><tr><td>21007</td><td>receipt是Sandbox receipt,但却发送至生产系统的验证服务</td></tr><tr><td>21008</td><td>receipt是生产receipt,但却发送至Sandbox环境的验证服务</td></tr><tr><td>21010</td><td>此收据无法授权,就像从未进行过购买一样对待</td></tr></tbody></table><ul><li><p>关于苹果服务器验证返回<code>21004</code>的问题说明<br>在购买类型是自动续订时,服务端做验证就要传入这个共享密钥,传入字段为<code>password</code>,共享密钥获取见附录,如果你们的商品不是自动续订,建议不要传入该字段,否则传入内容不正确可能会导致苹果返回<code>21004</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.06.19.05.png" alt=""></p></li><li><p>关于苹果服务器验证返回<code>21002</code>的问题说明<br>如果你们使用<code>key=value&key=value</code>拼接并<code>UTF8</code>编码的方式发送<code>post</code>请求,有可能导致后台接收的数据,里面的<code>+</code>号,变成<code>空格</code><br><strong>解决方案:</strong></p></li></ul><ol><li>客户端修改,可以参考如下方式解决,建议客户端处理</li><li>如果是线上问题,服务端可以临时修改,当收到<code>21002</code>时,对<code>receipt</code>进行处理<code>String newReceipt = receipt.replace(" ", "+");</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">if (parameter) {</span><br><span class="line"> NSMutableString * temp = [NSMutableString string];</span><br><span class="line"> NSEnumerator * keyEnum = [parameter keyEnumerator];</span><br><span class="line"> id key;</span><br><span class="line"> while (key = [keyEnum nextObject]) {</span><br><span class="line"> NSString * keyValueFormat = [NSString stringWithFormat:@"%@=%@&", key, [parameter valueForKey:key]];</span><br><span class="line"> [temp appendString:keyValueFormat];</span><br><span class="line"> }</span><br><span class="line"> NSString * result = [temp substringToIndex:temp.length - 1];</span><br><span class="line"> // 处理请求体中包含的如特殊字符,如“+”</span><br><span class="line"> NSString *endResutl = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)result, NULL, CFSTR("+"), kCFStringEncodingUTF8);</span><br><span class="line"> //把参数放到请求体内</span><br><span class="line"> request.HTTPBody = [endResutl dataUsingEncoding:NSUTF8StringEncoding];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><strong>9. 国内连接苹果服务器的稳定性</strong><br>开发之初,苹果方就很负责的告知:我们的服务器不稳定。真正开发之后,发现苹果方果然是很负责的,不仅是不稳定,而且足够慢。<code>app store server</code>验证一个收据需要<code>3-6s</code>时间</li></ol><p><strong>10. 经验总结,如下内容已经过验证</strong></p><ul><li><p>程序加入支付队列使用<code>SKMutablePayment</code>和<code>SKPayment</code>的区别<br>两者拥有的属性一样,唯一区别是属性读写权限不同,<code>SKMutablePayment</code>属性具有读写权限,<code>SKPayment</code>属性只读,如果你要使用<code>applicationUsername</code>透传字段,那么就一定要使用<code>SKMutablePayment</code>加入支付队列</p></li><li><p>透传字段<code>applicationUsername</code>可能返回的是<code>nil</code><br>在支付完成后,每笔订单都不调用<code>finishTransaction</code>,如此测试四五笔订单后,重新启动该应用,苹果自动补单会进行,在有些时候该字段就会为空,需要开发者注意</p></li><li><p><code>updatedTransactions:</code>在<code>App</code>整个生命周期只会走一次,所以只要不把订单<code>finishTransaction</code>掉,重启<code>App</code>就会重新走苹果的补单流程(自动调用<code>updatedTransactions:</code>注意需要<code>[[SKPaymentQueue defaultQueue] addTransactionObserver:instance];</code>添加观察者才可以),逻辑需要自己根据项目实现</p></li><li><p><code>SKPaymentTransaction *transaction</code>属性官方说明</p></li><li><code>transaction.transactionDate</code><br>将订单交易添加到服务器队列的日期,仅当状态为<code>SKPaymentTransactionStatePurchased</code>或<code>SKPaymentTransactionStateRestored</code>时有效</li><li><code>transaction.transactionIdentifier</code><br><code>transactionIdentifier</code>是唯一标识交易支付成功的字符串,此值的格式与收据中的事务<code>transaction_id</code>相同,但是值可能不相同,仅当状态为<code>SKPaymentTransactionStatePurchased</code>或<code>SKPaymentTransactionStateRestored</code>时有效</li><li><code>transaction.originalTransaction</code><br>原始交易<code>id</code>,仅当状态为<code>SKPaymentTransactionStateRestored</code>时有效<code>有值</code></li><li><code>transaction.payment.applicationUsername</code><br>获取之前设置的<code>applicationUsername</code></li><li><p>注意:凭证验证后返回的<code>original_transaction_id</code>和<code>transaction_id</code>一般情况下是相同的,只会在恢复购买时不一样</p></li><li><p><code>transactionReceiptData</code>可以无限验证通过,也就是说一个凭证可以被校验多次,这是刷单方法之一,需要开发者注意,苹果补单流程返回的<code>transactionReceiptData</code>即使同一笔订单也会变</p></li><li><p><code>transactionReceiptData</code>验证解析后,<code>in_app</code>字段出现为空或者多个购买项目,只要不<code>finishTransaction</code>掉订单,下次再支付成功后,返回的<code>transactionReceiptData</code>凭证,就是包含之前的购买记录,最近购买的商品会在列表的第一个</p></li><li>验证凭证,苹果服务器返回的数据<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "receipt": {</span><br><span class="line"> "receipt_type": "ProductionSandbox",</span><br><span class="line"> "adam_id": 0,</span><br><span class="line"> "app_item_id": 0,</span><br><span class="line"> "bundle_id": "com.Yo***ights",</span><br><span class="line"> "application_version": "1",</span><br><span class="line"> "download_id": 0,</span><br><span class="line"> "version_external_identifier": 0,</span><br><span class="line"> "receipt_creation_date": "2020-06-01 09:37:57 Etc/GMT",</span><br><span class="line"> "receipt_creation_date_ms": "1591004277000",</span><br><span class="line"> "receipt_creation_date_pst": "2020-06-01 02:37:57 America/Los_Angeles",</span><br><span class="line"> "request_date": "2020-06-01 09:38:55 Etc/GMT",</span><br><span class="line"> "request_date_ms": "1591004335844",</span><br><span class="line"> "request_date_pst": "2020-06-01 02:38:55 America/Los_Angeles",</span><br><span class="line"> "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",</span><br><span class="line"> "original_purchase_date_ms": "1375340400000",</span><br><span class="line"> "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",</span><br><span class="line"> "original_application_version": "1.0",</span><br><span class="line"> "in_app": [</span><br><span class="line"> {</span><br><span class="line"> "quantity": "1",</span><br><span class="line"> "product_id": "com.yo***thlycard",</span><br><span class="line"> "transaction_id": "10***4780",</span><br><span class="line"> "original_transaction_id": "10***4780",</span><br><span class="line"> "purchase_date": "2020-06-01 09:36:56 Etc/GMT",</span><br><span class="line"> "purchase_date_ms": "1591004216000",</span><br><span class="line"> "purchase_date_pst": "2020-06-01 02:36:56 America/Los_Angeles",</span><br><span class="line"> "original_purchase_date": "2020-06-01 09:36:56 Etc/GMT",</span><br><span class="line"> "original_purchase_date_ms": "1591004216000",</span><br><span class="line"> "original_purchase_date_pst": "2020-06-01 02:36:56 America/Los_Angeles",</span><br><span class="line"> "is_trial_period": "false"</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "quantity": "1",</span><br><span class="line"> "product_id": "com.yo***iteprime1",</span><br><span class="line"> "transaction_id": "10***3950",</span><br><span class="line"> "original_transaction_id": "10***3950",</span><br><span class="line"> "purchase_date": "2020-06-01 09:35:30 Etc/GMT",</span><br><span class="line"> "purchase_date_ms": "1591004130000",</span><br><span class="line"> "purchase_date_pst": "2020-06-01 02:35:30 America/Los_Angeles",</span><br><span class="line"> "original_purchase_date": "2020-06-01 09:35:30 Etc/GMT",</span><br><span class="line"> "original_purchase_date_ms": "1591004130000",</span><br><span class="line"> "original_purchase_date_pst": "2020-06-01 02:35:30 America/Los_Angeles",</span><br><span class="line"> "is_trial_period": "false"</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> "status": 0,</span><br><span class="line"> "environment": "Sandbox"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><a href="https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1">附:[官方文档] Validating Receipts With the App Store</a><br><a href="https://help.apple.com/app-store-connect/#/devf341c0f01">附:[官方文档] 配置自动续期订阅共享密钥</a><br><a href="https://www.jianshu.com/p/d9d742e82188">附:iOS开发内购流程</a></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS Toast 实现</title>
<link href="/2019/06/05/iOSToast%E5%AE%9E%E7%8E%B0/"/>
<url>/2019/06/05/iOSToast%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 对外暴露两个方法供调用</span><br><span class="line">+ (void)showToast:(NSString *)text;</span><br><span class="line">+ (void)showToast:(NSString *)text inView:(UIView *)superView;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">// 实现方法</span><br><span class="line">+ (void)showToast:(NSString *)text{</span><br><span class="line"> [ToastUtil showToast:text inView:[UIApplication sharedApplication].windows.lastObject];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (void)showToast:(NSString *)text inView:(UIView *)superView {</span><br><span class="line"> if (!superView) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> CGSize labelSize = [text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:20.f]}];</span><br><span class="line"> UILabel *label = [[UILabel alloc] init];</span><br><span class="line"> label.font = [UIFont systemFontOfSize:18.f];</span><br><span class="line"> label.text = text;</span><br><span class="line"> label.textAlignment = NSTextAlignmentCenter;</span><br><span class="line"> label.layer.cornerRadius = labelSize.height/4;</span><br><span class="line"> label.layer.masksToBounds = YES;</span><br><span class="line"> label.backgroundColor = [UIColor colorWithRed:38/255.f green:187/255.f blue:251/255.f alpha:1.f];</span><br><span class="line"> label.textColor = [UIColor whiteColor];</span><br><span class="line"> label.frame = CGRectMake((superView.bounds.size.width - labelSize.width)/2, 0, labelSize.width, labelSize.height);</span><br><span class="line"> [superView addSubview:label];</span><br><span class="line"> dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{</span><br><span class="line"> [label removeFromSuperview];</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 系统自带分享</title>
<link href="/2019/05/22/iOS%E7%B3%BB%E7%BB%9F%E8%87%AA%E5%B8%A6%E5%88%86%E4%BA%AB/"/>
<url>/2019/05/22/iOS%E7%B3%BB%E7%BB%9F%E8%87%AA%E5%B8%A6%E5%88%86%E4%BA%AB/</url>
<content type="html"><![CDATA[<ul><li><strong>注意:</strong>国行手机无法使用系统自带的facebook分享,国行手机facebook被阉割导致分享失败。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">* 分享</span><br><span class="line">* 多图分享,items里面直接放图片</span><br><span class="line">* 分享链接</span><br><span class="line">* NSString *textToShare = @"mq分享";</span><br><span class="line">* UIImage *imageToShare = [UIImage imageNamed:@"imageName"];</span><br><span class="line">* NSURL *urlToShare = [NSURL URLWithString:@"https:www.baidu.com"];</span><br><span class="line">* NSArray *items = @[urlToShare,textToShare,imageToShare];</span><br><span class="line">*/</span><br><span class="line">- (void)yoShare:(NSArray *)items success:(ShareSuccessBlock)successBlock fail:(ShareFailBlock)failBlock{</span><br><span class="line"> if (0 == items.count) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];</span><br><span class="line"> if (@available(iOS 11.0, *)) {</span><br><span class="line"> //UIActivityTypeMarkupAsPDF是在iOS 11.0 之后才有的</span><br><span class="line"> activityVC.excludedActivityTypes = @[UIActivityTypeMessage, UIActivityTypeMail, UIActivityTypeOpenInIBooks, UIActivityTypeMarkupAsPDF];</span><br><span class="line"> }else if (@available(iOS 9.0, *)){</span><br><span class="line"> //UIActivityTypeOpenInIBooks是在iOS 9.0 之后才有的</span><br><span class="line"> activityVC.excludedActivityTypes = @[UIActivityTypeMessage, UIActivityTypeMail, UIActivityTypeOpenInIBooks];</span><br><span class="line"> }else{</span><br><span class="line"> activityVC.excludedActivityTypes = @[UIActivityTypeMessage, UIActivityTypeMail];</span><br><span class="line"> }</span><br><span class="line"> activityVC.completionWithItemsHandler = ^(UIActivityType _Nullable activityType, BOOL completed, NSArray * _Nullable returnedItems, NSError * _Nullable activityError) {</span><br><span class="line"> if (completed) {</span><br><span class="line"> if (successBlock) {</span><br><span class="line"> NSNumber *codeNum = [NSNumber numberWithInteger:YoErrorCodeSuccess];</span><br><span class="line"> NSDictionary *result = @{RCODEKEY:codeNum, RMSGKEY:SuccessMsg, METHODKEY:SysShareMethod};</span><br><span class="line"> successBlock(result);</span><br><span class="line"> }</span><br><span class="line"> }else{</span><br><span class="line"> if (failBlock) {</span><br><span class="line"> NSNumber *codeNum = [NSNumber numberWithInteger:YoErrorCodeShareFail];</span><br><span class="line"> NSDictionary *result = @{RCODEKEY:codeNum, RMSGKEY:ShareFailMsg, METHODKEY:SysShareMethod};</span><br><span class="line"> failBlock(result);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> //这儿一定要做iPhone与iPad的判断,因为这儿只有iPhone可以present,iPad需pop,所以这儿actVC.popoverPresentationController.sourceView = self.view;在iPad下必须有,不然iPad会crash,self.view你可以换成任何view,你可以理解为弹出的窗需要找个依托。</span><br><span class="line"> UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;</span><br><span class="line"> if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {</span><br><span class="line"> activityVC.popoverPresentationController.sourceView = vc.view;</span><br><span class="line"> activityVC.popoverPresentationController.sourceRect = CGRectMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height, 0, 0);</span><br><span class="line"> [vc presentViewController:activityVC animated:YES completion:nil];</span><br><span class="line"> }else{</span><br><span class="line"> [vc presentViewController:activityVC animated:YES completion:nil];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 应用商店评分StoreReview</title>
<link href="/2019/05/15/iOS%E5%BA%94%E7%94%A8%E5%95%86%E5%BA%97%E8%AF%84%E5%88%86StoreReview/"/>
<url>/2019/05/15/iOS%E5%BA%94%E7%94%A8%E5%95%86%E5%BA%97%E8%AF%84%E5%88%86StoreReview/</url>
<content type="html"><![CDATA[<p>应用中引导用户去进行应用评论常用的方法大概有以下几种:</p><ol><li>使用<code>deepLink</code>;在<code>app</code>地址链接后边拼接上<code>action=write-review</code>可以直接跳转到<code>App Store</code>应用中对应的应用评价界面进行评价</li><li>使用<code>SKStoreReviewController</code>;在<code>iOS 10.3</code>之后,<code>iOS</code>提供了一种新的评价方式,可以不用跳转出应用在应用内就完成应用的星级评价<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.05.15.01.png" alt=""></li></ol><p>从这段说明里,我们看出官方给出的注意点:</p><ul><li>该方法在<code>iOS 10.3</code>之后才可以使用,所以在使用时需要进行版本判断</li><li>该方法主要用于申请用户评分,但这个方法不一定会显示<code>UI</code>,也就是说即使调用了该方法也不一定会有评级弹窗显示,最终是否有显示主要由<code>App Store</code>的相关政策决定,所以这个方法不适用于任何来自按钮或者其他用户直接交互的操作</li><li>该方法在开发模式下可以弹出交互界面,但是不能进行进行信息提交;在<code>TestFlight</code>模式下,调用该方法不会有任何反应</li><li>同一用户在同一个应用内每年只能提交三次评论,超出次数之后调用该方法就不会有任何反应,但未找到官方文档说明</li></ul><hr><p>代码如下:<br>导入头文件<code>#import <StoreKit/StoreKit.h></code><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">+ (void)yoStoreReview{</span><br><span class="line"> if (@available(iOS 10.3, *)) {</span><br><span class="line"> if ([SKStoreReviewController respondsToSelector:@selector(requestReview)]){</span><br><span class="line"> //防止键盘遮挡</span><br><span class="line"> [[UIApplication sharedApplication].keyWindow endEditing:YES];</span><br><span class="line"> // iOS10.3+ 直接在App内弹出评分框</span><br><span class="line"> // 此方式苹果允许的调用频率为3次/年</span><br><span class="line"> [SKStoreReviewController requestReview];</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> // <iOS10.3 跳转AppStore的评论页面</span><br><span class="line"> NSString *appIDStr = [NSString stringWithFormat:@"%@", [YostarUtilits getUserDefaultsForKey:@"APPLEID"]];</span><br><span class="line"> NSString *appStoreReviewStr = [NSString stringWithFormat:@"https://itunes.apple.com/app/id%@?action=write-review", appIDStr];</span><br><span class="line"> [[UIApplication sharedApplication] openURL:[NSURL URLWithString:appStoreReviewStr]];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><a href="https://developer.apple.com/documentation/storekit/skstorereviewcontroller/2851536-requestreview?language=objc">附:[官方文档] requestReview</a></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS KVC</title>
<link href="/2019/05/10/iOSKVC/"/>
<url>/2019/05/10/iOSKVC/</url>
<content type="html"><![CDATA[<p><strong>1. KVC简介</strong></p><ul><li>键/值编码中的基本调用是<code>-valueForKey:</code>和<code>-setValue:forKey:</code>方法<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">NSString *name = [car valueForKey:@"name"];</span><br><span class="line">valueForKey:会首先查找以参数名命名(格式为_name或_isName)的getter方法</span><br><span class="line">如果没有这样的getter方法,它将会在对象内寻找名称格式为_name或name的实例变量</span><br><span class="line">另外KVC会自动装箱和开箱标量值,也就是说,当使用-setValue:forKey:,它自动将标量值(int、float和struct)放入NSNumber或NSValue中;</span><br><span class="line">当时用-valueForKey:时,它自动将标量值从这些对象中取出,仅KVC具有这种自动装箱功能,常规方法调用和属性语法不具备该功能</span><br></pre></td></tr></table></figure><strong>2. KVC键路径</strong></li><li>键路径的基本调用是<code>-valueForKeyPath:</code>和<code>-setValue:forKeyPath:</code>方法<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[car setValue:[NSNumber numberWithInt:155] forKeyPath:@"engine.horsepower"];</span><br><span class="line">NSLog(@"horsepower is %@", [car valueForKeyPath:@"engine.horsepower"]);</span><br></pre></td></tr></table></figure><strong>3. KVC快速运算</strong></li><li>键路径不仅能引用对象值,还可以引用一些运算符来进行一些运算,例如能获取一组值的平均值或返回这组值中的最小值和最大值<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[garage valueForKeyPath:@"cars.@count"];</span><br><span class="line">[garage valueForKeyPath:@"[email protected]"];</span><br><span class="line">[garage valueForKeyPath:@"[email protected]"];</span><br><span class="line">[garage valueForKeyPath:@"[email protected]"];</span><br><span class="line">[garage valueForKeyPath:@"[email protected]"];</span><br></pre></td></tr></table></figure><strong>4. setter和getter方法命名规则</strong></li><li><code>setter</code>方法根据它所更改的属性名称来命名,并加上前缀<code>set</code>,如:<code>setName: 、setEngine:</code> 等</li><li><code>getter</code>方法则是以其返回的属性名称命名,如:<code>name、engine</code>等,不要将<code>get</code>用作<code>getter</code>方法的前缀</li><li>补充知识:<code>get</code>这个词在<code>Cocoa</code>中有着特殊的含义,如果<code>get</code>出现在<code>Cocoa</code>的方法名称中,就意味着这个方法会将你传递的参教作为指针来返回数值。例如,<code>NSData</code>中有一个<code>getBytes:</code>方法,它的参数就是用来存储字节的内存缓冲区的地址。如果你在存取方法的名称中使用了<code>get</code>,那么有经验的<code>Cocoa</code>编程人员就会习惯性地将指针当做参数传入这个方法,当他们发现这不过是一个简单的存取方法时就会感到困惑</li></ul><p><strong>5. setValue和setObject的区别</strong></p><ul><li><code>setObject:ForKey:</code> 是<code>NSMutableDictionary</code>特有的;<code>setValue:ForKey:</code>是<code>KVC</code>的主要方法</li><li><strong>总结两者的区别:</strong></li><li><code>setObject: forkey:</code>中<code>object</code>是不能够为<code>nil</code></li><li><code>setValue: forKey:</code>中<code>value</code>能够为<code>nil</code>,但是当<code>value</code>为<code>nil</code>的时候,会自动调用<code>removeObject: forKey:</code>方法</li><li><code>setValue: forKey:</code>中<code>key</code>的参数只能够是<code>NSString</code>类型</li><li><code>setObject: forKey:</code>的<code>key</code>可以是任何类型</li><li>注意:<code>setObject: forKey:</code>对象不能存放<code>nil</code>要与下面的这种情况区分:<br><code>[imageDictionary setObject:[NSNullnull] forKey:indexNumber];</code><br><code>[NSNull null]</code>表示的是一个空对象,并不是<code>nil</code></li><li>当<code>setValue: forKey:</code>方法调用者是对象的时候, <code>setValue: forKey:</code>方法是在<code>NSObject</code>对象中创建的,也就是说所有的<code>OC</code>对象都有这个方法,所以可以用于任何类</li></ul><p><strong>6. 正确比较字符串</strong></p><ul><li>比较字符串是否相等,应该使用<code>isEqualToString:</code>,而不能仅仅比较字符串的指针值;<code>==</code>运算符只判断两个字符串的指针数值,而不是它们所指的对象</li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS APNS device token特性</title>
<link href="/2019/05/09/iOSAPNSdevicetoken%E7%89%B9%E6%80%A7/"/>
<url>/2019/05/09/iOSAPNSdevicetoken%E7%89%B9%E6%80%A7/</url>
<content type="html"><![CDATA[<ul><li><strong>device token的一些特性:</strong></li></ul><ol><li>开发环境获取的<code>deviceToken</code>和发布环境获取的<code>deviceToken</code>是不一样的</li><li>在一台设备中,<code>deviceToken</code>是系统级别的,不同<code>App</code>获得的<code>deviceToken</code>是相同的</li><li><code>deviceToken</code>会过期</li><li>单个<code>App</code>的更新<code>deviceToken</code>不会发生改变</li><li>当进行备份恢复、或恢复出厂设置之类的操作时,<code>deviceToken</code>会发生改变,建议<code>App</code>在每次启动时都获取<code>deviceToken</code></li><li>用户抹除<code>iPhone</code>的数据时,为了保护隐私,<code>deviceToken</code>会改变</li><li>升级系统<code>deviceToken</code>有可能变化,猜测是升级大的系统版本后<code>deviceToken</code>会变化</li><li>在删除手机上的<code>App</code>之后,再次下载安装,<code>deviceToken</code>在部分系统上会改变</li></ol><ul><li><p><strong>注意:</strong> 推送相关证书只用在推送的后台即服务端使用,工程中只需打开推送相关开关即可,不需要推送证书</p></li><li><p><strong>device token在iOS 13的变化</strong></p></li></ul><ol><li>在<code>iOS 13</code>之前的版本中,大部分这样处理<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{</span><br><span class="line"> NSString *dt = [deviceToken description];</span><br><span class="line"> dt = [dt stringByReplacingOccurrencesOfString: @"<" withString: @""];</span><br><span class="line"> dt = [dt stringByReplacingOccurrencesOfString: @">" withString: @""];</span><br><span class="line"> dt = [dt stringByReplacingOccurrencesOfString: @" " withString: @""];</span><br><span class="line"> NSLog(@"**发送给服务器的token字符串***:%@\n", dt);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>在<code>iOS 13</code>之后的版本中,必须用以下方法处理(该方法在<code>iOS 13</code>之前也兼容)<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{</span><br><span class="line"> NSMutableString *deviceTokenString = [NSMutableString string];</span><br><span class="line"> const char *bytes = (const char *)deviceToken.bytes;</span><br><span class="line"> NSInteger count = deviceToken.length;</span><br><span class="line"> for (int i = 0; i < count; i++) {</span><br><span class="line"> [deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF];</span><br><span class="line"> }</span><br><span class="line"> NSLog(@"**发送给服务器的token字符串***:%@\n", deviceTokenString);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>或者<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{</span><br><span class="line"> if (![deviceToken isKindOfClass:[NSData class]]) return;</span><br><span class="line"> const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes];</span><br><span class="line"> NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",</span><br><span class="line"> ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),</span><br><span class="line"> ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),</span><br><span class="line"> ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];</span><br><span class="line"> NSLog(@"**发送给服务器的token字符串***:%@\n",hexToken);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 接入Twitter 相关注意点</title>
<link href="/2019/04/23/iOS%E6%8E%A5%E5%85%A5Twitter%E7%9B%B8%E5%85%B3%E6%B3%A8%E6%84%8F%E7%82%B9/"/>
<url>/2019/04/23/iOS%E6%8E%A5%E5%85%A5Twitter%E7%9B%B8%E5%85%B3%E6%B3%A8%E6%84%8F%E7%82%B9/</url>
<content type="html"><![CDATA[<p><strong>1. 接入前配置</strong></p><ul><li>Download and unzip <a href="https://ton.twimg.com/syndication/twitterkit/ios/3.3.0/Twitter-Kit-iOS.zip">Twitter Kit</a></li><li>Add <code>TwitterKit</code> to “Embedded Binaries” in your Xcode project settings(测试发现不添加也可以)</li><li>Add <code>TwitterKit</code> and <code>TwitterCore</code> to “Linked Frameworks and Libraries” in your Xcode project settings</li><li>Add <code>SafariServices.framework</code> to use SFSafariViewController</li><li>In your app’s Info.plist, add URL Schemes by adding code below after<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><key>CFBundleURLTypes</key></span><br><span class="line"><array></span><br><span class="line"> <dict></span><br><span class="line"> <key>CFBundleURLSchemes</key></span><br><span class="line"> <array></span><br><span class="line"> <string>twitterkit-<consumerKey></string></span><br><span class="line"> </array></span><br><span class="line"> </dict></span><br><span class="line"></array></span><br><span class="line"><key>LSApplicationQueriesSchemes</key></span><br><span class="line"><array></span><br><span class="line"> <string>twitter</string></span><br><span class="line"> <string>twitterauth</string></span><br><span class="line"></array></span><br></pre></td></tr></table></figure></li><li>Make sure to import the framework header: <code>#import <TwitterKit/TWTRKit.h></code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {</span><br><span class="line"> [[Twitter sharedInstance] startWithConsumerKey:@"hTpkPVU4pThkM0" consumerSecret:@"ovEqziMzLpUOF163Qg2mj"];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>Implement the application:openURL:options method in your Application Delegate, and pass along the redirect URL to Twitter Kit<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options {</span><br><span class="line"> return [[Twitter sharedInstance] application:app openURL:url options:options];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><strong>2. Twitter后台配置 <a href="https://apps.twitter.com/app">https://apps.twitter.com/app</a></strong></li></ul><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.04.23.01.png" alt="Twitter apps dashboard"></p><p><strong>From June 12th 2018 callback locking will <a href="https://developer.twitter.com/en/docs/basics/callback_url.html">no longer be optional</a>. The correct callback format for iOS apps is:<code>twitterkit-MY_CONSUMER_KEY://</code></strong></p><p><strong>3. 接入相关功能</strong></p><ul><li><strong>Log In Button</strong><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">TWTRLogInButton *logInButton = [TWTRLogInButton buttonWithLogInCompletion:^(TWTRSession *session, NSError *error) {</span><br><span class="line"> if (session) {</span><br><span class="line"> NSLog(@"signed in as %@", [session userName]);</span><br><span class="line"> } else {</span><br><span class="line"> NSLog(@"error: %@", [error localizedDescription]);</span><br><span class="line"> }</span><br><span class="line">}];</span><br><span class="line">logInButton.center = self.view.center;</span><br><span class="line">[self.view addSubview:logInButton];</span><br></pre></td></tr></table></figure></li><li><strong>Log In Method</strong><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[[Twitter sharedInstance] logInWithCompletion:^(TWTRSession *session, NSError *error) {</span><br><span class="line"> if (session) {</span><br><span class="line"> NSLog(@"signed in as %@", [session userName]);</span><br><span class="line"> } else {</span><br><span class="line"> NSLog(@"error: %@", [error localizedDescription]);</span><br><span class="line"> }</span><br><span class="line">}];</span><br></pre></td></tr></table></figure></li><li><strong>Request User Email Address</strong><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">TWTRAPIClient *client = [TWTRAPIClient clientWithCurrentUser];</span><br><span class="line">[client requestEmailForCurrentUser:^(NSString *email, NSError *error) {</span><br><span class="line"> if (email) {</span><br><span class="line"> NSLog(@"signed in as %@", email);</span><br><span class="line"> } else {</span><br><span class="line"> NSLog(@"error: %@", [error localizedDescription]);</span><br><span class="line"> }</span><br><span class="line">}];</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS runtime 相关实例</title>
<link href="/2019/04/19/iOSruntime%E7%9B%B8%E5%85%B3%E5%AE%9E%E4%BE%8B/"/>
<url>/2019/04/19/iOSruntime%E7%9B%B8%E5%85%B3%E5%AE%9E%E4%BE%8B/</url>
<content type="html"><![CDATA[<p><strong>避免按钮快速点击多次相应问题</strong></p><ul><li>创建UIButton 分类</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//</span><br><span class="line">// UIButton+time.h</span><br><span class="line">//</span><br><span class="line"></span><br><span class="line">#import <UIKit/UIKit.h></span><br><span class="line"></span><br><span class="line">@interface UIButton (time)</span><br><span class="line"></span><br><span class="line">/* 防止button重复点击,设置间隔 */</span><br><span class="line">@property (nonatomic, assign) NSTimeInterval acceptEventInterval;</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">//</span><br><span class="line">// UIButton+time.m</span><br><span class="line">//</span><br><span class="line"></span><br><span class="line">#import "UIButton+time.h"</span><br><span class="line">#import <objc/runtime.h></span><br><span class="line"></span><br><span class="line">@implementation UIButton (time)</span><br><span class="line"></span><br><span class="line">static const char *UIButton_acceptEventInterval = "UIButton_acceptEventInterval";</span><br><span class="line">static const char *UIButton_acceptEventTime = "UIButton_acceptEventTime";</span><br><span class="line"></span><br><span class="line">// get方法 获取时间间隔</span><br><span class="line">- (NSTimeInterval)acceptEventInterval{</span><br><span class="line"> return [objc_getAssociatedObject(self, UIButton_acceptEventInterval) doubleValue];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// set方法 赋值时间间隔</span><br><span class="line">- (void)setAcceptEventInterval:(NSTimeInterval)mm_acceptEventInterval{</span><br><span class="line"> objc_setAssociatedObject(self, UIButton_acceptEventInterval, @(mm_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// get方法 获取时间</span><br><span class="line">- (NSTimeInterval)acceptEventTime{</span><br><span class="line"> return [objc_getAssociatedObject(self, UIButton_acceptEventTime) doubleValue];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// set方法 赋值时间</span><br><span class="line">- (void)setAcceptEventTime:(NSTimeInterval)mm_acceptEventTime{</span><br><span class="line"> objc_setAssociatedObject(self, UIButton_acceptEventTime, @(mm_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line">class_addMethod:如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现</span><br><span class="line">1.如果返回成功:则说明被替换方法没有存在.也就是被替换的方法没有被实现,我们需要先把这个方法实现,然后再执行我们想要的效果,用我们自定义的方法去替换被替换的方法. 这里使用到的是'class_replaceMethod'这个方法. class_replaceMethod本身会尝试调用class_addMethod和method_setImplementation,所以直接调用class_replaceMethod就可以了)</span><br><span class="line">2.如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换即</span><br><span class="line">*/</span><br><span class="line">//分类中重写load方法,实现方法的交换(只要能让其执行一次方法交换语句,load再合适不过了)</span><br><span class="line">+ (void)load{</span><br><span class="line"> static dispatch_once_t onceToken;</span><br><span class="line"> dispatch_once(&onceToken, ^{</span><br><span class="line"> //获取这两个方法</span><br><span class="line"> Method systemMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));</span><br><span class="line"> SEL sysSEL = @selector(sendAction:to:forEvent:);</span><br><span class="line"></span><br><span class="line"> Method myMethod = class_getInstanceMethod(self, @selector(mm_sendAction:to:forEvent:));</span><br><span class="line"> SEL mySEL = @selector(mm_sendAction:to:forEvent:);</span><br><span class="line"></span><br><span class="line"> //添加方法进去</span><br><span class="line"> BOOL isAddMethod = class_addMethod(self, sysSEL, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));</span><br><span class="line"> //如果添加方法成功</span><br><span class="line"> if (isAddMethod) {</span><br><span class="line"> class_replaceMethod(self, mySEL, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));</span><br><span class="line"> }else{</span><br><span class="line"> method_exchangeImplementations(systemMethod, myMethod);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> /*-----以上主要是实现两个方法的互换,load是gcd的只shareinstance,果断保证执行一次-------*/</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)mm_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{</span><br><span class="line"> if ([NSDate date].timeIntervalSince1970 - self.acceptEventTime < self.acceptEventInterval) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (self.acceptEventInterval > 0) {</span><br><span class="line"> self.acceptEventTime = [NSDate date].timeIntervalSince1970;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> [self mm_sendAction:action to:target forEvent:event];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>c++传递字符串给c#使用问题</title>
<link href="/2019/04/18/C%E4%BC%A0%E9%80%92%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E7%94%A8%E9%97%AE%E9%A2%98/"/>
<url>/2019/04/18/C%E4%BC%A0%E9%80%92%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E7%94%A8%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<p>C++里,字符串要占用内存的。C++创建字符串,并传给C#,就会造成内存泄露(因为C#不知道C++如何创建,也就不知道如何销毁)。<br>因此,通常的做法(甚至是C++互相调用的惯例),是</p><ol><li>调用者(这里是C#)准备好一段内存缓冲区(这里是StringBuilder);</li><li>被调用者C++把字符串内容,填充到指定缓冲区去;</li><li>调用者从缓冲区能得到结果,也知道如何清理缓冲(C#自己会用垃圾回收),没有内存泄露问题。</li></ol>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS和Unity交互之参数传递</title>
<link href="/2019/03/07/iOS%E5%92%8CUnity%E4%BA%A4%E4%BA%92%E4%B9%8B%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92/"/>
<url>/2019/03/07/iOS%E5%92%8CUnity%E4%BA%A4%E4%BA%92%E4%B9%8B%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92/</url>
<content type="html"><![CDATA[<p><strong>1. 调用方法一</strong></p><ul><li>Unity调方法传参,有返回值<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// Unity代码</span><br><span class="line">[DllImport("__Internal")]</span><br><span class="line">// 给iOS传string参数,有返回值,返回值通过iOS的return方法返回给Unity</span><br><span class="line">private static extern string getIPv6(string mHost, string mPort)</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">// iOS代码</span><br><span class="line">extern "C" const char * getIPv6(const char *mHost, const char *mPort)</span><br><span class="line">{</span><br><span class="line"> // strdup(const char *__s1) 复制mHost字符串,通过Malloc()进行空间分配 </span><br><span class="line"> // return strdup(mHost);</span><br><span class="line"> return makeStringCopy(mHost);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">char* makeStringCopy(const char* string)</span><br><span class="line">{</span><br><span class="line"> if (NULL == string) {</span><br><span class="line"> return NULL;</span><br><span class="line"> }</span><br><span class="line"> char* res = (char*)malloc(strlen(string)+1);</span><br><span class="line"> strcpy(res, string);</span><br><span class="line"> return res;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>如果Unity传参为string类型,不执行strdup()方法而直接使用return方法,导致mHost没有分配内存空间而报错</li><li>这里的const char* 会被C#自动转换成string因为在.m文件中使用了内存申请,该段内存自然是处在堆内存中,这样转成string符合c#的内存管理机制,我们不用担心它的释放问题</li><li>如果Unity传参为int等基础数据类型,可以直接使用return方法</li><li>调用DllImport(“”)方法,需要引入命名空间:<code>using System.Runtime.InteropServices</code></li></ul><p><strong>2. 调用方法二</strong></p><ul><li>Unity调方法传参,无返回值<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// Unity代码</span><br><span class="line">// 传数据给iOS</span><br><span class="line">[DllImport("__Internal")]</span><br><span class="line">// 给iOS传string参数,无返回值,返回值通过iOS的UnitySendMessage方法返回给Unity</span><br><span class="line">private static extern void setDate(string date);</span><br><span class="line"></span><br><span class="line">// 接收iOS的数据</span><br><span class="line">public void GetDate(string date)</span><br><span class="line">{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">// iOS代码</span><br><span class="line">extern "C" void setDate(const char *date)</span><br><span class="line">{</span><br><span class="line"> /**</span><br><span class="line"> 发送数据给Unity</span><br><span class="line"> @param obj 模型名</span><br><span class="line"> @param method Unity接收iOS数据的方法名</span><br><span class="line"> @param msg 传给Unity的数据</span><br><span class="line"> UnitySendMessage(const char* obj, const char* method, const char* msg);</span><br><span class="line"> */</span><br><span class="line"> UnitySendMessage("PublicGameObject", "GetDate", date);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS iPhone X适配HomeIndicator相关实践</title>
<link href="/2019/03/06/iOSiPhoneX%E9%80%82%E9%85%8DHomeIndicator%E7%9B%B8%E5%85%B3%E5%AE%9E%E8%B7%B5/"/>
<url>/2019/03/06/iOSiPhoneX%E9%80%82%E9%85%8DHomeIndicator%E7%9B%B8%E5%85%B3%E5%AE%9E%E8%B7%B5/</url>
<content type="html"><![CDATA[<p><strong>1. 隐藏HomeIndicator</strong><br>一般情况只有视频全屏播放和游戏界面需要设置自动隐藏Home键指示器,隐藏HomeIndicator的方法,如下,<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)prefersHomeIndicatorAutoHidden {</span><br><span class="line"> return YES;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>在VC 里边重写 prefersHomeIndicatorAutoHidden 返回 YES(默认是NO),Home指示条就能自动隐藏了,此方法是在控制器push之后就会回调,屏幕若无交互事件响应时,延迟2秒左右会自动隐藏。经过测试发现,只要触摸页面就会重新出现,不操作页面一会儿会自动消失。主要适用于视频类等长时间不对页面做出交互的应用使用。</p><p><strong>2. 屏幕边缘手势冲突</strong><br>有时候你的App需要控制从状态栏下拉或者底部栏上滑,这个会跟系统的下拉通知中心手势和上滑控制中心手势冲突。如果你要优先自己处理手势可以将系统手势延迟。方法如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures{</span><br><span class="line"> return UIRectEdgeAll;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>然后其他地方不要修改(比如prefersHomeIndicatorAutoHidden);就可以像王者荣耀那样,一直显示白条,但是点击一次不会到桌面,也不会到多任务</p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS Xcode 添加调试真机设备和模拟器</title>
<link href="/2019/03/05/iOSXcode9%E8%B0%83%E8%AF%95iOS7.0%E4%BB%A5%E4%B8%8B%E7%89%88%E6%9C%AC/"/>
<url>/2019/03/05/iOSXcode9%E8%B0%83%E8%AF%95iOS7.0%E4%BB%A5%E4%B8%8B%E7%89%88%E6%9C%AC/</url>
<content type="html"><![CDATA[<ol><li>高版本<code>Xcode</code>调试低版本真机设备</li></ol><ul><li>前往文件夹或者找到<code>Xcode</code>安装包右键显示包内容查找路径<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport</span><br></pre></td></tr></table></figure><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.05.01.png" alt=""></li></ul><p>最新的<code>Xcode</code>默认是没有<code>7.0</code>和<code>7.1</code>文件夹,我们可以从<code>Xcode 7</code>的<code>DeviceSupport</code>文件夹下拷贝出来,然后复制进去</p><ul><li>前往文件夹或者找到<code>Xcode</code>安装包右键显示包内容查找路径<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist</span><br></pre></td></tr></table></figure>查找到<code>SDKSettings.plist</code>文件,在<code>DEPLOYMENT_TARGET_SUGGESTED_VALUES</code>字段下面添加<code>7.0</code>和<code>7.1</code>,如果第一步添加了更老的版本,这里也一起添加了<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.05.02.jpg" alt="SDKSettingPlist"></li></ul><p>如果是<code>Xcode 8</code>以下的版本调试适配<code>iOS 10</code>,方法是一样的,只不过需要在高版本的<code>Xcode</code>里面把配置文件拷贝出来</p><p>如果<code>SDKSettings.plist</code>这个文件提示无法修改的话,可以先将这个文件拷贝一份到桌面,修改后再覆盖进去即可</p><ol start="2"><li><code>Xcode</code>添加模拟器</li></ol><ul><li><p>首先打开<code>Xcode</code>,找到<code>Add Additional Simulators</code>点击<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.05.03.png" alt=""></p></li><li><p>点击<code>+</code>号选择<code>Add Simulator</code><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.05.04.png" alt=""></p></li><li><p>这里选择你需要的模拟器<code>Type</code>和<code>Version</code>后点击<code>Create</code>就可以了<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.05.05.png" alt=""></p></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 模态跳转推出透明背景方法</title>
<link href="/2019/03/04/iOS%E6%A8%A1%E6%80%81%E8%B7%B3%E8%BD%AC%E6%8E%A8%E5%87%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%96%B9%E6%B3%95/"/>
<url>/2019/03/04/iOS%E6%A8%A1%E6%80%81%E8%B7%B3%E8%BD%AC%E6%8E%A8%E5%87%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<p><strong>1. OS >= iOS 8.0</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">FZLoginViewController *fzLoginViewController = [[FZLoginViewController alloc] init];</span><br><span class="line">fzLoginViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;</span><br><span class="line">[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:fzLoginViewController animated:NO completion:nil];</span><br></pre></td></tr></table></figure><br><strong>2. 若系统需兼容7.0 需要加处理</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {</span><br><span class="line"> controller.modalPresentationStyle = UIModalPresentationOverCurrentContext;</span><br><span class="line"> [self presentViewController:controller animated:YES completion:nil];</span><br><span class="line">} else {</span><br><span class="line"> self.view.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;</span><br><span class="line"> [self presentViewController:controller animated:NO completion:nil];</span><br><span class="line"> self.view.window.rootViewController.modalPresentationStyle = UIModalPresentationFullScreen;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 静态库开发</title>
<link href="/2019/03/01/iOS%E9%9D%99%E6%80%81%E5%BA%93%E5%BC%80%E5%8F%91/"/>
<url>/2019/03/01/iOS%E9%9D%99%E6%80%81%E5%BA%93%E5%BC%80%E5%8F%91/</url>
<content type="html"><![CDATA[<p><strong>本文旨在说明静态库制作中的一些常见问题和特殊处理</strong><br><strong>1. 打包静态库需要的相关问题和设置</strong></p><ul><li>静态库中用到分类的需要在项目中设置这个参数:<code>Other Linker Flags</code>为<code>-ObjC</code>或者<code>-all_load</code></li><li>静态库中用到了<code>NSClassFromString</code>或者<code>runtime</code>的<code>objc_getClass</code>,但是转换出来的<code>Class</code> 一直为<code>nil</code>。解决方法:在主工程的<code>Other Linker Flags</code>需要添加参数<code>-ObjC</code>即可</li><li>如果Xcode找不到框架的头文件,可能是忘记将它们声明为<code>public</code>了</li><li><code>Base SDK</code>指的是当前编译所用的SDK 版本,一般默认为当前xocde的最新版</li><li><code>Build Active Architecture Only</code>设置成<code>No</code></li><li><code>Deployment Target</code>它控制着运行应用需要的最低操作系统版本</li><li><code>Skip Install</code>设置为<code>Yes</code></li><li><code>Mach-O Type</code>静态库设置为<code>Static Library</code>,动态库设置为<code>Dynamic Library</code>,制作<code>bundle</code>文件设置为<code>Bundle</code></li><li>静态库中最好不要用<code>xib</code>,要用的话就将<code>xib</code>放到<code>bundle</code>文件中编译,然后<code>xib</code>就会变成<code>.nib</code>的文件</li><li>如果开发的静态库里面有<code>C</code>或者<code>C++</code>,在使用的时候需要添加<code>libc++.tbd</code>或者<code>libstdc++.tbd</code></li><li><strong>关于C语言中<code>Implicit declaration of function ‘XXXX’ is invalid in C99</code>警告:</strong>C语言是过程化的编程语言,程序执行顺序是从上到下。如果在调用某函数的时候,函数在调用之前没有定义也没有声明,而是在调用之后定义,那么编译时<code>Implicit declaration of function ‘XXXX’ is invalid in C99</code>警告就产生了。这是有别于面向对象编程语言的地方</li></ul><p><strong>2. framework中Optional和Required的区别</strong></p><ul><li>Required:强引用,一定会被加载到内存中,即使不使用也会被加载到内存中</li><li>Optional:弱引用,开始并不会加载,在使用的时候才会加载,会节省加载时的时间。有一些库,如<code>Social.framework</code>和<code>AdSupport.framework</code>,是在iOS 6之后才被引入的,更新了一些新的特性,如果运行在5.0甚至更低的设备上,这些库不支持,会编译通不过,这时候就要使用弱引用了</li><li>当你遇到了<code>dyld:Library not found ……</code>说明你可能使用了不该有的强引用,根据日志将这个库的引用形式修改一下;或者是使用了动态库,就需要在<code>Embeded Binaries</code>选项中添加这个动态库</li></ul><p><strong>3. 如何看一个framework中的二进制文件是静态库还是动态库</strong></p><ul><li>使用file命令,如:<code>$ file /Users/yostar/Desktop/ProjectTest/YostarSDK/ThirdPath/TwitterKit.framework/TwitterKit</code>;见下面的截图,一个是静态库,一个是动态库<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.01.01.png" alt=""></li></ul><p><strong>4. 查看静态库是否支持bitcode</strong><br><code>$ otool -l /Users/yostar/Desktop/UnityLib/libYostarSDK.a | grep __LLVM</code><br>如果上述命令的输出结果有<code>__LLVM</code>,那么就说明,所用的<code>framework</code>或<code>.a</code>支持设置<code>Enable bitcode</code>为<code>YES</code>,否则不支持</p><p><strong>5. 静态库相关操作</strong></p><ul><li>查看一个库文件支持的指令集:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ lipo -info ./XXXX.a</span><br><span class="line">$ lipo -info ./XXXX.framework/XXXX</span><br></pre></td></tr></table></figure></li><li>合成指令集:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ lipo -create XXXX_iphoneos.a XXXX_iphonesimulator.a -output XXXX_all.a</span><br><span class="line">$ lipo -create XXXX_iphoneos.framework/XXXX_iphoneos XXXX_iphonesimulator.framework/XXXX_iphonesimulator -output XXXX_all</span><br></pre></td></tr></table></figure></li><li>拆分特定指令集:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ lipo -thin libname.a armv7(CPU架构名称) -output libname-armv7.a</span><br><span class="line">$ lipo -thin XXXX.framework/XXXX arm64 -output XXXX.framework/XXXX-arm64</span><br></pre></td></tr></table></figure></li><li><strong>注意</strong>framework和.a处理不同,.a可以直接使用,framework需要做替换处理;framework合并或者拆分完成后,再把输出的文件替换上面simulator文件夹或者iphoneos对应目录下的framework文件</li></ul><p><strong>6. 打包framework之嵌套另一静态库产生类文件重复问题</strong><br>将打包好的framework和第三方静态库引入项目,运行,产生两个静态库文件类名重复的问题。如下:<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.01.02.png" alt=""></p><p>这就说明在封装framework时将第三方静态库中的文件给引入了,从而造成两个库中有多个相同类名文件。</p><p><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.01.03.png" alt=""><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.03.01.04.png" alt=""></p><p>这样编译生成的framework就不会和引入的静态库有相同的类文件了</p><p><strong>7. 打包 C,C++文件及和OC混编,接口代码</strong></p><ul><li>静态库打包<code>C</code>代码<br>xcode新建文件<code>YostarUtilits.h</code>和<code>YostarUtilits.m</code>,例子如下:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#import <Foundation/Foundation.h></span><br><span class="line"></span><br><span class="line">const char * getIDFA();</span><br><span class="line"></span><br><span class="line">@interface YostarUtilits : NSObject</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">#import "YostarUtilits.h"</span><br><span class="line"></span><br><span class="line">const char * getIDFA(){</span><br><span class="line"> NSString *str = @"123";</span><br><span class="line"> const char *strC = [IDFAStr UTF8String];</span><br><span class="line"> </span><br><span class="line"> char *result = (char *)calloc(10, sizeof(char *));</span><br><span class="line"> if (result) {</span><br><span class="line"> strcpy(result, strC);</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@implementation YostarUtilits</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure></li><li>静态类库打包<code>C++</code>代码<br>xcode新建文件<code>YostarUtilits.h</code>和<code>YostarUtilits.mm</code>,例子如下:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#import <Foundation/Foundation.h></span><br><span class="line"></span><br><span class="line">@interface YostarUtilits : NSObject</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#import "YostarUtilits.h"</span><br><span class="line"></span><br><span class="line">#if defined(__cplusplus)</span><br><span class="line">extern "C"</span><br><span class="line">{</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">const char * getIDFA(){</span><br><span class="line"> NSString *str = @"123";</span><br><span class="line"> const char *strC = [IDFAStr UTF8String];</span><br><span class="line"> </span><br><span class="line"> char *result = (char *)calloc(10, sizeof(char *));</span><br><span class="line"> if (result) {</span><br><span class="line"> strcpy(result, strC);</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">#if defined(__cplusplus)</span><br><span class="line">}</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">@implementation YostarUtilits</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS tableViewCell相关设置</title>
<link href="/2019/02/28/iOStableViewCell%E7%9B%B8%E5%85%B3%E8%AE%BE%E7%BD%AE/"/>
<url>/2019/02/28/iOStableViewCell%E7%9B%B8%E5%85%B3%E8%AE%BE%E7%BD%AE/</url>
<content type="html"><![CDATA[<p><strong>1. 去掉底部多余的表格线</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[tableView setTableFooterView:[[UIView alloc] initWithFrame:CGRectZero]];</span><br></pre></td></tr></table></figure><br><strong>2. 在自定义tableViewCell中设置分割线 顶头显示 self代表cell</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">if ([self respondsToSelector:@selector(setSeparatorInset:)]) {</span><br><span class="line"> [self setSeparatorInset:UIEdgeInsetsZero];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if ([self respondsToSelector:@selector(setLayoutMargins:)]) {</span><br><span class="line"> [self setLayoutMargins:UIEdgeInsetsZero];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if([self respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]){</span><br><span class="line"> [self setPreservesSuperviewLayoutMargins:NO];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><strong>3. 代码布局tableview改变cell间隙布局,重写UITableViewCell的frame方法</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (void)setFrame:(CGRect)frame{</span><br><span class="line"> frame.origin.x += 5;</span><br><span class="line"> frame.origin.y += 5;</span><br><span class="line"> frame.size.height -= 5;</span><br><span class="line"> frame.size.width -= 10;</span><br><span class="line"> [super setFrame:frame];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><strong>4. 动态计算tableviewCell高度</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">// 获取最底部view 的bottom值</span><br><span class="line">// NSArray *views = [self.contentView.subviews sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {</span><br><span class="line"> // NSString *top1 = [NSString stringWithFormat:@"%f", view1.frame.origin.y];</span><br><span class="line"> // NSString *top2 = [NSString stringWithFormat:@"%f", view2.frame.origin.y];</span><br><span class="line"> // NSComparisonResult result = [top1 compare:top2 options:NSNumericSearch];</span><br><span class="line"> // NSLog(@"^^^::%ld", (long)result);</span><br><span class="line"> //</span><br><span class="line"> // return result == NSOrderedDescending;</span><br><span class="line"> // }];</span><br><span class="line">NSArray *views = self.contentView.subviews;</span><br><span class="line">UIView * bottomView = views.firstObject;</span><br><span class="line">CGFloat height = bottomView.frame.origin.y+bottomView.frame.size.height;</span><br><span class="line">_model.cellHeight = height + 10;</span><br></pre></td></tr></table></figure></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 获取屏幕最上层window以及响应者</title>
<link href="/2019/02/27/iOS%E8%8E%B7%E5%8F%96%E5%B1%8F%E5%B9%95%E6%9C%80%E4%B8%8A%E5%B1%82window%E4%BB%A5%E5%8F%8A%E5%93%8D%E5%BA%94%E8%80%85/"/>
<url>/2019/02/27/iOS%E8%8E%B7%E5%8F%96%E5%B1%8F%E5%B9%95%E6%9C%80%E4%B8%8A%E5%B1%82window%E4%BB%A5%E5%8F%8A%E5%93%8D%E5%BA%94%E8%80%85/</url>
<content type="html"><![CDATA[<p><strong>1. 通过UIApplication获取</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">UIWindow *window = [UIApplication sharedApplication].keyWindow;</span><br><span class="line">或者</span><br><span class="line">UIWindow *window = [[UIApplication sharedApplication].windows lastObject];</span><br></pre></td></tr></table></figure><br><strong>2. 比较严谨的获取方法:</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (UIWindow *)lastWindow{</span><br><span class="line"> NSArray *windows = [UIApplication sharedApplication].windows;</span><br><span class="line"> for (UIWindow *window in [windows reverseObjectEnumerator]) {</span><br><span class="line"> if ([window isKindOfClass:[UIWindow class]] && CGRectEqualToRect(window.bounds, [UIScreen mainScreen].bounds)) {</span><br><span class="line"> return window;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return [UIApplication sharedApplication].keyWindow;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><strong>3. 查找响应者</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 新建一个UIView的类目,把这个方法放进去,以后就可以直接通过view.findResponderViewController来获取视图的控制器了。</span><br><span class="line">- (UIViewController *)findResponderViewController{</span><br><span class="line"> UIResponder *next = self.nextResponder;</span><br><span class="line"> do {</span><br><span class="line"> if ([next isKindOfClass:[UIViewController class]]) {</span><br><span class="line"> return (UIViewController *)next;</span><br><span class="line"> }</span><br><span class="line"> next = next.nextResponder;</span><br><span class="line"> } while (next != nil);</span><br><span class="line"> return nil;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>Git SourceTree配置</title>
<link href="/2019/02/26/GitSourceTree%E9%85%8D%E7%BD%AE/"/>
<url>/2019/02/26/GitSourceTree%E9%85%8D%E7%BD%AE/</url>
<content type="html"><![CDATA[<p>创建SSH Key,因为本地的Git仓库与Github远程仓库之间是通过SSH加密的。首先,需要到主目录上查看是否有.ssh目录,再查看.ssh目录下有没有id_rsa和id_rsa.pub文件,如下<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2018.06.27.01.jpg" alt="查找.ssh目录"></p><p>发现没有上述的两个文件,这时需要创建:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C "[email protected]"</span><br></pre></td></tr></table></figure><br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2018.06.27.02.png" alt=""></p><p>出现上述描述,就证明你成功了,然后到主目录下找到.ssh目录,查看id_rsa和id_rsa.pub文件,id_rsa是私钥,需要自己保留好,id_rsa.pub是公钥,别人知道也无妨。<br>登录Github账户,打开Account settings,SSH Keys页面,添加id_rsa.pub文件的内容。</p>]]></content>
<tags>
<tag> 代码库 </tag>
</tags>
</entry>
<entry>
<title>iOS 面试题汇总</title>
<link href="/2019/02/25/iOS%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB/"/>
<url>/2019/02/25/iOS%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB/</url>
<content type="html"><![CDATA[<p><strong>1. 简单介绍下NSURLConnection类及<code>+ sendSynchronousRequest:returningResponse:error:</code>与<code>– initWithRequest:delegate:</code>两个方法的区别?</strong></p><blockquote><p>答: NSURLConnection主要用于网络访问,其中+ sendSynchronousRequest:returningResponse:error:是同步访问数据,即当前线程会阻塞,并等待request的返回的response,而– initWithRequest:delegate:使用的是异步加载,当其完成网络访问后,会通过delegate回到主线程,并其委托的对象。</p></blockquote><p><strong>2. 在项目什么时候选择使用GCD,什么时候选择NSOperation</strong></p><blockquote><p>答: 项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。<br>项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。</p></blockquote><p><strong>3. ViewController的didReceiveMemoryWarning怎么被调用</strong></p><blockquote><p>答:[supper didReceiveMemoryWarning];</p></blockquote><p><strong>4. 写一个setter方法用于完成<code>@property(nonatomic, retain) NSString *name</code>,写一个setter方法用于完成<code>@property(nonatomic, copy) NSString *name</code></strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">- (void)setName:(NSString *)str{</span><br><span class="line"> [str retain];</span><br><span class="line"> [_name release];</span><br><span class="line"> _name = str;</span><br><span class="line">}</span><br><span class="line">- (void)setName:(NSString *)str{</span><br><span class="line"> id t = [str copy];</span><br><span class="line"> [_name release];</span><br><span class="line"> _name = t;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><strong>5. 对于语句<code>NSString *obj = [[NSData alloc] init];</code> obj在编译时和运行时分别时什么类型的对象?</strong></p><blockquote><p>答: 编译时是NSString的类型;运行时是NSData类型的对象</p></blockquote><p><strong>6. Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?</strong></p><blockquote><p>答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:</p></blockquote><p><strong>7. 浅复制和深复制的区别?</strong></p><blockquote><p>答:浅层复制:只复制指向对象的指针,而不复制引用对象本身。<br>深层复制:复制引用对象本身。</p></blockquote><p><strong>8. PerformSelecter</strong></p><blockquote><p>当调用 NSObject 的<code>performSelecter:afterDelay:</code>后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。<br>当调用<code>performSelector:onThread:</code>时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。</p></blockquote><p><strong>9. 优化你是从哪几方面着手?</strong></p><blockquote><p>一、首页启动速度<br> 启动过程中做的事情越少越好(尽可能将多个接口合并)<br> 不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新节目)<br>在合适的时机开始后台任务(例如在用户指引节目就可以开始准备加载的数据)<br>二、页面浏览速度<br>json的处理(iOS 自带的NSJSONSerialization,Jsonkit,SBJson)<br>数据的分页(后端数据多的话,就要分页返回,例如网易新闻,或者 微博记录)<br>数据压缩(大数据也可以压缩返回,减少流量,加快反应速度)<br>内容缓存(例如网易新闻的最新新闻列表都是要缓存到本地,从本地加载,可以缓存到内存,或者数据库,根据情况而定)<br>延时加载tab(比如app有5个tab,可以先加载第一个要显示的tab,其他的在显示时候加载,按需加载)<br>算法的优化(核心算法的优化,例如有些app 有个 联系人姓名用汉语拼音的首字母排序)<br>三、操作流畅度优化<br>Tableview 优化(tableview cell的加载优化)<br>ViewController加载优化(不同view之间的跳转,可以提前准备好数据)<br>四、数据库的优化<br>数据库设计上面的重构<br>查询语句的优化<br>分库分表(数据太多的时候,可以分不同的表或者库)<br>五、服务器端和客户端的交互优化<br>客户端尽量减少请求<br>服务端尽量做多的逻辑处理<br>服务器端和客户端采取推拉结合的方式(可以利用一些同步机制)<br>通信协议的优化(减少报文的大小)<br>电量使用优化(尽量不要使用后台运行)<br>六、非技术性能优化<br>产品设计的逻辑性(产品的设计一定要符合逻辑,或者逻辑尽量简单,否则会让程序员抓狂,有时候用了好大力气,才可以完成一个小小的逻辑设计问题)<br>界面交互的规范(每个模块的界面的交互尽量统一,符合操作习惯)<br>代码规范(这个可以隐形带来app 性能的提高,比如 用if else 还是switch ,或者是用!还是 ==)<br>code review(坚持code Review 持续重构代码。减少代码的逻辑复杂度)</p></blockquote><p><strong>10. 什么情况使用 weak 关键字,相比 assign 有什么不同?</strong></p><blockquote><p>1.在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。<br>2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,如自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。<br>IBOutlet连出来的视图属性为什么可以被设置成weak?<br>答:因为父控件的subViews数组已经对它有一个强引用。<br>不同点<br>assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。<br>weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)</p></blockquote><p><strong>11. 用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?</strong></p><blockquote><p>答:用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。<br>1.因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。<br>2.如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。<br>总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。</p></blockquote><p><strong>12. runtime如何实现weak变量的自动置nil?</strong></p><blockquote><p>runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为nil</p></blockquote><p><strong>13. runloop是什么/runloop的概念?</strong></p><blockquote><p>runloop是线程相关的基础框架的一部分。一个runloop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。其实内部就是do-while循环,这个循环内部不断地处理各种任务(比如Source,Timer,Observer)。使用runloop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。</p></blockquote><p><strong>14. UITableViewCell上有个UILabel,显示NSTimer实现的秒表时间,手指滚动cell过程中,label是否刷新,为什么?</strong></p><blockquote><p>这是否刷新取决于timer加入到Run Loop中的Mode是什么。Mode主要是用来指定事件在运行循环中的优先级的,分为</p><ul><li>NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态</li><li>UITrackingRunLoopMode:ScrollView滑动时会切换到该Mode</li><li>UIInitializationRunLoopMode:run loop启动时,会切换到该mode</li><li>NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合<br>苹果公开提供的Mode有两个</li><li>NSDefaultRunLoopMode(kCFRunLoopDefaultMode)</li><li>NSRunLoopCommonModes(kCFRunLoopCommonModes)<br>在编程中:如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。当我们滚动的时候,也希望不调度,那就应该使用默认模式。但是,如果希望在滚动时,定时器也要回调,那就应该使用common mode。</li></ul></blockquote><p><strong>15. NStimer准吗?谈谈你的看法?如果不准该怎样实现一个精确的NSTimer?</strong></p><blockquote><p>不准;不准的原因如下<br>1、NSTimer加在main runloop中,模式是NSDefaultRunLoopMode,main负责所有主线程事件,例如UI界面的操作,复杂的运算,这样在同一个runloop中timer就会产生阻塞。<br>2、模式的改变。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。<br>当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个ScrollView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。所以就会影响到NSTimer不准的情况。<br>PS:DefaultMode 是 App 平时所处的状态,rackingRunLoopMode 是追踪 ScrollView 滑动时的状态。<br>方法:<br>1、在主线程中进行NSTimer操作,但是将NSTimer实例加到main runloop的特定mode(模式)中。避免被复杂运算操作或者UI界面刷新所干扰self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];<br>[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];<br>2、在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果<br>-(void)timerMethod2 {<br> NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];<br> [thread start];<br>}<br>-(void)newThread{<br> @autoreleasepool{<br> [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];<br> [[NSRunLoop currentRunLoop] run];<br> }<br>}</p></blockquote><p><strong>16. NSOperation 相比于 GCD 有哪些优势?</strong></p><blockquote><p>GCD是基于c的底层api,NSOperation属于object-c类。ios 首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用gcd实现的。<br>相对于GCD:<br>1、NSOperation拥有更多的函数可用,具体查看api。<br>2、在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。<br>3、有kvo可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。<br>4、NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。<br>GCD主要与block结合使用。代码简洁高效。<br>GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。<br>具体使用哪个,依需求而定。 从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。</p></blockquote><p><strong>17. 如何访问并修改一个类的私有属性?</strong></p><blockquote><p>有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性。</p></blockquote><p><strong>18. 如何捕获异常?</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">1. 在app启动时(didFinishLaunchingWithOptions),添加一个异常捕获的监听</span><br><span class="line">NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);</span><br><span class="line">2. 实现捕获异常日志并保存到本地的方法</span><br><span class="line">void UncaughtExceptionHandler(NSException *exception){</span><br><span class="line"> //异常日志获取</span><br><span class="line"> NSArray *excpArr = [exception callStackSymbols];</span><br><span class="line"> NSString *reason = [exception reason];</span><br><span class="line"> NSString *name = [exception name];</span><br><span class="line"> NSString *excpCnt = [NSString stringWithFormat:@"exceptionType: %@ \n reason: %@ \n stackSymbols: %@",name,reason,excpArr];</span><br><span class="line"> //日常日志保存(可以将此功能单独提炼到一个方法中)</span><br><span class="line"> NSArray *dirArr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);</span><br><span class="line"> NSString *dirPath = dirArr[0];</span><br><span class="line"> NSString *logDir = [dirPath stringByAppendingString:@"/CrashLog"];</span><br><span class="line"></span><br><span class="line"> BOOL isExistLogDir = YES;</span><br><span class="line"> NSFileManager *fileManager = [NSFileManager defaultManager];</span><br><span class="line"> if (![fileManager fileExistsAtPath:logDir]) {</span><br><span class="line"> isExistLogDir = [fileManager createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil];</span><br><span class="line"> }</span><br><span class="line"> if (isExistLogDir) {</span><br><span class="line"> //此处可扩展</span><br><span class="line"> NSString *logPath = [logDir stringByAppendingString:@"/crashLog.txt"];</span><br><span class="line"> [excpCnt writeToFile:logPath atomically:YES encoding:NSUTF8StringEncoding error:nil];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><strong>19. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?</strong></p><blockquote><p>答:Object-c的类不可以多重继承;可以实现多个接口,通过实现多个接口可以完成C++的多重继承;Category是类别,一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。</p></blockquote><p><strong>20. Category(分类),Extension(扩展)和继承的区别</strong></p><blockquote><p>答:1.分类<br>category原则上只能在现有类基础上添加新的方法(能添加属性的原因只是通过runtime解决无setter/getter的问题而已),类别中的方法没被实现编译器是不会有任何警告的,这是因为类别是在运行时添加到类中的<br>2.扩展<br>iOS中的extension就是匿名的分类,只有头文件没有实现文件。类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(使用范围只能在自身类,而不是子类或其他地方),类扩展中声明的方法没被实现,编译器会报警,这是因为类扩展是在编译阶段被添加到类中的<br>3.继承<br>在iOS中继承是单继承,既只能有一个父类。在继承中,子类可以使用父类的方法和变量,当子类想对本类或者父类的变量进行初始化,那么需要重写init()方法 。父类也可以访问子类的方法和成员变量</p></blockquote><p><strong>21. 简述内存分区情况</strong></p><blockquote><p>1).代码区:存放函数二进制代码<br> 2).数据区:系统运行时申请内存并初始化,系统退出时由系统释放。存放全局变量、静态变量、常量<br> 3).堆区:通过malloc等函数或new等操作符动态申请得到,需程序员手动申请和释放<br> 4).栈区:函数模块内申请,函数结束时由系统自动释放。存放局部变量、函数参数</p></blockquote><p><strong>22. 直接调用_objc_msgForward函数将会发生什么?</strong></p><blockquote><p>_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。<br>直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。<br>一旦调用_objc_msgForward,将跳过查找 IMP 的过程,直接触发“消息转发”,如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:“我没有在这个对象里找到这个方法的实现”</p></blockquote><p><strong>23. 对于Run Loop的理解</strong></p><blockquote><ul><li>RunLoop,是多线程的法宝,即一个线程一次只能执行一个任务,执行完任务后就会退出线程。主线程执行完即时任务时会继续等待接收事件而不退出。非主线程通常来说就是为了执行某一任务的,执行完毕就需要归还资源,因此默认是不运行RunLoop的;</li><li>每一个线程都有其对应的RunLoop,只是默认只有主线程的RunLoop是启动的,其它子线程的RunLoop默认是不启动的,若要启动则需要手动启动;</li><li>在一个单独的线程中,如果需要在处理完某个任务后不退出,继续等待接收事件,则需要启用RunLoop;</li><li>NSRunLoop提供了一个添加NSTimer的方法,可以指定Mode,如果要让任何情况下都回调,则需要设置Mode为Common模式;</li><li>实质上,对于子线程的runloop默认是不存在的,因为苹果采用了懒加载的方式。如果我们没有手动调用[NSRunLoop currentRunLoop]的话,就不会去查询是否存在当前线程的RunLoop,也就不会去加载,更不会创建。</li></ul></blockquote><p><strong>24. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)</strong></p><blockquote><p>1.每一个类对象中都一个对象方法列表(对象方法缓存)<br> 2.类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)<br> 3.方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.<br> 4.当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找<br> 5.当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找</p></blockquote><p><strong>25. runtime 中,SEL 和 IMP 的区别</strong></p><blockquote><p>方法名 SEL – 表示该方法的名称;<br> IMP – 指向该方法的具体实现的函数指针,说白了IMP就是实现方法。</p></blockquote><p><strong>26.block底层实现</strong></p><blockquote><p>block本质是指向一个结构体的一个指针<br>运行时机制 比较高级的特性 纯C语言<br>平时写的OC代码 转换成C语言运行时的代码<br>指令:clang -rewrite-objc main.m(可以打印验证)<br>默认情况下,任何block都是在栈里面的,随时可能被回收<br>只要对其做一次copy操作 block的内存就会放在堆里面 不会释放<br>只有copy才能产生一个新的内存地址 所有地址会发生改变</p></blockquote><p><strong>27. TCP协议三次握手</strong></p><blockquote><p>TCP协议采用了三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达。握手过程中使用了TCP的标志——SYN(synchronize)和ACK(acknowledgement)。发送端首先发送一个带SYN标志的数据包给对方。接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。<br><img src="https://raw.githubusercontent.com/Gsl201600/PicGoImg/master/img/2019.02.25.01.png" alt="TCP协议三次握手示意图"></p></blockquote><p><strong>28. @property 的本质是什么?</strong></p><blockquote><p>@property = ivar + getter + setter;<br>“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)</p></blockquote><p><strong>29. KVC的底层实现?</strong></p><blockquote><p>当一个对象调用setValue方法时,方法内部会做以下操作:<br>1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。<br>2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。<br>3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。<br>4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。<br>这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。</p></blockquote><p><strong>30. ViewController生命周期</strong></p><blockquote><p>按照执行顺序排列:<br>1). initWithCoder:通过nib文件初始化时触发。<br>2). awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。<br>3). loadView:开始加载视图控制器自带的view。<br>4). viewDidLoad:视图控制器的view被加载完成。<br>5). viewWillAppear:视图控制器的view将要显示在window上。<br>6). updateViewConstraints:视图控制器的view开始更新AutoLayout约束。<br>7). viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。<br>8). viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。<br>9). viewDidAppear:视图控制器的view已经展示到window上。<br>10). viewWillDisappear:视图控制器的view将要从window上消失。<br>11). viewDidDisappear:视图控制器的view已经从window上消失。</p></blockquote><p><strong>31. 如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。</span><br><span class="line">// 创建队列组</span><br><span class="line">dispatch_group_t group = dispatch_group_create();</span><br><span class="line">// 获取全局并发队列</span><br><span class="line">dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);</span><br><span class="line">dispatch_group_async(group, queue, ^{ /*加载图片1 */ });</span><br><span class="line">dispatch_group_async(group, queue, ^{ /*加载图片2 */ });</span><br><span class="line">dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); </span><br><span class="line">// 当并发队列组中的任务执行完毕后才会执行这里的代码</span><br><span class="line">dispatch_group_notify(group, dispatch_get_main_queue(), ^{</span><br><span class="line"> // 合并图片</span><br><span class="line">});</span><br></pre></td></tr></table></figure><br><strong>32. dispatch_barrier_async(栅栏函数)的作用是什么?</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);</span><br><span class="line">作用:</span><br><span class="line"> 1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。</span><br><span class="line"> 2.避免数据竞争</span><br><span class="line"></span><br><span class="line">// 1.创建并发队列</span><br><span class="line">dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);</span><br><span class="line">// 2.向队列中添加任务</span><br><span class="line">dispatch_async(queue, ^{ // 1.2是并行的</span><br><span class="line"> NSLog(@"任务1, %@",[NSThread currentThread]);</span><br><span class="line">});</span><br><span class="line">dispatch_async(queue, ^{</span><br><span class="line"> NSLog(@"任务2, %@",[NSThread currentThread]);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">dispatch_barrier_async(queue, ^{</span><br><span class="line"> NSLog(@"任务 barrier, %@", [NSThread currentThread]);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">dispatch_async(queue, ^{ // 这两个是同时执行的</span><br><span class="line"> NSLog(@"任务3, %@",[NSThread currentThread]);</span><br><span class="line">});</span><br><span class="line">dispatch_async(queue, ^{</span><br><span class="line"> NSLog(@"任务4, %@",[NSThread currentThread]);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4 </span><br><span class="line">// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。</span><br></pre></td></tr></table></figure></p>]]></content>
<tags>