-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1468 lines (943 loc) · 68.4 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[GeforceLee's Blog]]></title>
<link href="http://GeforceLee.github.io/atom.xml" rel="self"/>
<link href="http://GeforceLee.github.io/"/>
<updated>2013-12-31T15:20:10+08:00</updated>
<id>http://GeforceLee.github.io/</id>
<author>
<name><![CDATA[GeforceLee]]></name>
<email><![CDATA[[email protected]]]></email>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[2013年工作总结]]></title>
<link href="http://GeforceLee.github.io/blog/2013/12/31/2013nian-gong-zuo-zong-jie/"/>
<updated>2013-12-31T10:06:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/12/31/2013nian-gong-zuo-zong-jie</id>
<content type="html"><![CDATA[<p>2013年是我毕业的第二年,也是第一次创业的第一年.对我来说是意义重大的一年.</p>
<!-- more -->
<h2>艰辛</h2>
<p>在这一年中,有很多步艰辛的旅程.</p>
<h3>招人</h3>
<p>首先要说的就是招人难.</p>
<ul>
<li>在公司筹划中,必不可少的人员(程序,美术,策划),我们每个职位都有一个人员.计划没有变化快,在实施过程中,美术离开了.</li>
<li>公司开始的时候只有程序和策划.我找来了@李楠,这位一直跟我有着密切联系的哥们,在我们急需要人的时候离开大公司加入我们.</li>
<li>@束永兴 也是第一批加入团队中的.一直负责iOS客户端的开发.勤勤恳恳,工作认真负责.</li>
<li>@张雷 的加入保证了我们的基础运维,经常工作到天亮.</li>
<li>@董然 美术的加入,是我们结束了找”带图”做游戏的时代</li>
</ul>
<p>以上就是公司第一批加入的员工.
在公司发展的同时,我们也在花重金招人.创业公司招人真的很困难,大部分来的都是新手(需要手把手带的),但是在这种人员紧缺的情况下是没有过多的时间来实施这个工作的.期间夹杂着”成手”,经过面试+机试后也是原形毕露.真正的牛人不会光靠画饼和一个未上线的项目敢轻易的尝试这创业.</p>
<ul>
<li>@马士华 这位有着10多年编程经验的老马加入,是我们在技术和经验上大大得到了提升.保证了后台的稳定运行.</li>
<li>@杨强 北邮的才子来公司实习.参与了项目1.0的开发与发布.</li>
<li>@程雨婧 同学加入,大大改善了工作流程上的缺陷.落实了策划文档先行这一个1.0版本中忽略的问题.</li>
<li>@苏月 美女的加入,让公司一些男同学们眼前一亮.</li>
<li>@张磊 我的大学同学,加入iOS客户端,助力2.0版本的发布.</li>
<li>@王丽丽 第二位美术.</li>
</ul>
<h3>技术</h3>
<p>第一批成员在之前都是做客户端,或者是BS的.第一次接触到服务端,通信用的是socket加上服务端要用node.一时间难以上手.我也从客户端转到服务端.</p>
<h3>服务端技术过程</h3>
<ol>
<li>语言js,数据存储MySQL</li>
<li>数据存储换成mongo+redis</li>
<li>语言换成coffee</li>
</ol>
<h3>客户端</h3>
<ol>
<li>开始时为完成工作的随意写</li>
<li>结构上的分割</li>
<li>基础控件的封装</li>
<li>网络访问的封装</li>
<li>动画”类引擎”的封装</li>
<li>操作记录
这些技术都是我们团队成员没有接触过的.经过这一年的洗礼.大家技术上提升都是很明显的.</li>
</ol>
<h3>2013不足</h3>
<p>在这2013里做了很多的尝试,想提高团队的工作效率,改善不是很明显.我认为主要原因:</p>
<ul>
<li>个人自觉性不是很高</li>
<li>一些特权行为</li>
<li>时间安排不够紧,导致经常加班.</li>
</ul>
<h3>展望2014</h3>
<ul>
<li>提高团队的协作能力</li>
<li>提高项目的稳定性</li>
<li>完善后台系统</li>
<li>开发数据挖掘系统</li>
<li>扩展团队,双项目并行开发</li>
</ul>
<h2>个人</h2>
<p>我自己在这一年里改变很大.尤其是在工作角色上.以前只是负责iOS客户端的开发.现在是哪里需要就转移到相应平台上.
平时也要负责整个项目团队.这个位置没有想象的那么简单,为人处世上要格外注意,话说重会伤及人员的自尊心,轻描淡写的说又起不到理想的作用.</p>
<h3>技术上get√</h3>
<ul>
<li>node基础</li>
<li>coffeescript语言</li>
<li>redis操作</li>
<li>Linux基础操作</li>
<li>iOS单元测试+UI自动化测试</li>
<li>树莓派刚开始研究</li>
</ul>
<h3>管理上get√</h3>
<ul>
<li>项目预估能力</li>
<li>开发,测试,发布流程掌控</li>
<li>工作协调</li>
<li>组织会议</li>
<li>项目总结</li>
</ul>
<p>这2013年过的很辛苦也很充实.希望一切都越来越好</p>
<p>2013-12-31 李云龙</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[iOS UI自动化测试]]></title>
<link href="http://GeforceLee.github.io/blog/2013/11/08/ios-uizi-dong-hua-ce-shi/"/>
<updated>2013-11-08T11:10:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/11/08/ios-uizi-dong-hua-ce-shi</id>
<content type="html"><![CDATA[<h3>前言</h3>
<p>经过近半年的创业经历,我们的团队在开发上发现了很多问题,也发现了很多重复的工作量,浪费了很多时间.所以我们开始了客户端测试的研究,这篇Blog是讲述UI自动化测试的简单记录.</p>
<p>不得不说Aplle在测试方面做了很多事情,Instruments这个工具非常强大.但之前我们也没有很好的利用,近期我们主要目标就是研究Instruments.</p>
<p>说了不少废话,现在来干货吧!</p>
<h3>第一个脚本</h3>
<p>先说一个Bug,当在Xcode直接用command+I命令直接启动Instruments,机器会很卡,然后执行起脚本也会出现各种问题.
所以就直接先把App按到对应的模拟器或真机上,然后再Instruments上选择target.这样运行起来会比较快(稍后会介绍更快的)
选择Automation,开始第一个脚本吧.</p>
<!--more -->
<p><img src="http://GeforceLee.github.io/images/UIAutomation/1.png"></p>
<p><img src="http://GeforceLee.github.io/images/UIAutomation/2.png"></p>
<p>这就是第一个脚本,里面可以用JS 写很多测试方法.</p>
<p><img src="http://GeforceLee.github.io/images/UIAutomation/3.png"></p>
<ul>
<li>点击Run的按钮就会执行脚本,</li>
<li>点击记录按钮就会自动的记录在app上的操作.</li>
</ul>
<p>这里有几个帮助的方法:</p>
<pre><code>var target = UIATarget.localTarget();
target.logElementTree();
</code></pre>
<p>这个方法在运行的时候会打印出当前屏幕上所有的元素,这样可以让我们写脚本更简单.</p>
<p><img src="http://GeforceLee.github.io/images/UIAutomation/4.png"></p>
<p>脚本该怎么写就不在文中提了,想看的话可以到<a href="https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html">脚本文档</a>来看</p>
<h4>写一个简单的例子</h4>
<p>一个TextFiled一个label的view.
在文本框中输入文字,然后点Return键,然后Label中在现实文本框中的文字.</p>
<p>在xib中可以设置这些属性
<img src="http://GeforceLee.github.io/images/UIAutomation/5.png"></p>
<pre><code>在xcode中的代码段:
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
NSString *str = textField.text;
self.lbl.text = str;
self.lbl.accessibilityValue = str;
[self.textFiled resignFirstResponder];
return YES;
}
在脚本中写的:
var testName = "Test 1";
var target = UIATarget.localTarget();
target.logElementTree();
var app = target.frontMostApp();
var window = app.mainWindow();
var str = "Text string";
window.textFields()[0].setValue(str);
app.keyboard().buttons()["Return"].tap();
target.logElementTree();
var textValue = window.staticTexts()["Label1"].value();
if (textValue === str){
UIALogger.logPass(testName);
}else{
UIALogger.logFail(testName);
}
</code></pre>
<p>运行一下.</p>
<h4>几个常用的库</h4>
<p>上面写不太友好,看的也不方便
可以用<a href="https://github.com/alexvollmer/tuneup_js">tuneup_js</a></p>
<p>为了更快,可以不启动Instruments下运行测试.用<a href="https://github.com/bendyworks/bwoken">bwoken</a></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[iOS单元测试]]></title>
<link href="http://GeforceLee.github.io/blog/2013/11/07/iosdan-yuan-ce-shi/"/>
<updated>2013-11-07T14:03:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/11/07/iosdan-yuan-ce-shi</id>
<content type="html"><![CDATA[<p>之前写了一遍关于iOS5的集成测试的Blog—<a href="http://blog.liyunlong.org/blog/2013/11/05/xcode-5-xia-de-dan-yuan-ce-shi/">Xcode 5 下的单元测试</a></p>
<p>在这篇Blog中我要介绍下Xcode5下新的单元测试框架<XCTest></p>
<p>在一个新建的项目中会自动的创建相对应的单元测试Target.同时在对应的一个文件夹下会有一个以Tests为结尾的文件.</p>
<p>我们看看这个文件的内容:</p>
<pre><code>#import <XCTest/XCTest.h>
@interface UnitTestTests : XCTestCase (1)
(2)
@end
@implementation UnitTestTests
- (void)setUp (3)
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown (4)
{
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testExample (5)
{
XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);(6)
}
</code></pre>
<!-- more -->
<p>下面解释下上面代码的含义</p>
<ul>
<li>(1)定义测试类的名字,继承自XCTestCase</li>
<li>(2)可以在interfce中定义变量</li>
<li>(3)setUp()这是在单元测试执行前运行的代码(可以简单的理解成init方法,只为了理解)在这可以初始化定义的变量,方便在测试方法调用</li>
<li>(4)tearDown()这是在单元测试结束后调用的方法(可以简单的理解成dealloc方法)</li>
<li>(5)testExample()这就是真正写单元测试的方法了.注意:一定是以”test”开始命名函数的方法,当执行测试的时候,这就是要测试的方法.</li>
<li>(6)这就是XCTest框架为我们提供的宏.这个红的意思就是测试失败.当command+U 的时候就会报错.</li>
</ul>
<h4>简单的例子:</h4>
<pre><code>- (NSNumber *)makeNumberWhitInt:(int)theInt{
return [NSNumber numberWithInt:theInt];
}
- (NSNumber *)plusANumber:(NSNumber *)a andBNumber:(NSNumber *)b{
return [NSNumber numberWithInt:[a intValue] + [b intValue]];
}
测试方法
- (void)testMakeNumberMethod{
NSNumber *one = [self makeNumberWhitInt:1];
XCTAssertNotNil(one, @"makeNumberWhitInt方法不能返回Null");
NSNumber *tow = [self makeNumberWhitInt:2];
XCTAssertNotNil(tow, @"makeNumberWhitInt方法不能返回Null");
NSNumber *three = [NSNumber numberWithInt:3];
NSNumber *result = [self plusANumber:one andBNumber:tow];
XCTAssertEqual(three, result, @"1+2=3");
}
</code></pre>
<p>还有很多的宏就不一一说明了.可以看看XCTest框架中看到.</p>
<h3>总结</h3>
<p>结合单元测试和新的集成测试我们能更好的保证我们程序的稳定性.
单元测试的方法很简单,但真正运用好却需要很多的经验,在这里我推荐一本<测试驱动的iOS开发>.这本书很好的介绍了测试驱动这个模式.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Xcode 5 下的单元测试]]></title>
<link href="http://GeforceLee.github.io/blog/2013/11/05/xcode-5-xia-de-dan-yuan-ce-shi/"/>
<updated>2013-11-05T15:37:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/11/05/xcode-5-xia-de-dan-yuan-ce-shi</id>
<content type="html"><![CDATA[<p>新版Xcode 5和Server发布以后,apple对单元测试的支持是越来越好了.从这一点看出apple对单元测试的也是越来越重视了.
这篇Blog就简单的介绍这集成化测试功能.</p>
<p>Server更新后是要重新付费的,但如果你有开发者账号的话,可以登录<a href="https://developer.apple.com">AppleDeveloper</a> 里面会有个兑换码.如果没有就需要单付费了…</p>
<h3>Server的变化</h3>
<p>里面会出现个新的选项—Xcode
<img src="http://GeforceLee.github.io/images/unittest/QQ20131105-12.png"></p>
<!-- more -->
<h3>从头开始</h3>
<p>创建项目</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-1.png"></p>
<p>创建远程测试服务</p>
<p>Product->Create Bot</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-2.png"></p>
<p>Configure Remotes</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-3.png"></p>
<p>Add</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-4.png"></p>
<p>Create New Remote</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-5.png"></p>
<p>Create</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-6.png"></p>
<p>Next</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-7.png"></p>
<p>这里面有很多选项
1. 选择执行条件,这里我们选择On Commit(只要Commit 就执行测试)</p>
<p>选择Next</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-8.png"></p>
<p>选择测试的设备</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-9.png"></p>
<p>这是成功或失败后发邮件的内容</p>
<p>选择Create Bot</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-10.png"></p>
<p>然后就可以上传对应代码.这里用的是Git</p>
<p>上传后Xcode中显示</p>
<p><img src="http://GeforceLee.github.io/images/unittest/QQ20131105-11.png"></p>
<p>以上就是Xcode5和新版Server自带的测试工具.</p>
<p>如果之前用Xcode 4的开发这可能会遇到不能执行测试的问题.
因为Xcode中使用了新的测试框架<XCTest>
解决办法是新加Target 把老的单元测试移到新Target中 然后用XCTest替换SenTesting</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[SublimeText编译c++11]]></title>
<link href="http://GeforceLee.github.io/blog/2013/10/22/sublimetextbian-yi-c-plus-plus-11/"/>
<updated>2013-10-22T22:38:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/10/22/sublimetextbian-yi-c-plus-plus-11</id>
<content type="html"><![CDATA[<p>在Tools下的Build System中选择new build system
输入</p>
<pre><code>{
"cmd": ["clang++", "-std=c++11", "${file}", "-o", "${file_path}/${file_base_name}"], // For GCC On Windows and Linux
//"cmd": ["CL", "/Fo${file_base_name}", "/O2", "${file}"], // For CL on Windows Only
"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
"working_dir": "${file_path}",
"selector": "source.c, source.c++",
"variants":
[
{
"name": "Run",
"cmd": ["bash", "-c", "clang++ -std=c++11 '${file}' -o '${file_path}/${file_base_name}' && '${file_path}/${file_base_name}'"] // Linux Only
//"cmd": ["CMD", "/U", "/C", "clang++ -std=c++11 ${file} -o ${file_base_name} && ${file_base_name}"] // For GCC On Windows Only
//"cmd": ["CMD", "/U", "/C", "CL /Fo${file_base_name} /O2 ${file} && ${file_base_name}"] // For CL On Windows Only
}
]
}
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[用node-apn发Apple Push]]></title>
<link href="http://GeforceLee.github.io/blog/2013/07/12/use-node-apn-send-apple-push/"/>
<updated>2013-07-12T18:23:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/07/12/use-node-apn-send-apple-push</id>
<content type="html"><![CDATA[<p>忙了一下午终于把这个Push弄完了</p>
<p>期间查了很多的资料都或多或少的遇到了些问题</p>
<p>废话不多说了</p>
<!-- more -->
<p>首先打开<code>要是串访问</code></p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_1.png"></p>
<p>选择<code>要是串访问</code>–><code>证书助理</code>–><code>从证书颁发机构请求证书</code>
<img src="http://GeforceLee.github.io/images/nodeapn/130713_2.png"></p>
<p>填写邮件和’常用名称’ 这个’常用名称’随便填
之后就一直下一步,直到完成 桌面上会出现一个<code>CertificateSigningRequest.certSigningRequest</code> 文件</p>
<p>完成后在<code>秘钥</code>中会有你刚才生成的秘钥</p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_3.png"></p>
<p>点击右键 选择 <code>导出</code></p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_4.png"></p>
<p>保存到桌面</p>
<p>然后就要登陆 <a href="https://developer.apple.com">https://developer.apple.com</a></p>
<p>选择<code>Certificates, Identifiers & Profiles</code></p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_5.png"></p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_6.png"></p>
<p>创建一个AppleId 记得把通知选项打开</p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_11.png"></p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_12.png"></p>
<p>选择在桌面<code>CertificateSigningRequest.certSigningRequest</code> 上传</p>
<p>生成后选择下载’aps_development.cer’到桌面</p>
<p>然后用新的AppleId 创建个证书
<img src="http://GeforceLee.github.io/images/nodeapn/130713_8.png">
<img src="http://GeforceLee.github.io/images/nodeapn/130713_9.png">
<img src="http://GeforceLee.github.io/images/nodeapn/130713_10.png"></p>
<p>接下来就是要在终端里输入命令了</p>
<pre><code>openssl x509 -in aps_development.cer -inform der -out cert.pem
openssl pkcs12 -in PushTest.p12 -out key.pem -nodes
</code></pre>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_13.png"></p>
<p>生成好的这2个文件放到对应服务端代码所在的目录里</p>
<p>在服务端
node-apn : <a href="https://github.com/argon/node-apn">https://github.com/argon/node-apn</a></p>
<p>代码很简单:</p>
<pre><code>var apns = require('apn');
var options = {
cert: './cert.pem', /* Certificate file path */
key: './key.pem', /* Key file path */
gateway: 'gateway.sandbox.push.apple.com',/* gateway address */
port: 2195, /* gateway port */
errorCallback: errorHappened , /* Callback when error occurs function(err,notification) */
};
function errorHappened(err, notification){
console.log("err " + err);
}
var apnsConnection = new apns.Connection(options);
var token = "30df54fd26XXXXXX……………………..";
var myDevice = new apns.Device(token);
var note = new apns.Notification();
note.expiry = Math.floor(Date.now() / 1000) + 100; // Expires 1 hour from now.
note.badge = 1;
note.sound = "ping.aiff";
note.alert = "Fuck you";
note.payload = {'messageFrom': 'Caroline'};
note.device = myDevice;
apnsConnection.sendNotification(note);
</code></pre>
<p>客户端:</p>
<p><img src="http://GeforceLee.github.io/images/nodeapn/130713_14.png"></p>
<p>选好新的证书</p>
<p>在Appdelegate里:</p>
<pre><code>- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"content---%@", token);
[[NSUserDefaults standardUserDefaults] setValue:token forKey:@"deviceToken"];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(@"push 注册失败");
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil];
} else {
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil];
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
[[UIApplication sharedApplication] registerForRemoteNotificationTypes: UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
return YES;
}
</code></pre>
<p>基本上就是这些了</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Redis系列教程7]]></title>
<link href="http://GeforceLee.github.io/blog/2013/05/07/Redis-Tutorials-7/"/>
<updated>2013-05-07T00:00:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/05/07/Redis-Tutorials-7</id>
<content type="html"><![CDATA[<h1>第六章 管理</h1>
<p>最后一章我们要了解对运行的Redis进行管理。这不是一个Redis管理综合的指南。在这我们只能了解一些基础的知识。</p>
<!-- more -->
<h2>配置</h2>
<p>当你第一次启动Redis server的时候,会出现警告:<code>redis.conf</code> 文件没有找到。这个文件是配置Redis用的。每次发布Redis的时候都有一个写的很详细的<code>redis.conf</code>。例子文件包含了默认的Redis配置选项,这对我们了解每个设置都是什么很有帮助。你可以在<a href="https://github.com/antirez/redis/raw/2.4.6/redis.conf">https://github.com/antirez/redis/raw/2.4.6/redis.conf</a>找到。</p>
<p>这个配置文件是Redis2.4.6版本的。你可以通过<code>info</code>命令查看你当前的版本。</p>
<p>因为这个文件注释很详细,我们就不在这一一介绍了。</p>
<p>我们要给<code>redis.conf</code>文件增加内容,就用<code>config set</code>命令,我们之前就设置了<code>slowlog-log-slower-than</code> 为0。</p>
<p>这还有一个<code>config get</code>命了显示设置的命令。这个命令还支持参数匹配。所以如果我们想显示所有跟log有关的我们可以用:</p>
<pre><code>config get *log*
</code></pre>
<h2>验证</h2>
<p>Redis能配置成需要密码的。可以通过<code>requirepass</code>设置(可以通过<code>redis.conf</code>文件或者<code>config set</code>命令设置)客户端需要用<code>auth password</code>命令。一旦客户端被验证,就可以用任何命令了。通过配置你可以给命令改名字:</p>
<pre><code>rename-command CONFIG 5ec4db169f9d4dddacbfb0c26ea7e5ef
rename-command FLUSHALL 1041285018a942a4922cbf76623b741e
</code></pre>
<p>或者你可以设置一个命令问空字符串来关闭命令。</p>
<h2>大小限制</h2>
<p>当你开始用Redis的时候,你可能很想知道 你能有多少个key呢?或者在hash中你能有多少个字段呢?或者在list中能有多少个元素呢?每个实例实际限制都是亿万的。</p>
<h2>Replication</h2>
<p>Redis支持复制,这就意味这让你写一个Redis实例(master),一个或多个实例(slaves)可以跟着主服务器一起更新。你可以配置一个slave通过用<code>slaveof</code>配置设置或者通过<code>slaveof</code>命令。</p>
<p>复制帮助我们保护数据通过考配到其他服务器上。Replication可以提高性能通过读分发到slaves上。不过这会得到一些过期的数据,但是很多应用是可以做一些权衡。</p>
<p>不幸的的是Redis还不支持自动的故障转移。如果master down了,salve必须手动的promoted。传统的高级工具会用一个心跳监控脚本来切换。</p>
<h2>备份</h2>
<p>备份Redis就是简单的将master拷贝一个快照到指定的位置(S3,FTP…)。默认的Redis保存快照是一个名为<code>dump.rdb</code>的文件。在任何时间点,你可以简单的<code>scp</code>,<code>ftp</code>,<code>cp</code>这文件。</p>
<p>关闭快照和只赠文件(aof)在master上而让slave做这些。这回减少master的压力。</p>
<h2>缩放和Redis集群</h2>
<p>Replication是第一个要用的。一些花费较高的命令(像sort)把他们分配到slave上执行可以挺高性能。</p>
<p>根据这个,我们可以分配kyes到多个Redis实例中(可以在同一个内存中,同一个机器)。目前这是你需要考虑的(虽然一些Redis的驱动提供同样的逻辑)。这教程就不告诉怎么把你的数据水平分布了。目前你不要担心的,但是当你用的是要考虑好。</p>
<p>好消息就是Redis集群正在完善。不仅提供水平伸缩,还提供的自动失效备份。</p>
<p>高性能可伸缩我们先在就可以达成,只要你愿意把时间和经历放在里面。Redis机会使事情变得简单。</p>
<h2>小节</h2>
<p>在这章给出了一些在Redis 应用的项目和站点。不用怀疑Reids,因为一个运行了一段时间,然而一些工具,特别是在安全和可用性上还比较年轻。Redis Cluster希望很快就能面面市。</p>
<h1>总结</h1>
<p>Redis可以简单高效的为你存储数据。要好好的利用他就要系统的学习。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Redis系列教程6]]></title>
<link href="http://GeforceLee.github.io/blog/2013/05/06/Redis-Tutorials-6/"/>
<updated>2013-05-06T00:00:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/05/06/Redis-Tutorials-6</id>
<content type="html"><![CDATA[<h1>第五章 Lua脚本</h1>
<p>在Redis2.6中,包含了一个Lua的解析器,让开发这可以在Redis内部写更好的查询语句。没错这很像大型的关系数据库中的存储过程。</p>
<p>这个最难的就是学习Lua,但是Lua是一个比其他流行语言小很多的脚本语言,他有很好的文档,有积极的社区。这章不会详细介绍Lua,但是会有一些有用的例子会被提到。</p>
<!-- more -->
<h2>为什么?</h2>
<p>在开始用Lua脚本之前,你会想知道为什么用他。很多开发者不喜欢传统的存储过程,这有什么不同吗?一个简短的答案:NO。不正当的运用,Redis的Lua脚本结果很难测试,因为有复杂的业务逻辑在里面。</p>
<p>适当的运用,可以有效的提高效率以及简化代码。在一个方法内,组合多命令,打包很多简单逻辑。代码会变得简单。会提高性能,因为去掉了一些中间过程的结果。最后的结果会在脚本中计算。</p>
<p>在接下来的例子中会很好解释上述内容。</p>
<h2>Eval</h2>
<p><code>eval</code>命令会执行Lua脚本。我们会运用很多的参数,让我们来看一个例子(在Ruby中执行,因为在命令行中执行多行不太好):</p>
<pre><code>script = <<-eos
local friend_names = redis.call(’smembers’, KEYS[1])
local friends = {}
for i = 1, #friend_names do
local friend_key = ’user:’ .. friend_names[i]
local gender = redis.call(’hget’, friend_key, ’gender’)
if gender == ARGV[1] then
table.insert(friends, redis.call(’hget’, friend_key, ’details’))
end
end
return friends eos
Redis.new.eval(script, [’friends:leto’], [’m’])
</code></pre>
<p>在上面的例子中我们获得Leto的所有男性朋友。我们用redis.call(“command”,ARG1,ARG2,…)来调用Redis命令。</p>
<p>如果你对Lua陌生,你要仔细看每一行代码。{}是创建一个空table(能代表array或dictionary),#TABLE 是获得Table中的元素的数量。 <code>..</code>连接字符串用。</p>
<p><code>eval</code>实际上接收4个参数。第二个蚕食应该是key 的数量。然而Ruby自动的帮我们填上了。考虑下:</p>
<pre><code>eval ”.....” ”friends:leto” ”m”
vs
eval ”.....” 1 ”friends:leto” ”m”
</code></pre>
<p>在第一个(错误)的例子中,Redis怎么会知道有多少个是key,有多少是参数呢?在第二个中,就明确了。</p>
<p>这会带来第二个问题:为什么必须明确列出keys?在Redis中要知道每个命令运行的时间,那些keys是需要的。这会让像Redis Cluster分布请求在多个Redis服务器中。你可能发现我们之前的例子是动态的读取key。<code>hget</code>获得Leto的所有男性朋友。这是因为提前列出key只是一个建议,并不是强制规则。上面的代码我们会很好的在一个单例中运行,但不能在尚未发布的Redis Cluster中。</p>
<h2>脚本管理</h2>
<p>即使通过<code>eval</code>可以把脚本缓存到Redis中,但是每次执行都要传递一个body并不理想。作为替代你可以注册一个脚本在Redis,然后用key执行。用<code>script load</code> 命令,他能返回一个SHA1值:</p>
<pre><code>redis = Redis.new
script_key = redis.script(:load, "THE_SCRIPT")
</code></pre>
<p>如果想加载这个脚本,我们可以用<code>evalsha</code> :</p>
<pre><code>redis.evalsha(script_key, ['friends:leto'], ['m'])
</code></pre>
<p><code>script kill</code>,<code>script flush</code>和<code>script exists</code>是一些管理Lua 脚本的命令。他们分别是:杀死运行的脚本,删除缓存中的所有脚本,和看一个脚本是否在缓存中。</p>
<h2>Libraries</h2>
<p>Redis的Lua提供了一个很有用库,当<code>table.lib</code>,<code>string.lib</code>,<code>math.lib</code>都是很有用的。<code>cjson.lib</code>值得拿出来说一下:</p>
<pre><code>redis.evalsha ".....", [KEY1], [JSON.fast_generate({gender: 'm', ghola: true})
</code></pre>
<p>你也可以反序列化在Lua脚本中:</p>
<pre><code>local arguments = cjson.decode(ARGV[1])
</code></pre>
<p>当然,JSON库也可以在Redis直接解析存储。之前的例子也可以写成这样:</p>
<pre><code>local friend_names = redis.call('smembers', KEYS[1])
local friends = {}
for i = 1, #friend_names do
local friend_raw = redis.call('get', 'user:' .. friend_names[i])
local friend_parsed = cjson.decode(friend_raw)
if friend_parsed.gender == ARGV[1] then
table.insert(friends, friend_raw)
end
end
return friends
</code></pre>
<p>从指定的hash字段里获得性别可以替换成直接从好友数据里获得。(这会比较慢。)</p>
<h2>原子</h2>
<p>因为Redis是单线程的,所有你不用担心你的Lua脚本会被别的Redis命令打断。一个最值得一提的优势就是key不会在执行的时候过期。如果一个key出现在开始脚本时,他会在任何点出现,除非你删除它。</p>
<h2>管理</h2>
<p>下一章我们会详细的介绍Redis的管理和配置,但是现在,简单的了解<code>lua-time-limit</code>定义了一个Lua脚本如许执行的时间。默认的是5秒。</p>
<h2>小节</h2>
<p>在这章我们介绍了Lua脚本。他可以在任何地方用到,但是谨慎的实现自己的功能,不要滥用。他就会使你的代码简洁,并且能提高效率。Lua脚本也可以像其他Reids命令一样做一些限制。如果可能尽可能的多练习。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Redis系列教程5]]></title>
<link href="http://GeforceLee.github.io/blog/2013/05/06/Redis-Tutorials-5/"/>
<updated>2013-05-06T00:00:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/05/06/Redis-Tutorials-5</id>
<content type="html"><![CDATA[<h1>第三章 基于数据结构</h1>
<p>Redis提供了五种数据结构,这有一些不是数据结构的命令。我们已经看到一些有用的命令:info,select,flushdb,multi,exec,discard,watch和kyes。这章我们会了解一些其他的重要的命令。</p>
<!-- more -->
<h2>过期</h2>
<p>Redis提供了你一个key 的存活期限,你可以给一个绝对时间(自1970年1月1日到现在的秒)或者是从现在开始的秒数。这是基于key的命令。所以value是什么都无所谓。</p>
<pre><code>expire pages:about 30
expireat pages:about 1256933600
</code></pre>
<p>第一个命令会在30秒后删除key。第二个做同样的是在2012年11月31号的12点。</p>
<p>这使Redis变成一个理想的缓冲引擎。你可以通过<code>ttl</code>命令查看一个key 存活了多久,你也可以删除一个key的过期时间:</p>
<pre><code>ttl pages:about
presist pages:about
</code></pre>
<p>最后,这有一个特殊的命令,<code>setex</code>让你设置指定的string,他是一个原子命令:</p>
<pre><code>setex pages:about 30 '<h1>about us</h1>....'
</code></pre>
<h2>发布和订阅</h2>
<p>Redis的list有<code>blpop</code>和<code>brpop</code>命令。他们返回删除的第一个元素并且返回。这可以很有用的用到一个简单的队列里。</p>
<p>基于这些,Redis很好的提供发布信息和订阅频道的功能。你可以打开一个新的redis-cli串口试试。在第一个窗口订阅频道:</p>
<pre><code>subscrive warnings
</code></pre>
<p>回应小心。现在你在另一个窗口,发布一个消息:</p>
<pre><code>publish warnigns "it's over 9000!"
</code></pre>
<p>如果你回到第一个窗口,你会接收到消息。</p>
<p>你可以订阅多个频道(subscribe channel1 channel2 …)订阅频道的模式(psubscribe warnings:*) 用<code>unsubscribe</code> 和 <code>punsubscribe</code>命令停止监听。</p>
<p>最后,注意<code>publish</code>命令返回的值:1。就是监听客户端的数量。</p>
<h2>监控和慢记录</h2>
<p><code>monitor</code>命令让你了解Redis运行状况。这是一个很好的调试工具了解你的应用程序与Redis的联系。在其中一个redis-cli的窗口中(如果你还在subscribed,你可以用unsubscribe命令取消,或者关闭重新开一个)敲入<code>monitor</code>命令。在另一个,执行一个命令。你可以看到这些信息。</p>
<p>在生产环境上用还是要注意吧。因为这是一个开发和测试工具。这没有什么多说的。</p>
<p>配合这monitor,Redis有一个<code>slowlog</code>这是一伟大的分析工具。他会log出消耗超过指定毫秒的命令。下一章我们会简单的了解下怎么配置Redis。现在你能配置Redis log出所有的命令:</p>
<pre><code>config set slowlog-log-slower-than 0
</code></pre>
<p>下一步,用一些命令,最后你能得到所有的log。获得最近的logs通过:</p>
<pre><code>slowlog get
slowlog get 10
</code></pre>
<p>你也可以获得数量通过输入slowlog len</p>
<p>从每个你输入的命令你能看到4个参数:</p>
<ul>
<li>一个自增的id</li>
<li>一个命令产生的时间戳</li>
<li>命令消耗多少时间(ms)</li>
<li>命令本身和参数</li>
</ul>
<p>slowlog在内存中维护,所以在生产环境中要慎重。</p>
<h2>排序</h2>
<p>Redis最重要的命令之一就是排序。他让你在list,set,sorted set中排序。在最简单的形式:</p>
<pre><code>rpush users:leto:guesses 5 9 10 2 4 10 19 2
sort users:leto:guesses
</code></pre>
<p>上面返回的是从低到高的排序后结果。</p>
<pre><code>sadd friends:ghanima leto paul chani jessica alia duncan
sort friends:ghanima limit 0 3 desc alpha
</code></pre>
<p>在上面的命令展示出我们应该如何分页排序好的记录(通过limit),怎么获得倒序(通过desc),通过字典排序规则代替数字规则(通过 alpha)。</p>
<p>排序的真正强大之处应该是根据引用的object排序。之前我们展示了lists,sets,sorted sets经常引用其他Redis的object。<code>sort</code>命令很解除引用,然后通过一些值排序。举个例子,我们有一个bug追中器,能让用户看到问题。我们可能会用set去跟踪:</p>
<pre><code>sadd watch:leto 12339 1282 338 9338
</code></pre>
<p>这可能会根据id排序会更好(默认就是这么做的),但是我们也想根据严重等级排序。为了这么做我们会告诉Redis排序的参数。首先我们增加一些对结构有意义的数据:</p>
<pre><code>set severity:12339 3
set severity:1382 2
set severity:338 5
set severity:9338 4
</code></pre>
<p>通过严重等级排序bugs:</p>
<pre><code>sort watch:leto by severity:* desc
</code></pre>
<p>Redis会替换*为我们传的参数。这会创建实际的key对应的value,然后排序。</p>
<p>虽然你可以在Redis中有很多的kyes,我想上面的例子你可能会有凌乱。幸好排序也同样适合在hashes的字段上。替换最高级的keys你可以用hashes:</p>
<pre><code>hset bug:12339 severity 3
hset bug:12339 priority 1
hset bug:12339 details ”{id: 12339, ....}”
hset bug:1382 severity 2
hset bug:1382 priority 2
hset bug:1382 details ”{id: 1382, ....}”
hset bug:338 severity 5
hset bug:338 priority 3
hset bug:338 details ”{id: 338, ....}”
hset bug:9338 severity 4
hset bug:9338 priority 2
hset bug:9338 details ”{id: 9338, ....}”
</code></pre>
<p>不仅整齐了,我们还能通过severity或者priority排序,同时我们还可以通过排序获得:</p>
<pre><code>sort watch:leto by bug:*->priority get bug:*->details
</code></pre>
<p>同样的结果。Redis认出->序列,然后查找出hash里的指定字段。我们同时也加了get参数,定义了子查询来获得bug的详细信息。</p>
<p>在大set里,排序会变得很慢。好消息是排过序的输出可以存储:</p>
<pre><code>sort watch:leto by bug:*->priority get bug:*->details store watch_by_priority:leto
</code></pre>
<p>存储一个排序后的结果,做一个漂亮的连招。</p>
<h2>小节</h2>
<p>在这章我们关注非数据结构命令。这些命令因情况而定。不一定每个应用都会用到expiration,publication/subscription 或者排序。但是在这都有了了解。我们在这只用了其中一些命令。文档上会有全部的命令<a href="http://redis.io/commands">http://redis.io/commands</a></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Redis系列教程4]]></title>
<link href="http://GeforceLee.github.io/blog/2013/05/06/Redis-Tutorials-4/"/>
<updated>2013-05-06T00:00:00+08:00</updated>
<id>http://GeforceLee.github.io/blog/2013/05/06/Redis-Tutorials-4</id>
<content type="html"><![CDATA[<h1>第三章 利用数据结构</h1>
<p>在之前的章节,我们了解了Redis的五种数据结构,以及了解一些我可以用这五个数据结构能解决的问题。接下来,我们将要看到一些新的功能,命令以及一些设计模式。</p>
<h2>O标识符(时间复杂度)</h2>
<!-- more -->
<p>贯穿这个教程,我们一直用O表示 从O(n)到O(1)。在Redis,这个会告诉我们一个命令有多快。</p>
<p>Redis的文档告诉我们每个命令的O标识符的结果。他也告诉我们应当关注与哪些影响性能。让我们看看一些例子。</p>
<p>最快的当然是O(1)。不管我们操作的是5个还是5百万条数据,这都是同样的效率。<code>sismember</code>命令会告诉我们一个value是不是属于一个set—-O(1)。<code>sismember</code>是一个很有用的命令,所以性能很重要。在Redis中有很多命令是O(1)。</p>
<p>对数或者 O(log(N)),是次最快的,因为他被越来越小的分割。用这样的命令操作,一个很大的数据会分成很多次的迭代。<code>zadd</code>就是一个O(log(N))的命令,N代表在set中的数据个数。</p>
<p>下一个就是线性命令,O(N).查找table中一个无序的列就是一个O(N)。<code>ltrim</code>就是一个这样的命令。但是在<code>ltrim</code> N不是list中元素的个数,而是要删除元素的个数。用<code>ltrim</code>删除一百万条数据中的一条比删除1000条数据中的10条要快(不过你感觉不出来,因为速度很快)。</p>
<p><code>zremrangebyscore</code>这个命令是从sorted set中删除2个分数之间的元素。他的时间复杂度是O(log(N)+M)。这是一个混合。我们从文档上看到N是set中有多少元素。M是有多少元素要删除。换句话说删除元素是关键。</p>
<p>排序命令我们会在下一章中详细讨论。他的时间复杂度为O(N+M*log(M))。看到这个你可能会说这是一个Redis最复杂命令之一了。</p>
<p>这还有2个其他的。O(N<sup>2</sup>)和O(C<sup>N</sup>)。N越大性能越差。在Redis中没有这么复杂的。</p>
<p>值得说明的O标示符表示的是最坏的情况。当我们说一些需要O(N)的,最好的请客是马上找到,最坏的情况是最后才找到。</p>
<h2>伪键查询</h2>
<p>在通常情况下我们想要通过不同的kye查询相同的value,举个例子,你可能想通过email查找用户,并且也想通过id找到他。一个可怕的解决办法就是重复2条string作为value:</p>
<pre><code>set users:[email protected] "{id:9001,email:'[email protected]',....}"
set users:9001 "{id:9001,email:'[email protected]',...}"
</code></pre>
<p>这非常不好,这非常难管理,它占2个内存。</p>
<p>如果Redis能提供一个key引用另一个就好了,但是他没有。Redis的一个主要成员要保持Redis的code和API整洁和简单。内部实现的连接Kye(我们可以对keys做很多事情)是hashes。</p>
<p>用hash,我们可以去掉复制的操作。</p>
<pre><code>set users:9001 "{id:9001,email:'[email protected]',....}"
hset users:loopup:email [email protected] 9001
</code></pre>
<p>上面就是我们用一个伪键引用一个user。通过id获得user。我们可以:</p>
<pre><code>get users:9001
</code></pre>
<p>通过emial获得(Ruby):</p>
<pre><code>id = redis.hget('users:loopup:email','[email protected]')
user = redis.get("user:#{id}");
</code></pre>
<p>这就是我们经常做的。对于我这就是hashes的用处,当你用到的时候就会体会的到。</p>
<h2>引用和索引</h2>
<p>我们已经看了一些引用的例子了。我们在list的例子中看到过,上面的hashes的例子我们用来简便查询操作。接下来我们要从本质上手动管理索引和引用。我实话说,在Redis中当你要考虑手动管理,更新,删除引用,很让人头疼,因为没有什么神奇的方法。</p>
<p>我们已经看到过set手动的管理索引:</p>
<pre><code>sadd friends:leto ghanima paul chani jessica
</code></pre>