-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
921 lines (699 loc) · 311 KB
/
atom.xml
File metadata and controls
921 lines (699 loc) · 311 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Spacetime</title>
<subtitle>Blogs from campusapp.cn's crazy developers</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://campusappcn.github.io/"/>
<updated>2016-05-07T08:39:25.000Z</updated>
<id>http://campusappcn.github.io/</id>
<author>
<name>developers from campusapp.cn</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>ios的widget的总结</title>
<link href="http://campusappcn.github.io/2016/05/07/ios%E7%9A%84widget%E7%9A%84%E6%80%BB%E7%BB%93/"/>
<id>http://campusappcn.github.io/2016/05/07/ios的widget的总结/</id>
<published>2016-05-07T08:05:00.000Z</published>
<updated>2016-05-07T08:39:25.000Z</updated>
<content type="html"><p>前段时间在研究ios中的widget的实现。发现其中有不少的坑~现在widget的开发也告一段落了。那么把这些widget的坑拿出来分享下吧~</p>
<p>首先来说下widget,该功能是从ios8开始的。在通知栏下拉下来的页面中有“今天”和“通知”两个tab,其中widget的内容都是在“今天”里面。之前我也没怎么关注过“今天”里面的内容。自从要在自己的app中加入widget之后,发现其实很多的app都带有widget的功能,只不过大家不知道而已。比如微博,uc什么的都是有的,而且体验也还不赖。</p>
<p>在ios8的介绍中,widget其实又叫today,它是归属于app Extension里面的一项。主要widget这个词比较通用,在其他的设备上也是有widget这个东东,如果说today不要说非ios开发,就算是ios开发,也会愣住。那么下面都直接用widget吧。至于如何创建widget以及widget的一些基本的开发,我这边就不说了。这个只要一谷歌就会有大批量的文章出现,而且不用纠结选取哪一篇,因为基本上都是一样的=,=。<br>ok,那么省略过中间的无数关于widget的基础开发的等等。下面我就说下我写这个的中心思想吧。也就是widget的多奇葩,以及那些一不小心就会坑死你的坑。(当然将下面的前提是,我认为看官们已经了解了接触过ios上的widget或者说已经着手开始做widget了)</p>
<p>首先说下关于调试方面的:调试widget还是比较蛋疼的,因为经常性的你不管测试单个的widget,还是整个应用,widget部分的内容经常会不更新,也许我上面那句话不是很好理解,就是说你虽然在widget那边写了先的逻辑,但是他执行的仍旧是你修改前的那份代码逻辑。所以很多时候需要做的就是“退到桌面,长按app删除,再来!”。而我自己在调试的时候,一般是会选择写测试数据,调试单个的widget,等全部ok之后,直接打release的包来看测试,然后一般我比较相信打log的测试方法,所以经常性的出问题了会打log。当然这也做的前提是widget部分的代码逻辑并不是很复杂。</p>
<p>接下来要说的是关于widget处的ui以及交互上的问题:对于widget,官方是这么说的,一般用来呈现一些比较简单的,交互性不强的内容。但是对于官方的这个说法我只能“呵呵”。因为我们基本上对于这种东西,所用的思路都是和官方对着干的。大家想想app的启动页就可以了。所以呢,在widget上免不了会绑定各种点击事件。首先要说的是点击区域的问题。经过切身实际的体验发现,一般的按钮不过是图片的按钮,只靠图片大小的那么点点击区域肯定是不够的。所以我们需要扩大点击区域。恩!扩大点击区域设置<code>UIEdgeInsets</code>就可以了,这种方法在我们之前的开发中,屡试不爽,还特别的优雅。但是,发现在widget中并不管用。而且如果你创建一个控件,空设置他的frame,但是不设置里面的内容,或者背景色什么的,那么这个控件就算绑定了事件,也是响应不了的。具体的原因是什么,我没有深究。也许是widget这边的一个bug也许是人家故意设计的feature也说不定。不过,如果设置一张图的话,就算是透明的图,还是可以响应事件的。那么久有方法了,可以设置一张边上是透明的,中间有实际内容的图,就可以解决这个问题了。还有一点,其实和上面说的差不多,就是如果你在一个控件上加了手势判断,如果其中没有被内容(图片,文字)覆盖到的地方,就没方法响应。之前我看到有的博客说<code>-(void)touchesBegan:(NSSet&lt;UITouch *&gt; *)touches withEvent:(UIEvent *)event</code>可以用这个方法来响应,但是实际用了之后还是不行,也许可以用下一张透明的图来做估计是可行的,具体我是还没有实际运用,但是其实和上面所说的效果是一样的。</p>
<p>再说一点是关于内存的:也许对于ios开发来说,对于内存这块的考虑,并不需要像安卓开发那么考虑的这么多,然而在widget这一块,却会让我们吃到苦头。具体关于widget的内存的分配什么的我是没有做过具体的研究,我只是通过了一个实际的案例发现的。再拿我之前的widget项目来说吧。由于我们的widget中需要加入图片。然后加载了一张200k+的图片,结果整个widget就奔溃了(当然我这边说的widget只是我们app对于的widget插件,并非是设备的整个widget系统),后来换了比较小的图,就没有发生过这样的问题。不过我有一点怀疑的是widget这边的内存,好像对于图片这块的管理或者说分配会比较紧,因为只有在使用大图片的时候才会出现这样的问题。</p>
<p>还要讲的一点是关于widget的更新的:这个更细的问题之前也是困扰了我好久,查资料也没查出什么结果来,所以我强烈的觉得这个是widget的bug,而不是feature。具体情况是什么呢,就是说你app的版本更新,其中widget的部分也做了更新,但是这时候你会发现你的widget并没有和app一样更新完,再重新打开就变成了最新版本。只有设备重启或者说删除app在重新安装才会更新,就算是widget移除在加入也是没有用的,我认为原因是整个widget系统的生命周期只有在关闭设备才会结束,而单个的widget也是在widget系统被销毁的时候或者说app删除的时候才会被销毁。其实我可以举一个栗子,对于某款app的一个widget应用,它非常的强盗,只要进入widget的页面就一直在跑循环,获取ssid的最新状态,我把这个widget从widget的显示处移除了,但是它还是一直在跑循环,只有重启设备之后才可以。所以我才会有上述的推断。甚至之前我还考虑过是不是说我把自己的widget搞crash了,它就可以重新加载最新的代码了,但是正如大家所知的,如果widget creash了那么让就不会再重新加载,除非重启widget,我觉得这一点苹果应该是在保护其他widget的利益吧。好了所以这一点,对于版本更新来说还是比较头疼的,毕竟现在重启手机的频率并不高。为了完美的解决这个问题,只能等苹果修复这个bug了。但是感觉希望渺茫,因为我走遍了各大论坛,发现对于这个问题的描述并不多,而且个人感觉苹果对于widget的热情也不高涨。</p>
<p>最后要说一点的是关于widget的兼容性方便的:众所周知widget是在发布ios8的时候提出的,那么当然widget最低支持的版本是ios8,但是这并不妨碍你的app最低的支持版本小于ios8。这样只是说小于ios8的用户享受不到widget那边分的插件而已。并不会造成ios8以下的用户闪退什么的。还有一点在app和widget之间肯定有数据的通信,或者说代码的重用什么的。在ios8的时候提出了动态链接库,当时我也很开心,把两边都需要用到的部分逻辑代码放到了一个framework中。结果在提交的时候发现瞎了,因为动态链接库这个最低支持的版本也是ios8!那么,那么就是说小于ios8的用户也没办法使用到这个framework,但是里面有部分逻辑代码是非widget也需要用到的(当然这个是我们的widget开发时候遇到的具体问题),又由于这部分的代码需要经常性改掉(比如说版本号什么的),这样也不方便做成静态的库什么的,所以最后还是用了比较low的方法,通过userdefault来传递。所以有时候在使用新技术的时候需要考虑到在低版本上会出现什么情况。</p>
<p>好了,以上的就是我在开发widget的时候遇到的一些问题以及一些想法吧算是。个人感觉widget还是蛮有用的。虽然之前一直没有打开设备上的widget,但是研究之后发现,有时候还是蛮方便的。只不过有些app的widget做的实在是不管恭维,还是说之前提到过的那个跑循环获取ssid的那个好了。它到好是直接打log的,所以我能够在控制台看到,如果把log隐藏掉了,一时半会儿我还真是不知道他做了这么强盗的事情。会出现这种的原因我觉得有两点:一个是由于这个widget可以说和app是相对来说比较分离的,意思说你widget干什么事情不管我app,就算crash了也和我app没关系,所以就乱来,还有一点就是很多人不知道这个widget这个功能,所以开发人员就更加乱来了,抱着反正没人用的心态在那边瞎搞。所以导致。。。</p>
<p>虽然ios上的widget的灵活度或者说可方法程度还不能和安卓的widget的相比较,但是我还是觉得这个widget还是很不错的,希望我能够有勇气打开更多app的widget~~~~</p>
</content>
<summary type="html">
前段时间在研究ios中的widget的实现。发现其中有不少的坑~现在widget的开发也告一段落了。那么把这些widget的坑拿出来分享下吧~
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="widget" scheme="http://campusappcn.github.io/tags/widget/"/>
<category term="today" scheme="http://campusappcn.github.io/tags/today/"/>
<category term="更新" scheme="http://campusappcn.github.io/tags/%E6%9B%B4%E6%96%B0/"/>
<category term="ios8" scheme="http://campusappcn.github.io/tags/ios8/"/>
</entry>
<entry>
<title>iOS的urlRoute</title>
<link href="http://campusappcn.github.io/2016/04/20/iOS%E7%9A%84urlRoute/"/>
<id>http://campusappcn.github.io/2016/04/20/iOS的urlRoute/</id>
<published>2016-04-20T07:23:33.000Z</published>
<updated>2016-04-20T08:48:30.000Z</updated>
<content type="html"><p>route,路由。这个词在前端中很常见。不过我今天要说的是移动端的route。<br>而是app中的路由,也就是可以成为urlRoute。这个urlRoute主要用于app内页面间的跳转。每个页面的跳转都通过一个特定的url的形式来跳转。<br>其实说了这么多主要是为了介绍一个框架。好吧,先把地址放出来吧( <a href="https://github.com/campusappcn/iOSUrlRoute" target="_blank" rel="external">https://github.com/campusappcn/iOSUrlRoute</a> ),以下的urlRoute的内容基本上是围绕着这个框架在讲的。如果满意请大家小手都下star下哈。<br>那么在app内运用urlRoute的形式来跳转有什么好处呢?<br>我觉得最大的好处就是页面间的解耦合。原先我们从A页面跳转到B页面,可以是pushVC或者presentVC,甚至是addSubView。但是不管怎么做你都需要在A页面相关的地方(有个能是在A的VC中,也有可能是在A的ViewModel中)导入B,初始化B之类的一系列操作。我相信这一些列的步骤,大家肯定很熟悉。也许有人可能会差异,难道不是这样做么?或者说这样做有什么不好么?……那么我先来说几种情况吧。1)如果B页面还没有完成,2)B页面有可能会被替换成其他页面。这里就列举这两种情况(好吧,其实一时半会儿我也想不到更多的=,=)如果这两种情况的时候,用上述的步骤是不是会觉得很头痛,因为B页面压根儿就还没完成,也许是分工给了你的同事,也许是你自己还没完成。那么在A页面中跳转的部分,就不得不缓缓,等到B页面完成或者说创建好了之后才能在来完成A页面跳转的部分内容。对于B有可能会被替换,那么久更头痛了。如果你是分开在各个页面中写的跳转代码的话,那么你就要全局搜索,来改动了(这可是一项不小的体力活)。<br>那么如果这时候用urlRoute呢,因为urlRoute是通过一个特殊的url来判断跳转,也就是说其实页面A并不是知道实质上跳转到的是哪个页面,他只是告诉了控制urlRoute的中心,我要跳转到B页面,那么具体怎么如何跳转,甚至跳转到哪个页面都是有urlRoute的中心在执行的。所以说A页面和B页面完全没有了关联。<br>第二个优点可以很动态的控制跳转的路径。其实这个也是我最初接触到app内路由的原因。当时我接触的场景是这样的。app内,由于某个页面出现了问题,急需要用其他页面来代替(网页),或者说某个特定的页面因为需要做活动,急需替换成一个其他的活动网页。这时候提交更新肯定是来不及了。那么在不更新代码的情况下要如何做呢。当然热更新可以做,直接重写跳转代码所在的那个方法。虽然可以做,但是不优雅(=,=),不优雅的原因在于:用热更新本身的执行效率会有影响,再者有点杀鸡用牛刀的感觉。当然这时候urlRoute就华丽的出场了,只要把跳转到那么特定页面的路由所配置的跳转路径动态替换下就可以了。当然这只是一个特定的场景,其实在很多时候动态的配置路由来控制跳转路径还是很有用的。再举一个例子,比如说做ABTest,关于打开app的时候先呈现什么内容给用户,这种场景用路由动态得控制也是非常赞的。<br>听了上面的介绍,是不是突然间觉得用urlRoute来控制页面间的跳转是件多么美好的事情。<br>那么下面将继续给你呈现更加美好的东东。<br>有不少同学可能会说,路由跳转固然是好,但是我如何传值到下个页面呢,或者说我从页面pop返回的时候,我在特定情况下需要刷新当前页面,这些光靠这个urlRoute的框架好像完成不了吧。不不不~这些在urlRoute这个框架中都考虑到。对于传参的情况是通过key&amp;value的形式传递过去,那么也就是说如果从A到B页面,需要传参,就必须知道B页面所需要的参数的名称和类型。通过key&amp;value的形式交给urlRoute中心去处理,在B页面通过key来获得value。而对于需要回调刷新之类的问题,是通过注册block来解决的。<br>当然这个框架除了以上这些外,还可以处理在app外打开app传值进入跳转到特定页面的场景。<br>urlRoute的宗旨是,把app内所有有关于页面间的跳转全部控制起来。这是一个伟大的事业~</p>
</content>
<summary type="html">
route,路由。这个词在前端中很常见。不过我今天要说的是移动端的route。
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="ios" scheme="http://campusappcn.github.io/tags/ios/"/>
<category term="urlRoute" scheme="http://campusappcn.github.io/tags/urlRoute/"/>
<category term="页面跳转" scheme="http://campusappcn.github.io/tags/%E9%A1%B5%E9%9D%A2%E8%B7%B3%E8%BD%AC/"/>
</entry>
<entry>
<title>vImage高斯模糊(Blur)</title>
<link href="http://campusappcn.github.io/2016/04/12/vImage%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A-Blur/"/>
<id>http://campusappcn.github.io/2016/04/12/vImage高斯模糊-Blur/</id>
<published>2016-04-12T08:57:09.000Z</published>
<updated>2016-04-20T08:49:38.000Z</updated>
<content type="html"><p>本文主要内容:<br>1.用vImage来做<code>实时</code>高斯模糊<br>2.遇到的坑<br>3.爬坑<br><a id="more"></a></p>
<p> 在iOS7以后,半透明模糊效果在系统中大量使用,不仅在iPhone上,Mac上也随处可见这种效果.在iOS上实现这种效果的方法很多,不同的框架(CoreImage,GPUImage…)和各种扩展(UIVisualEffectView)提供了不同的方式方法.<br> 具体可以查看stackoverflow上这个问题:</p>
<blockquote>
<p><a href="http://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view" target="_blank" rel="external">http://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view</a></p>
</blockquote>
<h2 id="用vImage来做实时高斯模糊"><a href="#用vImage来做实时高斯模糊" class="headerlink" title="用vImage来做实时高斯模糊"></a>用vImage来做<code>实时</code>高斯模糊</h2><p>这是<a href="http://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view" target="_blank" rel="external">http://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view</a>问题的回答中代码的一点修改<br><figure class="highlight objc"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">//#import &lt;Accelerate/Accelerate.h&gt;</span></span><br><span class="line"></span><br><span class="line">-(<span class="built_in">UIImage</span> *)boxblurImageWithBlur:(<span class="built_in">CGFloat</span>)blur oImg:(<span class="built_in">UIImage</span> *)oImg&#123;</span><br><span class="line"> <span class="keyword">if</span> (blur &lt; <span class="number">0.</span>f || blur &gt; <span class="number">1.</span>f) &#123;</span><br><span class="line"> blur = <span class="number">0.5</span>f;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">int</span> boxSize = (<span class="keyword">int</span>)(blur * <span class="number">50</span>);</span><br><span class="line"> boxSize = boxSize - (boxSize % <span class="number">2</span>) + <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">CGImageRef</span> img = oImg.CGImage;</span><br><span class="line"> vImage_Buffer inBuffer, outBuffer;</span><br><span class="line"> vImage_Error error;</span><br><span class="line"> <span class="keyword">void</span> *pixelBuffer;</span><br><span class="line"> <span class="built_in">CGDataProviderRef</span> inProvider = <span class="built_in">CGImageGetDataProvider</span>(img);</span><br><span class="line"> <span class="built_in">CFDataRef</span> inBitmapData = <span class="built_in">CGDataProviderCopyData</span>(inProvider);</span><br><span class="line"> inBuffer.width = <span class="built_in">CGImageGetWidth</span>(img);</span><br><span class="line"> inBuffer.height = <span class="built_in">CGImageGetHeight</span>(img);</span><br><span class="line"> inBuffer.rowBytes = <span class="built_in">CGImageGetBytesPerRow</span>(img);</span><br><span class="line"> inBuffer.data = (<span class="keyword">void</span>*)<span class="built_in">CFDataGetBytePtr</span>(inBitmapData);</span><br><span class="line"> pixelBuffer = malloc(<span class="built_in">CGImageGetBytesPerRow</span>(img) * <span class="built_in">CGImageGetHeight</span>(img));</span><br><span class="line"> <span class="keyword">if</span>(pixelBuffer == <span class="literal">NULL</span>)</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"No pixelbuffer"</span>);</span><br><span class="line"> outBuffer.data = pixelBuffer;</span><br><span class="line"> outBuffer.width = <span class="built_in">CGImageGetWidth</span>(img);</span><br><span class="line"> outBuffer.height = <span class="built_in">CGImageGetHeight</span>(img);</span><br><span class="line"> outBuffer.rowBytes = <span class="built_in">CGImageGetBytesPerRow</span>(img);</span><br><span class="line"> error = vImageBoxConvolve_ARGB8888(&amp;inBuffer, &amp;outBuffer, <span class="literal">NULL</span>, <span class="number">0</span>, <span class="number">0</span>, boxSize, boxSize, <span class="literal">NULL</span>, kvImageEdgeExtend);</span><br><span class="line"> <span class="keyword">if</span> (error) &#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"JFDepthView: error from convolution %ld"</span>, error);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="built_in">CGColorSpaceRef</span> colorSpace = <span class="built_in">CGColorSpaceCreateDeviceRGB</span>();</span><br><span class="line"> <span class="built_in">CGContextRef</span> ctx = <span class="built_in">CGBitmapContextCreate</span>(outBuffer.data,</span><br><span class="line"> outBuffer.width,</span><br><span class="line"> outBuffer.height,</span><br><span class="line"> <span class="number">8</span>,</span><br><span class="line"> outBuffer.rowBytes,</span><br><span class="line"> colorSpace,</span><br><span class="line"> k<span class="built_in">CGImageAlphaNoneSkipLast</span>);</span><br><span class="line"> <span class="built_in">CGImageRef</span> imageRef = <span class="built_in">CGBitmapContextCreateImage</span> (ctx);</span><br><span class="line"> <span class="built_in">UIImage</span> *returnImage = [<span class="built_in">UIImage</span> imageWith<span class="built_in">CGImage</span>:imageRef];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//clean up</span></span><br><span class="line"> <span class="built_in">CGContextRelease</span>(ctx);</span><br><span class="line"> <span class="built_in">CGColorSpaceRelease</span>(colorSpace);</span><br><span class="line"> </span><br><span class="line"> free(pixelBuffer);</span><br><span class="line"> <span class="built_in">CFRelease</span>(inBitmapData);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CGImageRelease</span>(imageRef);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> returnImage;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>赶紧搞个本地图片,写个小Demo测试下</p>
<p><a href="https://github.com/Sdoy/vImageBlur/blob/master/vImageBlur/TestDemoViewController.m#L30" target="_blank" rel="external">demo1</a><br><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UIImageView</span> *view1=[[<span class="built_in">UIImageView</span> alloc]initWithImage:_image1];</span><br><span class="line">_view1.image=[<span class="keyword">self</span> boxblurImageWithBlur:slider.value oImg:_image1];</span><br></pre></td></tr></table></figure></p>
<p>效果图:<br><img src="https://img.alicdn.com/imgextra/i4/373400920/TB2h8UmmVXXXXcJXXXXXXXXXXXX_!!373400920.gif" alt=""></p>
<p>恩,效果相当不错</p>
<h2 id="然后坑来了"><a href="#然后坑来了" class="headerlink" title="然后坑来了,"></a>然后坑来了,</h2><p>把本地图上传到图床,然后用SDWebImage下载之后再进行模糊</p>
<p><a href="https://github.com/Sdoy/vImageBlur/blob/master/vImageBlur/TestDemoViewController.m#L52" target="_blank" rel="external">demo2</a><br><figure class="highlight objc"><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">[view2 sd_setImageWithURL:[<span class="built_in">NSURL</span> URLWithString:<span class="string">@"https://img.alicdn.com/imgextra/i3/373400920/TB2KIkfmVXXXXbYXXXXXXXXXXXX_!!373400920.png"</span>] completed:^(<span class="built_in">UIImage</span> *image, <span class="built_in">NSError</span> *error, SDImageCacheType cacheType, <span class="built_in">NSURL</span> *imageURL) &#123;</span><br><span class="line"> _image2=image;</span><br><span class="line"> &#125;];</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">_view2.image=[<span class="keyword">self</span> boxblurImageWithBlur:slider.value oImg:_image2];</span><br></pre></td></tr></table></figure></p>
<p>效果图:<br><img src="https://img.alicdn.com/imgextra/i2/373400920/TB2BuD6mVXXXXcwXpXXXXXXXXXX_!!373400920.gif" alt=""></p>
<p>第一张是本地图,第二张是从网上下载的图<br>简直,不忍直视….</p>
<h2 id="脱坑"><a href="#脱坑" class="headerlink" title="脱坑"></a>脱坑</h2><p>哪有问题?</p>
<p>猜想:是不是下载的文件格式变了,PNG变JPG这种.<br>用下面的代码来测试<br><a href="https://github.com/Sdoy/vImageBlur/blob/master/vImageBlur/TestDemoViewController.m#L93" target="_blank" rel="external">-(NSString <em>)typeForImageData:(NSData </em>)data</a><br><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"image1-%@"</span>,[<span class="keyword">self</span> typeForImageData:<span class="built_in">UIImagePNGRepresentation</span>(_image1)]);</span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@"image2-%@"</span>,[<span class="keyword">self</span> typeForImageData:<span class="built_in">UIImagePNGRepresentation</span>(_image2)]);</span><br></pre></td></tr></table></figure></p>
<p>输出:image1-image/png<br> image2-image/png</p>
<p>然而格式并没有变</p>
<p>猜想:跟图片的Alpha有关系么.<br>所以来看看图片中的exif有什么不同吧<br>用下面的代码<br><a href="https://github.com/Sdoy/vImageBlur/blob/master/vImageBlur/TestDemoViewController.m#L82" target="_blank" rel="external">-(void)readExif:(UIImage *)image</a><br><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[<span class="keyword">self</span> readExif:_image1];</span><br><span class="line">[<span class="keyword">self</span> readExif:_image2];</span><br></pre></td></tr></table></figure></p>
<p>从输出的结果中发现<br>_image2的信息用多了一个字段HasAlpha<br>貌似图床把图片的Alpha通道打开了<br><img src="https://img.alicdn.com/imgextra/i4/373400920/TB28hj7mVXXXXcOXpXXXXXXXXXX_!!373400920.png" alt=""></p>
<p>在高斯模糊的方法中<a href="https://github.com/Sdoy/vImageBlur/blob/master/vImageBlur/TestDemoViewController.m#L138" target="_blank" rel="external">CGBitmapContextCreate</a>函数的最后一个参数uint32_t bitmapInfo跟Alpha有点关系,从命名中就可以知道</p>
<figure class="highlight objc"><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"><span class="keyword">typedef</span> <span class="built_in">CF_ENUM</span>(uint32_t, <span class="built_in">CGImageAlphaInfo</span>) &#123;</span><br><span class="line"> k<span class="built_in">CGImageAlphaNone</span>, <span class="comment">/* For example, RGB. */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaPremultipliedLast</span>, <span class="comment">/* For example, premultiplied RGBA */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaPremultipliedFirst</span>, <span class="comment">/* For example, premultiplied ARGB */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaLast</span>, <span class="comment">/* For example, non-premultiplied RGBA */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaFirst</span>, <span class="comment">/* For example, non-premultiplied ARGB */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaNoneSkipLast</span>, <span class="comment">/* For example, RBGX. */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaNoneSkipFirst</span>, <span class="comment">/* For example, XRGB. */</span></span><br><span class="line"> k<span class="built_in">CGImageAlphaOnly</span> <span class="comment">/* No color data, alpha data only */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>逐对之后一个参数一个个替换测试,当测试到kCGImageAlphaNoneSkipFirst时,<br>效果是这样的:<br><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2VevYmVXXXXasXFXXXXXXXXXX_!!373400920.gif" alt=""></p>
<p>有Alpha通道的变成正常的了,本地图片又坑爹了,<br>反复测试之后:</p>
<h2 id="总结出了这样的结果"><a href="#总结出了这样的结果" class="headerlink" title="总结出了这样的结果:"></a>总结出了这样的结果:</h2><p>1.如果图片没有Alpha通道,高斯模糊在创建图片的时候(CGBitmapContextCreate()方法),应该设置成kCGImageAlphaNoneSkipLast<br>2.如果图片有Alpha通道,高斯模糊在创建图片的时候(CGBitmapContextCreate()方法),应该设置成kCGImageAlphaNoneSkipFirst</p>
<p>那么我在blur时候指定bitmapInfo就好了.<br>(还有一个思路就是直接改UIImage的exif信息,我并没有尝试)</p>
<p>然后写了个UIImage的扩展类<br><a href="https://github.com/Sdoy/vImageBlur/blob/master/vImageBlur/Blur/UIImage%2BBlur.m" target="_blank" rel="external">UIImage+Blur</a></p>
<p>最后效果:<br>本地图片和远程图片都正常了<br><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2Bb.bmVXXXXcfXpXXXXXXXXXX_!!373400920.gif" alt=""></p>
<p>项目地址:<a href="https://github.com/Sdoy/vImageBlur" target="_blank" rel="external">SampleCode</a></p>
</content>
<summary type="html">
<p>本文主要内容:<br>1.用vImage来做<code>实时</code>高斯模糊<br>2.遇到的坑<br>3.爬坑<br>
</summary>
<category term="iOS" scheme="http://campusappcn.github.io/tags/iOS/"/>
<category term="UIImage" scheme="http://campusappcn.github.io/tags/UIImage/"/>
<category term="Blur" scheme="http://campusappcn.github.io/tags/Blur/"/>
</entry>
<entry>
<title>ios的mock</title>
<link href="http://campusappcn.github.io/2016/04/09/ios%E7%9A%84mock/"/>
<id>http://campusappcn.github.io/2016/04/09/ios的mock/</id>
<published>2016-04-09T08:01:39.000Z</published>
<updated>2016-04-09T08:58:54.000Z</updated>
<content type="html"><p>因为单元测试的缘故,在测试项目中接入了“OCMock”,觉得它的实现很有意思。所以就看了部分“OCMock”的源码。在mock的时候大部分用到的还是运行时的一些特性。之后觉得蛮好玩的,就根据OCMock的大致思路,自己写了一个小小的mock的类。<br>那么先来介绍一下什么是mock吧。<br>mock可以理解为纸老虎。当我们写单元测试的时候,不可避免的要去尽可能少的实例化一些具体的组件来保持测试既短又快。而且保持单元的隔离。在现代的面向对象系统中,测试的组件很可能会有几个依赖的对象。我们用mock来替代实例化具体的依赖class。mock是在测试中的一个伪造的有预定义行为的具体对象的替身对象。被测试的组件不知道其中的差异!你的组件是在一个更大的系统中被设计的,你可以很有信心的用mock来测试你的组件。<br>通过上面的文字其实已经能够说明,mock需要做的事情。一就是吧原有的类被一个mock的类替换掉,再者就是对于该类的方法,进行伪造。<br>在参考了OCMock的源码之后,发现需要完成这些操作,需要借鉴的主要是运行时的以下方法:<br><code>object_setClass(&lt;#id obj#&gt;, &lt;#__unsafe_unretained Class cls#&gt;)</code><br>这个方法将一个对象设置为别的类,而返回值仍旧是返回原来的类别,具体看下下面的这段代码吧</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSString</span> *str = <span class="string">@"hello"</span>;</span><br><span class="line">Class class1 = object_setClass(str, [<span class="built_in">UIButton</span> class]);</span><br></pre></td></tr></table></figure>
<p>在这段代码中实则的作用是将str从NSString类别转化为UIButton类别。而object_setClass的返回值class1还是NSString类别的。<br>这个方法看似很强大,实际上我认为没有多大的用处,我唯一能够想到的是在子类和父类的转化的时候估计可以用到一下。其他的倒还真是没有想到哪里可以用。<br>有了上面的这个方法,那么mock一个类可以轻轻松松的搞定了,当然mock一个类还远远不够,当然还需要mock这个类中的方法。对于mock一个类中的方法,主要用到的是消息转发的思想。在mock类中,执行之前没有的方法,那么重定向到一个已经存在的方法,通过一定的trick返回mock得到的值。这样就实现了整个mock的思路。<br>下面这段主要是在消息转发时候如何处理重定向到一个已经存在的方法:</p>
<figure class="highlight objectivec"><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">-(<span class="keyword">void</span>)forwardInvocation:(<span class="built_in">NSInvocation</span> *)anInvocation</span><br><span class="line">&#123;</span><br><span class="line"> <span class="built_in">BOOL</span> hasMoc = <span class="literal">NO</span>;</span><br><span class="line"> SEL selector = anInvocation.selector;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NSString</span> *selectorName = <span class="built_in">NSStringFromSelector</span>(selector);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">NSString</span> *mocSelectorName <span class="keyword">in</span> <span class="keyword">self</span>.mocSelNameLists) &#123;</span><br><span class="line"> <span class="keyword">if</span> ([mocSelectorName isEqualToString:selectorName]) &#123;</span><br><span class="line"> anInvocation.selector = <span class="keyword">@selector</span>(getValueInMocMethod:);</span><br><span class="line"> [anInvocation setArgument:&amp;mocSelectorName atIndex:<span class="number">2</span>];</span><br><span class="line"> [anInvocation invokeWithTarget:<span class="keyword">self</span>];</span><br><span class="line"></span><br><span class="line"> hasMoc = <span class="literal">YES</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (!hasMoc) &#123;</span><br><span class="line"> [<span class="keyword">super</span> forwardInvocation:anInvocation];</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其中先对需要方法进行筛选,如果是mock的方法,那么对其进行重定向。因为是需要重定向到一个新的方法,那么所以需要改变<code>NSInvocation</code>类中的部分属性,主要是指向的方法和传递参数。这里需要注意的是<code>[anInvocation setArgument:&amp;mocSelectorName atIndex:2];</code>这边的index必须从2开始,因为0和1分别被targe和selector占用了。<br>以上介绍的就是mock的主要的思路处的实现了。虽然自己完成了一个mock的小demo,但是mock这个好像除了在测试的时候能够派上用场,在其他地方还想也没什么用武之地了。或者说只是我现在暂时还没有碰到或者想到而已。<br>不过虽然这么说,但是在研究mock的过程中,对于oc在运行时能够做的事情,有了更深一步的了解。<br>下面是自己写的demo的git地址:<br><a href="https://github.com/YYDD/MockDemo" target="_blank" rel="external">https://github.com/YYDD/MockDemo</a></p>
</content>
<summary type="html">
关于ios中的mock的。。。
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="ios" scheme="http://campusappcn.github.io/tags/ios/"/>
<category term="mock" scheme="http://campusappcn.github.io/tags/mock/"/>
<category term="运行时" scheme="http://campusappcn.github.io/tags/%E8%BF%90%E8%A1%8C%E6%97%B6/"/>
</entry>
<entry>
<title>Android随机对象生成器的设计与实现</title>
<link href="http://campusappcn.github.io/2016/04/08/2016-4-9-Android%E9%9A%8F%E6%9C%BA%E5%AF%B9%E8%B1%A1%E7%94%9F%E6%88%90%E5%99%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0/"/>
<id>http://campusappcn.github.io/2016/04/08/2016-4-9-Android随机对象生成器的设计与实现/</id>
<published>2016-04-07T16:00:00.000Z</published>
<updated>2016-04-20T08:49:38.000Z</updated>
<content type="html"><h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>当完成一个新的Feature的时候,需要对其进行测试。但是由于服务器还没有部署该功能,或者单元测试的限制,往往需要程序员自己去伪造一些数据。但是手工伪造数据往往效率不高并且没有代表性。因此希望能够实现一个对象生成器,生成对象并往里面填充随机值。</p>
<h2 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h2><p><a href="https://github.com/campusappcn/rog" target="_blank" rel="external">rog</a></p>
<h2 id="设计要点"><a href="#设计要点" class="headerlink" title="设计要点"></a>设计要点</h2><p>对象生成器的总体思路是清晰的,获取到类,使用反射获取到其所有的域,采用dfs(深度优先搜索)遍历类的域树,在遍历过程中设置随机值。</p>
<h3 id="类型分类"><a href="#类型分类" class="headerlink" title="类型分类"></a>类型分类</h3><p>针对Java类的特点,可以将其简单分为几种类型。</p>
<h4 id="基础类型"><a href="#基础类型" class="headerlink" title="基础类型"></a>基础类型</h4><p>基础类型包括int、float、double、short、long、byte、char、boolean、String,需要针对这些基础类型提供默认的随机产生器。</p>
<h4 id="Array"><a href="#Array" class="headerlink" title="Array"></a>Array</h4><p>数组类型,默认随机产生器能够产生一个指定类的对象数组,并能够对数组成员赋随机对象。</p>
<h4 id="Enum"><a href="#Enum" class="headerlink" title="Enum"></a>Enum</h4><p>枚举类型。默认构造器能够随机产生一个枚举值。</p>
<h3 id="Interface-Or-Abstract"><a href="#Interface-Or-Abstract" class="headerlink" title="Interface Or Abstract"></a>Interface Or Abstract</h3><p>对于接口和抽象类,我们需要首先知道它们的子类,否则无法产生其实例。在这里,有两种方案,方案一是扫描类路径下的所有类,找到该接口或抽象类的所有非抽象的子类,或者在编译期就生成继承树并记录下来。但是考虑到这样实现会比较复杂,所以在第一个版本,并没有按照这样的方案实现。如果读者对该方案有兴趣的,可以参考<a href="https://github.com/ronmamo/reflections" target="_blank" rel="external">reflections</a>。它是一个Java的开源项目,但是由于使用了Java7的一些特性,所以无法直接使用到Android项目中。接下来,我们说方案二,其实很简单,就是由使用者通过接口告诉rog某接口或者抽象类的非抽象子类有哪些,Rog会从中随机选择一个并产生它的实例返回。</p>
<h3 id="其它类"><a href="#其它类" class="headerlink" title="其它类"></a>其它类</h3><p>除开上面提到的一些特殊的类,剩下的就是普通的一些类了,其它类的构造器ClassGenerator需要依赖于以上提到的构造器,将对应的一些的类对象的产生作业代理给以上产生器。</p>
<h3 id="UML图"><a href="#UML图" class="headerlink" title="UML图"></a>UML图</h3><p><img src="https://img.alicdn.com/imgextra/i1/754328530/TB2113WmFXXXXb7XXXXXXXXXXXX_!!754328530.png" alt=""><br>上图是rog的整体UML图,从图中我们可以看到所有的Generator都继承自IGenerator接口,接口包含两个方法,generate()方法用来产生随机对象,getClassToGenerate()获取该Genreator所产生的对象类型。 </p>
<p>针对所有基本类型都实现了相对应的Generator,并提供了一些方法用于限制随机值的产生,比如设置最大值,设置不产生负数等。这些基本类型对象产生器通过BasicTypeGeneratorFactory进行管理。这是一个全局的单例。</p>
<p>上面的BasicTypeGeneratorFactory实现了ITypeGeneratorFactory接口。同样实现了该接口的还有TypeGeneratorFactory,该类用来缓存对象生成器,之前的Generator都会缓存到该Factory中,提升性能。该类依赖了BasicTypeGeneratorFactory,对于基本类型的产生器的获取会代理给BasicTypeGeneratorFactory。</p>
<p>在整个rog中,最重要的类就是ClassGenerator,该类可以产生所有类型的对象,不管是基本类型,还是抽象类。它依赖了以上提到的所有默认提供的Generator,将对应的一些的类对象的产生作业代理给以上产生器。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><h3 id="final的处理"><a href="#final的处理" class="headerlink" title="final的处理"></a>final的处理</h3><p>对于有final修饰的域不再进行赋值。</p>
<h3 id="产生层级限制"><a href="#产生层级限制" class="headerlink" title="产生层级限制"></a>产生层级限制</h3><p>层级定义类引用的层数,比如Class1 为0层,且它有一个Class2的域,则该域的值对象的层级为1。如果不进行限制,则可能因为递归次数太多,而导致StackOverFlow或者无限循环无法结束程序。比如Class1有一个Class1类型的域。默认的最大层级为5,我们建议最大层级不要超过10。</p>
<h3 id="域的缓存"><a href="#域的缓存" class="headerlink" title="域的缓存"></a>域的缓存</h3><p>由于反射效率低下,所以通过反射获取到某个类的域列表时,应该将其缓存起来。</p>
</content>
<summary type="html">
本篇博客主要阐述如何实现一个随机对象生成器,并附上Github项目地址。
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Development" scheme="http://campusappcn.github.io/tags/Development/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
</entry>
<entry>
<title>浅析ReactiveX的多播——实现安卓双击检测遇到的坑</title>
<link href="http://campusappcn.github.io/2016/03/31/2016-03-31-%E6%B5%85%E6%9E%90ReactiveX%E7%9A%84%E5%A4%9A%E6%92%AD/"/>
<id>http://campusappcn.github.io/2016/03/31/2016-03-31-浅析ReactiveX的多播/</id>
<published>2016-03-30T16:00:00.000Z</published>
<updated>2016-04-20T08:49:38.000Z</updated>
<content type="html"><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>今天需要实现一个双击检测功能,以前的实现方式是自己记录上次点击时间与本次比对,如果小于门槛值,则发出双击事件。不过自从入了Rx的坑之后,凡事都喜欢用Rx的思想思考问题。于是上Github找找代码,还真找到一段,虽然是Kotlin的<a href="https://gist.github.com/imton/ee74249fabff5ac95b16" target="_blank" rel="external">一段错误的代码</a>,翻译成Java如下:(注:这段代码是有问题的,请不要看到这里就复制黏贴)</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doubleClickDetect</span><span class="params">(View view)</span></span>&#123;</span><br><span class="line"> Observable&lt;Void&gt; observable = RxView.clicks(view);</span><br><span class="line"> observable.buffer(observable.debounce(<span class="number">200</span>, TimeUnit.MILLISECONDS))</span><br><span class="line"> .observeOn(AndroidSchedulers.mainThread())</span><br><span class="line"> .subscribe(<span class="keyword">new</span> Action1&lt;List&lt;Void&gt;&gt;() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">call</span><span class="params">(List&lt;Void&gt; voids)</span> </span>&#123;</span><br><span class="line"> <span class="comment">//double click detected</span></span><br><span class="line"> &#125;</span><br><span class="line"> &#125;, <span class="keyword">new</span> Action1&lt;Throwable&gt;() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">call</span><span class="params">(Throwable throwable)</span> </span>&#123;</span><br><span class="line"> Timber.e(throwable, <span class="string">"error"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>我们先不讲这段代码的问题在哪,先来说说其中涉及到的几个操作符。<br>其中buffer()会将observable 发射的item缓存起来,直到它的参数Observable发射一个item时,它会将之前缓存的都作为一个list发射出去,这个还是比较好理解的,如果还是不懂,请参考 <a href="http://reactivex.io/documentation/operators/buffer.html" target="_blank" rel="external">buffer操作符文档</a>。</p>
<p>而debounce操作符会将在那些在参数时间间隔内跟着另一个item的items过滤掉。<img src="https://img.alicdn.com/imgextra/i3/754328530/TB2aHeWmXXXXXaCXXXXXXXXXXXX-754328530.png" alt=""><br>如图,1,5和6之后在一段时间内没有跟随其它item,所以被发射出来,而其它的都被过滤了。详见<a href="http://reactivex.io/documentation/operators/debounce.html" target="_blank" rel="external">debounce操作符文档</a></p>
<p>所以上面代码的意思就是当有点击事件item产生的时候先缓存起来,当一段事件内没有新的事件产生的时候把之前缓存的事件作为一个列表发射出去,当发现有大于等于2的事件时,认为用户在一定时间内连续点了两次。所以这段代码在逻辑上是ok的。但是实际运行起来发现没反应,buffer后面没有item被发射。但是在buffer之前是有的,所以将问题定位到debounce没有item产生。所以问题在哪呢?我们需要明确一点,普通的Observable是不支持多播的,即使被多个Subscriber所订阅,也只会有一个Subscriber收到items。在buffer中,其实订阅了参数Observable,但是这个Observable在buffer之后又被订阅了一次,所以debounce就收不到item了。</p>
<h2 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h2><p>那么该如何修改以上代码,让它达到我们需要的效果呢。下面先来介绍几个相关的操作符。</p>
<h3 id="Publish"><a href="#Publish" class="headerlink" title="Publish"></a>Publish</h3><p>通过Publish操作符可以将一个普通的Observable转换为一个Connectable Observable。Connectable Observable 可以被多次订阅,被多个Subscriber共享Stream。但是和普通的Observable不同,它在被subscribe之后并不开始产生item,而需要在调用connect()之后才会产生item。</p>
<h3 id="Connect"><a href="#Connect" class="headerlink" title="Connect"></a>Connect</h3><p>在Publish中已经提到,用来让Connectable Observable开始产生item。</p>
<h3 id="Refcount"><a href="#Refcount" class="headerlink" title="Refcount"></a>Refcount</h3><p>除了Connect,我们有另一种方式来让Connectable Observable 产生item,那就是Refcount,refCount会在第一个subscriber订阅之后自动connect,在最后一个subscriber unsubscribe之后自动disconnect。</p>
<h3 id="Share"><a href="#Share" class="headerlink" title="Share"></a>Share</h3><p>Share 其实就是publish().refCount();</p>
<p>基于以上操作符,我们可以修正我们上面的代码了,稍微改一下就能达到我们预期的效果了,代码如下:</p>
<figure class="highlight java"><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"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doubleClickDetect</span><span class="params">(View view)</span></span>&#123;</span><br><span class="line"> Observable&lt;Void&gt; observable = RxView.clicks(view).share();</span><br><span class="line"> observable.buffer(observable.debounce(<span class="number">200</span>, TimeUnit.MILLISECONDS))</span><br><span class="line"> .observeOn(AndroidSchedulers.mainThread())</span><br><span class="line"> .subscribe(<span class="keyword">new</span> Action1&lt;List&lt;Void&gt;&gt;() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">call</span><span class="params">(List&lt;Void&gt; voids)</span> </span>&#123;</span><br><span class="line"> <span class="comment">//double click detected</span></span><br><span class="line"> &#125;</span><br><span class="line"> &#125;, <span class="keyword">new</span> Action1&lt;Throwable&gt;() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">call</span><span class="params">(Throwable throwable)</span> </span>&#123;</span><br><span class="line"> Timber.e(throwable, <span class="string">"error"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>上面的修改在原有代码基础上使用share操作符将原本的Observable变成了可共享流的Connectable Observable。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>在使用refCount或者share的时候需要注意一点,那就是在第一个subscriber订阅之后Connectable Observable就被connect产生item了。所以后面subscribe 的订阅者可能就收不到之前的一些item了。如果需要所有的subscriber都收到一样的item。还是先subscribe,最后再connect吧。</p>
</content>
<summary type="html">
本文主要介绍如何在一个Observable订阅多个Subscriber。
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Development" scheme="http://campusappcn.github.io/tags/Development/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
<category term="RxJava" scheme="http://campusappcn.github.io/tags/RxJava/"/>
</entry>
<entry>
<title>Android路由框架设计与实现</title>
<link href="http://campusappcn.github.io/2016/03/23/2016-03-23-Android%E8%B7%AF%E7%94%B1%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1/"/>
<id>http://campusappcn.github.io/2016/03/23/2016-03-23-Android路由框架设计/</id>
<published>2016-03-22T16:00:00.000Z</published>
<updated>2016-04-09T08:59:46.000Z</updated>
<content type="html"><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>我们知道传统的网站开发框架一般支持用户设置路由表,如Django。而在Android开发中,我们打开页面的方式主要是startActivity()。使用startActivity()的缺点是需要打开的那个Activity的类已经存在,否则无法通过编译,但是在协同开发中,这往往是无法得到满足的。那么如何来解决这一问题呢,我们是否也能够像在Web开放中一样使用一个url来打开一个Activity呢?因此本文主要介绍在Android平台设计和实现一个路由框架。</p>
<h1 id="框架整体设计"><a href="#框架整体设计" class="headerlink" title="框架整体设计"></a>框架整体设计</h1><p>首先我们需要明确一点,那就是我们的框架不能仅仅局限于打开Activity,打开Activity只是框架中的一种行为实现,我们的框架应该是包含了Activity以及其它比较常用的路由。并支持使用者自己添加自己的路由实现的,如使用者希望使用url调用一个方法,或者调用一个Runnable,那么他就可以自己实现一个Router来支持这种行为。基于这样的思路,路由框架的整体架构设计如下。<br><img src="https://img.alicdn.com/imgextra/i2/754328530/TB2XFdcepXXXXX5XXXXXXXXXXXX-754328530.jpg" alt="路由框架设计"></p>
<p>在以上框架中,由RouterManager维护所有的IRouter列表。我们的框架自身实现了打开Activity的ActivityRouter。同时用户可以添加自己的IRouter实现。当用户想要打开某个url时,调用RouterManager的open()方法,RouterManager遍历列表,调用IRouter的canOpenTheUrl方法找到第一个能够打开该url的IRouter,并将该open的任务dispatch给该Router。<br>但是在分析实际使用情景中,我们发现这样的框架设计并不能满足实际使用需求,主要有如下几点。</p>
<ul>
<li>在打开Activity可能需要设置动画。</li>
<li>需要选择使用startActivityForResult()来打开Activity。</li>
<li>需要传递一些无法放到url中的传递参数,如Parcelable等。</li>
</ul>
<p>虽然这仅仅是ActivityRouter的需求,但我们也需要让我们的框架支持这些。<br>因此,我们对以上框架设计做了一些修改如下。<br><img src="https://img.alicdn.com/imgextra/i1/754328530/TB20x_2epXXXXapXpXXXXXXXXXX-754328530.jpg" alt="路由框架设计修改版"><br>用户在需要额外设置一些参数的时候可以不选择使用open打开url,而是使用getRoute()方法获得抽象的Route对象,通过Route对象进行额外的设置,最后可以通过IRoute接口的open方法打开它自己。IRouter和IRoute的关系应该是一对一或者一对多,一种IRouter应该可以打开一或者多种不同IRoute。而一个IRoute则一般只能由一种IRouter打开。</p>
<h1 id="路由格式"><a href="#路由格式" class="headerlink" title="路由格式"></a>路由格式</h1><p>在路由框架中,url应该包含两点功能,一是唯一确定一条路由,二是提供一些参数。我们可以以一个路由的例子来讲解ActivityRouter的路由规则。本规则参考了REST。<br>例:activity://main/:i{key1}/path1/:f{key2}</p>
<ul>
<li>scheme为activity代表该url可以被ActivityRouter打开。</li>
<li>host为main一般表示决定的Activity。</li>
<li>而:{key1}则表示一个值的key,这个path segment在url中会被具体的值替换,:后面的i表示该key对应的值的类型为int型。</li>
<li>path1为固定的path segment,与上面的key用来传递值不同。用来区分路由,与host功能类似。</li>
</ul>
<p>因此一个url与路由匹配需要scheme,host以及path中的固定部分相同,而key部分被具体的值代替。以下举一个匹配上面的路由的url的例子。<br>activity:main/123/path1/12.4。则会调用intent的putExtra()方法将key1=123, key2=12.4放到intent的bundle中。在被跳的Activity中就可以获取对应的值。如果有些值是不必须的,已可以放到query parameter中。如activity://main/123/path1?des=hello。但query parameter不影响路由匹配。<br>以上只是我实现的ActivityRouter的路由格式,如果你需要实现自己的路由,完全可以根据自己的想法设计路由格式。</p>
<h3 id="路由表的初始化"><a href="#路由表的初始化" class="headerlink" title="路由表的初始化"></a>路由表的初始化</h3><p>Router需要提供一个方法让使用者初始化路由表。如下ActivityRouter的实现,当然这并不必要,如打开网页的Router就不需要路由表,只需要调用系统浏览器打开网页即可。所以该方法没有在接口中定义。</p>
<figure class="highlight java"><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 class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">(Context appContext, IActivityRouteTableInitializer initializer)</span> </span>&#123;</span><br><span class="line"> mBaseContext = appContext;</span><br><span class="line"> initializer.initRouterTable(mRouteTable);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><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 class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IActivityRouteTableInitializer</span> </span>&#123;</span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * init the router table</span><br><span class="line"> * <span class="doctag">@param</span> router the router map to</span><br><span class="line"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">initRouterTable</span><span class="params">(Map&lt;String, Class&lt;? extends Activity&gt;&gt; router)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>我们发现将原本startActivity方式替换成路由方式后,有以下几个明显的优点。</p>
<ul>
<li>便于协同开发</li>
<li>便于测试,可以在测试中替换一个路由表,打开测试用Activity。</li>
<li>便于从外部链接跳转到app中的任意界面。我们只需要设置一个外链入口Activity,让其接收外部链接,并调用路由管理器打开该链接即可。</li>
</ul>
<h1 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h1><p><a href="https://github.com/campusappcn/AndRouter" target="_blank" rel="external">https://github.com/campusappcn/AndRouter</a></p>
</content>
<summary type="html">
本篇文章主要介绍AndRouter,基于Android的路由框架。
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Development" scheme="http://campusappcn.github.io/tags/Development/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
</entry>
<entry>
<title>mysql使用binlog恢复数据</title>
<link href="http://campusappcn.github.io/2016/03/22/2016-03-22-mysql%E4%BD%BF%E7%94%A8binlog%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/"/>
<id>http://campusappcn.github.io/2016/03/22/2016-03-22-mysql使用binlog恢复数据/</id>
<published>2016-03-21T16:00:00.000Z</published>
<updated>2016-04-09T08:59:46.000Z</updated>
<content type="html"><p>当mysql因为某些原因数据丢失时,可以使用binlog来恢复数据。<br><a id="more"></a><br>本文基于mysql5.6,主要记录一些binlog相关的内容。</p>
<h3 id="binlog"><a href="#binlog" class="headerlink" title="binlog"></a>binlog</h3><p>首先介绍mysql的binlog<br>binlog是binary log的缩写,是mysql记录数据库变动的二进制日志,会包含所有使数据和表结构发生变化的事件。根据官方文档,mysql的binary log主要有两个目的:</p>
<blockquote>
<p>1、实现复制集:replication中的master通过binary log记录数据变动然后发送给slave;<br>2、恢复数据。</p>
</blockquote>
<p>可以通过配置 <code>--log-bin[=base_name]</code>变量启动mysql server以启用binlog。<br>可以通过<code>--binlog-format={ROW|STATEMENT|MIXED}</code>来指定mysql使用哪种binlog格式:</p>
<blockquote>
<p>1、statement-based格式的binlog下,replication中的slave通过执行master的binlog中的sql与master同步,这样的副本集也叫SBR(statement-based replication)。statement日志会记录sql、服务器信息、sql执行时间戳、sql执行时长等等信息;<br>2、row-based格式的binlog下,master会把数据每一列的改动前后都记录下来,slave复制这些改动来同步master,这样的副本集叫做RBR(row-based replication);<br>3、mixed-based格式的binlog默认采用statement-based,只有在某些情况下会自动切换到row-based格式。<br>可以看出,row-based binlog的特点十分适合用于数据恢复与增量备份。</p>
</blockquote>
<h3 id="mysqlbinlog"><a href="#mysqlbinlog" class="headerlink" title="mysqlbinlog"></a>mysqlbinlog</h3><p>mysqlbinlog是mysql自带的binlog工具,可以直接将binlog用于数据恢复。<br><strong>将log_file的变动应用到远程mysql:</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">shell&gt; mysqlbinlog log_file | mysql -h server_name</span><br></pre></td></tr></table></figure></p>
<p><strong>将数据恢复到某个时间点:</strong><br>假设要恢复数据的mysql开启了binlog并且有一个A时间点的备份,现在要把数据恢复到发生了数据丢失的B时间点前。那么就可以在A时间点的备份上应用A-B区间的binlog,得到一个B时间点前数据集。<br>具体操作可以用到以下命令:<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">shell&gt; mysqlbinlog --start-datetime=&quot;2005-04-20 9:55:00&quot; --stop-datetime=&quot;2005-04-20 10:05:00&quot; /var/log/mysql/bin.123456 | mysql -u root -p</span><br></pre></td></tr></table></figure></p>
<p>同理还可以使用<code>--start-positon</code>、<code>--stop-position</code>达到同样目的。<br>可以看出这里的binlog其实也起着增量备份数据的作用。阿里云rds所实现的创建一个有效期内任意时间点的rds实例应该就是用了类似上面的方法。</p>
<p><strong>导出数据变动到文件:</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">mysqlbinlog binlog_files.000001 &gt; tmpfile //覆盖</span><br><span class="line">mysqlbinlog binlog_files.000002 &gt;&gt; tmpfile //追加</span><br></pre></td></tr></table></figure></p>
<p>导出的文件同样可以直接应用到mysql<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">mysql -u root -p &lt; tmpfile</span><br></pre></td></tr></table></figure></p>
<p><strong>导出可读的binlog:</strong><br>binlog是二进制日志,直接用mysqlbinlog binlog_file命令导出的日志是base64编码过的。<br>对于row格式的binlog可以用 <code>-versbose(-v)</code>和<code>--base64-output=DECODE-ROWS</code>将binlog输出为可读的sql伪码,这些伪码会记录下每次数据操作造成的列改动:<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></pre></td><td class="code"><pre><span class="line">shell&gt; mysqlbinlog -v --base64-output=DECODE-ROWS log_file</span><br><span class="line">...</span><br><span class="line"># at 218</span><br><span class="line">#080828 15:03:08 server id 1 end_log_pos 258 Write_rows: table id 17 flags: STMT_END_F</span><br><span class="line">### INSERT INTO test.t</span><br><span class="line">### SET</span><br><span class="line">### @1=1</span><br><span class="line">### @2=&apos;apple&apos;</span><br><span class="line">### @3=NULL</span><br><span class="line">...</span><br><span class="line"># at 302</span><br><span class="line">#080828 15:03:08 server id 1 end_log_pos 356 Update_rows: table id 17 flags: STMT_END_F</span><br><span class="line">### UPDATE test.t</span><br><span class="line">### WHERE</span><br><span class="line">### @1=1</span><br><span class="line">### @2=&apos;apple&apos;</span><br><span class="line">### @3=NULL</span><br><span class="line">### SET</span><br><span class="line">### @1=1</span><br><span class="line">### @2=&apos;pear&apos;</span><br><span class="line">### @3=&apos;2009:01:01&apos;</span><br><span class="line">...</span><br><span class="line"># at 400</span><br><span class="line">#080828 15:03:08 server id 1 end_log_pos 442 Delete_rows: table id 17 flags: STMT_END_F</span><br><span class="line">### DELETE FROM test.t</span><br><span class="line">### WHERE</span><br><span class="line">### @1=1</span><br><span class="line">### @2=&apos;pear&apos;</span><br><span class="line">### @3=&apos;2009:01:01&apos;</span><br></pre></td></tr></table></figure></p>
<h3 id="mysql-server-logs"><a href="#mysql-server-logs" class="headerlink" title="mysql server logs"></a>mysql server logs</h3><p>除了用于复制集和恢复数据的binlog,mysql还提供了以下日志</p>
<table>
<thead>
<tr>
<th>Log Type</th>
<th>Information Written to Log</th>
</tr>
</thead>
<tbody>
<tr>
<td>Error log</td>
<td>Problems encountered starting, running, or stopping mysqld</td>
</tr>
<tr>
<td>General query log</td>
<td>Established client connections and statements received from clients</td>
</tr>
<tr>
<td>Binary log</td>
<td>Statements that change data (also used for replication)</td>
</tr>
<tr>
<td>Relay log</td>
<td>Data changes received from a replication master server</td>
</tr>
<tr>
<td>Slow query log</td>
<td>Queries that took more than long_query_time seconds to execute</td>
</tr>
<tr>
<td>DDL log</td>
<td>(metadata log) Metadata operations performed by DDL statements</td>
</tr>
</tbody>
</table>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>为了保证数据可恢复性,mysql官方文档提出了一些备份恢复的建议:</p>
<blockquote>
<p>1、永远开启<code>--log-bin</code>运行mysql server<br>2、定时使用<code>mysqldump</code>全量备份<br>3、定时使用<code>mysqladmin flush-logs [log_type ...]</code> 增量备份</p>
</blockquote>
<p>可以看到,阿里云的一系列备份恢复措施正是这些建议的实践。</p>
<p>为了应对一些突发状况造成的数据丢失,备份与恢复机制是数据基础建设必不可少的一环。<br>对应mysql来说,开启row-based的binlog并且定时备份数据是一个实用的方案。<br>但是在不可避免的意外造成的数据丢失之外还需要警惕的是某些人为误操作引起的数据丢失——在直接操作线上数据时要慎之又慎,尤其对于批量数据的修改最好要先dump或复制一份数据再操作。</p>
<p>参考文档:<br><a href="http://dev.mysql.com/doc/refman/5.6/en/binary-log.html" target="_blank" rel="external">binary-log</a><br><a href="http://dev.mysql.com/doc/refman/5.6/en/mysqlbinlog.html" target="_blank" rel="external">mysqlbinlog</a></p>
</content>
<summary type="html">
<p>当mysql因为某些原因数据丢失时,可以使用binlog来恢复数据。<br>
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Mysql" scheme="http://campusappcn.github.io/tags/Mysql/"/>
</entry>
<entry>
<title>Android实现多次闪退清除数据</title>
<link href="http://campusappcn.github.io/2016/03/22/2016-03-22-Android%E5%AE%9E%E7%8E%B0%E5%A4%9A%E6%AC%A1%E9%97%AA%E9%80%80%E6%B8%85%E9%99%A4%E6%95%B0%E6%8D%AE/"/>
<id>http://campusappcn.github.io/2016/03/22/2016-03-22-Android实现多次闪退清除数据/</id>
<published>2016-03-21T16:00:00.000Z</published>
<updated>2016-04-09T08:59:46.000Z</updated>
<content type="html"><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>很多时候由于后台返回的数据异常,可能会导致App闪退。而如果这些异常数据被App本地缓存下来,那么即使杀掉进程重新进入还是会发生闪退。唯一的解决方法就是清除App数据,但是用户可能没有这个意识或者嫌麻烦就直接不再使用了,这是我们无法接受的。在使用淘宝、追书神器等App时我发现有时候它们也会连续闪退,但是往往闪退三次后就恢复正常了,所以一般成熟的App都会做连续闪退三次后清除缓存数据的工作。而目前笔者搜不到有哪篇blog来讲这方面的事情,所以就姑且由我来讲讲此事,为希望提高App用户体验的朋友提供些许参考。</p>
<h1 id="ACRA"><a href="#ACRA" class="headerlink" title="ACRA"></a>ACRA</h1><p>为了能够在闪退的时候做一些事情,我们可以使用ACRA,这是Github上的一个开源项目,允许使用者设置一些Sender在App闪退的时候做一些事情。具体使用可以直接参考<a href="https://github.com/ACRA/acra" target="_blank" rel="external">Github</a></p>
<h1 id="实现清除数据"><a href="#实现清除数据" class="headerlink" title="实现清除数据"></a>实现清除数据</h1><p>ACRA提供了自己的一些Sender,如使用系统邮件客户端向指定邮箱发送邮件的EmailIntentSender。而我们希望记录闪退次数和清除数据则需要implements ReportSender接口。</p>
<figure class="highlight java"><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 class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CrashHandler</span> <span class="keyword">implements</span> <span class="title">ReportSender</span> </span>&#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">send</span><span class="params">(Context context, CrashReportData errorContent)</span> <span class="keyword">throws</span> ReportSenderException </span>&#123;</span><br><span class="line"> Timber.i(<span class="string">"闪退,检查是否需要清空数据"</span>);</span><br><span class="line"> <span class="keyword">new</span> CrashModel().checkAndClearData();</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里我们写了一个CrashModel用来记录闪退次数和时间决定是否需要清空数据,具体代码如下。<br>由于在ReportSender的时候无法打开其它线程,所以我们无法使用SharedPerferences来清理数据(打开SP的时候其实打开了一个新线程)。为此需要找到数据缓存的位置并将文件删除。同样道理,记录闪退时间也只能通过文件记录。当然,你可以选择一些文件不进行删除,如用户信息等不太容易出问题的数据。</p>
<figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CrashModel</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_CRASH_TIMES = <span class="string">"crash_times"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String CRASH_TIME_FILE_NAME = <span class="string">"crash_time"</span>;</span><br><span class="line"> <span class="comment">//不能通过App.getPackageName来获取包名,否则会有问题,只能默认为cn.campusapp.campus。所以对于debug或者运营版本,清数据会把release的清掉</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String FILE_DIR = String.format(<span class="string">"/data/data/%s/"</span>, BuildConfig.APPLICATION_ID);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ACCOUNT_FILE_NAME = String.format(<span class="string">"%s%s"</span>, FILE_DIR, <span class="string">"shared_prefs/account_pref.xml"</span>);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ArrayList&lt;String&gt; FILES_DONTNEED_DELETE = <span class="keyword">new</span> ArrayList&lt;&gt;(); <span class="comment">//该目录中的文件不会被删除</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> &#123;</span><br><span class="line"> FILES_DONTNEED_DELETE.add(ACCOUNT_FILE_NAME); <span class="comment">//目前账号信息文件不会被删除,但是会手动改变数据,只保留userId accessToken 和school</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> ArrayList&lt;Long&gt; mCrashTimes;</span><br><span class="line"> Gson gson = <span class="keyword">new</span> Gson();</span><br><span class="line"> <span class="keyword">private</span> File mFileDir;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">CrashModel</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> mFileDir = <span class="keyword">new</span> File(FILE_DIR);</span><br><span class="line"> mCrashTimes = readCrashTimes();</span><br><span class="line"> <span class="keyword">if</span> (mCrashTimes == <span class="keyword">null</span>) &#123;</span><br><span class="line"> mCrashTimes = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"> storeCrashTimes(mCrashTimes);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">checkAndClearData</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">long</span> timeNow = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (checkClearData(timeNow, <span class="keyword">new</span> ArrayList&lt;&gt;(mCrashTimes))) &#123;</span><br><span class="line"> Timber.i(<span class="string">"已经在5分钟之内有三次闪退,需要清理数据"</span>);</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> clearData();</span><br><span class="line"> &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"> Timber.e(e, <span class="string">"清空所有数据失败"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> mCrashTimes.add(timeNow);</span><br><span class="line"> storeCrashTimes(mCrashTimes);</span><br><span class="line"> Timber.i(<span class="string">"此次不需要清空数据, %s"</span>, gson.toJson(mCrashTimes));</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">storeCrashTimes</span><span class="params">(ArrayList&lt;Long&gt; crashTimes)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> String str = gson.toJson(crashTimes);</span><br><span class="line"> Files.writeToFile(mFileDir, CRASH_TIME_FILE_NAME, str);</span><br><span class="line"> &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"> Timber.e(e, <span class="string">"保存闪退时间失败"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> ArrayList&lt;Long&gt; <span class="title">readCrashTimes</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> String timeStr = Files.readFileContent(mFileDir, CRASH_TIME_FILE_NAME);</span><br><span class="line"> <span class="keyword">return</span> gson.fromJson(timeStr, <span class="keyword">new</span> TypeToken&lt;ArrayList&lt;Long&gt;&gt;() &#123;</span><br><span class="line"> &#125;.getType());</span><br><span class="line"> &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"> Timber.e(e, <span class="string">"读取闪退时间失败"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * 检查是否需要清空数据,目前的清空策略是在5分钟之内有三次闪退的就清空数据,也就是从后往前遍历,只要前两次闪退发生在5分钟之内,就清空数据</span><br><span class="line"> *</span><br><span class="line"> * <span class="doctag">@return</span></span><br><span class="line"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">checkClearData</span><span class="params">(<span class="keyword">long</span> time, ArrayList&lt;Long&gt; crashTimes)</span> </span>&#123;</span><br><span class="line"> Timber.i(gson.toJson(crashTimes));</span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = crashTimes.size() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line"> <span class="keyword">long</span> crashTime = crashTimes.get(i);</span><br><span class="line"> <span class="keyword">if</span> (time - crashTime &lt;= <span class="number">5</span> * <span class="number">60</span> * <span class="number">1000</span>) &#123;</span><br><span class="line"> count++;</span><br><span class="line"> <span class="keyword">if</span> (count &gt;= <span class="number">2</span>) &#123;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">if</span> (count &gt;= <span class="number">2</span>) &#123;</span><br><span class="line"> <span class="comment">//在5分钟之内有三次闪退,这时候需要清空数据</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * 清空数据,包括数据库中的和SharedPreferences中的</span><br><span class="line"> *</span><br><span class="line"> * <span class="doctag">@throws</span> Exception</span><br><span class="line"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">clearData</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"> Timber.i(<span class="string">"开始清理数据"</span>);</span><br><span class="line"> Files.deleteFilesExceptSomeInDirectory(mFileDir, FILES_DONTNEED_DELETE);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后我们需要将CrashHandler 添加到ACRA的异常处理Sender列表中。在你的Application类中添加如下代码。</p>
<figure class="highlight java"><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"><span class="meta">@ReportsCrashes</span>(</span><br><span class="line"> <span class="comment">//一些ACRA的设置,具体参考ACRA文档,因为我们使用自定义Sender,所以这里完全可以不用设置</span></span><br><span class="line"> <span class="comment">//mailTo = "bugs@treeholeapp.cn",</span></span><br><span class="line"> <span class="comment">//mode = ReportingInteractionMode.TOAST,</span></span><br><span class="line"> <span class="comment">//resToastText = R.string.crash_toast_text</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (!BuildConfig.DEBUG) &#123; <span class="comment">//这里我判断只有在非DEBUG下才清除数据,主要是为了在开发过程中能够保留线程。</span></span><br><span class="line"> ACRA.init(APPLICATION_CONTEXT);</span><br><span class="line"> CrashHandler handler = <span class="keyword">new</span> CrashHandler();</span><br><span class="line"> ACRA.getErrorReporter().setReportSender(handler); <span class="comment">//在闪退时检查是否要清空数据</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>以上即为实现多次闪退后清除数据的实现,希望大家开发的App Bug越来越少,鲁棒性越来越强。</p>
</content>
<summary type="html">
本篇文章主要说明如何在Android App多次闪退后清除数据
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Development" scheme="http://campusappcn.github.io/tags/Development/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
</entry>
<entry>
<title>Getting Started | Pan</title>
<link href="http://campusappcn.github.io/2016/03/21/2016-03-21-Getting%20Started%20%7C%20Pan/"/>
<id>http://campusappcn.github.io/2016/03/21/2016-03-21-Getting Started | Pan/</id>
<published>2016-03-20T16:00:00.000Z</published>
<updated>2016-04-09T08:59:46.000Z</updated>
<content type="html"><p><a href="https://github.com/campusappcn/Pan" target="_blank" rel="external">Pan (https://github.com/campusappcn/Pan)</a>框架使用起来很简单。采用<a href="https://github.com/campusappcn/Pan" target="_blank" rel="external">Pan</a>来编写界面和控制代码,可以和原有的代码完全兼容、并存。</p>
<p>首先,是轻量化的Activity代码,主要通过Pan的工厂方法with,得到ViewModel的实例,绑定ViewModel和Controller到Activity上。工厂方法with有很多重载,也可以传入使用实例化好的对象。</p>
<figure class="highlight java"><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 class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> <span class="keyword">extends</span> <span class="title">PanFragmentActivity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> MainViewModel mMainViewModel;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"></span><br><span class="line"> mMainViewModel = Pan.with(<span class="keyword">this</span>, MainViewModel.class)</span><br><span class="line"> .controlledBy(MainController.class)</span><br><span class="line"> .getViewModel()</span><br><span class="line"> .setHelloString(<span class="string">"hello Pan!"</span>)</span><br><span class="line"> .render();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>How clean! 事实上,常见的Activity中需要实现的onResume等方法,也无需写在这里,可以完全交给Controller。</p>
<a id="more"></a>
<p>接下来编写具体ViewModel实现类。ViewModel主要完成渲染逻辑,因此ViewModel的成员变量包含两种:</p>
<ol>
<li>以v作为前缀的View对象</li>
<li>以m作为前缀的ViewModel具体的字段</li>
</ol>
<p>render方法负责将ViewModel字段渲染到View上<br><figure class="highlight java"><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 class="meta">@Xml</span>(R.layout.activity_main) <span class="comment">//可选,让ViewModel语义更明确。当需要自己实例化新View时必选。</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainViewModel</span> <span class="keyword">extends</span> <span class="title">GeneralViewModel</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bind</span>(R.id.hello) <span class="comment">//Butterknife</span></span><br><span class="line"> Button vHelloTv;</span><br><span class="line"></span><br><span class="line"> String mHelloString;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> MainViewModel <span class="title">render</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> vHelloTv.setText(mHelloString);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> MainViewModel <span class="title">setHelloString</span><span class="params">(String string)</span> </span>&#123;</span><br><span class="line"> mHelloString = string;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>其次,是Controller实现类。当然一个View不必要有Controller,如果不需要监听任何事件的话。<br>Controller通过泛型参数,与ViewModel实现绑定,可以处理两类事件:</p>
<ol>
<li>用户交互,通过bindEvents()方法实现,$vm为绑定的ViewModel对象</li>
<li>所处Activity/Fragment的生命周期,通过实现接口(例如,OnResume)进行监听</li>
</ol>
<figure class="highlight java"><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"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainController</span> <span class="keyword">extends</span> <span class="title">GeneralController</span>&lt;<span class="title">MainViewModel</span>&gt; </span><br><span class="line"> <span class="keyword">implements</span> <span class="title">OnResume</span> </span>&#123; <span class="comment">//以监听Activity的OnResume事件</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">bindEvents</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> $vm.vHelloTv.setOnClickListener(<span class="keyword">new</span> View.OnClickListener() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</span><br><span class="line"> Toast.makeText(getActivity(), $vm.mHelloString, Toast.LENGTH_SHORT).show();</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onResume</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> Log.d(<span class="string">"MainController"</span>, <span class="string">"On Resume For "</span> + getActivity());</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对应的布局XML:<br><figure class="highlight xml"><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">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span><br><span class="line"><span class="tag">&lt;<span class="name">LinearLayout</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span><br><span class="line"> <span class="attr">android:orientation</span>=<span class="string">"vertical"</span></span><br><span class="line"> <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span><br><span class="line"> <span class="attr">android:layout_height</span>=<span class="string">"match_parent"</span>&gt;</span></span><br><span class="line"></span><br><span class="line"> <span class="tag">&lt;<span class="name">Button</span></span><br><span class="line"> <span class="attr">android:id</span>=<span class="string">"@+id/hello"</span></span><br><span class="line"> <span class="attr">android:text</span>=<span class="string">"Hello World!"</span></span><br><span class="line"> <span class="attr">android:layout_width</span>=<span class="string">"wrap_content"</span></span><br><span class="line"> <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">LinearLayout</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>添加Pan的依赖:</p>
<figure class="highlight groovy"><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">repositories &#123;</span><br><span class="line"> <span class="comment">//jitpack repository</span></span><br><span class="line"> maven &#123; url <span class="string">"https://jitpack.io"</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line"> compile <span class="string">'com.github.campusappcn:Pan:0.2.2'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</content>
<summary type="html">
<p><a href="https://github.com/campusappcn/Pan">Pan (https://github.com/campusappcn/Pan)</a>框架使用起来很简单。采用<a href="https://github.com/campusappcn/Pan">Pan</a>来编写界面和控制代码,可以和原有的代码完全兼容、并存。</p>
<p>首先,是轻量化的Activity代码,主要通过Pan的工厂方法with,得到ViewModel的实例,绑定ViewModel和Controller到Activity上。工厂方法with有很多重载,也可以传入使用实例化好的对象。</p>
<figure class="highlight java"><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 class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> <span class="keyword">extends</span> <span class="title">PanFragmentActivity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> MainViewModel mMainViewModel;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"></span><br><span class="line"> mMainViewModel = Pan.with(<span class="keyword">this</span>, MainViewModel.class)</span><br><span class="line"> .controlledBy(MainController.class)</span><br><span class="line"> .getViewModel()</span><br><span class="line"> .setHelloString(<span class="string">"hello Pan!"</span>)</span><br><span class="line"> .render();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>How clean! 事实上,常见的Activity中需要实现的onResume等方法,也无需写在这里,可以完全交给Controller。</p>
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
<category term="MVVM" scheme="http://campusappcn.github.io/tags/MVVM/"/>
<category term="MVC" scheme="http://campusappcn.github.io/tags/MVC/"/>
<category term="MVP" scheme="http://campusappcn.github.io/tags/MVP/"/>
<category term="Pan" scheme="http://campusappcn.github.io/tags/Pan/"/>
</entry>
<entry>
<title>移动应用开发的安全性问题</title>
<link href="http://campusappcn.github.io/2016/03/20/%E7%A7%BB%E5%8A%A8%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E7%9A%84%E5%AE%89%E5%85%A8%E6%80%A7%E9%97%AE%E9%A2%98/"/>
<id>http://campusappcn.github.io/2016/03/20/移动应用开发的安全性问题/</id>
<published>2016-03-19T16:00:00.000Z</published>
<updated>2016-03-20T09:02:24.000Z</updated>
<content type="html"><p>昨天去“云栖小镇”听了一场关于《初创期间的App和服务端安全探讨》的分享会。本来是抱着很大的期望去的。因为毕竟我们的app还没达到一定的高度,所以对于安全性的考虑有时候会比较少,这方面相对来说涉及到的就不多,然而真的是应验了“希望越大,失望越大。”这句老古话。在分享会上,听到了各种的广告。虽然广告量很大。但是还有有一些小启发的。</p>
<p>记得对我印象最深的一点是,是分享会上的一个嘉宾他自己开发的一个自测工具。具体叫什么,我不知道。然而我们也没法体验。不过他说现在已经在走阿里的开源流程中,相信过段时间我们也能够用上这款iOS上的自测工具。而安卓的他也在计划中。而通过他对于这款工具的介绍,我了解到了我们的app是多么的不安全。对于这款工具,他列举了这么几点:辅助iOS应用审计、辅助iOS逆向分析、安全小白可迅速上手、提高安全审计效率、目前拥有57个功能。</p>
<p>期间他给我们介绍了,通过这款工具,能够很便捷的对市场上的大部分app进行逆向分析,直接拿到app中所有的头文件,以及每个头文件中的相应的方法。我相信拿到这些方法后,就能做一些简单的hack了。而且这款工具还提供了重签名打包的功能。虽然说这个工具功能很强大,对于它的开源还是有些小期待的。但是如果他开源了,面向了所有的开发者,那么也就是说我们的app对于更多的人是裸露的。这想起来还是有一点小恐怖的。特别是对于初创公司的应用。一般情况下,初创公司希望做的是更快的完成需求,去验证需求的可行性。也许对于安全性考虑的就没这么周全。</p>
<p>不过这里想要安利一款产品,当然也是昨天的分享会上被安利的。这款产品就是“阿里聚安全”。应该是有自动化的检测和人工的检测。也就是说能够通过他们产品帮我们的app做自动化的扫描,找出一般的安全性问题,还有功能是阿里的安全专家会手动的对app做安全检查,当然他们还提供了sdk,方便保存密码等敏感信息。<br>前面说的都是昨天分享会上得到的讯息。</p>
<p>那么下面我发表一下自己对于应用应用安全性问题的想法吧。</p>
<p>我记得之前的我参与过的一个项目中碰到这样一个问题。比如说app中有关注的需求。而这个实现用的是http请求。当时协议是这样的只告诉服务端,我是谁,以及我要关注的那个用户是谁。就这个需求。当时被攻击了,他拿到一组用户id之后,疯狂的发请求让别人关注自己,然后自己更新信息来做广告。相信这种类似的需求在很多场景下都会存在。当时我们采取的措施是,在每次http请求的时候带上一个动态的验证信息。如果验证信息非法那么就抛弃掉当前的请求。</p>
<p>还有如果有app中有长连接需求的,可以把长连接的ip地址通过短连接中的某个请求中返回,这样可以理想化的做到不同的长连接的地址有可能是不同的。防止一些非法的连接。而且同时也能够做到动态的切换。</p>
<p>还有一点是通过“验证码”、“图片识别”等需求延伸出来受到的启发。我相信基本上的app中都会有统计的,如果把统计的数据来验证本次请求是否合法,这何尝不是一种解决方法。比如说注册用户的请求,服务端收到注册的请求,那么这时候判断下当前的设备是否有关于注册方面的统计事件,如果有那么ok,这个在这个层面上可以理解为是合法的请求。反之如果没有,那么可以理解为是非法的请求。</p>
<p>当然最好的是用https替代http,本身苹果就是鼓励代价请求都用https的。还有就是存在本地的敏感数据全部加密,哪怕是用最简单的md5加密,也胜过明文。如果涉及到消费类的需求的,所有的数据能够通过服务端来判断就通过服务端来判断。</p>
<p>总结下:虽然说,防御的手段有很多。但是所有的防御都只是提高了被破解,被攻击的门槛而已。所以app的安全性永远是一个不断提高的点。</p>
</content>
<summary type="html">
对于移动应用的安全性问题的一些见解
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="安全" scheme="http://campusappcn.github.io/tags/%E5%AE%89%E5%85%A8/"/>
<category term="黑客" scheme="http://campusappcn.github.io/tags/%E9%BB%91%E5%AE%A2/"/>
<category term="攻击" scheme="http://campusappcn.github.io/tags/%E6%94%BB%E5%87%BB/"/>
</entry>
<entry>
<title>Pan的简介和设计思路</title>
<link href="http://campusappcn.github.io/2016/03/18/2016-03-18-Pan%E7%9A%84%E7%AE%80%E4%BB%8B%E5%92%8C%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF/"/>
<id>http://campusappcn.github.io/2016/03/18/2016-03-18-Pan的简介和设计思路/</id>
<published>2016-03-17T16:00:00.000Z</published>
<updated>2016-03-20T09:06:36.000Z</updated>
<content type="html"><p><a href="https://github.com/campusappcn/Pan" target="_blank" rel="external">Pan</a>是一个我在2015年中开始设计并实现的一个安卓端的MV*框架,经过大量的实践和修正,现在的Pan已经十分稳定,成为项目中的核心框架。时至今日,安卓端的MVVM和MVP类型的框架也有一些,但和2015年相比,该有的问题依然存在,而Pan框架的设计思路,也仍然能够超越这些框架,在达到目的的同时,更方便开发者的上手和使用。</p>
<h2 id="臃肿的Activity和Fragment"><a href="#臃肿的Activity和Fragment" class="headerlink" title="臃肿的Activity和Fragment"></a>臃肿的Activity和Fragment</h2><p>相信写安卓的人都深有体会,Activity里面一不小心就会有上千行的代码。安卓的MVC设计,Activity本身承担部分Controller的角色;而View的角色由View类型和XML来承担,同时View类型的EventListener由会承担部分的Controller角色;Model交给开发者自由选择。而一旦到真实的实践中,Activity或者Fragment往往会承担大量的代码,主要包含:</p>
<ol>
<li>页面的生命周期管理,e.g. onStop</li>
<li>View的渲染细节控制,e.g. 例如setText</li>
<li>用户交互事件的绑定,e.g. 例如setOnClickListener</li>
<li>异步网络请求</li>
<li>Fragment生命周期管理</li>
</ol>
<p>简单的页面或许没什么问题,一旦业务逻辑复杂,Activity到后期基本无法维护,因为要做的事情太多了。</p>
<a id="more"></a>
<p>或许你会认为,这是程序员代码能力差导致的。考虑到实际情况,团队中的人也许并不是你能选择的,或者团队成员彼此无法认同对方的编码思路,最终的结果仍然是bad ending。</p>
<h2 id="MVP-难以上手的mortar,解耦不彻底的nucleus和Data-binding语法糖"><a href="#MVP-难以上手的mortar,解耦不彻底的nucleus和Data-binding语法糖" class="headerlink" title="MVP: 难以上手的mortar,解耦不彻底的nucleus和Data-binding语法糖"></a>MVP: 难以上手的<a href="https://github.com/square/mortar" target="_blank" rel="external">mortar</a>,解耦不彻底的<a href="https://github.com/konmik/nucleus" target="_blank" rel="external">nucleus</a>和<a href="https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;cad=rja&amp;uact=8&amp;ved=0ahUKEwj1xY_7icrLAhUsnoMKHVbaCEIQFggcMAA&amp;url=http%3A%2F%2Fdeveloper.android.com%2Ftools%2Fdata-binding%2Fguide.html&amp;usg=AFQjCNH-v4_t0AHIBOQYWBUL_p85OAupyg&amp;sig2=2xv9qnfsbku8FWvKjct6GQ" target="_blank" rel="external">Data-binding</a>语法糖</h2><p>说起MVP框架,首先可能想到的就是square的mortar。mortar很早就写出来了,搭配自家的依赖注入Dagger和Flow。mortar里面很多设计理念很先进,例如通过view组织界面而不是Activity/Fragment,彻底分离View、Presenter、Screen、Activty/Fragment、model层的请求,成功的将整个程序拆成了多种单元,通过依赖注入粘合在一起。这也同样造成了他流行不起来,因为想要mortar,就得把square家族大礼包全部都加进来,彻底的认同一整套square的思维方式,锁定技术栈。学习成本和定制成本都很高。</p>
<p>nucleus是一个轻量级的MVP框架,轻量到……Activity里还是有很多代码,nucleus只把Presenter分离出来,但同时Presenter是与Activity绑定的,这让Presenter的可复用性大大下降,例如一个界面上,局部的Presenter完全可以用到别的Activity/Fragment上的,所以必须要将Presenter和Activity解耦。在实际的编码过程中,ListView或者RecyclerView中Item都是一个可以独立看待的部件,且可能会在其他的非ListView环境中使用。这意味着和Activity绑定后很多内容的复用还得自己想办法。而且nucleus并不管事件跳转,还是写在Activity里,所以Activity还是会臃肿。</p>
<p>官方Data-binding本身只是个setText的语法糖,只把上面提到的View的渲染细节控制解决了一下,而且一定无法彻底解决,逻辑一复杂,还是得用java。</p>
<h2 id="Pan的设计目标"><a href="#Pan的设计目标" class="headerlink" title="Pan的设计目标"></a><a href="https://github.com/campusappcn/Pan" target="_blank" rel="external">Pan</a>的设计目标</h2><p>Pan的设计初衷,就是要给Activity瘦身,最好保持在100行以下,甚至50行以下。综合考虑,还有下面这些目标:</p>
<ol>
<li>兼容性,可以和不使用Pan框架的代码兼容,支持逐步重构</li>
<li>解耦Activity和View,界面部件可以在Activity、Fragment、View中复用</li>
<li>View通过ViewModel被动更新,即ViewModel -&gt; View</li>
<li>分离渲染逻辑和控制逻辑,setText和setOnClickListener不要写在一起</li>
<li>控制逻辑中,应该能够对当前的Activity、Fragment的生命周期进行监控,从而将UI操作和界面周期控制放在一起</li>
<li>不追求魔法,低学习成本,KISS</li>
</ol>
<p>当然,除了上述目标外,到具体实现层面,还有些务实的目标,例如兼容安卓的ViewHolder重用机制。</p>
<h2 id="Pan的模型,MV"><a href="#Pan的模型,MV" class="headerlink" title="Pan的模型,MV*"></a>Pan的模型,MV*</h2><p>依据上述的目标,Pan的模型比传统的MVC、MVVM模型更加务实,不追求理论上的完美,而在乎实际使用中的易于上手。我们可以称之为<a href="http://stackoverflow.com/questions/13329485/mvw-what-does-it-stand-for" target="_blank" rel="external">MVW(Whatever)</a></p>
<p><img src="https://img.alicdn.com/imgextra/i4/56380417/TB2KrLBlVXXXXcWXXXXXXXXXXXX_!!56380417.png" alt="MVC &amp; MVVM &amp; Pan"></p>
<p>后续的文章会进一步介绍Pan,并逐渐完善Pan的文档。</p>
</content>
<summary type="html">
<p><a href="https://github.com/campusappcn/Pan">Pan</a>是一个我在2015年中开始设计并实现的一个安卓端的MV*框架,经过大量的实践和修正,现在的Pan已经十分稳定,成为项目中的核心框架。时至今日,安卓端的MVVM和MVP类型的框架也有一些,但和2015年相比,该有的问题依然存在,而Pan框架的设计思路,也仍然能够超越这些框架,在达到目的的同时,更方便开发者的上手和使用。</p>
<h2 id="臃肿的Activity和Fragment"><a href="#臃肿的Activity和Fragment" class="headerlink" title="臃肿的Activity和Fragment"></a>臃肿的Activity和Fragment</h2><p>相信写安卓的人都深有体会,Activity里面一不小心就会有上千行的代码。安卓的MVC设计,Activity本身承担部分Controller的角色;而View的角色由View类型和XML来承担,同时View类型的EventListener由会承担部分的Controller角色;Model交给开发者自由选择。而一旦到真实的实践中,Activity或者Fragment往往会承担大量的代码,主要包含:</p>
<ol>
<li>页面的生命周期管理,e.g. onStop</li>
<li>View的渲染细节控制,e.g. 例如setText</li>
<li>用户交互事件的绑定,e.g. 例如setOnClickListener</li>
<li>异步网络请求</li>
<li>Fragment生命周期管理</li>
</ol>
<p>简单的页面或许没什么问题,一旦业务逻辑复杂,Activity到后期基本无法维护,因为要做的事情太多了。</p>
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
<category term="MVVM" scheme="http://campusappcn.github.io/tags/MVVM/"/>
<category term="MVC" scheme="http://campusappcn.github.io/tags/MVC/"/>
<category term="MVP" scheme="http://campusappcn.github.io/tags/MVP/"/>
<category term="Pan" scheme="http://campusappcn.github.io/tags/Pan/"/>
</entry>
<entry>
<title>iOS中富文本的AttributeNames</title>
<link href="http://campusappcn.github.io/2016/03/15/iOS%E4%B8%AD%E5%AF%8C%E6%96%87%E6%9C%AC%E7%9A%84AttributeNames/"/>
<id>http://campusappcn.github.io/2016/03/15/iOS中富文本的AttributeNames/</id>
<published>2016-03-15T03:14:28.000Z</published>
<updated>2016-04-09T08:59:46.000Z</updated>
<content type="html"><p>本文主要为项目中无处不在的NSAttributedString的属性做一次整理,方便之后开发时查询和测试</p>
<a id="more"></a>
<h2 id="目录"><a href="#目录" class="headerlink" title="目录:"></a>目录:</h2><p><a href="#jump1">1. NSFontAttributeName;</a><br><a href="#jump2">2. NSParagraphStyleAttributeName;</a><br><a href="#jump3">3. NSForegroundColorAttributeName;</a><br><a href="#jump4">4. NSBackgroundColorAttributeName;</a><br><a href="#jump5">5. NSLigatureAttributeName;</a><br><a href="#jump6">6. NSKernAttributeName;</a><br><a href="#jump7">7. NSStrikethroughStyleAttributeName;</a><br><a href="#jump8">8. NSUnderlineStyleAttributeName;</a><br><a href="#jump9">9. NSStrokeColorAttributeName;</a><br><a href="#jump10">10.NSStrokeWidthAttributeName;</a><br><a href="#jump11">11.NSShadowAttributeName;</a><br><a href="#jump12">12.NSTextEffectAttributeName;</a><br><a href="#jump13">13.NSAttachmentAttributeName;</a><br><a href="#jump14">14.NSLinkAttributeName;</a><br><a href="#jump15">15.NSBaselineOffsetAttributeName;</a><br><a href="#jump16">16.NSUnderlineColorAttributeName;</a><br><a href="#jump17">17.NSStrikethroughColorAttributeName;</a><br><a href="#jump18">18.NSObliquenessAttributeName;</a><br><a href="#jump19">19.NSExpansionAttributeName;</a><br><a href="#jump20">20.NSWritingDirectionAttributeName;</a><br><a href="#jump21">21.NSVerticalGlyphFormAttributeName;</a></p>
<hr>
<h3 id="1-NSFontAttributeName-字体-UIFont"><a href="#1-NSFontAttributeName-字体-UIFont" class="headerlink" title="1.NSFontAttributeName//字体 UIFont"></a>1.<span id="jump1"><em><code>NSFontAttributeName</code></em><span>//字体 UIFont</span></span></h3><h3 id="2-NSParagraphStyleAttributeName-段落样式"><a href="#2-NSParagraphStyleAttributeName-段落样式" class="headerlink" title="2.NSParagraphStyleAttributeName//段落样式"></a>2.<span id="jump2"><em><code>NSParagraphStyleAttributeName</code></em><span>//段落样式</span></span></h3><p>以下截图来自<a href="https://github.com/ibireme/YYText" target="_blank" rel="external">YYText</a><br><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2ZS2ylVXXXXcpXXXXXXXXXXXX_!!373400920.png" width="446" height="847" style="margin: 0"></p>
<h2 id=""><a href="#" class="headerlink" title=""></a><img src="https://img.alicdn.com/imgextra/i2/373400920/TB2zzYBlVXXXXb_XXXXXXXXXXXX_!!373400920.png" width="446" height="606" style="margin: 0"></h2><h3 id="3-NSForegroundColorAttributeName-字颜色-UIColor"><a href="#3-NSForegroundColorAttributeName-字颜色-UIColor" class="headerlink" title="3.NSForegroundColorAttributeName //字颜色 UIColor"></a>3.<span id="jump3"><em><code>NSForegroundColorAttributeName</code></em> <span>//字颜色 UIColor</span></span></h3><h3 id="4-NSBackgroundColorAttributeName-背景色-UIColor"><a href="#4-NSBackgroundColorAttributeName-背景色-UIColor" class="headerlink" title="4.NSBackgroundColorAttributeName //背景色 UIColor"></a>4.<span id="jump4"><em><code>NSBackgroundColorAttributeName</code></em> <span>//背景色 UIColor</span></span></h3><hr>
<h3 id="5-NSLigatureAttributeName-连写-iOS只支持-0-和-1"><a href="#5-NSLigatureAttributeName-连写-iOS只支持-0-和-1" class="headerlink" title="5.NSLigatureAttributeName //连写,iOS只支持@(0)和@(1)"></a>5.<span id="jump5"><em><code>NSLigatureAttributeName</code></em><span> //连写,iOS只支持@(0)和@(1)</span></span></h3><p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2D1MflFXXXXaOXXXXXXXXXXXX_!!373400920.png" width="272" height="201" style="margin: 0"></p>
<hr>
<h3 id="6-NSKernAttributeName-字间距"><a href="#6-NSKernAttributeName-字间距" class="headerlink" title="6.NSKernAttributeName//字间距"></a>6.<span id="jump6"><em><code>NSKernAttributeName</code></em></span>//字间距</h3><p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2CDEqlFXXXXXfXXXXXXXXXXXX_!!373400920.png" width="373" height="220" style="margin: 0"></p>
<hr>
<h3 id="7-NSStrikethroughStyleAttributeName-删除线"><a href="#7-NSStrikethroughStyleAttributeName-删除线" class="headerlink" title="7.NSStrikethroughStyleAttributeName // 删除线"></a>7.<span id="jump7"><em><code>NSStrikethroughStyleAttributeName</code></em></span> // 删除线</h3><figure class="highlight objc"><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">@(<span class="built_in">NSUnderlineStyle</span>)枚举值</span><br><span class="line"><span class="built_in">NSUnderlineStyleNone</span> = <span class="number">0x00</span>,</span><br><span class="line"><span class="built_in">NSUnderlineStyleSingle</span> = <span class="number">0x01</span>,</span><br><span class="line"><span class="built_in">NSUnderlineStyleThick</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x02</span>, <span class="number">2</span>~<span class="number">8</span> 取值越大,线越粗</span><br><span class="line"><span class="built_in">NSUnderlineStyleDouble</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x09</span></span><br></pre></td></tr></table></figure>
<p><img src="https://img.alicdn.com/imgextra/i4/373400920/TB2vm28lFXXXXc1XXXXXXXXXXXX_!!373400920.png" width="369" height="268" style="margin: 0"></p>
<p>剩下的几个枚举需要配合上面的枚举来使用<br><figure class="highlight objc"><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 class="built_in">NSUnderlinePatternSolid</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x0000</span>,<span class="comment">//实线</span></span><br><span class="line"><span class="built_in">NSUnderlinePatternDot</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x0100</span>,<span class="comment">//短 循环</span></span><br><span class="line"><span class="built_in">NSUnderlinePatternDash</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x0200</span>,<span class="comment">//长 循环</span></span><br><span class="line"><span class="built_in">NSUnderlinePatternDashDot</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x0300</span>,<span class="comment">//长短 循环</span></span><br><span class="line"><span class="built_in">NSUnderlinePatternDashDotDot</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x0400</span>,<span class="comment">//长短短 循环</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">NSUnderlineByWord</span> <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_0, <span class="number">7</span>_0) = <span class="number">0x8000</span><span class="comment">//按单词分割</span></span><br></pre></td></tr></table></figure></p>
<h2 id="-1"><a href="#-1" class="headerlink" title=" "></a><img src="https://img.alicdn.com/imgextra/i2/373400920/TB2KBvUlFXXXXcqXpXXXXXXXXXX_!!373400920.png" width="371" height="411" style="margin: 0"> </h2><h3 id="8-NSUnderlineStyleAttributeName-下划线-值也是枚举NSUnderlineStyle的数字类型-NSUnderlineStyle-参考NSStrikethroughStyleAttributeName"><a href="#8-NSUnderlineStyleAttributeName-下划线-值也是枚举NSUnderlineStyle的数字类型-NSUnderlineStyle-参考NSStrikethroughStyleAttributeName" class="headerlink" title="8.NSUnderlineStyleAttributeName// 下划线(值也是枚举NSUnderlineStyle的数字类型-@(NSUnderlineStyle)参考NSStrikethroughStyleAttributeName)"></a>8.<span id="jump8"><em><code>NSUnderlineStyleAttributeName</code></em></span>// 下划线(值也是枚举NSUnderlineStyle的数字类型-@(NSUnderlineStyle)参考NSStrikethroughStyleAttributeName)</h3><p><img src="https://img.alicdn.com/imgextra/i4/373400920/TB2qJEblFXXXXXhXpXXXXXXXXXX_!!373400920.png" width="371" height="372" style="margin: 0"> </p>
<hr>
<h3 id="9-NSStrokeColorAttributeName-笔画宽度和当前字的pointSize-字体大小-的比例"><a href="#9-NSStrokeColorAttributeName-笔画宽度和当前字的pointSize-字体大小-的比例" class="headerlink" title="9.NSStrokeColorAttributeName// 笔画宽度和当前字的pointSize(字体大小)的比例,"></a>9.<span id="jump9"><em><code>NSStrokeColorAttributeName</code></em></span>// 笔画宽度和当前字的pointSize(字体大小)的比例,</h3><p>正数真空效果<br><img src="https://img.alicdn.com/imgextra/i4/373400920/TB2d9UylFXXXXX4XXXXXXXXXXXX_!!373400920.png" width="372" height="235" style="margin: 0"></p>
<hr>
<h3 id="10-NSStrokeColorAttributeName-NSStrokeColorAttributeName的颜色"><a href="#10-NSStrokeColorAttributeName-NSStrokeColorAttributeName的颜色" class="headerlink" title="10.NSStrokeColorAttributeName//NSStrokeColorAttributeName的颜色"></a>10.<span id="jump10"><em><code>NSStrokeColorAttributeName</code></em></span>//NSStrokeColorAttributeName的颜色</h3><p><img src="https://img.alicdn.com/imgextra/i1/373400920/TB20GkzlFXXXXafXXXXXXXXXXXX_!!373400920.png" width="373" height="121" style="margin: 0"></p>
<hr>
<h3 id="11-NSShadowAttributeName-阴影-参考NSShadow"><a href="#11-NSShadowAttributeName-阴影-参考NSShadow" class="headerlink" title="11.NSShadowAttributeName //阴影,参考NSShadow"></a>11.<span id="jump11"><em><code>NSShadowAttributeName</code></em></span> //阴影,参考NSShadow</h3><p><img src="https://img.alicdn.com/imgextra/i1/373400920/TB2Wmv1lFXXXXbRXpXXXXXXXXXX_!!373400920.png" width="371" height="72" style="margin: 0"></p>
<hr>
<h3 id="12-NSTextEffectAttributeName-凸版印刷体-现在就只有NSTextEffectLetterpressStyle一个值"><a href="#12-NSTextEffectAttributeName-凸版印刷体-现在就只有NSTextEffectLetterpressStyle一个值" class="headerlink" title="12.NSTextEffectAttributeName//凸版印刷体(现在就只有NSTextEffectLetterpressStyle一个值)"></a>12.<span id="jump12"><em><code>NSTextEffectAttributeName</code></em></span>//凸版印刷体(现在就只有NSTextEffectLetterpressStyle一个值)</h3><p>凸版印刷替效果是给文字加上奇妙阴影和高光,让文字看起有凹凸感,像是被压在屏幕上(这个描述真是有够夸张 = =!)<br><img src="https://img.alicdn.com/imgextra/i3/373400920/TB29UgalFXXXXaXXpXXXXXXXXXX_!!373400920.png" width="466" height="47" style="margin: 0"></p>
<hr>
<h3 id="13-NSAttachmentAttributeName-图文混排相关"><a href="#13-NSAttachmentAttributeName-图文混排相关" class="headerlink" title="13.NSAttachmentAttributeName//图文混排相关"></a>13.<span id="jump13"><em><code>NSAttachmentAttributeName</code></em></span>//图文混排相关</h3><figure class="highlight objc"><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 class="built_in">NSTextAttachment</span> *attach=[[<span class="built_in">NSTextAttachment</span> alloc]init];</span><br><span class="line"> attach.image=[<span class="built_in">UIImage</span> imageNamed:<span class="string">@"1178298162bf1917"</span>];</span><br><span class="line"> [base insertAttributedString:[<span class="built_in">NSAttributedString</span> attributedStringWithAttachment:attach] atIndex:<span class="number">6</span>];</span><br></pre></td></tr></table></figure>
<p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2v12_lFXXXXa3XpXXXXXXXXXX_!!373400920.png" width="369" height="177" style="margin: 0"></p>
<hr>
<h3 id="14-NSLinkAttributeName-链接-但是不负责点击的处理"><a href="#14-NSLinkAttributeName-链接-但是不负责点击的处理" class="headerlink" title="14.NSLinkAttributeName//链接,但是不负责点击的处理"></a>14.<span id="jump14"><em><code>NSLinkAttributeName</code></em></span>//链接,但是不负责点击的处理</h3><figure class="highlight objc"><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 class="built_in">NSMutableAttributedString</span> *base=[[<span class="built_in">NSMutableAttributedString</span> alloc]initWithString:string attributes:<span class="literal">nil</span>];</span><br><span class="line"><span class="built_in">NSRange</span> rang=<span class="built_in">NSMakeRange</span>(<span class="number">0</span>, base.length);</span><br><span class="line">[base addAttribute:<span class="built_in">NSLinkAttributeName</span> value:[<span class="built_in">NSURL</span> URLWithString:<span class="string">@"http://www.google.com"</span>] </span><br><span class="line">range:[string rangeOfString:<span class="string">@"http://www.google.com"</span>]];</span><br></pre></td></tr></table></figure>
<p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2ud.vlFXXXXbzXXXXXXXXXXXX_!!373400920.png" width="365" height="45" style="margin: 0"></p>
<p>一般配合UITextView使用<br><figure class="highlight objc"><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">- (<span class="built_in">BOOL</span>)textView:(<span class="built_in">UITextView</span> *)textView shouldInteractWithURL:(<span class="built_in">NSURL</span> *)URL inRange:(<span class="built_in">NSRange</span>)characterRange</span><br><span class="line">&#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"=============%@"</span>,URL);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<hr>
<h3 id="15-NSBaselineOffsetAttributeName-离BaseLine的距离"><a href="#15-NSBaselineOffsetAttributeName-离BaseLine的距离" class="headerlink" title="15.NSBaselineOffsetAttributeName//离BaseLine的距离"></a>15.<span id="jump15"><em><code>NSBaselineOffsetAttributeName</code></em></span>//离BaseLine的距离</h3><p><a href="https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html#//apple_ref/doc/uid/TP40009459-CH5-SW1" target="_blank" rel="external">什么是BaseLine?</a><br><img src="https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png" alt=""><br><img src="https://img.alicdn.com/imgextra/i4/373400920/TB29L.tlFXXXXcsXXXXXXXXXXXX_!!373400920.png" width="367" height="75" style="margin: 0"></p>
<hr>
<h3 id="16-NSUnderlineColorAttributeName-下划线的颜色"><a href="#16-NSUnderlineColorAttributeName-下划线的颜色" class="headerlink" title="16.NSUnderlineColorAttributeName//下划线的颜色"></a>16.<span id="jump16"><em><code>NSUnderlineColorAttributeName</code></em></span>//下划线的颜色</h3><p>相关属性(NSUnderlineStyleAttributeName)</p>
<h3 id="17-NSStrikethroughColorAttributeName-中划线的颜色"><a href="#17-NSStrikethroughColorAttributeName-中划线的颜色" class="headerlink" title="17.NSStrikethroughColorAttributeName//中划线的颜色"></a>17.<span id="jump17"><em><code>NSStrikethroughColorAttributeName</code></em></span>//中划线的颜色</h3><p>相关属性(NSStrikethroughStyleAttributeName)</p>
<hr>
<h3 id="18-NSObliquenessAttributeName-倾斜"><a href="#18-NSObliquenessAttributeName-倾斜" class="headerlink" title="18.NSObliquenessAttributeName//倾斜"></a>18.<span id="jump18"><em><code>NSObliquenessAttributeName</code></em></span>//倾斜</h3><p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2le.DlFXXXXaDXXXXXXXXXXXX_!!373400920.png" width="371" height="383" style="margin: 0"></p>
<hr>
<h3 id="19-NSExpansionAttributeName-“胖”or-“瘦”-拉伸or压缩"><a href="#19-NSExpansionAttributeName-“胖”or-“瘦”-拉伸or压缩" class="headerlink" title="19.NSExpansionAttributeName//“胖”or “瘦”(拉伸or压缩)"></a>19.<span id="jump19"><em><code>NSExpansionAttributeName</code></em></span>//“胖”or “瘦”(拉伸or压缩)</h3><p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2ru.qlFXXXXc_XXXXXXXXXXXX_!!373400920.png" width="370" height="286" style="margin: 0"></p>
<hr>
<h3 id="20-NSWritingDirectionAttributeName-文字的排布顺序-从左到右还是从右到左"><a href="#20-NSWritingDirectionAttributeName-文字的排布顺序-从左到右还是从右到左" class="headerlink" title="20.NSWritingDirectionAttributeName//文字的排布顺序(从左到右还是从右到左)"></a>20.<span id="jump20"><em><code>NSWritingDirectionAttributeName</code></em></span>//文字的排布顺序(从左到右还是从右到左)</h3><p><img src="https://img.alicdn.com/imgextra/i3/373400920/TB2YfgLlFXXXXX8XXXXXXXXXXXX_!!373400920.png" width="686" height="273" style="margin: 0"></p>
<figure class="highlight objc"><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"><span class="comment">//不太明白这个枚举值的两个意思...之后如果有机会明白再解释他们</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_ENUM</span>(<span class="built_in">NSInteger</span>, <span class="built_in">NSWritingDirectionFormatType</span>) &#123;</span><br><span class="line"> <span class="built_in">NSWritingDirectionEmbedding</span> = (<span class="number">0</span> &lt;&lt; <span class="number">1</span>),</span><br><span class="line"> <span class="built_in">NSWritingDirectionOverride</span> = (<span class="number">1</span> &lt;&lt; <span class="number">1</span>)</span><br><span class="line">&#125; <span class="built_in">NS_ENUM_AVAILABLE</span>(<span class="number">10</span>_11, <span class="number">9</span>_0);</span><br></pre></td></tr></table></figure>
<hr>
<h3 id="21-NSVerticalGlyphFormAttributeName-0横-1竖"><a href="#21-NSVerticalGlyphFormAttributeName-0横-1竖" class="headerlink" title="21.NSVerticalGlyphFormAttributeName//0横,1竖"></a>21.<span id="jump21"><em><code>NSVerticalGlyphFormAttributeName</code></em></span>//0横,1竖</h3><p>在iOS上只有横排,不过可以通过CoreText来修改成竖排</p>
</content>
<summary type="html">
<p>本文主要为项目中无处不在的NSAttributedString的属性做一次整理,方便之后开发时查询和测试</p>
</summary>
<category term="iOS" scheme="http://campusappcn.github.io/tags/iOS/"/>
<category term="CoreText" scheme="http://campusappcn.github.io/tags/CoreText/"/>
<category term="NSAttributedString" scheme="http://campusappcn.github.io/tags/NSAttributedString/"/>
</entry>
<entry>
<title>类型判断上的小技巧</title>
<link href="http://campusappcn.github.io/2016/03/12/%E7%B1%BB%E5%9E%8B%E5%88%A4%E6%96%AD%E4%B8%8A%E7%9A%84%E5%B0%8F%E6%8A%80%E5%B7%A7/"/>
<id>http://campusappcn.github.io/2016/03/12/类型判断上的小技巧/</id>
<published>2016-03-11T16:00:00.000Z</published>
<updated>2016-03-12T15:13:42.000Z</updated>
<content type="html"><p> 不知道大伙儿有没有这样的经历:<br> 每次看到<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">-[__NSCFConstantString objectForKey:]: unrecognized selector sent to instance 0x1050a6be8</span><br></pre></td></tr></table></figure></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-[__NSCFNumber isEqualToString:]: unrecognized selector sent to instance 0xb0000000000007b2</span><br></pre></td></tr></table></figure>
<p>这样的crash是不是会有种想死的感觉。不管怎样,我每次碰到这种crash都会有中想死的冲动。虽然说这种crash修改起来超级容易,只是在进行操作前加上类型判断就可以,或者说是直接理解错误了变量的数据类型。<br>但是这种crash还是经常的出现,为什么呢?我思考了下,我觉得主要主要的原因有两个:<br>1、使用了“万能”的id;直接上例子代码吧<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></pre></td><td class="code"><pre><span class="line">id x = @&quot;123&quot;;</span><br><span class="line">//many steps...</span><br><span class="line">//...</span><br><span class="line">NSString *str = @&quot;hello&quot;;</span><br><span class="line">[x isEqualToString:str];</span><br></pre></td></tr></table></figure></p>
<p>如上的代码,开始笼统的将变量x定义为id类型的,后面又将x直接当做nsstring类型来做逻辑。也许一个人写的时候,他很清楚这块的逻辑。但是换一个人或者说是过段时间再来修改这块的代码。那么也许就会改成如下代码的可能性。因为这样子既没有报错,也没有warning。<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></pre></td><td class="code"><pre><span class="line">id x = @(123);</span><br><span class="line">//many steps...</span><br><span class="line">//...</span><br><span class="line">NSString *str = @&quot;hello&quot;;</span><br><span class="line">[x isEqualToString:str];</span><br></pre></td></tr></table></figure></p>
<p>这是一个原因,这个也是我在之前遇到过的。<br>2、使用自己服务器或者其他渠道的数据。<br>这点我相信只要做过非纯本地app的人,都深有感触。就是说虽然协议或者说约定好的x这个字段是string类型的。但是保不准到时候会出现错误传个int或者其他类型过来。记得我们cto大人说过,客户端不要轻易相信服务器给过来的数据;反之也是。其实这句话的有一层意思就是说,虽然我们大家都会按照定好的规则来执行。但是保不准有时候会出现问题。但是最终这些所有的问题都是需要由客户端来买单的。所以类型判断必不可少!</p>
<p>然而并不是所有的地方都会记得加上类型判断的。所以还是时不时会产生文字一开始说的那些crash。那么我就在想能不能一劳永逸,在最底层的地方加上类型判断之类的方法解决这个头痛的问题呢。<br>自然而然的想到了消息传递。其实通常的方法调用<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">[someObject messageName:parameter];</span><br></pre></td></tr></table></figure></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">objc_msgSend(someObject,@selector(messageName:),parameter);</span><br></pre></td></tr></table></figure></p>
<p>那么先说下向一个实例发送一个消息后,系统的处理是这样的:<br>1、发送消息([self sendMsg])<br>2、系统会check是否能够respnse这个消息<br>3、如果能response则调用想要方法,不能则抛出异常<br>而在第二步中,系统是如何check实例是否能response消息呢?如果实例没事就有想要的response,那么久会相应返回,如果没有系统就会发出methodSignatureForSelector消息,询问他这个消息是否有效?有效就返回相应的方法地址之类的信息,无效则返回nil。如果nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这是也就会crash,如果不是nil接着发送forwardInvocation消息。<br>看了上述的流程。那么是否只要在methodSignatureForSelector这一步,如果在确定本来返回的是nil的时候我自己构造一个将其返回是不是就可以不会闪退了呢。答案是:可以的。methodSignatureForSelector这一步其实也就是对方法的一个签名的返回。那么如果在这一步按照原有的签名生成的原则伪造一个假的签名,就可以蒙混过关。本会crash的方法将无响应。<br>以下的是我根据上面的思路写的一个nsobject的扩展类。重写了forwardInvocation和methodSignatureForSelector方法。而这里我只对了几个基础的类型做了检验。因为很多类型是ios非公开的,好像会有点问题。所以相当于先打个补丁在这里。</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></pre></td><td class="code"><pre><span class="line">-(void)forwardInvocation:(NSInvocation *)anInvocation</span><br><span class="line">&#123;</span><br><span class="line"> NSLog(@&quot;forwardInvocation&quot;);</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector</span><br><span class="line">&#123;</span><br><span class="line"> </span><br><span class="line"> NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:selector];</span><br><span class="line"></span><br><span class="line"> if (!sig) &#123;</span><br><span class="line"> BOOL shouldGo = NO;</span><br><span class="line"> for (Class class in [self totalClassArr]) &#123;</span><br><span class="line"> if ([self isKindOfClass:class] || [self isMemberOfClass:class]) &#123;</span><br><span class="line"> shouldGo = YES;</span><br><span class="line"> break;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> if (shouldGo) &#123;</span><br><span class="line"> //show error</span><br><span class="line"> </span><br><span class="line"> if ([sel rangeOfString:@&quot;set&quot;].location == 0)</span><br><span class="line"> &#123;</span><br><span class="line"> return [NSMethodSignature signatureWithObjCTypes:&quot;v@:@&quot;];</span><br><span class="line"> &#125;</span><br><span class="line"> else</span><br><span class="line"> &#123;</span><br><span class="line"> return [NSMethodSignature signatureWithObjCTypes:&quot;@@:&quot;];</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> return sig;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">-(NSArray *)totalClassArr</span><br><span class="line">&#123;</span><br><span class="line"> NSMutableArray *arr = [[NSMutableArray alloc]init];</span><br><span class="line"> [arr addObject:[NSArray class]];</span><br><span class="line"> [arr addObject:[NSNumber class]];</span><br><span class="line"> [arr addObject:[NSString class]];</span><br><span class="line"> [arr addObject:[NSDictionary class]];</span><br><span class="line"> [arr addObject:[NSSet class]];</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> return [arr copy];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下图是有关于消息传递的一张流程图<br>我觉得能够比较好的理解消息传递这一块<br><img src="http://7xrcp9.com1.z0.glb.clouddn.com/blogimage_note64270_1.png?imageView2/2/w/800/q/75" alt="消息传递"></p>
<p>而这篇博文关于消息传递我觉得还是比较可以推荐的 <a href="https://www.zybuluo.com/MicroCai/note/64270" target="_blank" rel="external">https://www.zybuluo.com/MicroCai/note/64270</a></p>
</content>
<summary type="html">
统一的做一个类型判断,告别此类闪退
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="crash" scheme="http://campusappcn.github.io/tags/crash/"/>
<category term="类型判断" scheme="http://campusappcn.github.io/tags/%E7%B1%BB%E5%9E%8B%E5%88%A4%E6%96%AD/"/>
<category term="消息传递" scheme="http://campusappcn.github.io/tags/%E6%B6%88%E6%81%AF%E4%BC%A0%E9%80%92/"/>
</entry>
<entry>
<title>iOS中如何优雅的应对无限闪退</title>
<link href="http://campusappcn.github.io/2016/03/08/iOS%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E5%BA%94%E5%AF%B9%E6%97%A0%E9%99%90%E9%97%AA%E9%80%80/"/>
<id>http://campusappcn.github.io/2016/03/08/iOS中如何优雅的应对无限闪退/</id>
<published>2016-03-07T16:00:00.000Z</published>
<updated>2016-03-08T07:13:51.000Z</updated>
<content type="html"><p> 恩,这是背景。每次更新版本的时候,必须要做的一件事情就是兼容老版本。因为从老版本升级的时候也许会出现数据的兼容性问题。特别是对于数据库的兼容尤为重要。然而如果一不小心兼容性处理的不好,那么就有可能出现无限性的闪退。也许闪退这个动作对于我们开发来说,是很明了的一个现象(app出现问题,导致crash)。但是对于一般的用户而言他们并不知道这到底发生了什么事情。之前和用户接触,他们对于我们所说的闪退他们的描述有很多种:有的说我打开应用就退出来了;有的说我打开应用一下子就回到了桌面,还有的说我打不开应用。由此可以看出用户对于闪退这件事情并不是很理解。有的甚至会以为因为装了这个app导致手机出现了问题。好吧,说了这么多,我只想说明一点就是“无限闪退”是一件很恐怖的事情。<br>之前我安装的某应用,由于升级版本的时候的数据库升级问题,导致无限闪退,只能卸载app重装。在之前由于在一个项目中,我将错误的数据类型保存在本地,并且没有加类型判断,导致了app无限闪退,这种情况下也只能卸载app,重新安装才能正常运行。<br>通过这些惨痛的经历,我一不小心想到了“防爆系统”,这个霸气名字,呵呵~。是对于app出现无限闪退的时候,进行的一些操作。<br>主要的代码就是下面这个方法:</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">static void uncaughtExceptionHandler(NSException *exception) &#123;</span><br><span class="line"> //do something ....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在这个方法中你可以把闪退的堆栈,原因等记录下来,通过一定的渠道获取(发邮件,上报服务器等)。<br>下图主要是整个防爆系统的实现逻辑。<br><img src="http://7xrcp9.com1.z0.glb.clouddn.com/blog%E9%98%B2%E7%88%86%E7%B3%BB%E7%BB%9F.png?imageView2/2/w/800/q/75" alt="实现逻辑"><br>其实这个逻辑很简单,主要是一种思路。之前使用手淘的时候,无意中闪退了多次,然后再次进入app的时候我的所有数据都被清除了。所以我在“是否达到了需要清除本地数据的条件”这里是这样设定的,每次闪退必定会记录下闪退的时间,每次进入app判断如果每次闪退的时间间隔为x(例如1分钟)则可以认为上一次以及上上次的闪退为有效记录,再判断如果闪退的有效记录已经达到n次(例如5次)则我认为有可能是本地的持久化数据导致了app出现问题,所以需要清除数据。<br>当然x和n这两个值,应该根据自己的app取舍。甚至可以在记录闪退数据的时候可以把闪退原因记录下来,对闪退的原因进行比较归类,这样的话就更加精确了。</p>
<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><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">//检查并处理闪退的数据</span><br><span class="line">-(void)checkAndDealCrashData</span><br><span class="line">&#123;</span><br><span class="line"> NSDictionary *lastCrashDict = [self.crashArr lastObject];</span><br><span class="line"> long long lastCrashTms = [lastCrashDict[@&quot;crashTms&quot;]longLongValue];</span><br><span class="line"> long long curTms = [self curSystemTms];</span><br><span class="line"> if (curTms - lastCrashTms &gt; maxCrashTimeInterval) &#123;</span><br><span class="line"> //说明 闪退间隔已经超出了 可以理解为不需要记录</span><br><span class="line"> [self cleanCrashArr];</span><br><span class="line"> [self addCrashToData];</span><br><span class="line"> &#125;else</span><br><span class="line"> &#123;</span><br><span class="line"> if (self.crashArr.count &gt;= (maxCrashTimes - 1)) &#123;</span><br><span class="line"> //需要清除数据</span><br><span class="line"> [self cleanAppLocalData];</span><br><span class="line"> &#125;else</span><br><span class="line"> &#123;</span><br><span class="line"> //还没有到达上限</span><br><span class="line"> [self addCrashToData];</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>好了,防爆系统的思路就是这些。让我们优雅的面对闪退吧~</p>
</content>
<summary type="html">
告别无限闪退带来的烦恼
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="crash" scheme="http://campusappcn.github.io/tags/crash/"/>
<category term="防爆" scheme="http://campusappcn.github.io/tags/%E9%98%B2%E7%88%86/"/>
<category term="闪退" scheme="http://campusappcn.github.io/tags/%E9%97%AA%E9%80%80/"/>
</entry>
<entry>
<title>如何判断 TextView 内容是否被缩略</title>
<link href="http://campusappcn.github.io/2016/03/07/%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD-TextView-%E5%86%85%E5%AE%B9%E6%98%AF%E5%90%A6%E8%A2%AB%E7%BC%A9%E7%95%A5/"/>
<id>http://campusappcn.github.io/2016/03/07/如何判断-TextView-内容是否被缩略/</id>
<published>2016-03-07T02:43:46.000Z</published>
<updated>2016-03-20T09:06:36.000Z</updated>
<content type="html"><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>TextView 中所谓的缩略其实如下, 实际上是在显示文本时将最后一个字符替换成 … 而已:<br><img src="/2016/03/07/如何判断-TextView-内容是否被缩略/ellipse_example.png" alt="所谓的缩略" title="所谓的缩略"></p>
<h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>产品需求而已, 不细说了, 都是泪啊</p>
<h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><h3 id="机制"><a href="#机制" class="headerlink" title="机制"></a>机制</h3><p>TextView 中的文本到底是如何排版以及显示的呢? 答案是 <a href="http://developer.android.com/intl/es/reference/android/text/Layout.html" target="_blank" rel="external">Layout</a></p>
<p>在我们调用 <code>textView.setText(&quot;Some very long text&quot;)</code> 后, 真正展示在界面上的可能是 “Some very l…”, 然而此时我们调用 <code>textView.getText()</code> 得到的却依旧是 “Some very long text”.<br><img src="/2016/03/07/如何判断-TextView-内容是否被缩略/text_get_text.png" alt="textView.getText() 的结果" title="textView.getText() 的结果"></p>
<p>因吹丝停!</p>
<p>那么这时我们通过 <code>textView.getLayout().getText()</code> 拿到的是什么呢? 答案是 “Some very l…ng text”.<br><img src="/2016/03/07/如何判断-TextView-内容是否被缩略/text_layout_get_text.png" alt="textView.getLayout.getText()的结果" title="textView.getLayout.getText()的结果"></p>
<p>WTH?!</p>
<p>原来只是把<strong>字符替换</strong>了一下而已</p>
<h3 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h3><p>了解到 TextView 的 trick 之后, 我们也可以利用它来判断当前显示的文本是否被缩略了:<br><figure class="highlight java"><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"><span class="comment">/**</span><br><span class="line"> * 判断一个 TextView 显示的内容是否被缩略了</span><br><span class="line"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isTextEllipse</span><span class="params">(@NonNull TextView textView)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> CharSequence rawText = textView.getText();</span><br><span class="line"> CharSequence displayText = textView.getLayout().getText();</span><br><span class="line"> <span class="keyword">return</span> !TextUtils.equals(rawText, displayText);</span><br><span class="line"> &#125; <span class="keyword">catch</span>(Exception ignored) &#123;</span><br><span class="line"> <span class="comment">// getLayout() 可能返回 null</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="More"><a href="#More" class="headerlink" title="More"></a>More</h2><p>由于渲染机制, 文中的方法需要在 textView.setText 之后调用, 并且需要 post 到主线程中才能生效:<br><figure class="highlight java"><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">textView.setText(<span class="string">"Some very long text"</span>);</span><br><span class="line">textView.post(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (isTextEllipse(textView) &#123;</span><br><span class="line"> <span class="comment">// do some work</span></span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="comment">// some other work</span></span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p>
</content>
<summary type="html">
奇技淫巧之如何判断一个 TextView 的内容是否被省略了
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
<category term="TextView" scheme="http://campusappcn.github.io/tags/TextView/"/>
<category term="缩略" scheme="http://campusappcn.github.io/tags/%E7%BC%A9%E7%95%A5/"/>
<category term="Ellipse" scheme="http://campusappcn.github.io/tags/Ellipse/"/>
</entry>
<entry>
<title>MVVM的图形界面单元测试</title>
<link href="http://campusappcn.github.io/2016/03/07/2016-03-07-MVVM%E7%9A%84%E5%9B%BE%E5%BD%A2%E7%95%8C%E9%9D%A2%E6%B5%8B%E8%AF%95/"/>
<id>http://campusappcn.github.io/2016/03/07/2016-03-07-MVVM的图形界面测试/</id>
<published>2016-03-06T16:00:00.000Z</published>
<updated>2016-03-20T09:06:36.000Z</updated>
<content type="html"><p>图形界面的测试一直都是一个麻烦事,一般来说,都是开发人员开发的时候和设计稿比对,测试的时候过一遍,最终产品经理验收。正常的数据一般来说显示都没有问题的,往往是上线后的用户数据产生了特定的corner case,出现了意料之外的视觉效果。</p>
<h2 id="MVVM"><a href="#MVVM" class="headerlink" title="MVVM"></a>MVVM</h2><p>MVVM的核心思路,是将界面展示拆分成了VM和V两个部分,甚至很多MVVM的框架,只专注于VM和V的部分,提供双向绑定,把剩下的更新、缓存、业务逻辑都交给开发者自己决定。</p>
<p>而视觉效果的测试,则可以只关注双向绑定的一个方向,即 VM -&gt; V 这个方向,即特定的VM状态,所产生的界面应该是合理的、符合设计规范的。当然双向的测试更为理想,可以测试交互细节,答案略。O__O “…好吧,是我们可以放到后面讨论。</p>
<h2 id="GUI测试"><a href="#GUI测试" class="headerlink" title="GUI测试"></a>GUI测试</h2><p>现行的很多自动化UI测试框架,一般给出两种方案,一种是直接用代码检查界面是否正确显示,这里是否有这个字,那里是不是红色;一种是直接给出截图,程序员可以直接看图,甚至在图中标出预期和实际的差别。然而这类框架的一个重要缺陷是,界面改动可能十分频繁,特别是迭代速度非常快的情况下,很多时候逻辑并没有改变多少,但是界面已经迭代了好多版本。这样一来,写好的测试用例基本白搭,又得重头开始写。而视觉相关的测试用例本来就纷繁冗杂……╮(╯▽╰)╭</p>
<h2 id="MVVM-GUI-MGTest测试框架"><a href="#MVVM-GUI-MGTest测试框架" class="headerlink" title="MVVM + GUI = MGTest测试框架"></a>MVVM + GUI = MGTest测试框架</h2><p>脚踏实地,我们认为程序员在调试界面的时候仍然愿意去“看”,而不是用代码去检查,那么就可以利用MVVM的特点来达到这一目的。</p>
<ol>
<li>MVVM框架保证所有的页面V,是由VM状态直接映射的,即VM -&gt; V</li>
<li>MGT框架负责遍历所有的VM状态,生成截图</li>
<li>程序员/测试/产品通过查看截图判定视觉的正确性</li>
</ol>
<p>这样做,有很多直接的好处。</p>
<p>首先,可以分离视觉测试和开发工作,相当于视觉的单元测试无需编写额外的单元测试代码,一个不用太懂编码的测试即可完成工作。</p>
<p>其次,查看特定的界面效果,不依赖重现某一个特定业务逻辑。这点在日常开发中,程序员自己“目测”的过程中非常常见,为了看特定状态下是否达到了预期的效果,往往需要多次重复复杂的业务逻辑,才能做一次review,效率很低。而如果是自动遍历所有状态,自然会包括特定业务逻辑的状态在其中。</p>
<h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><ol>
<li>深度遍历VM到基本类型</li>
<li>提供针对基本类型取值范围的通用配置</li>
<li>允许对特定的VM变量进行单独的变量范围配置</li>
<li>结合VM框架,自动运行接入的界面,并对不同状态的界面进行截图</li>
<li>展示截图,例如网页、邮件、甚至一个测试用portal</li>
</ol>
<p>中间,第4步需要对VM的各个变量的各个取值进行交叉遍历,有可能会产生大量的数据,例如10个变量,每个变量2个取值就有1024张图了,实际并不需要这么多,应该首先生成能够遍历所有变量的n张图,其中n是这些变量中,最多取值的变量的取值数量。剩下的自由组合可以随机生成一些,以保证生成的图片能够迅速看完,又不会失去检查corner case的机会。</p>
</content>
<summary type="html">
讨论如何对图形界面进行单元测试
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="MVVM" scheme="http://campusappcn.github.io/tags/MVVM/"/>
<category term="GUI" scheme="http://campusappcn.github.io/tags/GUI/"/>
<category term="测试" scheme="http://campusappcn.github.io/tags/%E6%B5%8B%E8%AF%95/"/>
<category term="单元测试" scheme="http://campusappcn.github.io/tags/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/"/>
<category term="图形界面" scheme="http://campusappcn.github.io/tags/%E5%9B%BE%E5%BD%A2%E7%95%8C%E9%9D%A2/"/>
</entry>
<entry>
<title>Android单元测试详解(1)——— AndroidJunitTest</title>
<link href="http://campusappcn.github.io/2016/03/06/2016-03-06-Android%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%AF%A6%E8%A7%A31(AndroidJunitTest)/"/>
<id>http://campusappcn.github.io/2016/03/06/2016-03-06-Android单元测试详解1(AndroidJunitTest)/</id>
<published>2016-03-05T16:00:00.000Z</published>
<updated>2016-03-08T07:17:13.000Z</updated>
<content type="html"><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>最近稍等空闲,考虑到我们项目测试覆盖率几乎等于0,因此痛下决心决定研究下Android单元测试。</p>
<h1 id="单元测试的类型"><a href="#单元测试的类型" class="headerlink" title="单元测试的类型"></a>单元测试的类型</h1><p>首先我们需要明确安卓的单元测试主要分为两种类型:</p>
<ul>
<li>在开发主机Java虚拟机上运行的Junit Test</li>
<li>在Android真机或者虚拟机上运行的Instrumented unit tests</li>
</ul>
<p>因为Junit Test免去了将apk包安装到android设备上的步骤,因此速度上会比Instrumented unit tests快很多。但是跟android相关的一些库只能运行在android设备上,而无法再本机Java虚拟机上运行,比如很多包名中带android的库。因此我们这里需要做一个选择,如果某些类没有用到android相关的库,则完全可以使用Junit Test,加快测试速率。</p>
<p>如果是在Android Studio上开发,则文件结构应该是这样的,<br><img src="https://img.alicdn.com/imgextra/i1/754328530/TB2VoYRlXXXXXc7XXXXXXXXXXXX-754328530.png" alt="单元测试文件结构"><br>其中androidTest包中存放的是Instrumented unit tests的文件,而test中则是Junit Test,这两个文件夹在项目创建的时候AS都会帮我们创建好。可能细心的同学已经发现了,test文件夹中的文件都不能运行。<br>如图,我们需要将Test Artiface改为Unit Test<br><img src="https://img.alicdn.com/imgextra/i1/754328530/TB2JdHIlXXXXXarXpXXXXXXXXXX-754328530.png" alt="选择测试类型"><br>另外这两种测试都只有在debugable = true的编译类型的时候才能运行。<br>了解了单元测试的两个基本类型,接下来让我们来了解下目前主流的单元测试框架或者说工具。</p>
<h1 id="AndroidJunit"><a href="#AndroidJunit" class="headerlink" title="AndroidJunit"></a>AndroidJunit</h1><p>Junit是Java开发中应用最多的单元测试框架,目前已经到Junit4。在Android的单元测试中我们同样可以使用它,如果是Junit Test,可以直接使用Java的Junit4。而如果测试的类必须使用Instrument Test,则我们需要使用AndroidJunitTest<br>首先在build.gradle文件中的defaultConfig中加入如下设置,设置使用的Instrument test的runner 为AndroidJunitRunner。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">testInstrumentationRunner &quot;android.support.test.runner.AndroidJUnitRunner&quot;</span><br></pre></td></tr></table></figure>
<p>然后在androidTest的java文件夹中创建测试文件</p>
<figure class="highlight java"><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"><span class="meta">@RunWith</span>(AndroidJUnit4.class)</span><br><span class="line"><span class="meta">@LargeTest</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivityExprssoTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Rule</span></span><br><span class="line"> <span class="keyword">public</span> ActivityTestRule&lt;MainActivity&gt; mActivityRule = <span class="keyword">new</span> ActivityTestRule&lt;MainActivity&gt;(MainActivity.class);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Before</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line"> Log.d(<span class="string">"MainActivity test"</span>, <span class="string">"init test MainActivity"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testOnBtnClick</span><span class="params">()</span></span>&#123;</span><br><span class="line"> onView(withId(R.id.text_btn1))</span><br><span class="line"> .perform(click()) ;</span><br><span class="line"> Assert.assertTrue(mActivityRule.getActivity().getKey());</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@After</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">destroy</span><span class="params">()</span></span>&#123;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们可以在@Before 中做一些初始化操作,在@After中做一些单元测试结束前需要做的一些工作。而带@Test的方法则是具体的测试方法。AndroidJunitRunner会逐个运行这些方法。关于其他的一些anntotation其实跟Junit中是一样的,具体可以参考<a href="http://developer.android.com/intl/zh-cn/tools/testing-support-library/index.html#AndroidJUnitRunner" target="_blank" rel="external">Android官方文档</a>。</p>
<p>另外以前做过Android Activity单元测试的同学可能会发现上面的代码跟你写的有些不一样。Corrcet,之前的单元测试类如果是对某个Activity做测试需要继承自ActivityInstrumentationTestCase2<calculatoractivity>,传入需要测试的Activity类型作为模板测试。目前该类型已被deprectd,而使用一个ActivityTestRule,作为模板参数的Activity会在@Before方法之前被launch,在@After之后被停止运行,在这之间我们可以去调用它的一些方法。</calculatoractivity></p>
<p>如果你不需要对Activity做测试,但是需要访问一些图片等resource资源,则需要让你的测试类继承自AndroidTestCase,这样你就可以通过getContext()获取应用的上下文了。</p>
<p>ok,本期对AndroidJunitTest的介绍就到这里。下一期我们将会介绍界面测试框架Expresso,敬请期待。</p>
</content>
<summary type="html">
本篇博客主要分析目前主流的Android测试框架AndroidJunitTest
</summary>
<category term="blog" scheme="http://campusappcn.github.io/categories/blog/"/>
<category term="Development" scheme="http://campusappcn.github.io/tags/Development/"/>
<category term="Android" scheme="http://campusappcn.github.io/tags/Android/"/>
</entry>
<entry>
<title>iOS中一个可以获得参数个数的宏</title>
<link href="http://campusappcn.github.io/2016/03/03/iOS%E4%B8%AD%E4%B8%80%E4%B8%AA%E5%8F%AF%E4%BB%A5%E8%8E%B7%E5%BE%97%E5%8F%82%E6%95%B0%E4%B8%AA%E6%95%B0%E7%9A%84%E5%AE%8F/"/>
<id>http://campusappcn.github.io/2016/03/03/iOS中一个可以获得参数个数的宏/</id>
<published>2016-03-03T08:03:23.000Z</published>
<updated>2016-03-20T09:06:36.000Z</updated>
<content type="html"><h2 id="metamacro-argcount-…"><a href="#metamacro-argcount-…" class="headerlink" title="metamacro_argcount(…)"></a><a href="https://github.com/ReactiveCocoa/ReactiveCocoa/blob/e16f47cf9cb568136ebd81430b24af274c3c27c7/ReactiveCocoa/Objective-C/extobjc/metamacros.h#L45" target="_blank" rel="external">metamacro_argcount(…)</a></h2><p>metamacro_argcount一个可以获得传入参数个数的Macro,<br><figure class="highlight objc"><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">metamacro_argcount(<span class="built_in">NSObject</span>, version); 结果为<span class="number">2.</span></span><br><span class="line">metamacro_argcount(<span class="built_in">NSObject</span>, version, <span class="keyword">super</span>); 结果为<span class="number">3</span></span><br><span class="line">metamacro_argcount(<span class="built_in">NSObject</span>, version, <span class="literal">nil</span>); 结果为<span class="number">3</span></span><br></pre></td></tr></table></figure></p>
<p>这个宏在编译期(并非运行期)就获得参数个数,是不是很神奇?来看看里面有什么鬼<br><figure class="highlight c"><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 class="comment">//RACmetamacros.h中</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_argcount(...) \</span><br><span class="line"> metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)</span></span><br></pre></td></tr></table></figure></p>
<p>需要展开下,以下就<code>metamacro_argcount(NSObject, version)</code>做展开<br><figure class="highlight c"><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">metamacro_argcount(NSObject, version)</span><br><span class="line">--&gt;</span><br><span class="line">metamacro_at(<span class="number">20</span>, NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>)</span><br></pre></td></tr></table></figure></p>
<a id="more"></a>
<p><code>metamacro_at(N, ...)</code>定义:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_at(N, ...) \</span><br><span class="line"> metamacro_concat(metamacro_at, N)(__VA_ARGS__)</span></span><br></pre></td></tr></table></figure>
<p>继续展开<br><figure class="highlight c"><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">metamacro_at(<span class="number">20</span>, NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>)</span><br><span class="line">--&gt;</span><br><span class="line"> metamacro_concat(metamacro_at,<span class="number">20</span>)(NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>);</span><br></pre></td></tr></table></figure></p>
<p><code>metamacro_concat</code>定义:<br><figure class="highlight c"><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 class="meta">#<span class="meta-keyword">define</span> metamacro_concat(A, B) \</span><br><span class="line"> metamacro_concat_(A, B)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_concat_(A, B) A ## B</span></span><br></pre></td></tr></table></figure></p>
<p>宏定义中<code>A ## B</code>,<a href="https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html#Concatenation" target="_blank" rel="external"><code>##</code></a>对A,B分割,再将A,B代入拼接,<br>所以上文中`metamacro_concat(metamacro_at,20)就变成了metamacro_at20<br><figure class="highlight c"><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">metamacro_concat(metamacro_at,<span class="number">20</span>)(NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>);</span><br><span class="line">--&gt;</span><br><span class="line">metamacro_at20(NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>);</span><br></pre></td></tr></table></figure></p>
<p>metamacro_at20又是一个宏定义<br><code>metamacro_at##Nth</code>定义:<br><figure class="highlight c"><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 class="meta">#<span class="meta-keyword">define</span> metamacro_at0...</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_at19...</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)</span></span><br></pre></td></tr></table></figure></p>
<p>继续转化 = =…<br><figure class="highlight c"><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">NSObject对应_0</span><br><span class="line">version对应_1</span><br><span class="line"><span class="number">20</span>对应_2</span><br><span class="line">...</span><br><span class="line"><span class="number">3</span>对应_19</span><br><span class="line"></span><br><span class="line">还剩(<span class="number">2</span>,<span class="number">1</span>)就是metamacro_head(__VA_ARGS__)的参数</span><br><span class="line">所以</span><br><span class="line"></span><br><span class="line">metamacro_at20(NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>);</span><br><span class="line">--&gt;</span><br><span class="line">metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(<span class="number">2</span>,<span class="number">1</span>)</span><br></pre></td></tr></table></figure></p>
<p><code>metamacro_head</code>定义:<br><figure class="highlight c"><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 class="meta">#<span class="meta-keyword">define</span> metamacro_head(...) \</span><br><span class="line"> metamacro_head_(__VA_ARGS__, 0)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_head_(FIRST, ...) FIRST</span></span><br></pre></td></tr></table></figure></p>
<p>再转化<br><figure class="highlight c"><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">metamacro_head(<span class="number">2</span>,<span class="number">1</span>)</span><br><span class="line">--&gt;</span><br><span class="line">metamacro_head_(<span class="number">2</span>,<span class="number">1</span>,<span class="number">0</span>) <span class="number">2</span></span><br><span class="line">FIRST==<span class="number">2</span></span><br></pre></td></tr></table></figure></p>
<p>经过各种展开<br><figure class="highlight c"><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">metamacro_argcount(NSObject, version)</span><br><span class="line">--&gt;</span><br><span class="line">metamacro_head(<span class="number">2</span>,<span class="number">1</span>)</span><br><span class="line">--&gt;</span><br><span class="line"><span class="number">2</span></span><br></pre></td></tr></table></figure></p>
<p>硬是获得了参数个数2……</p>
<p>大家都很熟悉数组,用数组来类比下metamacro_argcount的功能<br><figure class="highlight objc"><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 class="built_in">NSArray</span> *array20=@[@<span class="number">20</span>, @<span class="number">19</span>, @<span class="number">18</span>, @<span class="number">17</span>, @<span class="number">16</span>, @<span class="number">15</span>, @<span class="number">14</span>,@<span class="number">13</span>, @<span class="number">12</span>, @<span class="number">11</span>, @<span class="number">10</span>, @<span class="number">9</span>, @<span class="number">8</span>, @<span class="number">7</span>, @<span class="number">6</span>, @<span class="number">5</span>, @<span class="number">4</span>, @<span class="number">3</span>, @<span class="number">2</span>, @<span class="number">1</span>,@<span class="number">0</span>];</span><br><span class="line">array20就取第<span class="number">20</span>个</span><br><span class="line">array20[<span class="number">20</span>]=<span class="number">0</span>默认没有参数</span><br></pre></td></tr></table></figure></p>
<p>当我有参数的时候,我就插入到数组前面,取值就只取第20个元素<br>插入NSObjcet<br><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSArray</span> *array20=@[<span class="built_in">NSObjcet</span>,@<span class="number">20</span>, @<span class="number">19</span>, @<span class="number">18</span>, @<span class="number">17</span>, @<span class="number">16</span>, @<span class="number">15</span>, @<span class="number">14</span>,@<span class="number">13</span>, @<span class="number">12</span>, @<span class="number">11</span>, @<span class="number">10</span>, @<span class="number">9</span>, @<span class="number">8</span>, @<span class="number">7</span>, @<span class="number">6</span>, @<span class="number">5</span>, @<span class="number">4</span>, @<span class="number">3</span>, @<span class="number">2</span>, @<span class="number">1</span>,@<span class="number">0</span>];</span><br><span class="line">array20[<span class="number">20</span>]的值就变成了@<span class="number">1</span>,就是<span class="number">1</span>个参数</span><br></pre></td></tr></table></figure></p>
<p>再插入version<br><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSArray</span> *array20=@[<span class="built_in">NSObjcet</span>,version,@<span class="number">20</span>, @<span class="number">19</span>, @<span class="number">18</span>, @<span class="number">17</span>, @<span class="number">16</span>, @<span class="number">15</span>, @<span class="number">14</span>,@<span class="number">13</span>, @<span class="number">12</span>, @<span class="number">11</span>, @<span class="number">10</span>, @<span class="number">9</span>, @<span class="number">8</span>, @<span class="number">7</span>, @<span class="number">6</span>, @<span class="number">5</span>, @<span class="number">4</span>, @<span class="number">3</span>, @<span class="number">2</span>, @<span class="number">1</span>,@<span class="number">0</span>];</span><br><span class="line">array20[<span class="number">20</span>]的值就变成了@<span class="number">2</span>,就是<span class="number">2</span>个参数</span><br></pre></td></tr></table></figure></p>
</content>
<summary type="html">
<h2 id="metamacro-argcount-…"><a href="#metamacro-argcount-…" class="headerlink" title="metamacro_argcount(…)"></a><a href="https://github.com/ReactiveCocoa/ReactiveCocoa/blob/e16f47cf9cb568136ebd81430b24af274c3c27c7/ReactiveCocoa/Objective-C/extobjc/metamacros.h#L45">metamacro_argcount(…)</a></h2><p>metamacro_argcount一个可以获得传入参数个数的Macro,<br><figure class="highlight objc"><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">metamacro_argcount(<span class="built_in">NSObject</span>, version); 结果为<span class="number">2.</span></span><br><span class="line">metamacro_argcount(<span class="built_in">NSObject</span>, version, <span class="keyword">super</span>); 结果为<span class="number">3</span></span><br><span class="line">metamacro_argcount(<span class="built_in">NSObject</span>, version, <span class="literal">nil</span>); 结果为<span class="number">3</span></span><br></pre></td></tr></table></figure></p>
<p>这个宏在编译期(并非运行期)就获得参数个数,是不是很神奇?来看看里面有什么鬼<br><figure class="highlight c"><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 class="comment">//RACmetamacros.h中</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> metamacro_argcount(...) \</span><br><span class="line"> metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)</span></span><br></pre></td></tr></table></figure></p>
<p>需要展开下,以下就<code>metamacro_argcount(NSObject, version)</code>做展开<br><figure class="highlight c"><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">metamacro_argcount(NSObject, version)</span><br><span class="line">--&gt;</span><br><span class="line">metamacro_at(<span class="number">20</span>, NSObject, version, <span class="number">20</span>, <span class="number">19</span>, <span class="number">18</span>, <span class="number">17</span>, <span class="number">16</span>, <span class="number">15</span>, <span class="number">14</span>, <span class="number">13</span>, <span class="number">12</span>, <span class="number">11</span>, <span class="number">10</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>)</span><br></pre></td></tr></table></figure></p>
</summary>
<category term="iOS" scheme="http://campusappcn.github.io/tags/iOS/"/>
<category term="ReactiveCocoa" scheme="http://campusappcn.github.io/tags/ReactiveCocoa/"/>
<category term="Macros" scheme="http://campusappcn.github.io/tags/Macros/"/>
</entry>
<entry>
<title>ReactiveCocoa2-5修炼 (三)</title>
<link href="http://campusappcn.github.io/2016/03/02/ReactiveCocoa2-5%E4%BF%AE%E7%82%BC-%E4%B8%89/"/>
<id>http://campusappcn.github.io/2016/03/02/ReactiveCocoa2-5修炼-三/</id>
<published>2016-03-02T02:37:18.000Z</published>
<updated>2016-04-09T08:59:46.000Z</updated>
<content type="html"><p>上一篇做了流转化源码的阅读,再看看流还有哪些用的到的操作,并阅读下源码,<br>1.concat<br>2.then<br>3.merge<br>4.flatten<br><a id="more"></a></p>
<h2 id="concat"><a href="#concat" class="headerlink" title="concat:"></a>concat:</h2><p>写了简单的例子测试下:<br><figure class="highlight objc"><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">RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> [subscriber sendNext:<span class="string">@"ABC"</span>];</span><br><span class="line"> <span class="comment">//[subscriber sendCompleted];</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">&#125;];</span><br><span class="line">RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> [subscriber sendNext:<span class="string">@"CBA"</span>];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">&#125;];</span><br><span class="line">[[signalA concat:signalB] subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"x--%@"</span>,x);</span><br><span class="line">&#125;];</span><br><span class="line"><span class="comment">//输出:2016-03-02 11:15:43.704 RACFunTest[2366:155132] x--ABC</span></span><br></pre></td></tr></table></figure></p>
<p>只输出了ABC</p>
<h3 id="看看concat的源码"><a href="#看看concat的源码" class="headerlink" title="看看concat的源码"></a>看看concat的源码</h3><figure class="highlight objc"><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">- (RACSignal *)concat:(RACSignal *)signal &#123;</span><br><span class="line"><span class="keyword">return</span> [[RACSignal createSignal:^(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];</span><br><span class="line"> RACDisposable *sourceDisposable = [<span class="keyword">self</span> subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> [subscriber sendNext:x];</span><br><span class="line"> &#125; error:^(<span class="built_in">NSError</span> *error) &#123;</span><br><span class="line"> [subscriber sendError:error];</span><br><span class="line"> &#125; completed:^&#123;</span><br><span class="line"> RACDisposable *concattedDisposable = [signal subscribe:subscriber];</span><br><span class="line"> serialDisposable.disposable = concattedDisposable;</span><br><span class="line"> &#125;];</span><br><span class="line"> serialDisposable.disposable = sourceDisposable;</span><br><span class="line"> <span class="keyword">return</span> serialDisposable;</span><br><span class="line">&#125;] setNameWithFormat:<span class="string">@"[%@] -concat: %@"</span>, <span class="keyword">self</span>.name, signal];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到内部创建了一个新的RACSignal,继续看代码<code>[self subscribeNext:^(id x)</code>,因为是<code>[signalA concat:signalB]</code>,所以这里的self就是signalA,<code>[subscriber sendNext:x];</code>,符合输出的结果,在<code>completed</code>的block中调用了<code>[signal subscribe:subscriber]</code>,原来signalA发了<code>sendCompleted</code>signalB才会被<code>subscribe:</code><br><figure class="highlight objc"><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 class="comment">//打开被注释掉的代码</span></span><br><span class="line">[subscriber sendCompleted];</span><br><span class="line"><span class="comment">//2016-03-02 11:36:38.561 RACFunTest[2422:163940] x--ABC</span></span><br><span class="line"><span class="comment">//2016-03-02 11:36:38.562 RACFunTest[2422:163940] x--CBA</span></span><br></pre></td></tr></table></figure></p>
<h3 id="concat能干嘛"><a href="#concat能干嘛" class="headerlink" title="concat能干嘛?"></a>concat能干嘛?</h3><p>让信号能按顺序来给订阅者发消息.<br>A concat B ,A发完了B再发<br>B concat A ,B发完了A再发<br>另外结合源码的理解,可以知道concat之后可以继续concat,A concat B concat C<br>只要B能够发送sendCompleted那么C发送的值就能被订阅者收到.</p>
<h2 id="then"><a href="#then" class="headerlink" title="then"></a>then</h2><p>继续写个栗子:<br><figure class="highlight objc"><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">[[signalA then:^RACSignal *&#123;</span><br><span class="line"> <span class="keyword">return</span> signalB;</span><br><span class="line">&#125;] subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"x--%@"</span>,x);</span><br><span class="line">&#125;];</span><br><span class="line">输出:<span class="string">@"CBA"</span></span><br></pre></td></tr></table></figure></p>
<h3 id="看看then的源码"><a href="#看看then的源码" class="headerlink" title="看看then的源码"></a>看看then的源码</h3><figure class="highlight objc"><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">- (RACSignal *)then:(RACSignal * (^)(<span class="keyword">void</span>))block &#123;</span><br><span class="line"> <span class="built_in">NSCParameterAssert</span>(block != <span class="literal">nil</span>);</span><br><span class="line"> <span class="keyword">return</span> [[[<span class="keyword">self</span></span><br><span class="line"> ignoreValues]</span><br><span class="line"> concat:[RACSignal defer:block]]</span><br><span class="line"> setNameWithFormat:<span class="string">@"[%@] -then:"</span>, <span class="keyword">self</span>.name];</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//[self ignoreValues]</span></span><br><span class="line">- (RACSignal *)ignoreValues &#123;</span><br><span class="line"> <span class="keyword">return</span> [[<span class="keyword">self</span> filter:^(<span class="keyword">id</span> _) &#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NO</span>;</span><br><span class="line"> &#125;] setNameWithFormat:<span class="string">@"[%@] -ignoreValues"</span>, <span class="keyword">self</span>.name];</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//[RACSignal defer:block]</span></span><br><span class="line">+ (RACSignal *)defer:(RACSignal * (^)(<span class="keyword">void</span>))block &#123;</span><br><span class="line"> <span class="built_in">NSCParameterAssert</span>(block != <span class="literal">NULL</span>);</span><br><span class="line"> <span class="keyword">return</span> [[RACSignal createSignal:^(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> <span class="keyword">return</span> [block() subscribe:subscriber];</span><br><span class="line"> &#125;] setNameWithFormat:<span class="string">@"+defer:"</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>第一步方法的调用,使SignalA的值全过滤掉了,然后concat了signalB,所以跟concat的不同是,只会输出signalB的值,不会输出signalA,而且从concat源码中了解到,必须在signalA发送了sendCompleted,signalB才会起效,所以then操作也是这样,只有前一个结束了,后一个才有效</p>
<h2 id="merge"><a href="#merge" class="headerlink" title="merge"></a>merge</h2><p>简单栗子:<br><figure class="highlight objc"><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">RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> [subscriber sendNext:<span class="string">@"A"</span>];</span><br><span class="line"> dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<span class="number">0.01</span> * <span class="built_in">NSEC_PER_SEC</span>)), dispatch_get_main_queue(), ^&#123;</span><br><span class="line"> [subscriber sendNext:<span class="string">@"A2"</span>];</span><br><span class="line"> &#125;);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">&#125;];</span><br><span class="line">RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> [subscriber sendNext:<span class="string">@"B"</span>];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">&#125;];</span><br><span class="line"></span><br><span class="line">[[signalA merge:signalB] subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"x--%@"</span>,x);</span><br><span class="line">&#125;];</span><br><span class="line">输出:</span><br><span class="line"><span class="number">2016</span><span class="number">-03</span><span class="number">-02</span> <span class="number">14</span>:<span class="number">29</span>:<span class="number">57.832</span> RA<span class="built_in">CFunTest</span>[<span class="number">2935</span>:<span class="number">218636</span>] x--A</span><br><span class="line"><span class="number">2016</span><span class="number">-03</span><span class="number">-02</span> <span class="number">14</span>:<span class="number">29</span>:<span class="number">57.833</span> RA<span class="built_in">CFunTest</span>[<span class="number">2935</span>:<span class="number">218636</span>] x--B</span><br><span class="line"><span class="number">2016</span><span class="number">-03</span><span class="number">-02</span> <span class="number">14</span>:<span class="number">29</span>:<span class="number">57.860</span> RA<span class="built_in">CFunTest</span>[<span class="number">2935</span>:<span class="number">218636</span>] x--A2</span><br></pre></td></tr></table></figure></p>
<h3 id="看看merge源码"><a href="#看看merge源码" class="headerlink" title="看看merge源码"></a>看看merge源码</h3><figure class="highlight objc"><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"><span class="comment">//在RACSignal+Operations中</span></span><br><span class="line">- (RACSignal *)merge:(RACSignal *)signal &#123;</span><br><span class="line"> <span class="keyword">return</span> [[RACSignal</span><br><span class="line"> merge:@[ <span class="keyword">self</span>, signal ]]</span><br><span class="line"> setNameWithFormat:<span class="string">@"[%@] -merge: %@"</span>, <span class="keyword">self</span>.name, signal];</span><br><span class="line">&#125;</span><br><span class="line">+ (RACSignal *)merge:(<span class="keyword">id</span>&lt;<span class="built_in">NSFastEnumeration</span>&gt;)signals &#123;</span><br><span class="line"> <span class="built_in">NSMutableArray</span> *copiedSignals = [[<span class="built_in">NSMutableArray</span> alloc] init];</span><br><span class="line"> <span class="keyword">for</span> (RACSignal *signal <span class="keyword">in</span> signals) &#123;</span><br><span class="line"> [copiedSignals addObject:signal];</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> [[[RACSignal</span><br><span class="line"> createSignal:^ RACDisposable * (<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> <span class="keyword">for</span> (RACSignal *signal <span class="keyword">in</span> copiedSignals) &#123;</span><br><span class="line"> [subscriber sendNext:signal];</span><br><span class="line"> &#125;</span><br><span class="line"> [subscriber sendCompleted];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"> &#125;]</span><br><span class="line"> flatten]</span><br><span class="line"> setNameWithFormat:<span class="string">@"+merge: %@"</span>, copiedSignals];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>merge也产生了新的流,当这个流被订阅的时候,他就遍历自己的copiedSignals数组,将signal发送,将signal当做数据发送,那么我们在订阅的时候,<code>id x</code>就是一个信号,还需要对x进行订阅才能触发didSubscribe?这里还涉及到另一个操作,<code>flatten</code>,</p>
<h2 id="flatten"><a href="#flatten" class="headerlink" title="flatten"></a>flatten</h2><figure class="highlight objc"><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 class="comment">//在RACStream.m中</span></span><br><span class="line">- (instancetype)flatten &#123;</span><br><span class="line"> __<span class="keyword">weak</span> RACStream *stream __attribute__((unused)) = <span class="keyword">self</span>;</span><br><span class="line"> <span class="keyword">return</span> [[<span class="keyword">self</span> flattenMap:^(<span class="keyword">id</span> value) &#123;</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> &#125;] setNameWithFormat:<span class="string">@"[%@] -flatten"</span>, <span class="keyword">self</span>.name];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里的flattenMap逻辑可以按照修炼(二)中的过程走一遍,</p>
<p>从表面上看,flatten作用就是:如果一个SignalB的sendNext:发送了一个signalA,可以将这个signalB做flatten操作后,订阅者看起来就是订阅的signalA.</p>
<p>根据修炼二中的逻辑过一遍的时候,<br><figure class="highlight objc"><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 class="keyword">id</span> stream = block(value) ?: [class empty];</span><br><span class="line"><span class="built_in">NSCAssert</span>([stream isKindOfClass:RACStream.class], <span class="string">@"Value returned from -flattenMap: is not a stream: %@"</span>, stream);</span><br><span class="line"><span class="keyword">return</span> stream;</span><br></pre></td></tr></table></figure></p>
<p>回调到这一段代码时,block(value)在flatten方法中就是<code>return value;</code>,即signalA<br>再回到<code>bind:</code>方法内,执行<code>addSignal(signal)</code>….</p>
<p>不得不感叹,RAC设计者的逻辑真是强大</p>
<p>搞明白了flatten,那么merge中<code>[subscriber sendNext:signal];</code>也就明白了,外部订阅者拿到的就是signal发送的</p>
<h3 id="flatten的作用-merge的作用"><a href="#flatten的作用-merge的作用" class="headerlink" title="flatten的作用,merge的作用"></a>flatten的作用,merge的作用</h3><p><em>flatten的作用:</em>SignalY发送signalX,flatten SignalY之后产生signalZ,<br>订阅signalZ的作用跟订阅signalX的作用一样,还是写个栗子吧</p>
<figure class="highlight objc"><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">RACSignal *signalX = [RACSignal createSignal:^RACDisposable *(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> [subscriber sendNext:<span class="string">@"A"</span>];</span><br><span class="line"> [subscriber sendNext:<span class="string">@"B"</span>];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">&#125;];</span><br><span class="line">RACSignal *signalY = [RACSignal createSignal:^RACDisposable *(<span class="keyword">id</span>&lt;RACSubscriber&gt; subscriber) &#123;</span><br><span class="line"> [subscriber sendNext:signalX];</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line">&#125;];</span><br><span class="line"></span><br><span class="line"><span class="comment">//*不用flatten:*</span></span><br><span class="line">[signalY subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> RACSignal *signal=x;</span><br><span class="line"> [signal subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"x--%@"</span>,x);</span><br><span class="line"> &#125;];</span><br><span class="line">&#125;];</span><br><span class="line"><span class="comment">//*用flatten*</span></span><br><span class="line">[[signalY flatten] subscribeNext:^(<span class="keyword">id</span> x) &#123;</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"x--%@"</span>,x);</span><br><span class="line">&#125;];</span><br><span class="line"></span><br><span class="line">输出:A B</span><br></pre></td></tr></table></figure>
<p><em>merge的作用</em>将2个signal合并成一个,任一一个发出信息,订阅者都能接受,</p>
<h2 id="总结下"><a href="#总结下" class="headerlink" title="总结下"></a>总结下</h2><p>操作:signalA 操作符 signalB<br>1.concat : 会先输出signalA的值,signalA发送了sendCompleted,signalB才会起效,接着输出signalB<br>2.then : 不会输出signalA的值,但需要等到signalA执行完了自己的sendNext之后sendCompleted,signalB才会起效,只输出signalB<br>3.merge : signalA和signalB,按sendNext的执行顺序依次输出<br>操作:[signalA sendNext:signalB]<br>4.flatten : 就相当于,解包,订阅flatten之后的Signal跟订阅signalB一样.</p>
<h2 id="写了3篇的源码阅读的回顾-谈谈收获了啥"><a href="#写了3篇的源码阅读的回顾-谈谈收获了啥" class="headerlink" title="写了3篇的源码阅读的回顾,谈谈收获了啥."></a>写了3篇的源码阅读的回顾,谈谈收获了啥.</h2><p>1.对RACSignal相关的一系列类的父子类关系有了一点了解,RACEmptySignal,来干嘛的呀,<br>RACReturnSignal是干嘛的呀,这些等等.不然看别人写的RAC看的一脸懵逼<br>2.之前没想到过block来返回block,(别笑…),等于就是传递IMP,可以来统一接口<br>3.再看RACSignal的各种方法(<code>ignore:</code>,<code>startWith:</code>,…)轻松多了,看下源码,再结合源码文档的注释基本知道在干嘛.</p>
</content>
<summary type="html">
<p>上一篇做了流转化源码的阅读,再看看流还有哪些用的到的操作,并阅读下源码,<br>1.concat<br>2.then<br>3.merge<br>4.flatten<br>
</summary>
<category term="iOS" scheme="http://campusappcn.github.io/tags/iOS/"/>
<category term="ReactiveCocoa" scheme="http://campusappcn.github.io/tags/ReactiveCocoa/"/>
</entry>
</feed>